diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index 3699c8a74fe..c52ae5f48fc 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -12611,9 +12611,49 @@ describe('OrcaRuntimeService', () => { await runtime.createTerminal(`path:${TEST_WORKTREE_PATH}`, { startupAgent: 'cursor' }) const spawnCall = spawn.mock.calls[0]?.[0] as { command?: string } | undefined - expect(spawnCall?.command).toMatch(/^cursor-agent(\s|$)/) + // Why: assert the cmd.exe double quoting too — a platform-insensitive prefix + // match would pass on any OS and prove nothing about the reported platform. + expect(spawnCall?.command).toBe('cursor-agent "--yolo"') }) + // Why: claude-agent-teams is the only agent whose launcher name varies by + // platform (launchCmdByPlatform), so it is what proves resolution is + // platform-aware rather than a fixed string. + it.each([ + { platform: 'win32' as const, expected: 'orca.cmd claude-teams' }, + { platform: 'linux' as const, expected: 'orca-ide claude-teams' }, + { platform: 'darwin' as const, expected: 'orca claude-teams' } + ])( + 'resolves a startupAgent through the $platform launcher name', + async ({ platform, expected }) => { + setPlatform(platform) + const spawn = vi.fn().mockResolvedValue({ id: 'pty-bg' }) + const runtime = new OrcaRuntimeService({ + ...store, + getSettings: () => ({ + ...store.getSettings(), + disabledTuiAgents: [], + agentCmdOverrides: {}, + agentDefaultArgs: { 'claude-agent-teams': '' }, + agentDefaultEnv: {} + }) + }) + runtime.setPtyController({ + spawn, + write: () => true, + kill: () => true, + getForegroundProcess: async () => null + }) + + await runtime.createTerminal(`path:${TEST_WORKTREE_PATH}`, { + startupAgent: 'claude-agent-teams' + }) + + const spawnCall = spawn.mock.calls[0]?.[0] as { command?: string } | undefined + expect(spawnCall?.command).toBe(expected) + } + ) + // 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 () => { @@ -12672,7 +12712,10 @@ describe('OrcaRuntimeService', () => { { 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 } + { launchAgent: 'cursor' as const }, + { launchConfig: { agentArgs: '', agentEnv: {} } as never }, + { startupCommandDelivery: 'provider' as never }, + { claudeAgentTeamsSourceCommand: 'claude' } ]) { await expect( runtime.createTerminal(`path:${TEST_WORKTREE_PATH}`, { diff --git a/src/main/runtime/rpc/methods/orchestration-federation-agent-launch.test.ts b/src/main/runtime/rpc/methods/orchestration-federation-agent-launch.test.ts new file mode 100644 index 00000000000..af3467a9bd5 --- /dev/null +++ b/src/main/runtime/rpc/methods/orchestration-federation-agent-launch.test.ts @@ -0,0 +1,74 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { OrcaRuntimeService } from '../../orca-runtime' +import { OrchestrationDb } from '../../orchestration/db' +import { ORCHESTRATION_METHODS } from './orchestration' + +// Why: a federated worker terminal is created from an agent id. Passing that id +// as a shell command launched Cursor's desktop app instead of `cursor-agent` +// (issue #11926), so the remote path must resolve through the TUI agent config +// exactly like the local one. +describe('federated worker agent launch', () => { + let db: OrchestrationDb | undefined + + afterEach(() => { + db?.close() + vi.restoreAllMocks() + }) + + it('creates the remote worker terminal from the agent id, never as a command', async () => { + db = new OrchestrationDb(':memory:') + const runtime = new OrcaRuntimeService() + runtime.setOrchestrationDb(db) + vi.spyOn(runtime, 'validateOrchestrationAgentLauncher').mockImplementation(() => {}) + vi.spyOn(runtime, 'showManagedWorktree').mockResolvedValue({ + id: 'repo::remote-worktree' + } as never) + const createTerminal = vi.spyOn(runtime, 'createTerminal').mockResolvedValue({ + handle: 'term_remote_worker', + worktreeId: 'repo::remote-worktree', + title: 'worker' + }) + vi.spyOn(runtime, 'waitForTerminal').mockResolvedValue({ + handle: 'term_remote_worker', + condition: 'tui-idle', + satisfied: true, + status: 'running', + exitCode: null + }) + const method = ORCHESTRATION_METHODS.find( + (candidate) => candidate.name === 'orchestration.federationAttachStart' + ) + if (!method) { + throw new Error('federationAttachStart method is not registered') + } + + await method.handler( + method.params!.parse({ + dispatchId: 'ctx_remote', + taskId: 'task_remote', + taskSpec: 'remote cursor worker', + protocolVersion: 1, + worktree: 'id:repo::remote-worktree', + agent: 'cursor' + }), + { + runtime, + orchestrationMutation: { + callerFingerprint: 'home_peer', + requestId: 'request_remote', + method: 'orchestration.federationAttachStart', + payloadHash: 'remote_payload' + } + } + ) + + expect(createTerminal).toHaveBeenCalledWith( + 'id:repo::remote-worktree', + expect.objectContaining({ startupAgent: 'cursor' }) + ) + expect(createTerminal).toHaveBeenCalledWith( + 'id:repo::remote-worktree', + expect.not.objectContaining({ command: expect.anything() }) + ) + }) +})