Files
orca/src/shared/codex-subagent-roster.ts
T
Brennan Benson 978b207cc1 fix(agent-status): never drop a running helper for lack of room (#30416)
* fix(agent-status): never drop a running helper for lack of room

Helper state (subagents, background tasks and commands) was bounded by counts and byte budgets that refused or evicted live records once hit, so the agent kept working while Orca listed it wrong. Every one of these records already dies with its session, pane or chat, so the count and size caps go; identity-history, transcript and transport bounds stay.

* fix(agent-status): keep the status field limit, measured without the subagent roster

The previous commit dropped the 256 KiB status check outright; it still guards every other field. Only the roster, whose rows are bounded individually, is left out of the measurement.

* fix(agent-status): fit helper previews and walk lineage once

* fix(agent-status): skip an oversized Pi row instead of stopping, and parse every envelope the store can write

Review follow-up: one long row no longer hides later rows that still fit the request, and the transport envelope's reader no longer refuses a size its writer now produces.
2026-10-19 00:31:42 -07:00

138 lines
3.9 KiB
TypeScript

import {
AGENT_MODEL_MAX_LENGTH,
AGENT_STATUS_TOOL_INPUT_MAX_LENGTH,
AGENT_TYPE_MAX_LENGTH,
type AgentSubagentSnapshot
} from './agent-status-types'
import { normalizeOptionalField } from './agent-status-field-normalization'
import {
agentChildWorkLiveness,
type AgentChildWorkLiveness
} from './agent-status-child-work-liveness'
const CODEX_SUBAGENT_ID_MAX_LENGTH = 64
export type CodexSubagentRoster = Map<string, TrackedCodexSubagent>
type TrackedCodexSubagent = {
agentType?: string
description?: string
model?: string
state: 'working' | 'waiting'
startedAt: number
}
export function upsertCodexSubagent(
roster: CodexSubagentRoster,
id: string,
fields: {
agentType?: string
description?: string
model?: string
state: 'working' | 'waiting'
},
now: number
): void {
const normalizedId = id.trim()
if (normalizedId.length === 0 || normalizedId.length > CODEX_SUBAGENT_ID_MAX_LENGTH) {
return
}
const agentType = normalizeOptionalField(fields.agentType, AGENT_TYPE_MAX_LENGTH)
const description = normalizeOptionalField(fields.description, AGENT_STATUS_TOOL_INPUT_MAX_LENGTH)
const model = normalizeOptionalField(fields.model, AGENT_MODEL_MAX_LENGTH)
const existing = roster.get(normalizedId)
if (existing) {
existing.agentType = agentType ?? existing.agentType
existing.description = description ?? existing.description
existing.model = model ?? existing.model
existing.state = fields.state
return
}
roster.set(normalizedId, {
agentType,
description,
model,
state: fields.state,
startedAt: now
})
}
export function finishCodexSubagent(roster: CodexSubagentRoster, id: string): void {
roster.delete(id.trim())
}
/**
* Record the model a already-tracked child is running. Deliberately narrower
* than `upsertCodexSubagent`: it never creates a roster entry and never touches
* `state`, so late model discovery from a child rollout cannot resurrect a
* finished child nor move any child's lifecycle.
*/
export function setCodexSubagentModel(
roster: CodexSubagentRoster,
id: string,
model: string | undefined
): void {
const normalizedModel = normalizeOptionalField(model, AGENT_MODEL_MAX_LENGTH)
if (!normalizedModel) {
return
}
const existing = roster.get(id.trim())
if (!existing) {
return
}
existing.model = normalizedModel
}
export function seedCodexSubagentRoster(
roster: CodexSubagentRoster,
snapshots: readonly AgentSubagentSnapshot[]
): void {
for (const snapshot of snapshots) {
if (snapshot.state !== 'working' && snapshot.state !== 'waiting') {
continue
}
upsertCodexSubagent(
roster,
snapshot.id,
{
agentType: snapshot.agentType,
description: snapshot.description,
model: snapshot.model,
state: snapshot.state
},
snapshot.startedAt
)
}
}
export function codexRosterToSnapshots(
roster: CodexSubagentRoster | undefined
): AgentSubagentSnapshot[] | undefined {
if (!roster || roster.size === 0) {
return undefined
}
const snapshots = Array.from(roster, ([id, tracked]) => ({
id,
agentType: tracked.agentType,
description: tracked.description,
model: tracked.model,
state: tracked.state,
startedAt: tracked.startedAt
}))
snapshots.sort((a, b) => a.startedAt - b.startedAt || a.id.localeCompare(b.id))
return snapshots
}
/** The roster as child-work evidence for the shared fold. Every tracked child is a spawned
* agent thread, classified by the one kind test the other lanes use, so a waiting child
* reads `waiting` and a live one `working`; nothing here is a watch loop. */
export function codexRosterChildWorkLiveness(
roster: CodexSubagentRoster | undefined
): AgentChildWorkLiveness {
return agentChildWorkLiveness(
roster
? Array.from(roster.values(), (tracked) => ({ kind: 'agent' as const, state: tracked.state }))
: undefined
)
}