From 17f032dbd554a0ac237dbaa67c48dbabff06d8b2 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Mon, 13 Jul 2026 05:39:52 -0700 Subject: [PATCH] Fix WSL orchestration CLI preambles (#8561) --- src/main/ipc/pty.test.ts | 40 ++++++++++--- src/main/ipc/pty.ts | 16 ++++- src/main/pty/wsl-orca-env.test.ts | 2 + src/main/pty/wsl-orca-env.ts | 1 + src/main/runtime/orca-runtime.ts | 43 +++++++++++++- .../runtime/orchestration/cli-command.test.ts | 59 +++++++++++++++++++ src/main/runtime/orchestration/cli-command.ts | 25 ++++++++ .../runtime/orchestration/coordinator.test.ts | 7 +++ src/main/runtime/orchestration/coordinator.ts | 6 ++ .../runtime/orchestration/preamble.test.ts | 11 +++- src/main/runtime/orchestration/preamble.ts | 7 ++- .../runtime/rpc/methods/orchestration.test.ts | 16 +++++ src/main/runtime/rpc/methods/orchestration.ts | 14 +++-- 13 files changed, 228 insertions(+), 19 deletions(-) create mode 100644 src/main/runtime/orchestration/cli-command.test.ts create mode 100644 src/main/runtime/orchestration/cli-command.ts diff --git a/src/main/ipc/pty.test.ts b/src/main/ipc/pty.test.ts index 35d386eb4..483871180 100644 --- a/src/main/ipc/pty.test.ts +++ b/src/main/ipc/pty.test.ts @@ -1702,6 +1702,7 @@ describe('registerPtyHandlers', () => { // (the GNOME screen reader) inside Orca-managed terminals (#7904). expect(entries.indexOf(shimDir)).toBeGreaterThanOrEqual(0) expect(entries.indexOf(shimDir)).toBeLessThan(entries.indexOf('/usr/bin')) + expect(env.ORCA_CLI_COMMAND).toBeUndefined() } finally { Object.defineProperty(process, 'platform', { configurable: true, @@ -1801,10 +1802,13 @@ describe('registerPtyHandlers', () => { // Why: runtime-created spawns (e.g. the mobile-create materialize path) // must thread the same {tabId, leafId} so the catch-path rescue can find // and keep their live PTY (#7587). - expect(runtime.registerPty).toHaveBeenCalledWith(expect.any(String), 'wt-runtime', null, { - tabId: 'tab-1', - leafId - }) + expect(runtime.registerPty).toHaveBeenCalledWith( + expect.any(String), + 'wt-runtime', + null, + { tabId: 'tab-1', leafId }, + false + ) }) it('uses the owning project WSL runtime for runtime-created daemon PTYs', async () => { @@ -1859,6 +1863,13 @@ describe('registerPtyHandlers', () => { expect(spawnOptions.shellOverride).toBe('wsl.exe') expect(spawnOptions.terminalWindowsWslDistro).toBe('Ubuntu') expect(spawnOptions.terminalWindowsPowerShellImplementation).toBe('auto') + expect(runtime.registerPty).toHaveBeenCalledWith( + expect.any(String), + 'repo-1::C:\\repo', + null, + undefined, + true + ) }) }) @@ -4558,10 +4569,13 @@ describe('registerPtyHandlers', () => { // Why: this is the load-bearing wiring for #7587 — the runtime can only back a // stalled mobile create from a live spawn if the spawn threads {tabId, leafId}. - expect(runtime.registerPty).toHaveBeenCalledWith(expect.any(String), 'wt-1', null, { - tabId: 'tab-1', - leafId - }) + expect(runtime.registerPty).toHaveBeenCalledWith( + expect.any(String), + 'wt-1', + null, + { tabId: 'tab-1', leafId }, + false + ) }) it('omits the pane identity from registerPty when the leafId is not a terminal leaf (#7587)', async () => { @@ -4590,7 +4604,13 @@ describe('registerPtyHandlers', () => { // Why: legacy numeric pane ids (`pane:N`) are not terminal leaf ids, so the // spawn seam must not fabricate a binding for them (registerPty would ignore // it anyway); this pins that the seam passes a clean `undefined`. - expect(runtime.registerPty).toHaveBeenCalledWith(expect.any(String), 'wt-1', null, undefined) + expect(runtime.registerPty).toHaveBeenCalledWith( + expect.any(String), + 'wt-1', + null, + undefined, + false + ) }) it('refreshes native Agent Teams env when captured teammate mode lives in launch args', async () => { @@ -6069,10 +6089,12 @@ describe('registerPtyHandlers', () => { expect(spawnCall[0]).toBe('wsl.exe') expect(env.ORCA_TERMINAL_HANDLE).toBe('term_wsl') expect(env.ORCA_USER_DATA_PATH).toBe('/tmp/orca-user-data') + expect(env.ORCA_CLI_COMMAND).toBe('orca-ide') expect(env.WSLENV?.split(':')).toEqual( expect.arrayContaining([ 'ORCA_TERMINAL_HANDLE/u', 'ORCA_USER_DATA_PATH/p', + 'ORCA_CLI_COMMAND/u', 'ORCA_AGENT_HOOK_PORT/u', 'ORCA_AGENT_HOOK_TOKEN/u', 'ORCA_OMP_SOURCE_AGENT_DIR/p', diff --git a/src/main/ipc/pty.ts b/src/main/ipc/pty.ts index 305ebabfc..0dd30b24b 100644 --- a/src/main/ipc/pty.ts +++ b/src/main/ipc/pty.ts @@ -980,8 +980,14 @@ export function buildPtyHostEnv( // Why: WSL shells need the managed userData root for shell-ready wrappers; dev-mode terminals need the same export so `orca` targets the live dev instance. if (opts.isWsl) { baseEnv.ORCA_USER_DATA_PATH = opts.userDataPath - } else if (!opts.isPackaged) { - baseEnv.ORCA_USER_DATA_PATH ??= opts.userDataPath + // Why: managed WSL registration deliberately uses `orca-ide`; exposing + // that literal keeps agent guidance scoped to WSL without a bare-orca shim. + baseEnv.ORCA_CLI_COMMAND = opts.isPackaged ? 'orca-ide' : 'orca-dev' + } else { + if (!opts.isPackaged) { + baseEnv.ORCA_USER_DATA_PATH ??= opts.userDataPath + } + delete baseEnv.ORCA_CLI_COMMAND } // Why: dev mode needs the launcher PATH override so `orca` resolves to the dev build instead of the production binary at /usr/local/bin/orca. if (!opts.isPackaged) { @@ -3234,6 +3240,9 @@ export function registerPtyHandlers( args.tabId.length <= 512 && metadataLeafId !== null ? { tabId: args.tabId, leafId: metadataLeafId } + : undefined, + !args.connectionId + ? shouldSkipCodexHomeEnvForWindowsShell(daemonShellOverride, cwd) : undefined ) } @@ -4282,6 +4291,9 @@ export function registerPtyHandlers( args.tabId.length <= 512 && metadataLeafId !== null ? { tabId: args.tabId, leafId: metadataLeafId } + : undefined, + !args.connectionId + ? shouldSkipCodexHomeEnvForWindowsShell(effectiveShellOverride, cwd) : undefined ) } diff --git a/src/main/pty/wsl-orca-env.test.ts b/src/main/pty/wsl-orca-env.test.ts index 550295460..fe0d94ab9 100644 --- a/src/main/pty/wsl-orca-env.test.ts +++ b/src/main/pty/wsl-orca-env.test.ts @@ -24,6 +24,7 @@ describe('addOrcaWslInteropEnv', () => { const env: Record = { ORCA_TERMINAL_HANDLE: 'term_wsl', ORCA_USER_DATA_PATH: 'C:\\Users\\jin\\AppData\\Roaming\\Orca', + ORCA_CLI_COMMAND: 'orca-ide', ORCA_OMP_STATUS_EXTENSION: 'C:\\Users\\jin\\.omp\\agent\\extensions\\orca-agent-status.ts', ORCA_PANE_KEY: 'tab-1:leaf-1', ORCA_TAB_ID: 'tab-1', @@ -38,6 +39,7 @@ describe('addOrcaWslInteropEnv', () => { expect(env.WSLENV).toContain('ORCA_TERMINAL_HANDLE/u') expect(env.WSLENV).toContain('ORCA_USER_DATA_PATH/p') + expect(env.WSLENV).toContain('ORCA_CLI_COMMAND/u') expect(env.WSLENV).toContain('ORCA_OMP_STATUS_EXTENSION/p') expect(env.WSLENV).toContain('ORCA_PANE_KEY/u') expect(env.WSLENV).toContain('ORCA_TAB_ID/u') diff --git a/src/main/pty/wsl-orca-env.ts b/src/main/pty/wsl-orca-env.ts index 161621e8c..3f136e0a2 100644 --- a/src/main/pty/wsl-orca-env.ts +++ b/src/main/pty/wsl-orca-env.ts @@ -24,6 +24,7 @@ export function addOrcaWslInteropEnv(env: Record): void { const passthroughEntries = [ 'ORCA_TERMINAL_HANDLE/u', 'ORCA_USER_DATA_PATH/p', + 'ORCA_CLI_COMMAND/u', 'ORCA_PANE_KEY/u', 'ORCA_TAB_ID/u', 'ORCA_WORKTREE_ID/u', diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index 293075176..09185299c 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -469,7 +469,9 @@ import { resolveLocalProjectRuntimeForRepo, resolveLocalProjectRuntimesForRepos } from '../project-runtime-git-options' +import { resolveLocalProjectRuntimeForWorktreeId } from '../local-project-runtime-resolution' import type { ProjectExecutionRuntimeResolution } from '../../shared/project-execution-runtime' +import { resolveTerminalOrchestrationCliCommand } from './orchestration/cli-command' import { getLocalWorktreePathAccess, removeLocalWorktreePath, @@ -976,6 +978,9 @@ type RuntimePtyWorktreeRecord = { ptyId: string worktreeId: string connectionId: string | null + // Why: a Windows host can own both native and WSL panes; preamble command + // selection must follow the pane that executes it, not process.platform. + isWsl: boolean | null // Why: background CLI PTYs can outlive a failed renderer reveal. Preserve the // spawn-time tab/pane identity so later reveals can adopt under the env key. tabId: string | null @@ -5529,7 +5534,8 @@ export class OrcaRuntimeService { ptyId: string, worktreeId: string, connectionId: string | null = null, - binding?: { tabId: string; leafId: string } + binding?: { tabId: string; leafId: string }, + isWsl?: boolean ): void { // Why: record the renderer pane identity at spawn time so a stalled graph // sync can't hide that a live PTY already backs a pending mobile create. @@ -5540,6 +5546,7 @@ export class OrcaRuntimeService { this.recordPtyWorktree(ptyId, worktreeId, { connected: true, connectionId, + ...(isWsl !== undefined ? { isWsl } : {}), ...(binding && paneKey ? { tabId: binding.tabId, paneKey } : {}) }) // Why: the renderer's own PTY spawn is the reliable signal that the pending @@ -7234,6 +7241,27 @@ export class OrcaRuntimeService { return pty ? { worktreeId: pty.worktreeId, connectionId: pty.connectionId } : null } + getTerminalOrchestrationCliCommand(handle: string): 'orca' | 'orca-ide' { + let pty: RuntimePtyWorktreeRecord | null = null + try { + const ptyId = this.resolveLeafForHandle(handle)?.ptyId + pty = ptyId ? (this.ptysById.get(ptyId) ?? null) : null + } catch { + return 'orca' + } + if (!pty) { + return 'orca' + } + return resolveTerminalOrchestrationCliCommand({ + connectionId: pty.connectionId, + isWsl: pty.isWsl, + worktreeId: pty.worktreeId, + projectRuntime: this.store + ? resolveLocalProjectRuntimeForWorktreeId(this.requireStore(), pty.worktreeId) + : undefined + }) + } + hasRecentTerminalOutputPath(handle: string, pathText: string, absolutePath: string): boolean { const ptyId = this.resolveLeafForHandle(handle)?.ptyId const recentOutput = ptyId ? this.recentPtyOutputById.get(ptyId) : null @@ -20057,7 +20085,14 @@ export class OrcaRuntimeService { state: Partial< Pick< RuntimePtyWorktreeRecord, - 'connected' | 'lastOutputAt' | 'preview' | 'tabId' | 'paneKey' | 'title' | 'connectionId' + | 'connected' + | 'lastOutputAt' + | 'preview' + | 'tabId' + | 'paneKey' + | 'title' + | 'connectionId' + | 'isWsl' > > = {} ): RuntimePtyWorktreeRecord { @@ -20068,6 +20103,7 @@ export class OrcaRuntimeService { ptyId, worktreeId, connectionId: state.connectionId ?? parseAppSshPtyId(ptyId)?.connectionId ?? null, + isWsl: state.isWsl ?? null, tabId: state.tabId ?? null, paneKey: state.paneKey ?? null, launchConfig: null, @@ -20109,6 +20145,9 @@ export class OrcaRuntimeService { if (state.connectionId !== undefined) { pty.connectionId = state.connectionId } + if (state.isWsl !== undefined) { + pty.isWsl = state.isWsl + } if (state.tabId !== undefined) { pty.tabId = state.tabId } diff --git a/src/main/runtime/orchestration/cli-command.test.ts b/src/main/runtime/orchestration/cli-command.test.ts new file mode 100644 index 000000000..1d07d335c --- /dev/null +++ b/src/main/runtime/orchestration/cli-command.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest' +import { resolveTerminalOrchestrationCliCommand } from './cli-command' + +describe('resolveTerminalOrchestrationCliCommand', () => { + it('uses orca-ide for a pane recorded as WSL', () => { + expect( + resolveTerminalOrchestrationCliCommand({ + connectionId: null, + isWsl: true, + worktreeId: 'repo::C:\\repo' + }) + ).toBe('orca-ide') + }) + + it('uses project runtime and WSL paths when restored pane metadata is unavailable', () => { + expect( + resolveTerminalOrchestrationCliCommand({ + connectionId: null, + isWsl: null, + worktreeId: 'repo::C:\\repo', + projectRuntime: { + status: 'resolved', + runtime: { + kind: 'wsl', + hostPlatform: 'wsl', + projectId: 'project', + distro: 'Ubuntu', + reason: 'project-override', + cacheKey: 'project:wsl:Ubuntu' + } + } + }) + ).toBe('orca-ide') + expect( + resolveTerminalOrchestrationCliCommand({ + connectionId: null, + isWsl: null, + worktreeId: 'repo::\\\\wsl.localhost\\Ubuntu\\home\\alice\\repo' + }) + ).toBe('orca-ide') + }) + + it('preserves native and SSH bare-orca commands', () => { + expect( + resolveTerminalOrchestrationCliCommand({ + connectionId: null, + isWsl: false, + worktreeId: 'repo::/home/alice/repo' + }) + ).toBe('orca') + expect( + resolveTerminalOrchestrationCliCommand({ + connectionId: 'ssh-1', + isWsl: null, + worktreeId: 'repo::\\\\wsl.localhost\\Ubuntu\\home\\alice\\repo' + }) + ).toBe('orca') + }) +}) diff --git a/src/main/runtime/orchestration/cli-command.ts b/src/main/runtime/orchestration/cli-command.ts new file mode 100644 index 000000000..c0f4883f1 --- /dev/null +++ b/src/main/runtime/orchestration/cli-command.ts @@ -0,0 +1,25 @@ +import type { ProjectExecutionRuntimeResolution } from '../../../shared/project-execution-runtime' +import { isWslUncPath } from '../../../shared/wsl-paths' +import { splitWorktreeIdForFilesystem } from '../../../shared/worktree-id' + +export type OrchestrationCliCommand = 'orca' | 'orca-ide' + +export function resolveTerminalOrchestrationCliCommand(args: { + connectionId: string | null + isWsl: boolean | null | undefined + worktreeId: string + projectRuntime?: ProjectExecutionRuntimeResolution +}): OrchestrationCliCommand { + if (args.connectionId) { + return 'orca' + } + if (args.isWsl !== null && args.isWsl !== undefined) { + return args.isWsl ? 'orca-ide' : 'orca' + } + if (args.projectRuntime?.status === 'resolved' && args.projectRuntime.runtime.kind === 'wsl') { + return 'orca-ide' + } + + const worktreePath = splitWorktreeIdForFilesystem(args.worktreeId)?.worktreePath + return worktreePath && isWslUncPath(worktreePath) ? 'orca-ide' : 'orca' +} diff --git a/src/main/runtime/orchestration/coordinator.test.ts b/src/main/runtime/orchestration/coordinator.test.ts index 8563c3a1e..cb532b71e 100644 --- a/src/main/runtime/orchestration/coordinator.test.ts +++ b/src/main/runtime/orchestration/coordinator.test.ts @@ -21,6 +21,7 @@ function createMockRuntime(): CoordinatorRuntime & { createdTerminalOptions: { title?: string }[] probeDriftCalls: string[] probeDriftResult: DriftResult + cliCommand: 'orca' | 'orca-ide' setProbeDrift(result: DriftResult): void throwProbeDrift: Error | null } { @@ -36,6 +37,7 @@ function createMockRuntime(): CoordinatorRuntime & { createdTerminalOptions: [] as { title?: string }[], probeDriftCalls: [] as string[], probeDriftResult: null as DriftResult, + cliCommand: 'orca' as 'orca' | 'orca-ide', throwProbeDrift: null as Error | null, setProbeDrift(result: DriftResult): void { mock.probeDriftResult = result @@ -63,6 +65,9 @@ function createMockRuntime(): CoordinatorRuntime & { throw mock.throwProbeDrift } return mock.probeDriftResult + }, + getTerminalOrchestrationCliCommand() { + return mock.cliCommand } } return mock @@ -121,6 +126,7 @@ describe('Coordinator', () => { it('dispatches a ready task to an available terminal', async () => { db = new OrchestrationDb(':memory:') const runtime = createMockRuntime() + runtime.cliCommand = 'orca-ide' runtime.terminals = [{ handle: 'term_a', worktreeId: 'wt1', connected: true, writable: true }] const task = db.createTask({ spec: 'implement feature' }) @@ -147,6 +153,7 @@ describe('Coordinator', () => { expect(result.status).toBe('completed') expect(result.completedTasks).toContain(task.id) expect(runtime.sentMessages.length).toBeGreaterThan(0) + expect(runtime.sentMessages[0].text).toContain('orca-ide orchestration send') }) it('records the assignee pane key when the runtime can resolve one', async () => { diff --git a/src/main/runtime/orchestration/coordinator.ts b/src/main/runtime/orchestration/coordinator.ts index 73307eddd..04f078857 100644 --- a/src/main/runtime/orchestration/coordinator.ts +++ b/src/main/runtime/orchestration/coordinator.ts @@ -32,6 +32,9 @@ export type CoordinatorRuntime = { // Why: optional so lightweight runtime fakes keep compiling; when present, // dispatch records the remint-stable pane identity of the assignee. getTerminalPaneKey?(handle: string): string | null + // Why: Windows can host native and WSL workers concurrently, so the + // worker pane—not the coordinator process—selects the packaged CLI name. + getTerminalOrchestrationCliCommand?(handle: string): 'orca' | 'orca-ide' } // Why (§3.1): single threshold, no warn/refuse split. Coordinator picked 20 @@ -486,6 +489,9 @@ export class Coordinator { coordinatorHandle: this.opts.coordinatorHandle, workerHandle: targetHandle, devMode: process.env.ORCA_USER_DATA_PATH?.includes('orca-dev'), + ...(this.runtime.getTerminalOrchestrationCliCommand + ? { cliCommand: this.runtime.getTerminalOrchestrationCliCommand(targetHandle) } + : {}), // Why (§3.2): drift section fires only when behind > 0. The preamble // builder gates on this itself; passing the object unconditionally lets // the coordinator stay dumb about the display rule. diff --git a/src/main/runtime/orchestration/preamble.test.ts b/src/main/runtime/orchestration/preamble.test.ts index 198437e4c..ef1335510 100644 --- a/src/main/runtime/orchestration/preamble.test.ts +++ b/src/main/runtime/orchestration/preamble.test.ts @@ -159,7 +159,7 @@ describe('buildDispatchPreamble', () => { }) it('uses orca-dev CLI when devMode is true', () => { - const result = buildDispatchPreamble(baseParams({ devMode: true })) + const result = buildDispatchPreamble(baseParams({ devMode: true, cliCommand: 'orca-ide' })) expect(result).toContain('orca-dev orchestration send') expect(result).toContain('orca-dev orchestration check') expect(result).toContain('orca-dev orchestration ask') @@ -175,6 +175,15 @@ describe('buildDispatchPreamble', () => { expect(result).toContain('orca orchestration check') }) + it('uses the exact orca-ide command for packaged WSL workers', () => { + const result = buildDispatchPreamble(baseParams({ cliCommand: 'orca-ide' })) + + expect(result).toContain('orca-ide orchestration send') + expect(result).toContain('orca-ide orchestration check') + expect(result).toContain('orca-ide orchestration ask') + expect(result).not.toMatch(/(^|\s)orca orchestration/m) + }) + it('appends a BASE DRIFT section when baseDrift.behind > 0', () => { const result = buildDispatchPreamble({ taskId: 'task_x', diff --git a/src/main/runtime/orchestration/preamble.ts b/src/main/runtime/orchestration/preamble.ts index 9aaa48831..a8012c568 100644 --- a/src/main/runtime/orchestration/preamble.ts +++ b/src/main/runtime/orchestration/preamble.ts @@ -1,3 +1,5 @@ +import type { OrchestrationCliCommand } from './cli-command' + export type PreambleParams = { taskId: string // Why: completion and heartbeat payloads attribute activity to a specific @@ -10,6 +12,9 @@ export type PreambleParams = { coordinatorHandle: string workerHandle: string devMode?: boolean + // Why: packaged WSL panes install the scoped launcher as `orca-ide`; + // other execution hosts keep their existing bare `orca` bridge. + cliCommand?: OrchestrationCliCommand // Why: populated by the coordinator's dispatch pre-flight (§3.1) only // when the target worktree is behind its tracking remote. When absent // or when `behind === 0`, the preamble emits no drift section. Callers @@ -42,7 +47,7 @@ export function buildDispatchPreamble(params: PreambleParams): string { // Why: in dev mode, agents must use orca-dev to connect to the dev runtime's // socket. Without this, agents inside the dev Electron app would call the // production CLI and talk to the wrong Orca instance (Section 6.4). - const cli = params.devMode ? 'orca-dev' : 'orca' + const cli = params.devMode ? 'orca-dev' : (params.cliCommand ?? 'orca') const postDoneInstructions = buildPostWorkerDoneInstructions({ cli, workerKind: params.workerKind ?? 'prompt-returning-agent' diff --git a/src/main/runtime/rpc/methods/orchestration.test.ts b/src/main/runtime/rpc/methods/orchestration.test.ts index fc7536c2e..95339cb31 100644 --- a/src/main/runtime/rpc/methods/orchestration.test.ts +++ b/src/main/runtime/rpc/methods/orchestration.test.ts @@ -1369,6 +1369,22 @@ describe('orchestration RPC methods', () => { ) }) + it('uses the target pane CLI command for the returned preamble', async () => { + setup() + const task = db.createTask({ spec: 'work' }) + vi.spyOn(runtime, 'getTerminalOrchestrationCliCommand').mockReturnValue('orca-ide') + + const result = (await call('orchestration.dispatch', { + task: task.id, + to: 'term_wsl', + returnPreamble: true + })) as { preamble: string } + + expect(runtime.getTerminalOrchestrationCliCommand).toHaveBeenCalledWith('term_wsl') + expect(result.preamble).toContain('orca-ide orchestration send') + expect(result.preamble).not.toMatch(/(^|\s)orca orchestration/m) + }) + it('injects preamble through the agent prompt path instead of raw terminal send', async () => { setup() const task = db.createTask({ spec: 'line one\nline two' }) diff --git a/src/main/runtime/rpc/methods/orchestration.ts b/src/main/runtime/rpc/methods/orchestration.ts index 9caaf1ac5..bd2721d1b 100644 --- a/src/main/runtime/rpc/methods/orchestration.ts +++ b/src/main/runtime/rpc/methods/orchestration.ts @@ -495,7 +495,10 @@ export const ORCHESTRATION_METHODS: RpcMethod[] = [ taskSpec: task.spec, coordinatorHandle: params.from ?? 'coordinator', workerHandle: params.to ?? 'worker', - devMode: params.devMode + devMode: params.devMode, + ...(params.to + ? { cliCommand: runtime.getTerminalOrchestrationCliCommand(params.to) } + : {}) }) return { dispatch: null, injected: false, dryRun: true, preamble } } @@ -540,7 +543,8 @@ export const ORCHESTRATION_METHODS: RpcMethod[] = [ taskSpec: task.spec, coordinatorHandle: params.from ?? 'coordinator', workerHandle: to, - devMode: params.devMode + devMode: params.devMode, + cliCommand: runtime.getTerminalOrchestrationCliCommand(to) }) let injected = false @@ -583,6 +587,7 @@ export const ORCHESTRATION_METHODS: RpcMethod[] = [ if (!task) { throw new Error(`Task not found: ${params.task}`) } + const workerHandle = ctx?.assignee_handle ?? 'worker' const preamble = buildDispatchPreamble({ taskId: task.id, // Why: prefer the existing dispatch context's id if we have one @@ -591,8 +596,9 @@ export const ORCHESTRATION_METHODS: RpcMethod[] = [ dispatchId: ctx?.id ?? 'ctx_preview', taskSpec: task.spec, coordinatorHandle: params.from ?? 'coordinator', - workerHandle: ctx?.assignee_handle ?? 'worker', - devMode: params.devMode + workerHandle, + devMode: params.devMode, + ...(ctx ? { cliCommand: runtime.getTerminalOrchestrationCliCommand(workerHandle) } : {}) }) return { dispatch: ctx ?? null, preamble } }