fix(orchestration): fail loudly when a startupAgent launch cannot resolve

The guard ordering let a caller that set startupAgent together with a
caller-supplied launch (env/launchConfig/launchAgent/startupCommandDelivery/
claudeAgentTeamsSourceCommand), a raw command, a resume session, or that hit an
unavailable store, fall through with an unresolved command and spawn a bare
shell — the exact silent timeout at agent_readiness that startupAgent exists to
prevent. No current caller does this, so it was latent rather than live.

Those combinations now throw instead. A raw `command` counts as a contradiction
because it would be silently overwritten by the built launch, and
`resumeProviderSession` because it survives to spawn and would pair resume
identity with a freshly built fresh-launch command.

The non-startupAgent path is unchanged: verified bit-for-bit over all 256
combinations of the eight guard inputs.

Also covers the repo-less folder-workspace branch with a test, which the new
isRemote fallback made reachable for the first time.
This commit is contained in:
OrcaWin
2026-08-02 17:37:42 -07:00
parent 3e38d67c14
commit b9cf69a2d3
2 changed files with 91 additions and 10 deletions
+70
View File
@@ -12614,6 +12614,76 @@ describe('OrcaRuntimeService', () => {
expect(spawnCall?.command).toMatch(/^cursor-agent(\s|$)/)
})
// Why: folder workspaces have no repo, so command sniffing skipped them entirely
// and spawned the bare string; an explicit agent must still resolve.
it('resolves a startupAgent in a repo-less folder workspace', async () => {
const folderPath = await mkdtemp(join(tmpdir(), 'orca-runtime-folder-startup-agent-'))
const spawn = vi.fn().mockResolvedValue({ id: 'pty-bg' })
const folderWorkspace = makeFolderWorkspace({ folderPath })
const projectGroup = makeFolderProjectGroup({ parentPath: folderPath })
const runtime = new OrcaRuntimeService({
...createFolderWorkspaceRuntimeStore(folderWorkspace, projectGroup),
getSettings: () => ({
...store.getSettings(),
disabledTuiAgents: [],
agentCmdOverrides: {},
agentDefaultArgs: { cursor: '--force' },
agentDefaultEnv: {}
})
} as never)
runtime.setPtyController({
spawn,
write: () => true,
kill: () => true,
getForegroundProcess: async () => null
})
await runtime.createTerminal(`id:${TEST_FOLDER_WORKSPACE_KEY}`, { startupAgent: 'cursor' })
const spawnCall = spawn.mock.calls[0]?.[0] as
| { command?: string; launchAgent?: string }
| undefined
expect(spawnCall?.command).toBe("cursor-agent '--force'")
expect(spawnCall?.launchAgent).toBe('cursor')
})
// Why: silently returning the caller's opts would spawn a bare shell that can
// only time out at agent readiness — the failure startupAgent exists to stop.
it('rejects a startupAgent create that also supplies its own launch', async () => {
const spawn = vi.fn().mockResolvedValue({ id: 'pty-bg' })
const runtime = new OrcaRuntimeService({
...store,
getSettings: () => ({
...store.getSettings(),
disabledTuiAgents: [],
agentCmdOverrides: {}
})
})
runtime.setPtyController({
spawn,
write: () => true,
kill: () => true,
getForegroundProcess: async () => null
})
for (const conflicting of [
{ env: { SOME_VAR: 'set' } },
// Why: a raw command would be silently overwritten by the built launch.
{ command: 'cursor-agent --resume' },
// Why: resume identity paired with a freshly built launch is incoherent.
{ resumeProviderSession: { key: 'session_id', id: 'prior-session' } as never },
{ launchAgent: 'cursor' as const }
]) {
await expect(
runtime.createTerminal(`path:${TEST_WORKTREE_PATH}`, {
startupAgent: 'cursor',
...conflicting
})
).rejects.toThrow(/cannot combine/)
}
expect(spawn).not.toHaveBeenCalled()
})
it('rejects a startupAgent create for a disabled agent', async () => {
const spawn = vi.fn().mockResolvedValue({ id: 'pty-bg' })
const runtimeStore = {
+21 -10
View File
@@ -23876,23 +23876,34 @@ export class OrcaRuntimeService {
): Promise<TerminalCreateOptions> {
// Why: raw shell commands like `codex exec` must remain user-authored shell.
// Only unmanaged, repo-backed, bare agent launches get Settings defaults.
if (
const callerSuppliedLaunch =
opts.env ||
opts.launchConfig ||
opts.launchAgent ||
opts.startupCommandDelivery ||
opts.claudeAgentTeamsSourceCommand ||
!this.store
) {
return opts
}
// An explicit startupAgent is already the agent's identity, so it needs no
// command sniffing and works for repo-less folder workspaces too.
if (!opts.startupAgent && (!opts.command || !workspace.repo)) {
opts.claudeAgentTeamsSourceCommand
const store = this.store
if (opts.startupAgent) {
// Why: returning opts unresolved here would spawn a bare shell that can only
// time out waiting for an agent — the silent failure startupAgent exists to
// prevent. An explicit agent needs no command sniffing and no repo, so the
// only unresolvable cases are a contradictory caller or a dead runtime.
// `command` and `resumeProviderSession` count as contradictions too: the
// first would be silently overwritten, the second would pair resume identity
// with a freshly built launch.
if (callerSuppliedLaunch || opts.command || opts.resumeProviderSession) {
throw new Error(
`startupAgent ${opts.startupAgent} cannot combine with a caller-supplied launch.`
)
}
if (!store) {
throw new Error('runtime_unavailable')
}
} else if (callerSuppliedLaunch || !store || !opts.command || !workspace.repo) {
return opts
}
const settings = this.store.getSettings()
const settings = store.getSettings()
const platform = this.getAgentLaunchPlatformForWorkspace(workspace)
const isRemote = workspace.repo ? repoIsRemote(workspace.repo) : Boolean(workspace.connectionId)
const queuedShell = resolveLocalWindowsAgentStartupShell({