Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion apps/sim/lib/mothership/tools/handlers/sim-cli-bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof import('sim/embed')>()),
runEmbeddedCli: mockRunEmbeddedCli,
createEmbeddedClient: vi.fn(),
}))
Expand Down Expand Up @@ -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 },
})
})
})
12 changes: 8 additions & 4 deletions apps/sim/lib/mothership/tools/handlers/sim-cli.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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<string, unknown>,
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion packages/sim-cli/src/commands/protocol/workflow-run-wait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/sim-cli/src/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions packages/sim-cli/src/exit-codes.ts
Original file line number Diff line number Diff line change
@@ -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
Loading