test(claude): pin why the roster empties mid-turn in a sequential fan-out

The strip unmounting between two sequential subagents is truthful, not a swept
row: A leaves on the provider's own terminal frame, B does not exist yet, and
backgrounded work spanning the same gap holds the roster open — so an empty
roster is never work the strip is hiding.

Also pins the previous-turn rule against the one the subagent roster already
applies on the same frame: a still-working FOREGROUND child becomes
`unverifiable` there and a backgrounded one is left alone, so the strip drops
the first and keeps the second rather than asserting `live` for either.
This commit is contained in:
Merge Sim
2026-09-09 14:54:28 -07:00
parent 0f812d0ca4
commit 00a9a138d8
@@ -114,6 +114,56 @@ describe('ClaudeBackgroundTaskTracker', () => {
})
})
it('empties only between one task retiring and the next starting', () => {
// The strip's mid-turn unmount in a sequential fan-out is TRUTHFUL: A leaves
// on the provider's own terminal frame for A, B does not exist yet, and
// nothing sweeps A early. An empty roster means no task is running.
const tracker = new ClaudeBackgroundTaskTracker()
tracker.observe({ type: 'user' }, true)
tracker.observe(
system('task_started', { task_id: 'A', task_type: 'local_agent', is_backgrounded: false })
)
expect(tracker.state?.tasks).toEqual([{ id: 'A', kind: 'agent', stoppable: false }])
tracker.observe(system('task_notification', { task_id: 'A', status: 'completed' }))
expect(tracker.state).toBeNull()
tracker.observe(
system('task_started', { task_id: 'B', task_type: 'local_agent', is_backgrounded: false })
)
expect(tracker.state?.tasks).toEqual([{ id: 'B', kind: 'agent', stoppable: false }])
// Backgrounded work spanning the same gap holds the roster open, so an
// empty one is never work the strip is hiding.
const spanned = new ClaudeBackgroundTaskTracker()
spanned.observe({ type: 'user' }, true)
spanned.observe(
system('task_started', { task_id: 'bg', task_type: 'local_bash', is_backgrounded: true })
)
spanned.observe(
system('task_started', { task_id: 'A', task_type: 'local_agent', is_backgrounded: false })
)
spanned.observe(system('task_notification', { task_id: 'A', status: 'completed' }))
expect(spanned.state?.tasks).toEqual([{ id: 'bg', kind: 'command' }])
})
it('settles a previous turn the way the subagent roster settles it', () => {
// On this same frame the roster's `settleTurn` moves a still-working
// FOREGROUND child to `unverifiable` and leaves a backgrounded one alone.
// The strip has no `unverifiable` row, so keeping one would assert `live`
// for work Orca has already stopped vouching for.
const tracker = new ClaudeBackgroundTaskTracker()
tracker.observe({ type: 'user' }, true)
tracker.observe(
system('task_started', { task_id: 'fore', task_type: 'local_agent', is_backgrounded: false })
)
tracker.observe(
system('task_started', { task_id: 'back', task_type: 'local_bash', is_backgrounded: true })
)
// No `result` for that turn; the next one starting is its only end.
tracker.observe({ type: 'user' }, true)
expect(tracker.state?.tasks).toEqual([{ id: 'back', kind: 'command' }])
})
it('retires a phantom foreground row when the next turn starts', () => {
// A foreground `task_started` with no turn open has no `result` coming to
// retire it, so it would sit in the strip — with no stop of its own — and