fix(agent-status): let a stop's own inventory veto removal-driven done

Review finding round 5: after a restart the listener can miss a child's
start; another child's stop still lists it running in background_tasks.
A running non-teammate task or live shell/cron now vetoes the no-lead
removal->done into working. Veto only — folding the inventory at stop
time would reap a just-parked teammate row before its TeammateIdle
confirmation arrives.
This commit is contained in:
Brennan Benson
2026-08-01 08:09:52 -07:00
parent 7fd2784cfd
commit 3c6c1f80b2
2 changed files with 43 additions and 1 deletions
+34
View File
@@ -3177,6 +3177,40 @@ describe('shared agent-hook-listener', () => {
expect(duplicatePark).toBeNull()
})
it('keeps working when a no-lead stop carries an inventory with another live child', () => {
// Why: after a restart the listener can miss a2's start; a1's stop still
// lists a2 running in background_tasks — the removal must not retire it.
claudeEvent({
hook_event_name: 'SubagentStart',
agent_id: 'a1',
agent_type: 'general-purpose'
})
const stopped = claudeEvent({
hook_event_name: 'SubagentStop',
agent_id: 'a1',
background_tasks: [
{ id: 'a2', type: 'subagent', status: 'running', agent_type: 'general-purpose' }
]
})
// Why: veto only — a2's row materializes from its own events or the next
// lead Stop's fold; the stop must merely not retire the pane.
expect(stopped?.payload.state).toBe('working')
})
it('keeps working when a no-lead drain leaves a live shell in the inventory', () => {
claudeEvent({
hook_event_name: 'SubagentStart',
agent_id: 'a1',
agent_type: 'general-purpose'
})
const stopped = claudeEvent({
hook_event_name: 'SubagentStop',
agent_id: 'a1',
background_tasks: [{ id: 's1', type: 'shell', status: 'running' }]
})
expect(stopped?.payload.state).toBe('working')
})
it('never claims done from a no-lead TeammateIdle park', () => {
claudeEvent({
hook_event_name: 'SubagentStart',
+9 -1
View File
@@ -2594,8 +2594,16 @@ function buildClaudeChildDrivenStatusPayload(
})
}
if (evidence.removedRow) {
// Why: the stop's own inventory can list work this listener never saw start (post-restart);
// a running non-teammate task or live shell/cron vetoes done. Teammate-typed entries prove
// nothing ("running" even while idle), and full folding here would reap a just-parked
// teammate row before its TeammateIdle confirmation arrives — veto only, no reconcile.
const inventory = readClaudeBackgroundAgentTasks(hookPayload)
const inventoryShowsLiveWork =
inventory.tasks.some((task) => task.running && !task.teammate) ||
hasActiveClaudeNonAgentBackgroundWork(hookPayload)
return buildClaudeStatusPayload(state, eventName, '', paneKey, hookPayload, {
stateName: 'done',
stateName: inventoryShowsLiveWork ? 'working' : 'done',
updateToolSnapshot: false
})
}