mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
* fix(agent-status): track Codex rollout subagents * fix(agent-status): resolve cross-day Codex child rollouts and unblock CI gate Codex files each rollout under its own local start date, so a session that runs past midnight spawns children into a sibling day directory. Scanning only the parent's directory left 13% of real subagent spawns (48/371 across local rollouts) permanently unresolved, which pinned a phantom "working" row and re-ran readdirSync every poll tick forever. Resolve the child's own day directory from occurred_at_ms, and time-box a child whose rollout stays unreadable so a deleted or never-written file can't leak a working row. Also make the hook HTTP handler return void: the changed-code quality gate keys findings by span overlap, so this PR's added line inside the pre-existing async createServer callback resurfaced no-misused-promises as a new finding. Tests cover cross-day resolution, grace-period retirement, and that the poll re-arms across successive roster changes (the prior tests passed even when the poll died after its first change). * fix(agent-status): keep the Codex subagent poll alive across nested hooks A nested non-codex CLI inherits its parent's ORCA_PANE_KEY, so its hook POST reached scheduleCodexSubagentPoll and tore the timer down before the source guard, silently ending polling while a rollout child was still live.
118 lines
3.1 KiB
TypeScript
118 lines
3.1 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())
|
|
}
|
|
|
|
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
|
|
}
|