Start orchestration workers and remote workers through the launch executor (#26724)

* Start worktree.create's agent through the launch executor

The host's worktree.create handler now starts a requested agent (startupAgent,
or a linked startupDraft) through executeAgentLaunch instead of calling the
create directly. Nothing on the wire changes: the request, the reply and
agentTerminalHandle are the same, the agent is still the workspace's startup
terminal whatever the chat default says, and the create still delivers the
text itself (folded into the command at any length, a follow-up for agents
that read it after start, and the draft pasted unsent).

Adds three host-internal executor inputs: terminalOnly, the legacy-host prompt
policy, and a shared entry (createWorktreeWithStartupAgent) that host
producers can call in place of createManagedWorktree.

* Keep a linked-draft create recording only the agent it actually started

Problem: after worktree.create moved onto the launch executor, a create from
a linked issue (phone "create from issue", or a raw worktree.create with
startupDraft) could differ from before in two narrow cases. When the draft's
agent came from the settings default or from detection and its launch command
could not be built (for example unbalanced quotes in the saved agent
arguments), the workspace was recorded as created with that agent even though
it never started, and a background create opened no terminal at all instead
of its plain shell. When no agent was found, detection ran twice (an extra SSH
round trip on remote repos).

User-facing change: none intended; both cases now behave exactly as before
the executor move.

Mechanism:
- The entry hands the draft's chosen agent to the create as a main-internal
  startupDraftAgent, which steers only the draft; createdWithAgent stays what
  the caller sent. A draft that resolves no agent is created without the
  draft, so the create does not detect again.
- AgentLaunchExecution is a discriminated union: every normal launch must
  bring its surface factory, and a legacy-host launch must be terminal-only,
  bring a workspace factory, and cannot bring surfaces. The runtime guards
  the types replace are gone; the target kind is checked before any create.
- Legacy-host receipts follow the delivery module's rule: a blank prompt is
  no prompt, a submit folded into the launch command is handed-to-terminal,
  a post-start send the create does not await is unconfirmed (host-internal),
  a draft is not-delivered.
- The no-agent error lives with the legacy-host policy and is caught only by
  the shared entry.
- Tests: a real-runtime parity test (stored agent, terminal spawns,
  detection count vs the create alone, including the unbuildable plan),
  exact-argument checks over every non-agent startup field, compile-time
  checks for the union, and direct tests of the draft agent resolver.

* Say which creates still bypass the shared agent entry

Two comments stated that headless automations and federation already go
through createWorktreeWithStartupAgent; both still call
createManagedWorktree directly. Reword them as the plan. Comment-only.

* Start orchestration workers and remote workers through the launch executor

A fresh worker (new worktree or existing, terminal or structured) and a
federated worker on its receiving host now start their agent through
executeAgentLaunch with the worker's own workspace and surface factories.
The worker hands in the mode it decided before its dispatch record existed
(decidedMode), so the host is asked once and the stored receipt is the one
that ran. Effects, setup receipt, stage rows and their order are unchanged;
a refused structured worker still fails rather than becoming a terminal.

* Tighten the worker launch contract and discard a worker session a later launch step strands

- decidedMode is now exclusive with terminalOnly and callerRendersStructured in the type, labelled temporary until one planner settles every launch.
- connectionId's doc says what is true: the executor carries it and nothing reads it yet.
- A worker launch that throws after its structured session exists discards that session itself, since the caller only tears down a returned placement.
- A federated create refuses a launch with no startup agent and narrows repo/name before recording its first stage.
- Tests: exact federated existing-path stages, a created-worktree host refusal that starts a worker terminal, and the stranded-session discard.

* Say exactly which inputs a decided launch mode refuses, and pin the refusal at compile time
This commit is contained in:
Brennan Benson
2026-10-09 00:39:13 -07:00
committed by GitHub
parent bfd9858242
commit 418c601d20
11 changed files with 943 additions and 197 deletions
@@ -0,0 +1,33 @@
import { describe, expect, it, vi } from 'vitest'
import type { AgentLaunchExecution } from './agent-launch-executor'
import type { AgentLaunchModeReceipt } from './agent-launch-mode'
import type { AgentLaunchSurfaceFactory } from './agent-launch-surface-factories'
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: only the execution's type is checked; nothing runs it.
const runtime = {} as AgentLaunchExecution['runtime']
const decidedMode: AgentLaunchModeReceipt = {
mode: 'terminal',
preferred: 'terminal',
reason: 'user_default',
detail: 'Started a terminal agent, the default for new agent tabs in your settings.'
}
describe('a caller-decided launch mode', () => {
it('cannot be combined with the pre-flight inputs it replaces', () => {
const intent = {
agent: 'claude' as const,
target: { kind: 'existing' as const, worktree: 'wt' }
}
const surfaces: AgentLaunchSurfaceFactory = {
createStructuredSession: vi.fn(),
createTerminalAgent: vi.fn()
}
const accepts = (execution: AgentLaunchExecution) => execution
// @ts-expect-error a decided mode already settled what terminalOnly would force
accepts({ runtime, intent, surfaces, decidedMode, terminalOnly: true })
// @ts-expect-error a decided mode already settled what the caller's chat support would downgrade
accepts({ runtime, intent, surfaces, decidedMode, callerRendersStructured: false })
expect(accepts({ runtime, intent, surfaces, decidedMode }).decidedMode).toBe(decidedMode)
expect(accepts({ runtime, intent, surfaces, terminalOnly: true }).terminalOnly).toBe(true)
})
})
@@ -11,7 +11,7 @@ import type {
AgentLaunchTarget
} from '../../shared/agent-launch-intent'
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
import type { AgentLaunchModeVocabulary } from './agent-launch-mode'
import type { AgentLaunchModeReceipt, AgentLaunchModeVocabulary } from './agent-launch-mode'
import type {
AgentLaunchSurfaceFactory,
AgentLaunchWorkspaceFactory
@@ -46,7 +46,19 @@ export type AgentLaunchSurfaceExecution = AgentLaunchExecutionBase & {
terminalOnly?: boolean
/** The launch's own delivery: argv only when the typed line carries it, else a paste. */
promptPolicy?: undefined
}
} & (
| { decidedMode?: undefined }
| {
/** Host-internal: the pre-flight a caller decided itself because it records the receipt
* before the launch runs (an orchestration dispatch). For an existing workspace it must
* already carry that host's answer; the executor asks the host only about a workspace it
* creates. It replaces the pre-flight, so `terminalOnly` and `callerRendersStructured` are
* refused. Temporary until one planner settles every launch. */
decidedMode: AgentLaunchModeReceipt
terminalOnly?: never
callerRendersStructured?: never
}
)
/**
* Host-internal, never on the wire. `legacy-host` is `worktree.create`'s own delivery, kept for the
@@ -64,6 +76,7 @@ export type AgentLaunchLegacyHostExecution = AgentLaunchExecutionBase & {
terminalOnly: true
workspaces: AgentLaunchWorkspaceFactory
surfaces?: never
decidedMode?: never
}
export type AgentLaunchExecution = AgentLaunchSurfaceExecution | AgentLaunchLegacyHostExecution
+34 -29
View File
@@ -1,8 +1,8 @@
/**
* The one place an agent launch is sequenced — for the surfaces moved onto it: `agent.launch`, and
* The one place an agent launch is sequenced — for the surfaces moved onto it: `agent.launch`,
* `worktree.create` (CLI and mobile create) through `createWorktreeWithStartupAgent`, whose
* `legacy-host` create still starts the agent itself. Orchestration dispatch and the desktop agent
* tab each still start agents their own way; moving them here is later stack work.
* `legacy-host` create still starts the agent itself, and orchestration workers, local and
* federated. The desktop agent tab still starts agents its own way; moving it here is later work.
*
* The mode decision is shared, not copied: `agent-launch-mode` owns it, and
* `orchestration-worker-start-mode` is a thin adapter over it supplying orchestration's receipt
@@ -81,22 +81,23 @@ export async function executeAgentLaunch(
assertOpenCodeModelLaunchPreferencesAbsent(intent.agent, intent.sessionOptions)
}
const vocabulary = execution.vocabulary ?? DEFAULT_LAUNCH_VOCABULARY
const settings = readAgentLaunchModeSettings(runtime)
const preflight = decideAgentLaunchMode({
placement: {
agent: intent.agent,
workspaceKind: launchWorkspaceKind(intent.target),
...(intent.reuseTerminal ? { terminal: intent.reuseTerminal.handle } : {}),
...(intent.cwd ? { cwd: intent.cwd } : {}),
...(intent.target.kind === 'existing' && intent.target.workspacePath
? { workspacePath: intent.target.workspacePath }
: {}),
...(execution.callerRendersStructured === false ? { callerRendersStructured: false } : {})
},
settings,
...(execution.terminalOnly ? { terminalOnly: true } : {}),
vocabulary
})
const preflight =
execution.decidedMode ??
decideAgentLaunchMode({
placement: {
agent: intent.agent,
workspaceKind: launchWorkspaceKind(intent.target),
...(intent.reuseTerminal ? { terminal: intent.reuseTerminal.handle } : {}),
...(intent.cwd ? { cwd: intent.cwd } : {}),
...(intent.target.kind === 'existing' && intent.target.workspacePath
? { workspacePath: intent.target.workspacePath }
: {}),
...(execution.callerRendersStructured === false ? { callerRendersStructured: false } : {})
},
settings: readAgentLaunchModeSettings(runtime),
...(execution.terminalOnly ? { terminalOnly: true } : {}),
vocabulary
})
// The create's startup terminal is this launch's only surface, and the create delivers the text.
if (execution.promptPolicy === 'legacy-host') {
assertLegacyHostTarget(execution)
@@ -152,13 +153,17 @@ export async function executeAgentLaunch(
}
execution.onStage?.('mode_settle')
let settled = await resolveAgentLaunchModeOnHost(
runtime,
preflight,
placed.worktreeId,
intent.agent,
vocabulary
)
// A caller's own decision about an existing workspace already carries that host's answer.
let settled =
execution.decidedMode && intent.target.kind === 'existing'
? preflight
: await resolveAgentLaunchModeOnHost(
runtime,
preflight,
placed.worktreeId,
intent.agent,
vocabulary
)
execution.onStage?.('surface_create')
let created: CreatedSurface
@@ -228,7 +233,7 @@ async function resolveWorkspace(
preflight: AgentLaunchModeReceipt
): Promise<{
worktreeId: string
connectionId: string | null | undefined
connectionId?: string | null
startupTerminalHandle: string | undefined
startupTerminalPaneKey?: string
warning?: string
@@ -275,7 +280,7 @@ export type CreatedSurface = {
async function createSurface(
execution: AgentLaunchSurfaceExecution,
workspace: { worktreeId: string; connectionId: string | null | undefined },
workspace: { worktreeId: string; connectionId?: string | null },
settled: AgentLaunchModeReceipt
): Promise<CreatedSurface> {
const { intent, surfaces } = execution
@@ -336,7 +341,7 @@ function terminalLaunchInputs(intent: AgentLaunchIntent) {
*/
async function createTerminalSurface(
execution: AgentLaunchSurfaceExecution,
workspace: { worktreeId: string; connectionId: string | null | undefined }
workspace: { worktreeId: string; connectionId?: string | null }
): Promise<CreatedSurface> {
const { intent, surfaces } = execution
const startupPrompt = argvLaunchPrompt(intent)
@@ -123,8 +123,9 @@ export type AgentLaunchWorkspaceFactory = {
options?: Readonly<Record<string, unknown>>
}): Promise<{
worktreeId: string
/** The new workspace's SSH connection; `null` is local. Decides what its agent tab can show. */
connectionId: string | null
/** The new workspace's SSH connection; `null` is local. The executor carries it but nothing
* reads it yet; absent when the factory did not resolve it. */
connectionId?: string | null
startupTerminalHandle: string | undefined
/** The pane minted with the startup terminal, when the runtime reported one. */
startupTerminalPaneKey?: string
@@ -85,7 +85,12 @@ export async function createStructuredWorkerSession(args: {
options?: Readonly<Record<string, string>>
/** Retried whenever the session's journal moves, which is the structured idle edge. */
onJournalActivity: (sessionId: string) => void
}): Promise<{ identity: StructuredWorkerIdentity; host: StructuredAgentSessionHost }> {
}): Promise<{
identity: StructuredWorkerIdentity
host: StructuredAgentSessionHost
/** The lease the create was admitted at. */
fence: number
}> {
const sessionId = randomUUID()
// Registered BEFORE the session is created, because `attach` is what spawns the provider child
// and the child's environment is read from this registry at spawn time. Registering afterwards
@@ -148,7 +153,7 @@ export async function createStructuredWorkerSession(args: {
handle: identity.handle,
disposeSubscription
})
return { identity, host }
return { identity, host, fence: created.value.fence }
} catch (error) {
// A start that fails after the session exists would otherwise strand a live provider child
// that no dispatch owns and that nothing else in the runtime will ever retire.
@@ -0,0 +1,177 @@
import { describe, expect, it, vi } from 'vitest'
import type { OrcaRuntimeService } from '../../../../orca-runtime'
import type { OrchestrationDb } from '../../../../orchestration/db'
import type { WorkerSetupReceipt } from '../worker/worker-topology'
import { launchFederatedWorkerAgent } from './federated-worker-agent-launch'
import type { FederationEffect } from './federation-effects'
import { FederationAttachStartParams } from './federation-start-schema'
function fakes(created: { startupTerminal?: { handle: string } } = {}) {
const stages: string[] = []
const runtime = {
createManagedWorktree: vi.fn(async () => ({
worktree: { id: 'repo::remote-wt', repoId: 'repo' },
setupReceipt: {
hookFound: true,
startupPolicy: 'wait-for-setup',
state: 'running',
terminalHandle: 'term_setup'
},
...created
})),
listTerminals: vi.fn(async () => ({
terminals: [
{ handle: 'term_setup', title: 'setup', tabId: 'tab_s', leafId: 'leaf_s' },
{ handle: 'term_agent', title: 'claude', tabId: 'tab_a', leafId: 'leaf_a' }
]
})),
createTerminal: vi.fn(async () => ({ handle: 'term_worker' })),
// Chat default on: federation still starts a terminal and never asks about a session.
getClientSettings: vi.fn(() => ({ experimentalNativeChat: true })),
getStructuredAgentSessionCreateSupport: vi.fn(async () => ({ supported: true }))
}
const db = {
recordRemoteAttachmentStage: vi.fn((row: { stage: string }) => {
stages.push(row.stage)
})
}
return { runtime, db, stages }
}
const PARAMS = FederationAttachStartParams.parse({
runId: 'run_home',
dispatchId: 'ctx_remote',
taskId: 'task_remote',
taskSpec: 'remote work',
protocolVersion: 3,
worktree: 'new-top-level',
repo: 'repo',
name: 'remote-wt',
agent: 'claude'
})
function launch(f: ReturnType<typeof fakes>, worktree?: { id: string }) {
const effects: FederationEffect[] = []
const setups: WorkerSetupReceipt[] = []
const stagesSeen: string[] = []
const done = launchFederatedWorkerAgent({
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the launch calls only the methods faked here.
runtime: f.runtime as unknown as OrcaRuntimeService,
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the launch records only attachment stages.
db: f.db as unknown as OrchestrationDb,
params: PARAMS,
agent: 'claude',
launchPreferences: { model: 'opus' },
...(worktree ? { worktree } : {}),
setupSource: 'orchestration_default',
effects,
onSetup: (setup) => setups.push(setup),
onStage: (stage) => stagesSeen.push(stage)
})
return { done, effects, setups, stagesSeen }
}
describe('a federated worker through the launch executor', () => {
it('creates the new top-level worktree agent-first with the attachment’s own records', async () => {
const f = fakes({ startupTerminal: { handle: 'term_agent' } })
const run = launch(f)
await expect(run.done).resolves.toEqual({
worktree: { id: 'repo::remote-wt', repoId: 'repo' },
terminalHandle: 'term_agent'
})
expect(f.runtime.createManagedWorktree).toHaveBeenCalledWith({
repoSelector: 'repo',
name: 'remote-wt',
baseBranch: undefined,
displayName: undefined,
displayNameKind: undefined,
comment: undefined,
runHooks: false,
setupDecision: 'run',
awaitTerminalProvisioning: true,
observeSetupCompletion: true,
createdWithAgent: 'claude',
startupAgent: 'claude',
startupLaunchSource: 'orchestration',
startupLaunchPreferences: { model: 'opus' },
activate: false,
lineage: { noParent: true }
})
expect(f.stages).toEqual(['worktree_creating'])
expect(run.stagesSeen).toEqual(['worktree_create'])
expect(f.runtime.createTerminal).not.toHaveBeenCalled()
expect(f.runtime.getStructuredAgentSessionCreateSupport).not.toHaveBeenCalled()
expect(run.effects).toEqual([
{ kind: 'worktree', action: 'created_top_level', id: 'repo::remote-wt' },
{
kind: 'terminal',
role: 'setup',
action: 'created',
id: 'term_setup',
tabId: 'tab_s',
leafId: 'leaf_s'
},
{
kind: 'terminal',
role: 'agent',
action: 'created',
id: 'term_agent',
tabId: 'tab_a',
leafId: 'leaf_a'
},
{
kind: 'setup',
action: 'run',
requested: 'run',
effective: 'run',
source: 'orchestration_default',
hookFound: true,
startupPolicy: 'wait-for-setup',
state: 'running',
terminalId: 'term_setup'
}
])
})
it('reports the created worktree’s setup before failing on a missing agent terminal', async () => {
const f = fakes({})
const run = launch(f)
await expect(run.done).rejects.toThrow('Agent-first worktree creation returned no terminal.')
expect(run.setups).toEqual([
{
requested: 'run',
effective: 'run',
source: 'orchestration_default',
hookFound: true,
startupPolicy: 'wait-for-setup',
state: 'running'
}
])
expect(f.runtime.createTerminal).not.toHaveBeenCalled()
})
it('opens a background worker-<task> terminal in an existing worktree', async () => {
const f = fakes()
const run = launch(f, { id: 'folder:remote' })
await expect(run.done).resolves.toEqual({
worktree: { id: 'folder:remote' },
terminalHandle: 'term_worker'
})
expect(f.runtime.createTerminal).toHaveBeenCalledWith('id:folder:remote', {
startupAgent: 'claude',
launchSource: 'orchestration',
launchPreferences: { model: 'opus' },
title: 'worker-task_remote',
presentation: 'background'
})
expect(run.effects).toEqual([
{ kind: 'terminal', role: 'agent', action: 'created', id: 'term_worker' }
])
expect(f.runtime.createManagedWorktree).not.toHaveBeenCalled()
expect(f.runtime.getStructuredAgentSessionCreateSupport).not.toHaveBeenCalled()
expect(run.stagesSeen).toEqual(['terminal_create', 'terminal_create'])
})
})
@@ -0,0 +1,150 @@
/**
* A federated worker's agent, started by the host that runs it through the shared executor.
*
* The receiving host owns execution, and federation creates terminal agents only, so the launch is
* `terminalOnly`. What makes it federation's is in the factories: a new top-level worktree created
* agent-first with the attachment's stages and effects, or a background `worker-<task>` terminal in
* an existing one. The dispatch brief is a later turn, outside the launch.
*/
import type { AgentLaunchPreferences } from '../../../../../../shared/agent-session-host-authority'
import type { TuiAgent } from '../../../../../../shared/tui-agent'
import { executeAgentLaunch } from '../../../../../agent-launch/agent-launch-executor'
import type { OrcaRuntimeService } from '../../../../orca-runtime'
import type { OrchestrationDb } from '../../../../orchestration/db'
import { WORKER_START_VOCABULARY } from '../../orchestration-worker-start-mode'
import type { WorkerSetupReceipt } from '../worker/worker-topology'
import {
appendFederationSetupEffect,
appendFederationTerminalEffects,
type FederationEffect
} from './federation-effects'
import type { FederationAttachStartInput } from './federation-start-schema'
const TERMINAL_ONLY_MESSAGE = 'A federated worker starts a terminal agent only.'
export async function launchFederatedWorkerAgent(args: {
runtime: OrcaRuntimeService
db: OrchestrationDb
params: FederationAttachStartInput
agent: TuiAgent | undefined
launchPreferences: AgentLaunchPreferences | undefined
/** The workspace to start in; absent creates the new top-level worktree the params describe. */
worktree?: { id: string }
setupSource: string
effects: FederationEffect[]
/** The created worktree's setup receipt, as soon as it exists, so a later failure reports it. */
onSetup: (setup: WorkerSetupReceipt) => void
onStage: (stage: 'worktree_create' | 'terminal_create') => void
}): Promise<{ worktree: { id: string }; terminalHandle: string }> {
const { runtime, db, params, effects } = args
if (!args.agent) {
// Validation refuses a fresh launch with no agent; this only narrows the type.
throw new Error(
'A configured --agent is required when federated worker-start creates a terminal.'
)
}
const agent = args.agent
let created: { id: string } | undefined
const launched = await executeAgentLaunch({
runtime,
intent: {
agent,
// Federation's own factory builds the create from the attachment, so the target carries none.
target: args.worktree
? { kind: 'existing', worktree: args.worktree.id }
: { kind: 'create-worktree', create: {} },
launchSource: 'orchestration'
},
terminalOnly: true,
vocabulary: WORKER_START_VOCABULARY,
onStage: (stage) =>
args.onStage(stage === 'worktree_create' ? 'worktree_create' : 'terminal_create'),
workspaces: {
createWorktree: async ({ startupAgent }) => {
// Agent-first is the only create here; a structured pre-flight must not reach it.
if (startupAgent === undefined) {
throw new Error(TERMINAL_ONLY_MESSAGE)
}
const { repo, name } = params
if (!repo || !name) {
// Validation refuses this before the attachment exists; this only narrows the types.
throw new Error('A remote new-top-level worktree requires --name and an explicit --repo.')
}
db.recordRemoteAttachmentStage({
dispatchId: params.dispatchId,
stage: 'worktree_creating'
})
const setupDecision = params.setup ?? 'run'
const result = await runtime.createManagedWorktree({
repoSelector: repo,
name,
baseBranch: params.baseBranch,
displayName: params.displayName,
displayNameKind: params.displayNameKind,
comment: params.comment,
// setupDecision runs setup without the legacy runHooks activation side effect.
runHooks: false,
setupDecision,
awaitTerminalProvisioning: true,
observeSetupCompletion: true,
createdWithAgent: agent,
startupAgent: agent,
startupLaunchSource: 'orchestration',
...(args.launchPreferences ? { startupLaunchPreferences: args.launchPreferences } : {}),
activate: false,
lineage: { noParent: true }
})
created = result.worktree
const terminalHandle = result.startupTerminal?.handle
effects.push({ kind: 'worktree', action: 'created_top_level', id: result.worktree.id })
const setup: WorkerSetupReceipt = {
requested: setupDecision,
effective: setupDecision,
source: args.setupSource,
hookFound: result.setupReceipt?.hookFound ?? false,
startupPolicy: result.setupReceipt?.startupPolicy ?? 'start-immediately',
state: result.setupReceipt?.state ?? 'not_configured'
}
args.onSetup(setup)
if (!terminalHandle) {
throw new Error(result.warning ?? 'Agent-first worktree creation returned no terminal.')
}
const listed = await runtime.listTerminals(`id:${result.worktree.id}`, undefined, {
includeVisualLayouts: false
})
appendFederationTerminalEffects(
effects,
listed.terminals,
terminalHandle,
result.setupReceipt?.terminalHandle
)
appendFederationSetupEffect(effects, setup)
return { worktreeId: result.worktree.id, startupTerminalHandle: terminalHandle }
}
},
surfaces: {
createStructuredSession: () => {
throw new Error(TERMINAL_ONLY_MESSAGE)
},
createTerminalAgent: async ({ worktreeId }) => {
const terminal = await runtime.createTerminal(`id:${worktreeId}`, {
// Why: agent ids are not shell commands (`cursor` is the desktop app,
// its CLI is `cursor-agent`); resolve through the TUI agent config.
startupAgent: agent,
launchSource: 'orchestration',
...(args.launchPreferences ? { launchPreferences: args.launchPreferences } : {}),
title: `worker-${params.taskId}`,
presentation: 'background'
})
effects.push({ kind: 'terminal', role: 'agent', action: 'created', id: terminal.handle })
return { handle: terminal.handle }
}
}
})
const worktree = args.worktree ?? created
if (!worktree) {
throw new Error('Federated worker topology did not resolve.')
}
return { worktree, terminalHandle: launched.outcome.handle }
}
@@ -1,5 +1,4 @@
import { waitForWorkerAgentReady } from '../../../../launched-agent-composer-readiness'
import type { TuiAgent } from '../../../../../../shared/tui-agent'
import { describeTerminalWaitBlockedReason } from '../../../../../../shared/terminal-wait-blocked-reason-legacy-alias'
import { buildDispatchPreamble } from '../../../../orchestration/preamble'
import { sendAgentTurn } from '../../../../orchestration/send-agent-turn'
@@ -7,11 +6,8 @@ import { createWorkerBriefWriteGuard } from '../../../../launched-agent-write-gu
import { OrchestrationError } from '../../../../orchestration/orchestration-error'
import { defineMethod } from '../../../core'
import { assertOrchestrationWorktreeCreationSupported } from '../worker/folder-worktree-placement'
import {
appendFederationSetupEffect,
appendFederationTerminalEffects,
type FederationEffect
} from './federation-effects'
import type { FederationEffect } from './federation-effects'
import { launchFederatedWorkerAgent } from './federated-worker-agent-launch'
import type { WorkerSetupReceipt } from '../worker/worker-topology'
import {
monitorFederatedSetup,
@@ -96,61 +92,25 @@ export const ORCHESTRATION_FEDERATION_ATTACH_METHODS = [
state: createsWorktree ? 'not_configured' : 'not_applicable'
}
try {
if (createsWorktree) {
db.recordRemoteAttachmentStage({
dispatchId: params.dispatchId,
stage: 'worktree_creating'
})
const setupDecision = params.setup ?? 'run'
const created = await runtime.createManagedWorktree({
repoSelector: params.repo as string,
name: params.name as string,
baseBranch: params.baseBranch,
displayName: params.displayName,
displayNameKind: params.displayNameKind,
comment: params.comment,
// setupDecision runs setup without the legacy runHooks activation side effect.
runHooks: false,
setupDecision,
awaitTerminalProvisioning: true,
observeSetupCompletion: true,
createdWithAgent: agent as TuiAgent,
startupAgent: agent as TuiAgent,
startupLaunchSource: 'orchestration',
...(launch.preferences ? { startupLaunchPreferences: launch.preferences } : {}),
activate: false,
lineage: { noParent: true }
})
worktree = created.worktree
terminalHandle = created.startupTerminal?.handle
effects.push({
kind: 'worktree',
action: 'created_top_level',
id: created.worktree.id
})
setup = {
requested: setupDecision,
effective: setupDecision,
source: setupSource,
hookFound: created.setupReceipt?.hookFound ?? false,
startupPolicy: created.setupReceipt?.startupPolicy ?? 'start-immediately',
state: created.setupReceipt?.state ?? 'not_configured'
}
if (!terminalHandle) {
throw new Error(
created.warning ?? 'Agent-first worktree creation returned no terminal.'
)
}
const listed = await runtime.listTerminals(`id:${created.worktree.id}`, undefined, {
includeVisualLayouts: false
})
appendFederationTerminalEffects(
const launchAgent = (existing?: { id: string }) =>
launchFederatedWorkerAgent({
runtime,
db,
params,
agent,
launchPreferences: launch.preferences,
...(existing ? { worktree: existing } : {}),
setupSource,
effects,
listed.terminals,
terminalHandle,
created.setupReceipt?.terminalHandle
)
appendFederationSetupEffect(effects, setup)
onSetup: (created) => {
setup = created
},
onStage: (stage) => {
failedStage = stage
}
})
if (createsWorktree) {
;({ worktree, terminalHandle } = await launchAgent())
} else {
worktree = await runtime.showManagedTerminalWorkspace(params.worktree).catch(() => {
throw new OrchestrationError(
@@ -184,22 +144,7 @@ export const ORCHESTRATION_FEDERATION_ATTACH_METHODS = [
})
} else {
failedStage = 'terminal_create'
const terminal = await runtime.createTerminal(`id:${worktree.id}`, {
// Why: agent ids are not shell commands (`cursor` is the desktop app,
// its CLI is `cursor-agent`); resolve through the TUI agent config.
startupAgent: agent as TuiAgent,
launchSource: 'orchestration',
...(launch.preferences ? { launchPreferences: launch.preferences } : {}),
title: `worker-${params.taskId}`,
presentation: 'background'
})
terminalHandle = terminal.handle
effects.push({
kind: 'terminal',
role: 'agent',
action: 'created',
id: terminal.handle
})
;({ terminalHandle } = await launchAgent(worktree))
}
}
if (!worktree || !terminalHandle) {
@@ -0,0 +1,102 @@
/**
* A structured worker whose launch throws after its session exists: the caller never gets a
* placement to tear down, so the placement itself must discard the session.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AgentLaunchModeReceipt } from '../../../../../agent-launch/agent-launch-mode'
import type { AgentLaunchExecution } from '../../../../../agent-launch/agent-launch-executor'
import { setStructuredAgentSessionHost } from '../../../../../native-chat/agent-session-wire/structured-agent-session-registry'
import type { OrcaRuntimeService } from '../../../../orca-runtime'
import type { OrchestrationDb } from '../../../../orchestration/db'
import type * as StructuredWorkerSessionModule from '../../orchestration-structured-worker-session'
import { structuredWorkerIdentities } from '../../../../structured-worker-identity'
import { WorkerStartParams } from './worker-start-schema'
const discard = vi.hoisted(() => vi.fn(async (_sessionId: string) => {}))
const surfaced = vi.hoisted((): { sessionId: string | null } => ({ sessionId: null }))
vi.mock('../../structured-agent-session-create', () => ({
createStructuredAgentSessionForWorktree: async (args: { envelope: { sessionId: string } }) => ({
ok: true,
value: { sessionId: args.envelope.sessionId, fence: 7 }
})
}))
vi.mock('../../orchestration-structured-worker-session', async (importOriginal) => ({
...(await importOriginal<typeof StructuredWorkerSessionModule>()),
discardStructuredWorkerSession: discard
}))
// No executor step after the surface throws today, so the seam stands in for a future one.
vi.mock('../../../../../agent-launch/agent-launch-executor', () => ({
executeAgentLaunch: async (execution: AgentLaunchExecution) => {
if (!execution.surfaces) {
throw new Error('a worker launch always brings its surfaces')
}
const session = await execution.surfaces.createStructuredSession({
agent: 'claude',
worktreeId: 'wt_existing'
})
surfaced.sessionId = session.sessionId
throw new Error('failed after the surface')
}
}))
const { placeWorkerAgent } = await import('./worker-start-agent-placement')
const STRUCTURED: AgentLaunchModeReceipt = {
mode: 'structured',
preferred: 'structured',
reason: 'user_default',
detail:
'Started a structured chat session worker, the default for new agent tabs in your settings.'
}
beforeEach(() => {
discard.mockClear()
surfaced.sessionId = null
structuredWorkerIdentities.clear()
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a worker create reads only `subscribe` and the record's location.
setStructuredAgentSessionHost({
subscribe: () => () => {},
deps: {
store: { getRecord: () => ({ location: { executionHostId: 'local', wslDistro: null } }) }
}
} as never)
})
describe('a worker launch that throws after its session exists', () => {
it('discards the session once before rethrowing', async () => {
const runtime = {
ensureStructuredAgentSessionHost: async () => {},
forgetStructuredSessionMail: vi.fn()
}
const db = { recordWorkerStage: vi.fn() }
await expect(
placeWorkerAgent({
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the placement calls only the methods faked here.
runtime: runtime as unknown as OrcaRuntimeService,
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the placement records only worker stages.
db: db as unknown as OrchestrationDb,
dispatchId: 'ctx_1',
taskId: 'task_1',
params: WorkerStartParams.parse({ from: 'term_coord', task: 'task_1' }),
requestedWorktree: 'id:wt_existing',
creationWorktree: undefined,
resolvedWorktree: { id: 'wt_existing', repoId: 'repo_1' },
mode: STRUCTURED,
agent: 'claude',
launchPreferences: undefined,
effects: [],
onStage: () => {}
})
).rejects.toThrow('failed after the surface')
expect(surfaced.sessionId).not.toBeNull()
expect(discard).toHaveBeenCalledTimes(1)
expect(discard).toHaveBeenCalledWith(surfaced.sessionId, runtime)
expect(structuredWorkerIdentities.list()).toEqual([])
})
})
@@ -1,26 +1,29 @@
/**
* Where a worker's agent comes from: a worktree this start creates, a structured session, a new
* terminal in an existing worktree, or the terminal the caller passed.
* Where a worker's agent comes from: the terminal the caller passed, or a fresh launch through the
* shared executor — into a worktree this start creates or into an existing one.
*
* A structured worker never takes the agent-first worktree path. `createWorkerWorktree` used to be
* the only way a new worktree was made, and it creates one WITH its startup agent terminal, which
* left the structured branch below it unreachable for every `--worktree new-child` dispatch. Here
* the worktree is created without a startup agent and the structured session is created for it
* afterwards — the same order the renderer's own structured worktree create uses.
* The executor owns the sequencing: a structured worker's worktree is created with no startup
* agent, the host is then asked about the workspace that now exists, and only then is the session
* created. What stays here is what makes it a worker: the worktree is created with the dispatch's
* lineage and effects, and the surface is a background session or a `worker-<task>` terminal that
* does not take the sidebar. The mode was decided before the dispatch record existed, so it is
* handed in rather than decided again.
*
* That reorder is also why the host verdict lands here: `agentSession.createSupport` can only be
* asked about a workspace that exists, so for a created worktree it cannot run before the start.
* A refusal becomes a terminal agent in the worktree that was just created, never a failed start.
* A refused structured session fails the start: the worker factory throws its own error, never the
* executor's downgrade signal, so a dispatch that asked for a chat does not quietly get a terminal.
*/
import type { AgentLaunchPreferences } from '../../../../../../shared/agent-session-host-authority'
import type { TuiAgent } from '../../../../../../shared/tui-agent'
import { executeAgentLaunch } from '../../../../../agent-launch/agent-launch-executor'
import type { AgentLaunchSurfaceFactory } from '../../../../../agent-launch/agent-launch-surface-factories'
import type { OrcaRuntimeService } from '../../../../orca-runtime'
import type { OrchestrationDb } from '../../../../orchestration/db'
import {
resolveWorkerStartModeOnHost,
WORKER_START_VOCABULARY,
type WorkerStartModeReceipt
} from '../../orchestration-worker-start-mode'
import { tearDownFailedWorkerStart } from './failed-worker-start-teardown'
import type { WorkerStartInput } from './worker-start-schema'
import {
createExistingWorktreeWorkerTerminal,
@@ -56,6 +59,7 @@ type WorkerAgentPlacementArgs = {
creationWorktree: PlacedWorktree | undefined
/** The already-resolved placement, present only when this start does not create one. */
resolvedWorktree: PlacedWorktree | undefined
/** Decided before the dispatch record; for an existing worktree, already settled on its host. */
mode: WorkerStartModeReceipt
agent: TuiAgent | undefined
launchPreferences: AgentLaunchPreferences | undefined
@@ -78,11 +82,8 @@ export const EXISTING_WORKTREE_SETUP: WorkerSetupReceipt = {
export async function placeWorkerAgent(
args: WorkerAgentPlacementArgs
): Promise<WorkerAgentPlacement> {
if (args.creationWorktree) {
return placeInCreatedWorktree(args, args.creationWorktree)
}
const worktree = requireWorktree(args.resolvedWorktree)
if (args.params.terminal) {
const existing = args.creationWorktree ? undefined : requireWorktree(args.resolvedWorktree)
if (existing && args.params.terminal) {
args.effects.push({
kind: 'terminal',
role: 'agent',
@@ -91,92 +92,127 @@ export async function placeWorkerAgent(
})
return {
mode: args.mode,
worktree,
worktree: existing,
terminalHandle: args.params.terminal,
structuredSession: null,
setupReceipt: EXISTING_WORKTREE_SETUP
}
}
return {
mode: args.mode,
worktree,
...(await createWorkerAgentSurface(args, worktree.id, args.mode)),
setupReceipt: EXISTING_WORKTREE_SETUP
}
return launchWorkerAgent(args, existing)
}
async function placeInCreatedWorktree(
async function launchWorkerAgent(
args: WorkerAgentPlacementArgs,
coordinatorWorktree: PlacedWorktree
existing: PlacedWorktree | undefined
): Promise<WorkerAgentPlacement> {
args.onStage('worktree_create')
const created = await createWorkerWorktree({
runtime: args.runtime,
db: args.db,
dispatchId: args.dispatchId,
requestedWorktree: args.requestedWorktree,
coordinatorWorktree,
params: args.params,
agent: args.agent as TuiAgent,
withAgentTerminal: args.mode.mode !== 'structured',
...(args.launchPreferences ? { launchPreferences: args.launchPreferences } : {}),
effects: args.effects
})
const worktree = requireWorktree(created.worktree)
if (args.mode.mode !== 'structured') {
return {
mode: args.mode,
worktree,
terminalHandle: requireTerminal(created.terminalHandle),
structuredSession: null,
setupReceipt: created.setupReceipt
}
const agent = requireAgent(args.agent)
let created: Awaited<ReturnType<typeof createWorkerWorktree>> | undefined
let structuredSession: WorkerStructuredSession | null = null
let launched: Awaited<ReturnType<typeof executeAgentLaunch>>
try {
launched = await executeAgentLaunch({
runtime: args.runtime,
intent: {
agent,
// The worker's own factory builds the create from the dispatch, so the target carries none.
target: existing
? { kind: 'existing', worktree: existing.id }
: { kind: 'create-worktree', create: {} },
launchSource: 'orchestration'
},
decidedMode: args.mode,
vocabulary: WORKER_START_VOCABULARY,
onStage: (stage) =>
args.onStage(stage === 'worktree_create' ? 'worktree_create' : 'terminal_create'),
workspaces: {
createWorktree: async ({ startupAgent }) => {
created = await createWorkerWorktree({
runtime: args.runtime,
db: args.db,
dispatchId: args.dispatchId,
requestedWorktree: args.requestedWorktree,
coordinatorWorktree: requireWorktree(args.creationWorktree),
params: args.params,
agent,
// The executor withholds the startup agent exactly when the worker is to be a session.
withAgentTerminal: startupAgent !== undefined,
...(args.launchPreferences ? { launchPreferences: args.launchPreferences } : {}),
effects: args.effects
})
return {
worktreeId: created.worktree.id,
startupTerminalHandle: created.terminalHandle
}
}
},
surfaces: workerSurfaceFactory(args, agent, (session) => {
structuredSession = session
})
})
} catch (error) {
// The caller only sees a returned placement, so a session made before this throw is ours.
await tearDownFailedWorkerStart({
runtime: args.runtime,
structuredSession,
dispatchId: args.dispatchId
})
throw error
}
args.onStage('terminal_create')
const mode = await resolveWorkerStartModeOnHost(args.runtime, args.mode, worktree.id, args.agent)
return {
mode,
worktree,
...(await createWorkerAgentSurface(args, worktree.id, mode)),
setupReceipt: created.setupReceipt
mode: launched.receipt,
worktree: existing ?? requireWorktree(created?.worktree),
terminalHandle: launched.outcome.handle,
structuredSession,
setupReceipt: created?.setupReceipt ?? EXISTING_WORKTREE_SETUP,
...(launched.warning ? { warning: launched.warning } : {})
}
}
/** The agent surface for a worktree that exists; the settled mode picks which one. */
async function createWorkerAgentSurface(
/** A worker's surface in a worktree that exists: a background session with the dispatch's redrive
* and mailbox, or a `worker-<task>` terminal that leaves the sidebar where the user has it. */
function workerSurfaceFactory(
args: WorkerAgentPlacementArgs,
worktreeId: string,
mode: WorkerStartModeReceipt
): Promise<Pick<WorkerAgentPlacement, 'terminalHandle' | 'structuredSession' | 'warning'>> {
args.db.recordWorkerStage({
dispatchId: args.dispatchId,
stage: 'terminal_creating',
worktreeId,
effects: args.effects
})
if (mode.mode === 'structured') {
const structuredSession = await createStructuredWorkerSessionForWorktree({
runtime: args.runtime,
worktreeId,
agent: args.agent as TuiAgent,
agent: TuiAgent,
onStructuredSession: (session: WorkerStructuredSession) => void
): AgentLaunchSurfaceFactory {
const recordSurfaceStage = (worktreeId: string): void => {
args.db.recordWorkerStage({
dispatchId: args.dispatchId,
...(args.launchPreferences ? { launchPreferences: args.launchPreferences } : {}),
stage: 'terminal_creating',
worktreeId,
effects: args.effects
})
return { terminalHandle: structuredSession.identity.handle, structuredSession }
}
const terminal = await createExistingWorktreeWorkerTerminal({
runtime: args.runtime,
worktreeId,
agent: args.agent as TuiAgent,
...(args.launchPreferences ? { launchPreferences: args.launchPreferences } : {}),
taskId: args.taskId,
effects: args.effects
})
return {
terminalHandle: terminal.handle,
structuredSession: null,
...(terminal.warning ? { warning: terminal.warning } : {})
createStructuredSession: async ({ worktreeId }) => {
recordSurfaceStage(worktreeId)
const session = await createStructuredWorkerSessionForWorktree({
runtime: args.runtime,
worktreeId,
agent,
dispatchId: args.dispatchId,
...(args.launchPreferences ? { launchPreferences: args.launchPreferences } : {}),
effects: args.effects
})
onStructuredSession(session)
return {
sessionId: session.identity.sessionId,
handle: session.identity.handle,
fence: session.fence
}
},
createTerminalAgent: async ({ worktreeId }) => {
recordSurfaceStage(worktreeId)
const terminal = await createExistingWorktreeWorkerTerminal({
runtime: args.runtime,
worktreeId,
agent,
...(args.launchPreferences ? { launchPreferences: args.launchPreferences } : {}),
taskId: args.taskId,
effects: args.effects
})
return { handle: terminal.handle, ...(terminal.warning ? { warning: terminal.warning } : {}) }
}
}
}
@@ -187,9 +223,10 @@ function requireWorktree(worktree: PlacedWorktree | undefined): PlacedWorktree {
return worktree
}
function requireTerminal(terminalHandle: string | undefined): string {
if (!terminalHandle) {
throw new Error('Worker topology did not resolve an agent terminal.')
/** Validation refuses a fresh launch with no agent; this only narrows the type. */
function requireAgent(agent: TuiAgent | undefined): TuiAgent {
if (!agent) {
throw new Error('Worker topology did not resolve an agent.')
}
return terminalHandle
return agent
}
@@ -0,0 +1,278 @@
/**
* A fresh worker starts through the shared launch executor, and its durable dispatch records must
* read exactly as they did when the placement sequenced the launch itself: the same effects, the
* same stage rows in the same order, one host question, and a refused chat that fails the start.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
downgradeAgentLaunchModeForHost,
type AgentLaunchModeReceipt
} from '../../../../../agent-launch/agent-launch-mode'
import { setStructuredAgentSessionHost } from '../../../../../native-chat/agent-session-wire/structured-agent-session-registry'
import type { OrcaRuntimeService } from '../../../../orca-runtime'
import type { OrchestrationDb } from '../../../../orchestration/db'
import { WORKER_START_VOCABULARY } from '../../orchestration-worker-start-mode'
import { structuredWorkerIdentities } from '../../../../structured-worker-identity'
import type { WorkerEffect } from './worker-topology'
import { WorkerStartParams } from './worker-start-schema'
const sessionCreate = vi.hoisted((): { refusal: { code: string } | null } => ({ refusal: null }))
vi.mock('../../structured-agent-session-create', () => ({
createStructuredAgentSessionForWorktree: async (args: { envelope: { sessionId: string } }) =>
sessionCreate.refusal
? { ok: false, refusal: { code: sessionCreate.refusal.code, message: 'refused here' } }
: { ok: true, value: { sessionId: args.envelope.sessionId, fence: 7 } }
}))
const { placeWorkerAgent } = await import('./worker-start-agent-placement')
const STRUCTURED: AgentLaunchModeReceipt = {
mode: 'structured',
preferred: 'structured',
reason: 'user_default',
detail:
'Started a structured chat session worker, the default for new agent tabs in your settings.'
}
const TERMINAL: AgentLaunchModeReceipt = {
mode: 'terminal',
preferred: 'terminal',
reason: 'user_default',
detail: 'Started a terminal agent worker, the default for new agent tabs in your settings.'
}
const COORDINATOR = { id: 'wt_coord', repoId: 'repo_1' }
const EXISTING = { id: 'wt_existing', repoId: 'repo_1' }
function fakes() {
const stages: string[] = []
const runtime = {
createManagedWorktree: vi.fn(async (args: { startupAgent?: string }) => ({
worktree: { id: 'wt_new', repoId: 'repo_1' },
...(args.startupAgent ? { startupTerminal: { handle: 'term_startup' } } : {})
})),
listTerminals: vi.fn(async (_selector: string) => ({
terminals: [{ handle: 'term_startup', title: 'claude', tabId: 'tab_1', leafId: 'leaf_1' }]
})),
createTerminal: vi.fn(async () => ({ handle: 'term_worker', surface: 'background' })),
getStructuredAgentSessionCreateSupport: vi.fn(
async (): Promise<{ supported: boolean; reason?: 'agent' | 'remote' | 'wsl' }> => ({
supported: true
})
),
getClientSettings: vi.fn(() => ({})),
ensureStructuredAgentSessionHost: async () => {}
}
const db = {
recordWorkerStage: vi.fn((row: { stage: string }) => {
stages.push(row.stage)
})
}
const failedStages: string[] = []
const effects: WorkerEffect[] = []
return { runtime, db, stages, failedStages, effects }
}
function place(
f: ReturnType<typeof fakes>,
args: { mode: AgentLaunchModeReceipt; existing?: boolean; terminal?: string }
) {
return placeWorkerAgent({
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the placement calls only the methods faked here.
runtime: f.runtime as unknown as OrcaRuntimeService,
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the placement records only worker stages.
db: f.db as unknown as OrchestrationDb,
dispatchId: 'ctx_1',
taskId: 'task_1',
params: WorkerStartParams.parse({
from: 'term_coord',
task: 'task_1',
...(args.existing ? {} : { name: 'child' }),
...(args.terminal ? { terminal: args.terminal } : {})
}),
requestedWorktree: args.existing ? 'id:wt_existing' : 'new-child',
creationWorktree: args.existing ? undefined : COORDINATOR,
resolvedWorktree: args.existing ? EXISTING : undefined,
mode: args.mode,
agent: args.terminal ? undefined : 'claude',
launchPreferences: undefined,
effects: f.effects,
onStage: (stage) => f.failedStages.push(stage)
})
}
function installHost() {
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: a worker create reads only `subscribe` and the record's location.
setStructuredAgentSessionHost({
subscribe: () => () => {},
deps: {
store: { getRecord: () => ({ location: { executionHostId: 'local', wslDistro: null } }) }
}
} as never)
}
beforeEach(() => {
sessionCreate.refusal = null
structuredWorkerIdentities.clear()
installHost()
})
describe('a fresh worker through the launch executor', () => {
it('fails a refused structured worker in an existing worktree instead of starting a terminal', async () => {
const f = fakes()
sessionCreate.refusal = { code: 'structured_agent_session_unsupported' }
await expect(place(f, { mode: STRUCTURED, existing: true })).rejects.toMatchObject({
code: 'agent_unconfigured'
})
expect(f.runtime.createTerminal).not.toHaveBeenCalled()
expect(f.stages).toEqual(['terminal_creating'])
})
it('fails a refused structured worker in the worktree it created, with no terminal', async () => {
const f = fakes()
sessionCreate.refusal = { code: 'structured_agent_session_unsupported' }
await expect(place(f, { mode: STRUCTURED })).rejects.toMatchObject({
code: 'agent_unconfigured'
})
expect(f.runtime.createTerminal).not.toHaveBeenCalled()
expect(f.failedStages).toEqual(['worktree_create', 'terminal_create', 'terminal_create'])
})
it('does not ask the host again about an existing worktree whose mode it already settled', async () => {
const f = fakes()
const placed = await place(f, { mode: STRUCTURED, existing: true })
expect(f.runtime.getStructuredAgentSessionCreateSupport).not.toHaveBeenCalled()
expect(f.runtime.getClientSettings).not.toHaveBeenCalled()
expect(placed.mode).toBe(STRUCTURED)
expect(placed.structuredSession?.identity.handle).toBe(placed.terminalHandle)
expect(f.stages).toEqual(['terminal_creating'])
expect(f.effects).toEqual([
{
kind: 'terminal',
role: 'agent',
action: 'created',
id: placed.terminalHandle,
surface: 'background'
}
])
})
it('creates a structured worker’s worktree with no agent, asks the host once, then records the surface stage', async () => {
const f = fakes()
const placed = await place(f, { mode: STRUCTURED })
expect(f.runtime.createManagedWorktree).toHaveBeenCalledWith(
expect.not.objectContaining({ startupAgent: expect.anything() })
)
expect(f.runtime.getStructuredAgentSessionCreateSupport).toHaveBeenCalledTimes(1)
expect(f.runtime.getStructuredAgentSessionCreateSupport).toHaveBeenCalledWith(
'id:wt_new',
'claude'
)
expect(f.stages).toEqual(['worktree_creating', 'worktree_created', 'terminal_creating'])
expect(f.failedStages).toEqual(['worktree_create', 'terminal_create', 'terminal_create'])
expect(placed.worktree.id).toBe('wt_new')
expect(placed.structuredSession).not.toBeNull()
})
it('starts a worker-<task> terminal in the worktree it created when the host refuses a chat', async () => {
const f = fakes()
f.runtime.getStructuredAgentSessionCreateSupport.mockResolvedValue({
supported: false,
reason: 'agent'
})
const placed = await place(f, { mode: STRUCTURED })
expect(f.runtime.createManagedWorktree).toHaveBeenCalledWith(
expect.not.objectContaining({ startupAgent: expect.anything() })
)
expect(f.runtime.createTerminal).toHaveBeenCalledTimes(1)
expect(f.runtime.createTerminal).toHaveBeenCalledWith('id:wt_new', {
startupAgent: 'claude',
launchSource: 'orchestration',
title: 'worker-task_1',
surfaceOwner: false
})
expect(f.stages).toEqual(['worktree_creating', 'worktree_created', 'terminal_creating'])
expect(f.failedStages).toEqual(['worktree_create', 'terminal_create', 'terminal_create'])
expect(placed.mode).toEqual(
downgradeAgentLaunchModeForHost(
STRUCTURED,
{ supported: false, reason: 'agent' },
WORKER_START_VOCABULARY
)
)
expect(placed.mode.reason).toBe('structured_unsupported_on_host')
expect(placed).toMatchObject({ terminalHandle: 'term_worker', structuredSession: null })
expect(f.effects.at(-1)).toEqual({
kind: 'terminal',
role: 'agent',
action: 'created',
id: 'term_worker',
surface: 'background',
warning: undefined
})
})
it('creates a terminal worker’s worktree agent-first: no host question and no surface stage', async () => {
const f = fakes()
const placed = await place(f, { mode: TERMINAL })
expect(f.runtime.createManagedWorktree).toHaveBeenCalledWith(
expect.objectContaining({ startupAgent: 'claude', startupLaunchSource: 'orchestration' })
)
expect(f.runtime.getStructuredAgentSessionCreateSupport).not.toHaveBeenCalled()
expect(f.runtime.createTerminal).not.toHaveBeenCalled()
expect(f.stages).toEqual(['worktree_creating', 'worktree_created'])
expect(f.failedStages).toEqual(['worktree_create'])
expect(placed).toMatchObject({
mode: TERMINAL,
worktree: { id: 'wt_new' },
terminalHandle: 'term_startup',
structuredSession: null,
setupReceipt: {
requested: 'run',
effective: 'run',
source: 'orchestration_default',
hookFound: false,
startupPolicy: 'start-immediately',
state: 'not_configured'
}
})
expect(placed).not.toHaveProperty('warning')
})
it('opens an existing worktree’s terminal worker as worker-<task>, leaving the sidebar alone', async () => {
const f = fakes()
const placed = await place(f, { mode: TERMINAL, existing: true })
expect(f.runtime.createTerminal).toHaveBeenCalledTimes(1)
expect(f.runtime.createTerminal).toHaveBeenCalledWith('id:wt_existing', {
startupAgent: 'claude',
launchSource: 'orchestration',
title: 'worker-task_1',
surfaceOwner: false
})
expect(f.stages).toEqual(['terminal_creating'])
expect(placed).toMatchObject({ terminalHandle: 'term_worker', structuredSession: null })
})
it('reuses --terminal without a launch', async () => {
const f = fakes()
const placed = await place(f, { mode: TERMINAL, existing: true, terminal: 'term_running' })
expect(placed.terminalHandle).toBe('term_running')
expect(f.runtime.createTerminal).not.toHaveBeenCalled()
expect(f.runtime.createManagedWorktree).not.toHaveBeenCalled()
expect(f.stages).toEqual([])
})
})