fix(orchestration): restore terminal identity on fleet status rows

The hook server publishes status rows carrying only a pane key, but the fleet
matcher compares terminal handle and dispatch id. Every local worker therefore
failed identity and projected unverifiable/missing_status while running.

Apply the same enrichment the renderer IPC boundary already applies, so the
shared matcher sees the identity it was written against.
This commit is contained in:
Jinwoo-H
2026-09-05 03:31:25 -04:00
parent 3979c5a159
commit 969b2ef40c
2 changed files with 168 additions and 1 deletions
@@ -15,6 +15,7 @@ import {
} from './orchestration/cli-command'
import { getAppEnvironment } from '../../shared/app-environment'
import type { AgentStatusIpcPayload } from '../../shared/agent-status-ipc-payload'
import { enrichAgentStatusIpcPayload } from '../ipc/agent-status-ipc-boundary'
export class OrcaRuntimeWithGetOrchestrationDispatchAuthority extends OrcaRuntimeWithVerifyOrchestrationCompatibilityCaller {
/** Every pane key this PTY could be addressed by, including restored receipts. */
@@ -195,7 +196,12 @@ export class OrcaRuntimeWithGetOrchestrationDispatchAuthority extends OrcaRuntim
/** Push-fed hook rows for local read-only fleet projection; callers must redact payload text. */
getOrchestrationFleetAgentStatusSnapshot(): readonly AgentStatusIpcPayload[] {
return this.getAgentStatusSnapshotFn?.() ?? []
// Why: hook rows carry only a pane key, but the fleet matcher compares terminal handle and
// dispatch id. Without the same enrichment the renderer boundary applies, every local worker
// fails identity and projects `missing_status` while it is demonstrably running.
return (this.getAgentStatusSnapshotFn?.() ?? []).map((entry) =>
enrichAgentStatusIpcPayload(entry, this)
)
}
getTerminalOrchestrationCliCommand(handle: string): OrchestrationCliCommand {
@@ -0,0 +1,161 @@
import { describe, expect, it } from 'vitest'
import type { OrcaRuntimeService } from '../../../../orca-runtime'
import type { OrchestrationDb } from '../../../../orchestration/db'
import { OrcaRuntimeWithGetOrchestrationDispatchAuthority } from '../../../../orca-runtime-get-orchestration-dispatch-authority'
import { toAgentStatusIpcPayload } from '../../../../../agent-hooks/server/server-status-identity'
import type { EnrichedAgentHookEventPayload } from '../../../../../agent-hooks/server/server-types'
import type { AgentStatusOrchestrationContext } from '../../../../../../shared/agent-status-types'
import { projectFleetWorkerPage } from './worker-observation'
const PANE_KEY = 'tab-fleet:leaf-fleet'
const TERMINAL_HANDLE = 'term_fleet'
const DISPATCH_ID = 'disp-fleet'
/** `projectFleetWorkerPage` stamps `Date.now()` itself, so the fixture must ride the wall clock. */
const observedAt = (): number => Date.now() - 1_000
/** Exactly what `agentHookServer.getStatusSnapshot()` publishes: pane identity, no terminal identity. */
function hookRowAsPublished(): ReturnType<typeof toAgentStatusIpcPayload> {
return toAgentStatusIpcPayload({
paneKey: PANE_KEY,
tabId: 'tab-fleet',
worktreeId: 'wt-fleet',
connectionId: null,
receivedAt: observedAt(),
stateStartedAt: observedAt(),
payload: { state: 'working', agentType: 'claude' }
} as unknown as EnrichedAgentHookEventPayload)
}
function createRuntime(args: {
handleForPane?: string
orchestration?: AgentStatusOrchestrationContext
}): OrcaRuntimeService {
const host = {
getAgentStatusSnapshotFn: () => [hookRowAsPublished()],
getAgentStatusTerminalHandleForPaneKey: (paneKey: string) =>
paneKey === PANE_KEY ? args.handleForPane : undefined,
getAgentStatusOrchestrationContextForPaneKey: (paneKey: string) =>
paneKey === PANE_KEY ? args.orchestration : undefined
}
return {
// Drive the shipping accessor, not a copy of it: the identity loss was in this method.
getOrchestrationFleetAgentStatusSnapshot: () =>
OrcaRuntimeWithGetOrchestrationDispatchAuthority.prototype.getOrchestrationFleetAgentStatusSnapshot.call(
host as never
)
} as unknown as OrcaRuntimeService
}
function createDb(): OrchestrationDb {
return {
listWorkerTerminalResources: () => [
{
dispatchId: DISPATCH_ID,
taskId: 'task-fleet',
runId: 'run-fleet',
parentTaskId: 'task-parent',
workerState: 'ready',
dispatchStatus: 'dispatched',
workerStage: 'input_accepted',
agentTerminalHandle: TERMINAL_HANDLE,
paneKey: PANE_KEY,
worktreeId: 'wt-fleet',
terminalState: 'active',
pendingInput: false,
pendingApproval: false,
terminationReason: null,
resource: {
id: 'res-fleet',
owner_dispatch_id: DISPATCH_ID,
worktree_id: 'wt-fleet',
pane_key: PANE_KEY,
process_incarnation: 'pty-fleet:inc-1',
endpoint_id: null,
endpoint_incarnation: null,
host_scope: JSON.stringify({ kind: 'local', hostId: 'local' }),
ownership_state: 'owned',
release_state: 'none',
updated_at: new Date().toISOString()
},
createdAt: new Date(Date.now() - 60_000).toISOString(),
databaseId: 1
}
],
getWorkerAttentionFactsForDispatches: () => new Map()
} as unknown as OrchestrationDb
}
describe('local fleet liveness from a hook row that carries only a pane key', () => {
it('publishes hook rows without terminal identity', () => {
// Guards the premise: the fix must add identity, not assume the hook server already does.
expect(hookRowAsPublished().terminalHandle).toBeUndefined()
expect(hookRowAsPublished().orchestration).toBeUndefined()
})
it('reads live for a running local worker whose pane still owns its handle', () => {
const page = projectFleetWorkerPage(
createRuntime({ handleForPane: TERMINAL_HANDLE }),
createDb(),
DISPATCH_ID
)
expect(page?.workers[0]).toMatchObject({
liveness: { verdict: 'live', source: 'agent_status' },
evidence: { liveStatus: 'fresh' },
stage: { activity: 'working' },
nextAction: { kind: 'none' },
attention: { requiresAction: false }
})
})
it('carries the dispatch context the renderer boundary attaches', () => {
const page = projectFleetWorkerPage(
createRuntime({
handleForPane: TERMINAL_HANDLE,
orchestration: { dispatchId: DISPATCH_ID } as AgentStatusOrchestrationContext
}),
createDb(),
DISPATCH_ID
)
expect(page?.workers[0]?.liveness.verdict).toBe('live')
})
it('refuses a pane whose handle now belongs to another terminal', () => {
const page = projectFleetWorkerPage(
createRuntime({ handleForPane: 'term_reused' }),
createDb(),
DISPATCH_ID
)
expect(page?.workers[0]).toMatchObject({
liveness: { verdict: 'unverifiable', reason: 'missing_status' },
evidence: { liveStatus: 'unavailable' }
})
})
it('refuses a pane whose handle now belongs to another dispatch', () => {
const page = projectFleetWorkerPage(
createRuntime({
handleForPane: TERMINAL_HANDLE,
orchestration: { dispatchId: 'disp-other' } as AgentStatusOrchestrationContext
}),
createDb(),
DISPATCH_ID
)
expect(page?.workers[0]?.liveness).toMatchObject({
verdict: 'unverifiable',
reason: 'missing_status'
})
})
it('refuses a pane that no longer resolves to a terminal', () => {
const page = projectFleetWorkerPage(createRuntime({}), createDb(), DISPATCH_ID)
expect(page?.workers[0]?.liveness).toMatchObject({
verdict: 'unverifiable',
reason: 'missing_status'
})
})
})