mirror of
https://github.com/stablyai/orca.git
synced 2026-10-27 16:02:42 +00:00
fix(agent-status): drop child events with whitespace-only agent ids
Review findings round 17: a whitespace-only agent_id canonicalized to undefined and reclassified the child event as the LEAD's, minting a lead waiting no lead Stop ever clears; agent_id-present events with an unattributable id are now ignored. The padded-child-tool regression now drains via a clean-id stop so it fails if canonicalization reverts.
This commit is contained in:
@@ -3383,16 +3383,30 @@ describe('shared agent-hook-listener', () => {
|
||||
it('routes a padded child tool event to the canonical roster row', () => {
|
||||
claudeEvent({ hook_event_name: 'UserPromptSubmit', prompt: 'go' })
|
||||
claudeEvent({ hook_event_name: 'Stop', background_tasks: [] })
|
||||
const childTool = claudeEvent({
|
||||
claudeEvent({
|
||||
hook_event_name: 'PreToolUse',
|
||||
agent_id: ' a4 ',
|
||||
agent_type: 'general-purpose',
|
||||
tool_name: 'Bash',
|
||||
tool_input: { command: 'true' }
|
||||
})
|
||||
expect(childTool?.payload.subagents).toEqual([
|
||||
expect.objectContaining({ id: 'a4', state: 'working' })
|
||||
])
|
||||
// Why: a clean-id stop must drain the padded tool event's row — under a raw
|
||||
// key it would survive and pin the lead's next Stop on working.
|
||||
claudeEvent({ hook_event_name: 'SubagentStop', agent_id: 'a4' })
|
||||
const leadStop = claudeEvent({ hook_event_name: 'Stop' })
|
||||
expect(leadStop?.payload.state).toBe('done')
|
||||
expect(leadStop?.payload.subagents).toBeUndefined()
|
||||
})
|
||||
|
||||
it('ignores child events whose agent_id is whitespace-only', () => {
|
||||
// Why: agent_id present means child-origin; an unattributable id treated
|
||||
// as the lead would mint a waiting no lead Stop ever clears.
|
||||
const waiting = claudeEvent({
|
||||
hook_event_name: 'PermissionRequest',
|
||||
agent_id: ' ',
|
||||
tool_name: 'Bash'
|
||||
})
|
||||
expect(waiting).toBeNull()
|
||||
})
|
||||
|
||||
it('tracks whitespace-padded inventory ids canonically so their stops can drain them', () => {
|
||||
|
||||
@@ -2734,7 +2734,14 @@ function normalizeClaudeEvent(
|
||||
|
||||
// Why: canonicalize like the inventory parser and lifecycle handler — a padded id must hit
|
||||
// the same roster key and wait ownership as its trimmed twin.
|
||||
const eventAgentId = readString(hookPayload, 'agent_id')?.trim() || undefined
|
||||
const rawAgentId = readString(hookPayload, 'agent_id')
|
||||
const eventAgentId = rawAgentId?.trim() || undefined
|
||||
// Why: agent_id present means child-origin by contract; a whitespace-only id cannot be
|
||||
// attributed, and treating the event as the LEAD's would mint lead waiting/working that no
|
||||
// lead Stop ever clears.
|
||||
if (rawAgentId !== undefined && eventAgentId === undefined) {
|
||||
return null
|
||||
}
|
||||
// Why: subagent/teammate events carry `agent_id` (lead's don't); child tool activity keeps its row live but must not become the lead's state or overwrite its tool/prompt caches (a live card would vanish).
|
||||
// Two exceptions take the full path below: waiting-inducing events (a child needs human attention on this pane) and the blocked child's own next tool event (approval granted — clear the wait as for the lead).
|
||||
const isWaitingInducing = stateName === 'waiting'
|
||||
|
||||
Reference in New Issue
Block a user