From 3283b4ac1db314e96c5ca0c22db05a9687791a74 Mon Sep 17 00:00:00 2001 From: ezl-keygraph Date: Mon, 5 Oct 2026 02:27:22 +0530 Subject: [PATCH] feat(cli): name cyber-access and app-login steps in the start loader --- apps/cli/src/commands/start.ts | 57 +++++++++++++++++++++++++++------ apps/cli/src/temporal-client.ts | 19 +++++++++++ 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/apps/cli/src/commands/start.ts b/apps/cli/src/commands/start.ts index 54bb8aed..87ef89d6 100644 --- a/apps/cli/src/commands/start.ts +++ b/apps/cli/src/commands/start.ts @@ -31,7 +31,12 @@ import { clearPendingWorkflowIdentity, writePendingWorkflowIdentity } from '../p import { indentFailureSegments, parseFailureSegments } from '../scan/failure.js'; import { resolveWorkflowId } from '../session.js'; import { displayPlainBanner, displaySplash } from '../splash.js'; -import { describeWorkflowLifecycle, getTerminalOutcome, queryProgress } from '../temporal-client.js'; +import { + describeWorkflowLifecycle, + getTerminalOutcome, + queryProgress, + runningActivityTypes, +} from '../temporal-client.js'; import { stdoutIsTerminal } from '../tty.js'; import { tailUntilComplete } from './logs.js'; @@ -490,10 +495,14 @@ export async function start(args: StartArgs): Promise { warn(`Scan ${workspace} started, but its launch record could not be removed.`); } - // Hold until preflight clears, so an unreachable target or bad credential is reported here + // Hold until startup clears, so an unreachable target or bad credential is reported here // rather than after "Scan started". - spinner.message('Running preflight checks'); - const outcome = await awaitPreflightOutcome(workflowId); + spinner.message(PREFLIGHT_LABEL); + const spec = resolveModelSpec(); + const providerId = typeof spec === 'string' ? '' : spec.providerId; + // The cyber-access probe only runs for OpenAI/Anthropic, so only name it there. + const showCyberAccess = providerId === 'anthropic' || providerId === 'openai'; + const outcome = await awaitStartupOutcome(workflowId, (label) => spinner.message(label), showCyberAccess); if (outcome.kind === 'failed') { spinner.error(couldNotStartLabel); printScanStartFailure(outcome.message); @@ -569,15 +578,26 @@ function readStartupError(startupErrorPath: string): StartupError | undefined { } } -/** Outcome of waiting for the in-workflow preflight to clear. */ +/** Outcome of waiting for in-workflow startup (preflight + auth validation) to clear. */ type PreflightOutcome = { kind: 'passed' } | { kind: 'failed'; message: string } | { kind: 'unconfirmed' }; +const PREFLIGHT_LABEL = 'Running preflight checks'; +const CYBER_ACCESS_LABEL = 'Checking cyber access'; +const APP_LOGIN_LABEL = 'Verifying app login with provided credentials'; + /** - * Wait for the registered workflow's preflight to pass or fail: passed once `currentPhase` moves - * beyond 'preflight' (or the scan already closed ok), failed when the workflow terminates with an - * error. Bounded, so a Temporal query outage falls through as 'unconfirmed' rather than hanging. + * Drive the startup spinner until the pentest begins, naming the cyber-access probe and the app + * login while their activity runs. Labels only advance, so a gap between them holds the last step + * rather than reverting to the generic line. Passed once the phase moves past preflight/auth (or + * the scan closed ok), failed on a terminal error, unconfirmed if a query outage outlasts the bound. */ -async function awaitPreflightOutcome(workflowId: string): Promise { +async function awaitStartupOutcome( + workflowId: string, + onLabel: (label: string) => void, + showCyberAccess: boolean, +): Promise { + let rank = 0; + let label = PREFLIGHT_LABEL; for (let attempts = 0; attempts < 80; attempts++) { try { const lifecycle = await describeWorkflowLifecycle(workflowId); @@ -586,8 +606,25 @@ async function awaitPreflightOutcome(workflowId: string): Promise { + try { + const client = await getClient(); + const desc = await client.workflow.getHandle(workflowId).describe(); + const names: string[] = []; + for (const pending of desc.raw.pendingActivities ?? []) { + const name = pending.activityType?.name; + if (name) names.push(name); + } + return names; + } catch { + return []; + } +} + /** Live progress of a running scan via the getProgress query. Null if the query can't be served (no worker). */ export async function queryProgress(workflowId: string): Promise { const client = await getClient();