From b9acc2702778445a668894fe65c9cdb39b394d52 Mon Sep 17 00:00:00 2001 From: Avi Alpert Date: Thu, 18 Jun 2026 12:54:49 -0400 Subject: [PATCH] fix: dont show deploy screen when deploy is skipped --- src/cli/commands/dev/browser-mode.ts | 4 +- src/cli/commands/dev/command.tsx | 65 +++++++++++++------ src/cli/operations/deploy/change-detection.ts | 20 ++++++ src/cli/tui/screens/dev/DevScreen.tsx | 36 +++++++--- 4 files changed, 97 insertions(+), 28 deletions(-) diff --git a/src/cli/commands/dev/browser-mode.ts b/src/cli/commands/dev/browser-mode.ts index 010c5bec8..02a3f74cb 100644 --- a/src/cli/commands/dev/browser-mode.ts +++ b/src/cli/commands/dev/browser-mode.ts @@ -2,6 +2,7 @@ import { ConfigIO, findConfigRoot, getWorkingDirectory } from '../../../lib'; import type { AgentCoreProjectSpec } from '../../../schema'; import { ANSI } from '../../constants'; import { isPreviewEnabled } from '../../feature-flags'; +import { isDeploySkippable } from '../../operations/deploy/change-detection'; import { getDevConfig, getDevSupportedAgents, loadDevEnv, loadProjectConfig } from '../../operations/dev'; import { type OtelCollector, startOtelCollector } from '../../operations/dev/otel'; import { @@ -133,7 +134,8 @@ export async function launchBrowserDev(): Promise { process.exit(1); } - if (hasHarnesses) { + // Only auto-deploy for harness-only projects, and skip if no CDK changes + if (hasHarnesses && !hasRuntimes && !(await isDeploySkippable())) { await runCliDeploy(); } diff --git a/src/cli/commands/dev/command.tsx b/src/cli/commands/dev/command.tsx index bec8ad94e..2522036a4 100644 --- a/src/cli/commands/dev/command.tsx +++ b/src/cli/commands/dev/command.tsx @@ -12,6 +12,7 @@ import { getErrorMessage } from '../../errors'; import { detectContainerRuntime } from '../../external-requirements'; import { isPreviewEnabled } from '../../feature-flags'; import { ExecLogger } from '../../logging'; +import { isDeploySkippable } from '../../operations/deploy/change-detection'; import { callMcpTool, createDevServer, @@ -509,28 +510,54 @@ export const registerDev = (program: Command) => { }; } - // Preview: show TUI deploy progress, then launch Agent Inspector in the browser + // Preview browser mode: check if deploy is needed BEFORE entering the TUI. + // This avoids an alt-screen flash when there's nothing to deploy. + // The TUI (launchTuiDevScreenWithPicker) is only used to show deploy progress. if (isPreviewEnabled()) { - const pickerResult = await launchTuiDevScreenWithPicker(workingDir, { - skipDeploy: opts.skipDeploy, - }); + const isHarnessOnly = hasHarnesses && supportedAgents.length === 0; + let needsTuiDeploy = false; - if (pickerResult != null) { - recorder.set({ ui_mode: 'browser' as const }); - return { - success: true as const, - blockingPromise: runBrowserMode({ - workingDir, - project, - port, - agentName: pickerResult.agentName, - harnessName: pickerResult.harnessName, - otelEnvVars, - collector, - }), - }; + if (isHarnessOnly && !opts.skipDeploy) { + needsTuiDeploy = !(await isDeploySkippable()); } - return { success: true as const, blockingPromise: Promise.resolve() }; + + if (needsTuiDeploy) { + // Deploy is needed — show TUI deploy progress, then launch browser + const pickerResult = await launchTuiDevScreenWithPicker(workingDir, { + skipDeploy: opts.skipDeploy, + }); + + if (pickerResult != null) { + recorder.set({ ui_mode: 'browser' as const }); + return { + success: true as const, + blockingPromise: runBrowserMode({ + workingDir, + project, + port, + agentName: pickerResult.agentName, + harnessName: pickerResult.harnessName, + otelEnvVars, + collector, + }), + }; + } + return { success: true as const, blockingPromise: Promise.resolve() }; + } + + // No deploy needed — skip TUI entirely, go straight to browser + recorder.set({ ui_mode: 'browser' as const }); + return { + success: true as const, + blockingPromise: runBrowserMode({ + workingDir, + project, + port, + agentName: opts.runtime, + otelEnvVars, + collector, + }), + }; } // Default: browser mode (blocks forever) diff --git a/src/cli/operations/deploy/change-detection.ts b/src/cli/operations/deploy/change-detection.ts index 65a513142..a3e520d0a 100644 --- a/src/cli/operations/deploy/change-detection.ts +++ b/src/cli/operations/deploy/change-detection.ts @@ -1,4 +1,5 @@ import { ConfigIO } from '../../../lib'; +import { ensureDefaultDeploymentTarget } from './ensure-target'; import { createHash } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; @@ -73,3 +74,22 @@ export async function canSkipDeploy(configIO: ConfigIO): Promise { return false; } } + +/** + * Checks whether a deploy is needed, handling target auto-population. + * Returns true if deploy can be safely skipped (no changes detected). + * Returns false if deploy is needed or if the check itself fails. + * + * This is the high-level entry point used by both the browser-mode gate + * (command.tsx) and the terminal-mode gate (DevScreen) to avoid showing + * deploy UI when there's nothing to deploy. + */ +export async function isDeploySkippable(): Promise { + try { + const configIO = new ConfigIO(); + await ensureDefaultDeploymentTarget(configIO); + return await canSkipDeploy(configIO); + } catch { + return false; + } +} diff --git a/src/cli/tui/screens/dev/DevScreen.tsx b/src/cli/tui/screens/dev/DevScreen.tsx index e52280b53..bffaba95b 100644 --- a/src/cli/tui/screens/dev/DevScreen.tsx +++ b/src/cli/tui/screens/dev/DevScreen.tsx @@ -1,5 +1,6 @@ import type { AgentEnvSpec } from '../../../../schema'; import { isPreviewEnabled } from '../../../feature-flags'; +import { isDeploySkippable } from '../../../operations/deploy/change-detection'; import { getDevSupportedAgents, getEndpointUrl, loadProjectConfig } from '../../../operations/dev'; import { DeployStatus, @@ -185,19 +186,35 @@ export function DevScreen(props: DevScreenProps) { } else if (agents.length === 1 && harnesses.length === 0 && agents[0]) { setSelectedAgentName(agents[0].name); if (!onLaunchBrowser) setMode('chat'); - } else if (harnesses.length === 1 && agents.length === 0) { - setSelectedHarness(harnesses[0]); - setMode('deploying'); + } else if (harnesses.length > 0 && agents.length === 0) { + // Harness-only projects: check if deploy is needed before showing deploy UI. + // This covers terminal mode (--no-browser). Browser mode is gated earlier in command.tsx. + if (harnesses.length === 1) setSelectedHarness(harnesses[0]); + + const skipDeploy = props.skipDeploy === true || (await isDeploySkippable()); + + if (skipDeploy) { + if (onLaunchBrowser) { + queueMicrotask(() => onLaunchBrowser({ harnessName: harnesses.length === 1 ? harnesses[0] : undefined })); + } else if (harnesses.length === 1) { + setMode('harness'); + } + // Multiple harnesses + terminal: stays in 'select-agent' (chooser) + } else { + setMode('deploying'); + } } else if (agents.length === 0 && harnesses.length === 0) { setNoAgentsError(true); } setAgentsLoaded(true); - // If onLaunchBrowser is set and only agents (no harnesses), auto-select immediately. - // Harness projects need deploy first — handled after deploy completes. - if (onLaunchBrowser && agents.length === 1 && harnesses.length === 0) { - queueMicrotask(() => onLaunchBrowser({ agentName: agents[0]?.name })); + // Browser mode: skip the terminal chooser and launch browser immediately. + // The web UI handles agent/harness selection. + // Harness-only projects need deploy first — handled after deploy completes. + if (onLaunchBrowser && agents.length > 0) { + const resolvedName = props.agentName ?? (agents.length === 1 ? agents[0]?.name : undefined); + queueMicrotask(() => onLaunchBrowser({ agentName: resolvedName })); } }; void load(); @@ -255,8 +272,11 @@ export function DevScreen(props: DevScreenProps) { queueMicrotask(() => { if (onLaunchBrowser) { onLaunchBrowser({ harnessName: selectedHarness }); - } else { + } else if (selectedHarness) { setMode('harness'); + } else { + // Multiple harnesses: show the chooser after deploy completes + setMode('select-agent'); } }); // eslint-disable-next-line react-hooks/exhaustive-deps