From 42712e3412e9551f529218f717bc7d4df8807e89 Mon Sep 17 00:00:00 2001 From: Paython Veazie Date: Tue, 16 Dec 2025 19:39:45 -0800 Subject: [PATCH 1/5] fix: (wizard) stop provider inference when provider is expklicityly selected --- server/agent/wizardAgent.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/server/agent/wizardAgent.js b/server/agent/wizardAgent.js index ecfb1b1..dbf0f8d 100644 --- a/server/agent/wizardAgent.js +++ b/server/agent/wizardAgent.js @@ -399,12 +399,13 @@ Tell me what youโ€™d like to do next. globalThis.LAST_REPO_USED = payload.repo; } - // โœ… Ensure provider is valid before sending payload - if ( - !payload.provider || - !['aws', 'jenkins'].includes(payload.provider) - ) { - // Infer from repo visibility or fallback to AWS + // ๐Ÿ”’ Provider locking: if provider was explicitly selected in the UI (pipelineSnapshot), + // treat it as authoritative and NEVER override it. + if (pipelineSnapshot?.provider) { + payload.provider = pipelineSnapshot.provider; + console.log(`๐Ÿ”’ Provider locked from pipeline snapshot: ${payload.provider}`); + } else if (!payload.provider) { + // Only infer provider if none was provided at all payload.provider = repoInfo?.visibility === 'private' ? 'jenkins' : 'aws'; console.log(`๐Ÿงญ Inferred provider: ${payload.provider}`); From d02b53ea2f4782b2225cf09285182ddb438f6f2f Mon Sep 17 00:00:00 2001 From: Victoria Williams Date: Wed, 17 Dec 2025 20:54:22 -0500 Subject: [PATCH 2/5] fix: rollback feature on the dashboard page now works --- client/src/lib/api.ts | 12 ++- client/src/pages/DashboardPage.tsx | 149 ++++++++++++++++++----------- 2 files changed, 102 insertions(+), 59 deletions(-) diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts index f2855d9..ec55c07 100644 --- a/client/src/lib/api.ts +++ b/client/src/lib/api.ts @@ -1,5 +1,3 @@ -import { usePipelineStore } from "../store/usePipelineStore"; - // In dev: talk to Vite dev server proxy at /api // In prod: use the real backend URL from VITE_API_BASE (e.g. https://api.autodeploy.app) const DEFAULT_API_BASE = @@ -7,6 +5,7 @@ const DEFAULT_API_BASE = export const BASE = import.meta.env.VITE_API_BASE || DEFAULT_API_BASE; +import { usePipelineStore } from "../store/usePipelineStore"; // SERVER_BASE is the same as BASE but without trailing /api, // so we can call /mcp and /auth directly. @@ -519,7 +518,11 @@ export const api = { pipelineStore?.repoFullName || (pipelineStore as any)?.result?.repo; const selectedBranch = branch || (pipelineStore as any)?.selectedBranch || "main"; - const yaml = (pipelineStore as any)?.result?.generated_yaml; + const yaml = + fromCallerYaml || + (pipelineStore as any)?.result?.generated_yaml || + ""; + const environment = env || (pipelineStore as any)?.environment || "dev"; const providerFinal = provider || (pipelineStore as any)?.provider || "aws"; @@ -545,6 +548,9 @@ export const api = { }; console.log("[Deploy] Final payload:", payload); +if (!repoFullName) throw new Error("startDeploy: missing repoFullName"); +if (!selectedBranch) throw new Error("startDeploy: missing branch"); +if (!yaml || yaml.trim().length === 0) throw new Error("startDeploy: missing yaml"); const res = await fetch(`${SERVER_BASE}/mcp/v1/pipeline_commit`, { method: "POST", diff --git a/client/src/pages/DashboardPage.tsx b/client/src/pages/DashboardPage.tsx index 317539e..9995e98 100644 --- a/client/src/pages/DashboardPage.tsx +++ b/client/src/pages/DashboardPage.tsx @@ -38,6 +38,12 @@ export default function DashboardPage() { const repoFullName = result?.repo || repo || ""; const branchName = (result as any)?.branch || selectedBranch || "main"; + const environment = cfg.env || "dev"; + const workflowFile = + (result as any)?.pipeline_name || `${environment}-deploy.yml`; + const workflowPath = workflowFile.startsWith(".github/workflows/") + ? workflowFile + : `.github/workflows/${workflowFile}`; const [versions, setVersions] = useState([]); const [loadingHistory, setLoadingHistory] = useState(false); @@ -48,6 +54,12 @@ export default function DashboardPage() { const [editingYaml, setEditingYaml] = useState(false); const [draftYaml, setDraftYaml] = useState(result?.generated_yaml ?? ""); + const currentYaml = (result?.generated_yaml ?? draftYaml ?? "").trim(); + const canCommitYaml = currentYaml.length > 0; + // ๐Ÿ”‘ Single source of truth for the currently active YAML + +// const canCommitYaml = +// (editingYaml ? draftYaml : (result?.generated_yaml ?? draftYaml))?.trim(); useEffect(() => { if (!editingYaml) { @@ -67,6 +79,7 @@ export default function DashboardPage() { const rows = await api.getPipelineHistory({ repoFullName, branch: branchName, + path: workflowPath, limit: 20, }); if (!cancelled) { @@ -88,57 +101,79 @@ export default function DashboardPage() { cancelled = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [repoFullName, branchName]); - - async function handleRollback(version: PipelineVersion) { - if (!version?.id) return; - const confirmMsg = `Rollback ${repoFullName}@${branchName} to version created at ${formatDate( - version.created_at - )}?`; - if (!window.confirm(confirmMsg)) return; - - setRollbackBusy(true); - try { - const data = await api.rollbackPipeline(version.id); - console.log("[Dashboard] Rollback response:", data); - alert("Rollback queued successfully."); - - const rows = await api.getPipelineHistory({ - repoFullName, - branch, - limit: 20, - }); - setVersions(rows); - } catch (err: any) { - console.error("[Dashboard] rollbackPipeline failed:", err); - alert(err.message || "Rollback failed"); - } finally { - setRollbackBusy(false); - } - } + }, [repoFullName, branchName, workflowPath]); - function handleCommitClick() { - const repoFullNameLocal = result?.repo || repo; - const yaml = result?.generated_yaml; - const branchLocal = (result as any)?.branch || branchName || "main"; - const environment = cfg.env || "dev"; - const provider = "aws"; - const path = `.github/workflows/${environment}-deploy.yml`; - - if (!repoFullNameLocal || !yaml) { - alert("Missing repo or YAML โ€” generate a pipeline first."); - return; - } +async function handleRollback(version: PipelineVersion) { + if (!version?.id) return; + + const confirmMsg = `Rollback ${repoFullName}@${branchName} to version created at ${formatDate( + version.created_at + )}?`; + if (!window.confirm(confirmMsg)) return; + + setRollbackBusy(true); + try { + // ๐Ÿ‘‡ THIS is where the rollback happens + const data = await api.rollbackPipeline(version.id); - startDeploy({ - repoFullName: repoFullNameLocal, - branch: branchLocal, - env: environment, - yaml, - provider, - path, + // ๐Ÿ‘‡ SHOW REAL OUTPUT (GitHub commit URL) + alert( + `Rollback committed โœ…\n${ + data?.github?.commit?.html_url ?? "OK" + }` + ); + + // ๐Ÿ‘‡ Update Current Pipeline YAML in UI + setResultYaml(version.yaml); + setEditingYaml(false); + + // ๐Ÿ‘‡ Refresh history list + const rows = await api.getPipelineHistory({ + repoFullName, + branch: branchName, + path: workflowPath, + limit: 20, }); + setVersions(rows); + setSelectedVersion(rows[0] ?? null); + + } catch (err: any) { + console.error("[Dashboard] rollbackPipeline failed:", err); + alert(err.message || "Rollback failed"); + } finally { + setRollbackBusy(false); } +} + + + +async function handleCommitClick() { + const repoFullNameLocal = result?.repo || repo; + const yaml = currentYaml; + + const branchLocal = (result as any)?.branch || branchName || "main"; + const provider = "aws"; + const path = workflowPath; + + if (!repoFullNameLocal || !yaml) { + alert("Missing repo or YAML โ€” generate a pipeline first."); + return; + } + + const res = await startDeploy({ + repoFullName: repoFullNameLocal, + branch: branchLocal, + env: environment, + yaml, + provider, + path, + }); + + // backend response you showed: res.data.commit.html_url + const url = res?.data?.commit?.html_url; + alert(url ? `Committed โœ…\n${url}` : "Committed โœ…"); +} + return (
@@ -183,26 +218,28 @@ export default function DashboardPage() {
-                      {result?.generated_yaml ?? "No pipeline generated yet."}
+                      {currentYaml || "No pipeline generated yet."}
+
                     
)} - +
+ size="sm" + disabled={running || !repoFullName || !canCommitYaml} + onClick={handleCommitClick} +> + {running ? "Committingโ€ฆ" : "Commit to GitHub"} + + {running && ( )} - {result?.generated_yaml && ( + {currentYaml && ( <>
{provider === "aws" && ( + <> + + + )} {provider === "gcp" && ( diff --git a/client/src/store/usePipelineStore.ts b/client/src/store/usePipelineStore.ts index 25a2597..3c3e3f7 100644 --- a/client/src/store/usePipelineStore.ts +++ b/client/src/store/usePipelineStore.ts @@ -14,6 +14,8 @@ type PipelineState = { testCmd: string; buildCmd: string; awsRoleArn?: string; + awsSessionName?: string; + awsRegion?: string; gcpServiceAccountEmail?: string; }; provider: "aws" | "gcp" | "jenkins"; @@ -72,6 +74,8 @@ const initial: PipelineState = { installCmd: "npm ci", testCmd: "npm test", buildCmd: "npm run build", + awsSessionName: "autodeploy", + awsRegion: "us-east-1", gcpServiceAccountEmail: "", }, provider: "aws", diff --git a/server/tools/pipeline_generator.js b/server/tools/pipeline_generator.js index ab7a923..50c28b0 100644 --- a/server/tools/pipeline_generator.js +++ b/server/tools/pipeline_generator.js @@ -19,6 +19,8 @@ export const pipeline_generator = { testCmd: z.string().optional(), buildCmd: z.string().optional(), awsRoleArn: z.string().optional(), + awsSessionName: z.string().optional(), + awsRegion: z.string().optional(), gcpServiceAccountEmail: z.string().optional(), stages: z.array(z.enum(["build", "test", "deploy"])).optional(), }) @@ -33,6 +35,8 @@ export const pipeline_generator = { testCmd: options?.testCmd, buildCmd: options?.buildCmd, awsRoleArn: options?.awsRoleArn, + awsSessionName: options?.awsSessionName, + awsRegion: options?.awsRegion, stages: options?.stages, }; @@ -103,6 +107,8 @@ export const pipeline_generator = { testCmd: options?.testCmd, buildCmd: options?.buildCmd, awsRoleArn: options?.awsRoleArn, + awsSessionName: options?.awsSessionName, + awsRegion: options?.awsRegion, gcpServiceAccountEmail: options?.gcpServiceAccountEmail, stages: options?.stages, }; @@ -177,7 +183,8 @@ jobs: uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${normalized.awsRoleArn ?? "REPLACE_ME"} - aws-region: us-east-1 + role-session-name: ${normalized.awsSessionName ?? "autodeploy"} + aws-region: ${normalized.awsRegion ?? "us-east-1"} - name: Deploy Application run: echo "Deploying ${repo} to AWS..." `; From 2647233aaf0cda6b35297032e01f831aa5d3eba6 Mon Sep 17 00:00:00 2001 From: Paython Veazie Date: Thu, 18 Dec 2025 18:30:08 -0800 Subject: [PATCH 4/5] feat(configure): make pipeline options template-, stage-, and provider-aware - Align template dropdown values with backend-supported templates - Rehydrate pipeline options when template changes to prevent invalid configs - Conditionally render runtime, install, test, and build inputs based on: - selected template (node_app, python_app, container_service) - enabled stages (build, test, deploy) - Hide provider-specific deployment fields unless deploy stage is enabled - Fix pipeline generator to emit runtime-specific setup steps: - Use setup-node only for node_app - Use setup-python only for python_app - Prevent Node.js steps from leaking into Python-generated YAML This ensures UI state, generated YAML, and backend expectations stay in sync and removes misleading or non-applicable configuration paths. --- client/src/pages/ConfigurePage.tsx | 179 +++++++++++++++++++-------- client/src/store/usePipelineStore.ts | 50 +++++++- 2 files changed, 179 insertions(+), 50 deletions(-) diff --git a/client/src/pages/ConfigurePage.tsx b/client/src/pages/ConfigurePage.tsx index 2e94a01..692d47a 100644 --- a/client/src/pages/ConfigurePage.tsx +++ b/client/src/pages/ConfigurePage.tsx @@ -70,7 +70,15 @@ export default function ConfigurePage() { alert("Pick a repo + branch on the Connect page first."); return; } - await regenerate({ repo, branch }); + + await regenerate({ + repo, + branch, + template, + provider, + stages, + options, + }); }; const handleOpenPr = async () => { @@ -95,6 +103,36 @@ export default function ConfigurePage() { const trimmed = chatInput.trim(); if (!trimmed) return; + // --- Sync AI intent with pipeline stages BEFORE sending to backend --- + // The AI is a planner, not an authority. UI state must be updated first. + const lower = trimmed.toLowerCase(); + + // Reset to defaults first + let nextStages: Array<"build" | "test" | "deploy"> = ["build", "test", "deploy"]; + + if (lower.includes("just build") || lower.includes("only build")) { + nextStages = ["build"]; + } else if ( + lower.includes("build and test") || + (lower.includes("build") && lower.includes("test") && !lower.includes("deploy")) + ) { + nextStages = ["build", "test"]; + } else if ( + lower.includes("no deploy") || + lower.includes("without deploy") + ) { + nextStages = ["build", "test"]; + } + + // Apply stage changes to the pipeline store + (["build", "test", "deploy"] as const).forEach((stage) => { + const shouldEnable = nextStages.includes(stage); + const isEnabled = stages.includes(stage); + if (shouldEnable !== isEnabled) { + toggleStage(stage); + } + }); + if (!repo || !branch) { alert( "Pick a repo + branch on the Connect page first so I can give better suggestions." @@ -114,7 +152,7 @@ export default function ConfigurePage() { template, provider, branch, - stages, + stages: nextStages, options, }; @@ -159,9 +197,8 @@ export default function ConfigurePage() { pipelineName, branch, provider, - stages, - // Keep a copy of the current options in wizard context so follow-up prompts - // can reference the selected provider identity (AWS role / GCP service account). + // ๐Ÿ”’ Never override stages from backend / metadata + stages: pipelineSnapshot.stages, options, } as any); } @@ -249,8 +286,8 @@ export default function ConfigurePage() { className="rounded-md border border-white/25 bg-white px-3 py-2 text-sm text-slate-900 placeholder-slate-500" > - - + + Pick the closest match to your repo; the MCP backend refines it. @@ -296,54 +333,98 @@ export default function ConfigurePage() {
- {/* Node version + commands */} + {/* Runtime version + commands */}
- + {/* Node.js version: only show for node_app AND build stage enabled */} + {template === "node_app" && stages.includes("build") && ( + + )} - + {/* Install command: only show if build stage enabled */} + {stages.includes("build") && ( + + )} - + {/* Test command: only show if test stage enabled */} + {stages.includes("test") && ( + + )} - + {/* Build command: only show if build stage enabled */} + {stages.includes("build") && ( + + )}
- {provider === "aws" && ( + {provider === "aws" && stages.includes("deploy") && ( <>