fix: notify inactive hook completions (#2408)

This commit is contained in:
Neil 2026-05-19 23:39:58 -07:00 committed by GitHub
parent 5ce01c8644
commit 58c1bb6be8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 3 deletions

View File

@ -11,6 +11,15 @@ type MockStoreState = {
}
}
ptyIdsByTabId: Record<string, string[]>
terminalLayoutsByTabId: Record<
string,
{
root: { type: 'leaf'; leafId: string }
activeLeafId: string
expandedLeafId: string | null
ptyIdsByLeafId?: Record<string, string>
}
>
}
let mockStoreState: MockStoreState
@ -49,7 +58,8 @@ describe('agent hook completion notifications', () => {
},
ptyIdsByTabId: {
'tab-1': ['pty-1']
}
},
terminalLayoutsByTabId: {}
}
})
@ -90,4 +100,36 @@ describe('agent hook completion notifications', () => {
})
)
})
it('uses tab-level PTY liveness when an inactive pane leaf binding is temporarily missing', async () => {
mockStoreState.terminalLayoutsByTabId = {
'tab-1': {
root: { type: 'leaf', leafId: '11111111-1111-4111-8111-111111111111' },
activeLeafId: '11111111-1111-4111-8111-111111111111',
expandedLeafId: null,
ptyIdsByLeafId: {}
}
}
const { observeAgentHookCompletionForNotification } =
await import('./agent-hook-completion-notifications')
observeAgentHookCompletionForNotification({
paneKey,
worktreeId: 'wt-1',
payload: hookStatus('working')
})
observeAgentHookCompletionForNotification({
paneKey,
worktreeId: 'wt-1',
payload: hookStatus('done')
})
expect(dispatchTerminalNotification).toHaveBeenCalledWith(
'wt-1',
expect.objectContaining({
source: 'agent-task-complete',
paneKey
})
)
})
})

View File

@ -5,6 +5,7 @@ import { createAgentCompletionCoordinator } from '@/components/terminal-pane/age
import type { AgentCompletionCoordinator } from '@/components/terminal-pane/agent-completion-coordinator-types'
import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection'
import { dispatchTerminalNotification } from '@/components/terminal-pane/use-notification-dispatch'
import { collectLeafIdsInOrder } from '@/components/terminal-pane/layout-serialization'
type CoordinatorEntry = {
worktreeId: string
@ -49,10 +50,19 @@ function getPtyIdForPaneKey(paneKey: string): string | null {
// 'done' hook event fire a spurious notification. Resolve liveness through
// the leaf-keyed binding maintained by syncPanePtyLayoutBinding, which
// deletes the entry when the leaf closes.
const ptyIdsByLeafId = state.terminalLayoutsByTabId?.[parsed.tabId]?.ptyIdsByLeafId
const layout = state.terminalLayoutsByTabId?.[parsed.tabId]
const ptyIdsByLeafId = layout?.ptyIdsByLeafId
if (ptyIdsByLeafId) {
const leafPtyId = ptyIdsByLeafId[parsed.leafId]
return leafPtyId && tabPtyIds.includes(leafPtyId) ? leafPtyId : null
if (leafPtyId && tabPtyIds.includes(leafPtyId)) {
return leafPtyId
}
// Why: switching worktrees can unmount the terminal pane and clear the
// leaf binding before the hook completion arrives, while the tab PTY is
// still live. Keep closed leaves suppressed by requiring the leaf in layout.
return collectLeafIdsInOrder(layout.root).includes(parsed.leafId)
? (tabPtyIds[0] ?? null)
: null
}
return tabPtyIds[0] ?? null
}