Files
orca/src/shared/codex-subagent-roster.ts
T
Brennan Benson 6ee265e579 feat(agent-status): surface the model each Codex subagent is running (#8251) (#14627)
Codex child rows have carried a model field end-to-end since #9637, but the
transcript reader never populated it, so every transcript-discovered child
rendered with an empty model chip. Read the child's own turn_context.model
from the rollout records already fetched for completion detection, so the
sidebar can distinguish an orchestrator model from a subagent model.

No added file I/O and no added rows: the model is parsed from records the
reconcile pass already read, and both row components already render
entry.model.
2026-08-17 17:26:52 -07:00

140 lines
3.7 KiB
TypeScript

import {
AGENT_MODEL_MAX_LENGTH,
AGENT_STATUS_MAX_SUBAGENTS,
AGENT_STATUS_TOOL_INPUT_MAX_LENGTH,
AGENT_TYPE_MAX_LENGTH,
type AgentSubagentSnapshot
} from './agent-status-types'
import { normalizeOptionalField } from './agent-status-field-normalization'
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
}
if (roster.size >= AGENT_STATUS_MAX_SUBAGENTS) {
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
}
export function codexRosterEffectiveState(
roster: CodexSubagentRoster | undefined,
leadState: 'working' | 'waiting' | 'done'
): 'working' | 'waiting' | 'done' {
if (!roster || roster.size === 0) {
return leadState
}
for (const tracked of roster.values()) {
if (tracked.state === 'waiting') {
return 'waiting'
}
}
return leadState === 'done' ? 'working' : leadState
}