mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 11:07:42 +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.
130 lines
5.1 KiB
TypeScript
130 lines
5.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { AgentJournalItemBody } from '../../shared/agent-session-journal-types'
|
|
import { agentJournalItemKey } from '../../shared/agent-session-journal-item-key'
|
|
import { CodexBackgroundCommandTracker } from './codex-background-command-tracker'
|
|
import { createCodexJournalTranslator } from './codex-structured-journal-translation'
|
|
import type { CodexStructuredSessionEvent } from './codex-structured-session-state'
|
|
|
|
function notification(
|
|
method: string,
|
|
params: Record<string, unknown>
|
|
): Extract<CodexStructuredSessionEvent, { type: 'notification' }> {
|
|
return {
|
|
type: 'notification',
|
|
sessionId: 'session',
|
|
threadId: 'root',
|
|
method,
|
|
params: { threadId: 'root', turnId: 'turn', ...params }
|
|
}
|
|
}
|
|
|
|
function command(method: string, id = 'exec', threadId = 'root') {
|
|
return {
|
|
...notification(method, {
|
|
item: {
|
|
type: 'commandExecution',
|
|
id,
|
|
command: 'sleep 30',
|
|
source: 'unifiedExecStartup',
|
|
status: method === 'item/completed' ? 'completed' : 'inProgress',
|
|
exitCode: method === 'item/completed' ? 0 : null
|
|
}
|
|
}),
|
|
threadId
|
|
}
|
|
}
|
|
|
|
describe('persistent command ownership', () => {
|
|
it('promotes only the launching turn and recognizes a start delivered after its end', () => {
|
|
const tracker = new CodexBackgroundCommandTracker('root')
|
|
tracker.observe(command('item/started'))
|
|
expect(tracker.tasks()).toEqual([])
|
|
expect(tracker.endTurn('root', 'another-turn')).toEqual([])
|
|
expect(tracker.endTurn('child', 'turn')).toEqual([])
|
|
expect(tracker.tasks()).toEqual([])
|
|
expect(tracker.endTurn('root', 'turn')).toHaveLength(1)
|
|
expect(tracker.tasks()).toHaveLength(1)
|
|
expect(tracker.endTurn('root', 'turn')).toEqual([])
|
|
tracker.observe(command('item/started', 'late'))
|
|
expect(tracker.tasks()).toHaveLength(2)
|
|
tracker.clear()
|
|
tracker.observe(command('item/started', 'new'))
|
|
expect(tracker.tasks()).toEqual([])
|
|
})
|
|
|
|
it('keeps the journal running across turn completion and accepts late output and exit', () => {
|
|
const rows: { key: string; body: AgentJournalItemBody }[] = []
|
|
const translator = createCodexJournalTranslator({
|
|
primaryThreadId: () => 'root',
|
|
sink: {
|
|
appendItem: (identity, body) => rows.push({ key: agentJournalItemKey(identity), body }),
|
|
appendTombstone: () => {},
|
|
publish: () => {}
|
|
}
|
|
})
|
|
const tracker = new CodexBackgroundCommandTracker('root')
|
|
const deliver = (event: Extract<CodexStructuredSessionEvent, { type: 'notification' }>) => {
|
|
expect(translator.handle(event)).toEqual({ accepted: true })
|
|
tracker.observe(event)
|
|
}
|
|
deliver(notification('turn/started', { turn: { id: 'turn' } }))
|
|
deliver(command('item/started'))
|
|
const originalKey = rows.find(({ body }) => body.kind === 'tool-call')?.key
|
|
deliver(notification('turn/completed', { turn: { id: 'turn' } }))
|
|
tracker.endTurn('root', 'turn')
|
|
expect(rows.filter(({ body }) => body.kind === 'tool-call').map(({ body }) => body)).toEqual([
|
|
expect.objectContaining({ state: 'running' })
|
|
])
|
|
expect(tracker.threadCommands('root')).toHaveLength(1)
|
|
deliver(
|
|
notification('item/commandExecution/outputDelta', { itemId: 'exec', delta: 'late output' })
|
|
)
|
|
translator.flush()
|
|
expect(rows.at(-1)).toMatchObject({ key: originalKey, body: { state: 'running' } })
|
|
deliver(command('item/completed'))
|
|
expect(rows.at(-1)).toMatchObject({ key: originalKey, body: { state: 'completed' } })
|
|
expect(tracker.tasks()).toEqual([])
|
|
translator.dispose()
|
|
})
|
|
|
|
it('counts child shells only after the child stops covering them, without resurrecting exits', () => {
|
|
const tracker = new CodexBackgroundCommandTracker('root')
|
|
tracker.observe(command('item/started', 'child-exec', 'child'))
|
|
tracker.observe(
|
|
notification('item/started', {
|
|
item: {
|
|
type: 'commandExecution',
|
|
id: 'poll',
|
|
source: 'unifiedExecInteraction',
|
|
status: 'inProgress'
|
|
}
|
|
})
|
|
)
|
|
expect(tracker.tasks(new Set(['child']))).toEqual([])
|
|
tracker.endTurn('child', 'turn')
|
|
expect(tracker.tasks()).toEqual([
|
|
{ id: 'codex-command:thread:child:child-exec', kind: 'command', description: 'sleep 30' }
|
|
])
|
|
tracker.observe(command('item/completed', 'child-exec', 'child'))
|
|
tracker.observe(command('item/started'))
|
|
tracker.observe(command('item/completed'))
|
|
tracker.observe(command('item/started'))
|
|
expect(tracker.tasks()).toEqual([])
|
|
})
|
|
|
|
it('retains live commands while recycling bounded settled history', () => {
|
|
const tracker = new CodexBackgroundCommandTracker('root')
|
|
tracker.observe(command('item/started', 'long-lived'))
|
|
for (let index = 0; index < 300; index += 1) {
|
|
tracker.observe(command('item/started', `short-${index}`))
|
|
tracker.observe(command('item/completed', `short-${index}`))
|
|
}
|
|
tracker.endTurn('root', 'turn')
|
|
expect(tracker.tasks()).toEqual([
|
|
{ id: 'codex-command:primary:long-lived', kind: 'command', description: 'sleep 30' }
|
|
])
|
|
tracker.clear()
|
|
expect(tracker.tasks()).toEqual([])
|
|
})
|
|
})
|