mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 17:47:40 +00:00
* 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.
106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import { classifyClaudeBackgroundTaskKind } from './claude-background-task-kind'
|
|
import { isAgentChildWorkKind } from './agent-status-child-work-liveness'
|
|
|
|
const CLAUDE_TERMINAL_BACKGROUND_TASK_STATUSES = new Set([
|
|
'idle',
|
|
'done',
|
|
'success',
|
|
'succeeded',
|
|
'complete',
|
|
'completed',
|
|
'finished',
|
|
'failed',
|
|
'error',
|
|
'terminated',
|
|
'exited',
|
|
'aborted',
|
|
'expired',
|
|
'skipped',
|
|
'crashed',
|
|
'killed',
|
|
'cancelled',
|
|
'canceled',
|
|
'timed_out'
|
|
])
|
|
|
|
/** One agent entry from the `background_tasks` array Claude attaches to Stop
|
|
* (and SubagentStop) hook payloads. Non-agent tasks do not become rows. */
|
|
export type ClaudeBackgroundAgentTask = {
|
|
id: string
|
|
agentType?: string
|
|
description?: string
|
|
running: boolean
|
|
/** True for `type: "teammate"` entries. Their ids never match lifecycle
|
|
* agent_ids and they report "running" permanently — even after the named
|
|
* agent finished — so they carry no per-agent state at all. */
|
|
teammate: boolean
|
|
}
|
|
|
|
/** Read the agent-typed entries of a hook payload's `background_tasks` field.
|
|
* `present: false` means the field was absent/malformed (older Claude builds),
|
|
* so callers must keep their tracked roster instead of clearing it. */
|
|
export function readClaudeBackgroundAgentTasks(hookPayload: Record<string, unknown>): {
|
|
present: boolean
|
|
tasks: ClaudeBackgroundAgentTask[]
|
|
truncated: boolean
|
|
hasRunningNonAgentTask: boolean
|
|
/** Ids of the running shell-like entries; one without an id is counted but not listed. */
|
|
runningNonAgentTaskIds: string[]
|
|
} {
|
|
const raw = hookPayload['background_tasks']
|
|
if (!Array.isArray(raw)) {
|
|
return {
|
|
present: false,
|
|
tasks: [],
|
|
truncated: false,
|
|
hasRunningNonAgentTask: false,
|
|
runningNonAgentTaskIds: []
|
|
}
|
|
}
|
|
const tasks: ClaudeBackgroundAgentTask[] = []
|
|
let truncated = false
|
|
let hasRunningNonAgentTask = false
|
|
const runningNonAgentTaskIds: string[] = []
|
|
for (const item of raw) {
|
|
if (typeof item !== 'object' || item === null) {
|
|
truncated = true
|
|
hasRunningNonAgentTask = true
|
|
continue
|
|
}
|
|
const obj = item as Record<string, unknown>
|
|
const taskType = typeof obj.type === 'string' ? obj.type.trim().toLowerCase() : ''
|
|
const taskStatus = typeof obj.status === 'string' ? obj.status.trim().toLowerCase() : ''
|
|
const isTerminal =
|
|
taskStatus.length > 0 && CLAUDE_TERMINAL_BACKGROUND_TASK_STATUSES.has(taskStatus)
|
|
if (taskType.length === 0) {
|
|
truncated = true
|
|
hasRunningNonAgentTask ||= !isTerminal
|
|
continue
|
|
}
|
|
const isAgentTask = isAgentChildWorkKind(classifyClaudeBackgroundTaskKind(taskType))
|
|
// Why: future non-agent types and nonterminal labels must fail active; only typed agent rows or explicit terminal states can safely retire work.
|
|
if (!isAgentTask && !isTerminal) {
|
|
hasRunningNonAgentTask = true
|
|
const id = typeof obj.id === 'string' ? obj.id.trim() : ''
|
|
if (id.length > 0) {
|
|
runningNonAgentTaskIds.push(id)
|
|
}
|
|
}
|
|
if (!isAgentTask) {
|
|
continue
|
|
}
|
|
if (typeof obj.id !== 'string' || obj.id.trim().length === 0) {
|
|
truncated = true
|
|
continue
|
|
}
|
|
tasks.push({
|
|
id: obj.id.trim(),
|
|
agentType: typeof obj.agent_type === 'string' ? obj.agent_type : undefined,
|
|
description: typeof obj.description === 'string' ? obj.description : undefined,
|
|
running: !isTerminal,
|
|
teammate: taskType === 'teammate'
|
|
})
|
|
}
|
|
return { present: true, tasks, truncated, hasRunningNonAgentTask, runningNonAgentTaskIds }
|
|
}
|