diff --git a/apps/sim/lib/mothership/tools/handlers/sim-cli-bridge.test.ts b/apps/sim/lib/mothership/tools/handlers/sim-cli-bridge.test.ts index 9f32f8fab12..467ce693081 100644 --- a/apps/sim/lib/mothership/tools/handlers/sim-cli-bridge.test.ts +++ b/apps/sim/lib/mothership/tools/handlers/sim-cli-bridge.test.ts @@ -18,7 +18,8 @@ vi.mock('@/lib/execution/remote-sandbox/session-files', () => ({ writeSessionSandboxFile: mockWrite, resolveSessionPath: (path: string) => (path.startsWith('/') ? path : `/home/user/${path}`), })) -vi.mock('sim/embed', () => ({ +vi.mock('sim/embed', async (importOriginal) => ({ + ...(await importOriginal()), runEmbeddedCli: mockRunEmbeddedCli, createEmbeddedClient: vi.fn(), })) @@ -202,4 +203,17 @@ describe('sim-cli handler executes the worker-built request', () => { expect(mockWrite).not.toHaveBeenCalled() expect(result.success).toBe(false) }) + + it('settles a wait that outlasted --wait-timeout as done, keeping its exit status for the worker', async () => { + const stderr = 'Run run-1 is still running after 240s and continues in the background.' + mockRunEmbeddedCli.mockResolvedValue({ exitCode: 4, stdout: '{"status":"running"}', stderr }) + const result = await executeSimCli( + cli(['workflows', 'runs', 'wait', 'run-1', '--workflow', 'wf-1', '--wait-timeout', '240']), + context + ) + expect(result).toEqual({ + success: true, + output: { exitCode: 4, stdout: '{"status":"running"}', stderr }, + }) + }) }) diff --git a/apps/sim/lib/mothership/tools/handlers/sim-cli.ts b/apps/sim/lib/mothership/tools/handlers/sim-cli.ts index 25c43f1eac4..e3429167499 100644 --- a/apps/sim/lib/mothership/tools/handlers/sim-cli.ts +++ b/apps/sim/lib/mothership/tools/handlers/sim-cli.ts @@ -1,5 +1,6 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' +import { STILL_RUNNING_EXIT_CODE } from 'sim/embed' import { executeAgentCliRequest } from '@/lib/mothership/agent-cli' import { messageForCopilotApplicationError } from '@/lib/mothership/application/errors' import { AgentCliRequest } from '@/lib/mothership/generated/agent-cli' @@ -15,6 +16,9 @@ const logger = createLogger('MothershipSimCli') * (`request`) beside the model's raw argv (`args`, kept for display and the log); only * the request is executed — this side never re-parses tokens. The worker folds * exitCode/stdout/stderr into the model window and applies its own output budget. + * A wait whose `--wait-timeout` elapsed with the work still going settles the call as + * done: the worker continues it in further calls, so the chat, the tool record and the + * tool metrics must not read it as a failure. */ export async function executeSimCli( params: Record, @@ -63,12 +67,12 @@ export async function executeSimCli( sink: parsed.data.sink?.kind ?? 'none', stdoutBytes: result.stdout.length, }) + if (result.exitCode === 0 || result.exitCode === STILL_RUNNING_EXIT_CODE) + return { success: true, output: result } return { - success: result.exitCode === 0, + success: false, output: result, - ...(result.exitCode === 0 - ? {} - : { error: result.stderr.split('\n')[0] || `sim CLI exited with code ${result.exitCode}` }), + error: result.stderr.split('\n')[0] || `sim CLI exited with code ${result.exitCode}`, } } catch (error) { return { diff --git a/packages/sim-cli/src/commands/protocol/workflow-run-wait.ts b/packages/sim-cli/src/commands/protocol/workflow-run-wait.ts index 23fdcaa36dc..c6bdd2f07e2 100644 --- a/packages/sim-cli/src/commands/protocol/workflow-run-wait.ts +++ b/packages/sim-cli/src/commands/protocol/workflow-run-wait.ts @@ -6,6 +6,7 @@ import { hasProgressTerminal, styles } from '#sim-cli/output/presentation' import { CLI_CONTRACT } from '../../contract/commands' import type { CommandSpec } from '../../contract/types' import { setSoftExitCode } from '../../embed-context' +import { STILL_RUNNING_EXIT_CODE } from '../../exit-codes' import { V2_OPERATIONS } from '../../generated/v2-api' import { SimApiError } from '../../http/client' import { apiCommand, type Connection } from '../../runtime/called-operations' @@ -39,7 +40,7 @@ const WAIT_EXIT_CODES = { failed: 1, cancelled: 2, paused: 3, - timeout: 4, + timeout: STILL_RUNNING_EXIT_CODE, } as const type WaitOutcome = keyof typeof WAIT_EXIT_CODES diff --git a/packages/sim-cli/src/commands/protocol/workspace-operation-wait.ts b/packages/sim-cli/src/commands/protocol/workspace-operation-wait.ts index 80127b89403..9aa2672d9ed 100644 --- a/packages/sim-cli/src/commands/protocol/workspace-operation-wait.ts +++ b/packages/sim-cli/src/commands/protocol/workspace-operation-wait.ts @@ -1,6 +1,7 @@ import { sleep } from '@sim/utils/helpers' import type { Command } from 'commander' import { CLI_CONTRACT } from '../../contract/commands' +import { STILL_RUNNING_EXIT_CODE } from '../../exit-codes' import type { GetWorkspaceOperationResponse } from '../../generated/v2-api' import { SimApiError } from '../../http/client' import { apiCommand, type OperationClient } from '../../runtime/called-operations' @@ -55,7 +56,7 @@ export function assertWorkspaceOperationOutcome( report: WorkspaceOperation, timedOut = false ): void { - const exitCode = timedOut ? 4 : EXIT_CODES[report.status] + const exitCode = timedOut ? STILL_RUNNING_EXIT_CODE : EXIT_CODES[report.status] if (!exitCode) return throw new SimApiError( timedOut diff --git a/packages/sim-cli/src/embed.ts b/packages/sim-cli/src/embed.ts index d5342d430f1..f3c52b3a0f0 100644 --- a/packages/sim-cli/src/embed.ts +++ b/packages/sim-cli/src/embed.ts @@ -34,6 +34,7 @@ import { buildProgram } from './program' */ export type { EmbeddedCliIdentity, EmbeddedFileSnapshot } from './embed-context' +export { STILL_RUNNING_EXIT_CODE } from './exit-codes' export type { ExportWorkflowResponse, ListFilesResponse, diff --git a/packages/sim-cli/src/exit-codes.ts b/packages/sim-cli/src/exit-codes.ts new file mode 100644 index 00000000000..ca52836eb1d --- /dev/null +++ b/packages/sim-cli/src/exit-codes.ts @@ -0,0 +1,8 @@ +/** + * The exit status of a wait whose `--wait-timeout` elapsed while the work it watches + * keeps going: `workflows runs wait`, a synchronous `workflows run` that detached, and a + * workspace operation wait. The command did what it was asked and printed the record to + * continue from, so a host that runs the CLI for an agent treats it as a finished call, + * not a failed one; a script still branches on the non-zero status. + */ +export const STILL_RUNNING_EXIT_CODE = 4