Files
orca/src/main/codex/codex-background-task-frames.ts
T
Neil 643f93f009 fix(native-chat): drop the Codex error path #23682 removed (#23801)
#23682 established that only `turn/completed` ends a Codex turn and deleted
`codex-structured-journal-provider-verdicts.ts`, but the child-turn reader kept
importing `readCodexProviderVerdict` and branching on its `turn-failed` verdict.
The module is gone, so the import resolves to nothing: typecheck and static
analysis fail on every pull request, which skips the whole unit matrix behind
them, and the esbuild pass behind Bun profile scope detection throws, so every
run falls back to all six platforms.

A closed thread is now the only child-turn end without `turn/completed`, which
is what #23682 intended: Codex follows a turn-ending `error` with a failed
`turn/completed` for the same turn 0-32 ms later, and that completion carries
the duration and receipt time the error does not.
2026-09-28 23:14:41 -07:00

103 lines
3.3 KiB
TypeScript

import type { NativeChatSubagentState } from '../../shared/native-chat-types'
import {
codexSubagentLabel,
isCodexRootAgentActivity,
readCodexSubagentActivity
} from './codex-subagent-activity'
import { codexChildTurnState } from './codex-subagent-executions'
import { readRecord } from './codex-item-field-readers'
import { readCodexThreadItem } from './codex-structured-item-translation'
import { readCodexTurnId } from './codex-structured-thread-facts'
export type CodexBackgroundTaskFrame =
| {
kind: 'subagent'
agentThreadId: string
label: string | null
parentTurnId: string | null | undefined
/** The reporting thread, for a `started` activity: the agent that spawned the child. */
spawnerThreadId: string | undefined
}
| {
kind: 'turn'
threadId: string
turnId: string
state: NativeChatSubagentState
}
| {
/** A child turn that ended with no `turn/completed`. No `turnId`: the one it is running. */
kind: 'turn-ended'
threadId: string
turnId: null
state: Extract<NativeChatSubagentState, 'unverifiable'>
}
export type CodexBackgroundTaskEvent = {
method: string
threadId: string
params: unknown
}
/**
* The one way Codex ends a child's turn without `turn/completed`: a closed thread ran its last
* turn and Codex never said how it went. A turn-ending `error` is not one — Codex follows it with
* a failed `turn/completed` for the same turn, which is that turn's end and carries the duration
* and receipt time the error does not.
*/
function readCodexChildTurnEnding(
event: CodexBackgroundTaskEvent
): CodexBackgroundTaskFrame | null {
return event.method === 'thread/closed'
? { kind: 'turn-ended', threadId: event.threadId, turnId: null, state: 'unverifiable' }
: null
}
export function readCodexBackgroundTaskFrame(
event: CodexBackgroundTaskEvent,
primaryThreadId: string
): CodexBackgroundTaskFrame | null {
// The session's own turn ends through the journal's turn boundaries, never here.
const ending = event.threadId === primaryThreadId ? null : readCodexChildTurnEnding(event)
if (ending) {
return ending
}
if (event.method === 'turn/started' || event.method === 'turn/completed') {
const turnId = readCodexTurnId(event.params)
if (turnId === null) {
return null
}
return {
kind: 'turn',
threadId: event.threadId,
turnId,
state:
event.method === 'turn/started'
? 'working'
: codexChildTurnState(readRecord(readRecord(event.params).turn).status)
}
}
if (event.method !== 'item/started' && event.method !== 'item/completed') {
return null
}
const item = readCodexThreadItem(readRecord(event.params).item)
const activity = item && readCodexSubagentActivity(item)
if (
!activity ||
activity.agentThreadId === primaryThreadId ||
isCodexRootAgentActivity(activity)
) {
return null
}
return {
kind: 'subagent',
agentThreadId: activity.agentThreadId,
label: codexSubagentLabel(activity),
parentTurnId:
activity.kind === 'started' || activity.kind === 'interacted'
? readCodexTurnId(event.params)
: undefined,
// Only `started` names the spawner: other kinds ride whichever agent acted.
spawnerThreadId: activity.kind === 'started' ? event.threadId : undefined
}
}