fix: sweep completed Pi agent row when its terminal tab is closed

A 'done' Pi agent entry lingers in agentStatusByPaneKey after its
terminal tab is closed: its paneKey's tabId no longer matches any open
tab, so dropAgentStatusByTabPrefix never sweeps it. The sidebar selector
getLiveEntriesByWorktree used an unconditional fallback
(tabIdToWorktreeId.get(parsed.tabId) ?? entry.worktreeId), added in
#4371 to surface early child-agent rows before their tab reaches the
renderer. That fallback re-attributed the orphaned 'done' entry to the
worktree, so buildWorktreeAgentRows rendered it as a stale live 'Done'
row forever (Codex/Claude tear down cleanly because their pane/tab
keying matches the closed tab).

Restrict the worktree-attribution fallback to non-'done' entries so
completed rows whose tab is gone are dropped, while still surfacing
active child rows whose tab has not yet reached the renderer.

Supersedes #5914 (adopted and re-verified).

Fixes #5913

Co-authored-by: Alberto Cuervo <Br1NKOL@users.noreply.github.com>
This commit is contained in:
Neil
2026-07-10 03:08:19 -07:00
co-authored by Alberto Cuervo
parent c5669cceb5
commit 9dd94b8d8f
2 changed files with 24 additions and 1 deletions
@@ -150,6 +150,27 @@ describe('selectLiveAgentStatusEntriesForWorktree', () => {
expect(selectLiveAgentStatusEntriesForWorktree(state, 'wt-1')).toEqual([childEntry])
})
it('does not use worktree attribution for a completed row whose tab is gone', () => {
const closedEntry = makeEntry(PANE_KEY_1, 1000, {
state: 'done',
worktreeId: 'wt-1',
tabId: 'tab-1',
agentType: 'pi'
})
const state = {
tabsByWorktree: {
'wt-1': [makeTab('tab-live')]
},
agentStatusByPaneKey: {
[PANE_KEY_1]: closedEntry
},
migrationUnsupportedByPtyId: {},
retainedAgentsByPaneKey: {}
}
expect(selectLiveAgentStatusEntriesForWorktree(state, 'wt-1')).toEqual([])
})
})
describe('selectRuntimeAgentOrchestrationForWorktree', () => {
@@ -97,7 +97,9 @@ function getLiveEntriesByWorktree(state: WorktreeAgentRowsState): Map<string, Ag
if (!parsed) {
continue
}
const worktreeId = tabIdToWorktreeId.get(parsed.tabId) ?? entry.worktreeId
const tabWorktreeId = tabIdToWorktreeId.get(parsed.tabId)
// Why: keep early attributed child rows, but hide completed rows once their tab is gone.
const worktreeId = tabWorktreeId ?? (entry.state === 'done' ? undefined : entry.worktreeId)
if (!worktreeId) {
continue
}