Files
orca/tests/e2e/structured-native-chat-routing-authority.unit.test.ts
T
Brennan BensonandMerge Sim 852495d35c fix(native-chat): unify launch routing and support structured worker placement (#19431)
* fix(orchestration): let worker-start actually produce a structured chat

`orchestration.workerStart` reads the user's "open agent tabs in chat"
default, but two placement checks downgraded a structured-preferring
worker to a PTY terminal agent for the two flags a routine dispatch
always passes:

  --worktree new-child / new-top-level  -> worktree_creation
  --model / --effort                    -> launch_preferences

so in practice a structured worker never happened.

launch_preferences was stale. PR #19040 gave AgentSessionAttachParams
`options` and added resolveStructuredLaunchSeedOptions, which narrows a
saved selection to exactly `model` and `effort` — the two ids both
structured providers accept as strings. --model/--effort now go through
that same narrowing (extracted as narrowStructuredLaunchSeedOptions) and
seed the worker's session instead of forcing a terminal. An option set
that narrows to nothing resolves to undefined, never `{}`, which would
fail the record's bounded-string guard under a code that is not a wire
refusal and strand the launch with no fallback.

worktree_creation was a consequence of createWorkerWorktree creating
agent-first: its startup terminal WAS the worker, so the structured
branch below it was unreachable for any new worktree. A structured
worker now creates the worktree with no startup agent and creates its
session for the worktree afterwards — the order the renderer's own
structured worktree create already uses. Because the executing host can
only answer agentSession.createSupport for a workspace that exists, that
verdict moved after creation: a refusal (WSL, and the rest) becomes a
terminal agent in the worktree just created, never a failed start.

--on and --terminal still downgrade, with their reasons intact, and
every remaining downgrade still states itself in the mode receipt.

The wait-for-setup gate is preserved explicitly. A PTY worker got it for
free — agent-first creation sequences the agent's startup command behind
the setup runner, so tui-idle could not arrive until setup exited. A
structured session has no startup command to sequence, so the gate is
now awaited directly, bounded by the start's own timeout.

Split out worker-worktree-creation.ts and worker-start-agent-placement.ts
rather than growing two files that were both pinned at the max-lines cap.

* refactor(native-chat): make shared feasibility authoritative for launch routing

* Type the structured setup gate's absent blocked reason so the wait union stays property-typed

The type-aware audit rejected the blocked-reason template literal: narrowing the
wait union with an 'in' check left the field typed unknown. Declaring that a
structured setup gate never carries a blocked reason restores the direct read.

---------

Co-authored-by: Merge Sim <sim@local>
2026-09-08 21:40:23 -07:00

126 lines
4.7 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from 'vitest'
import type * as SharedLaunchRoute from '../../src/shared/structured-native-chat-launch-route'
import { decideWorkerStartMode } from '../../src/main/runtime/rpc/methods/orchestration-worker-start-mode'
import {
resolveAgentLaunchRoute,
structuredAgentLaunchSupported,
type AgentLaunchRoutingInput
} from '../../src/renderer/src/lib/agent-launch-routing'
import { RUNTIME_CAPABILITIES } from '../../src/shared/protocol-version'
import {
resolveStructuredNativeChatSupport,
type StructuredNativeChatBlocker
} from '../../src/shared/structured-native-chat-launch-route'
vi.mock('../../src/shared/structured-native-chat-launch-route', async (importOriginal) => {
const actual = await importOriginal<typeof SharedLaunchRoute>()
return {
...actual,
resolveStructuredNativeChatSupport: vi.fn(actual.resolveStructuredNativeChatSupport)
}
})
const settings = {
experimentalNativeChat: true,
experimentalStructuredNativeChat: true,
openAgentTabsInChatByDefault: true
}
const predicate = vi.mocked(resolveStructuredNativeChatSupport)
afterEach(() => predicate.mockReset())
const placements = [
{},
{ on: 'server-1' },
{ on: 'local' },
{ terminal: 'term_1' },
{ worktree: 'current' },
{ worktree: 'new-child' },
{ worktree: 'new-top-level' },
{ model: 'opus', effort: 'high' },
{ worktree: 'new-child', model: 'opus', effort: 'high' }
]
const blockers: StructuredNativeChatBlocker[] = [
'reused-terminal',
'agent-without-structured-session',
'draft-prompt',
'floating-workspace',
'tui-launch-customization',
'remote-execution-host',
'project-runtime',
'runtime-capability',
'runtime-capability-unknown'
]
describe('shared feasibility owns every caller decision', () => {
it.each(placements)('orchestration cannot override the shared verdict for %j', (placement) => {
for (const agent of ['claude', 'codex', 'grok', 'openclaude'] as const) {
for (const customized of [false, true]) {
const input = {
params: { agent, ...placement },
settings: {
...settings,
...(customized ? { agentDefaultArgs: { [agent]: '--custom' } } : {})
}
}
predicate.mockReturnValue({ supported: true })
expect(decideWorkerStartMode(input).mode).toBe('structured')
expect(predicate).toHaveBeenLastCalledWith(
expect.objectContaining({
agent,
executionHostId: placement.on ? `runtime:${placement.on}` : 'local',
reusesTerminal: Boolean(placement.terminal),
requiresTuiLaunchCustomization: customized
})
)
for (const blocker of blockers) {
predicate.mockReturnValue({ supported: false, blocker })
const receipt = decideWorkerStartMode(input)
expect(receipt).toMatchObject({ mode: 'terminal', preferred: 'structured' })
expect(receipt.reason).not.toBe('user_default')
expect(receipt.detail).toContain('Your default is a structured chat session')
if (blocker === 'runtime-capability-unknown') {
expect(receipt.reason).toBe('structured_support_unknown')
expect(receipt.detail).toContain('has not established')
}
}
}
}
})
it('renderer presentation cannot override shared feasibility', () => {
for (const agent of ['claude', 'codex', 'grok', 'openclaude'] as const) {
for (const executionHostId of ['local', 'ssh:host-1']) {
for (const promptDelivery of ['auto-submit', 'draft'] as const) {
const input: AgentLaunchRoutingInput = {
settings,
agent,
executionHostId,
promptDelivery,
hostCapabilities: RUNTIME_CAPABILITIES,
requiresTuiLaunchCustomization: true,
workspaceKind: 'folder',
initialSessionOptions: { model: 'model-1', effort: 'high' }
}
predicate.mockReturnValue({ supported: true })
expect(resolveAgentLaunchRoute(input)).toBe('structured-native-chat')
expect(structuredAgentLaunchSupported(input)).toBe(true)
expect(predicate).toHaveBeenLastCalledWith(
expect.objectContaining({
agent,
executionHostId,
isDraftPrompt: promptDelivery === 'draft',
requiresTuiLaunchCustomization: true,
workspaceKind: 'folder'
})
)
for (const blocker of blockers) {
predicate.mockReturnValue({ supported: false, blocker })
expect(resolveAgentLaunchRoute(input)).not.toBe('structured-native-chat')
expect(structuredAgentLaunchSupported(input)).toBe(false)
}
}
}
}
})
})