mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
Keep remount replay state while sibling lives
This commit is contained in:
+49
@@ -101,4 +101,53 @@ describe('agent completion coordinator identity map stays bounded (leak regressi
|
||||
coordinator.dispose() // isLive() still true -> remount, not teardown
|
||||
expect(getAgentCompletionCoordinatorIdentityCountForTest()).toBe(1)
|
||||
})
|
||||
|
||||
it('retains stamped replay state when a mounted sibling disposes before remount', () => {
|
||||
const paneKey = 'tab-1:leaf-production-remount'
|
||||
const dispatchCompletion = vi.fn()
|
||||
const hookLive = { value: true }
|
||||
const paneLive = { value: true }
|
||||
const hookCoordinator = createAgentCompletionCoordinator({
|
||||
...makeOptions(paneKey, hookLive),
|
||||
dispatchCompletion
|
||||
})
|
||||
const paneCoordinator = createAgentCompletionCoordinator({
|
||||
...makeOptions(paneKey, paneLive),
|
||||
dispatchCompletion
|
||||
})
|
||||
|
||||
hookCoordinator.observeHookStatus({
|
||||
state: 'working',
|
||||
prompt: 'review the PR',
|
||||
agentType: 'claude',
|
||||
stateStartedAt: 1_000
|
||||
})
|
||||
hookCoordinator.observeHookStatus({
|
||||
state: 'working',
|
||||
prompt: 'review the PR',
|
||||
agentType: 'claude',
|
||||
stateStartedAt: 1_000,
|
||||
turnCompletedAt: 2_000
|
||||
})
|
||||
expect(dispatchCompletion).toHaveBeenCalledTimes(1)
|
||||
|
||||
paneLive.value = false
|
||||
paneCoordinator.dispose()
|
||||
const remountedLive = { value: true }
|
||||
const remounted = createAgentCompletionCoordinator({
|
||||
...makeOptions(paneKey, remountedLive),
|
||||
dispatchCompletion
|
||||
})
|
||||
vi.advanceTimersByTime(2_000)
|
||||
remounted.observeTitleWorking()
|
||||
remounted.observeClassifiedTitleCompletion('Claude done')
|
||||
|
||||
expect(dispatchCompletion).toHaveBeenCalledTimes(1)
|
||||
|
||||
hookLive.value = false
|
||||
remountedLive.value = false
|
||||
hookCoordinator.dispose()
|
||||
remounted.dispose()
|
||||
expect(getAgentCompletionCoordinatorIdentityCountForTest()).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,6 +41,7 @@ const lastCompletionIdentityByPaneKey = new Map<string, LastCompletionIdentity>(
|
||||
const handledTurnCompletedAtByPaneKey = new Map<string, number>()
|
||||
// Why: title fallback stays blocked only until the stamped background tail reaches its all-clear.
|
||||
const pendingStampedTailByPaneKey = new Map<string, number>()
|
||||
const coordinatorCountByPaneKey = new Map<string, number>()
|
||||
|
||||
const IDLE_POLL_INTERVAL_MS = 2_000
|
||||
const ACTIVE_POLL_INTERVAL_MS = 750
|
||||
@@ -82,6 +83,10 @@ function isAttentionHookState(state: ParsedAgentStatusPayload['state']): boolean
|
||||
export function createAgentCompletionCoordinator(
|
||||
options: AgentCompletionCoordinatorOptions
|
||||
): AgentCompletionCoordinator {
|
||||
coordinatorCountByPaneKey.set(
|
||||
options.paneKey,
|
||||
(coordinatorCountByPaneKey.get(options.paneKey) ?? 0) + 1
|
||||
)
|
||||
let disposed = false
|
||||
let agentIdentityEstablished = false
|
||||
let hasAgentRunEvidence = false
|
||||
@@ -1033,13 +1038,22 @@ export function createAgentCompletionCoordinator(
|
||||
}
|
||||
|
||||
function dispose(): void {
|
||||
if (disposed) {
|
||||
return
|
||||
}
|
||||
disposed = true
|
||||
clearPollTimer()
|
||||
clearPendingHookDone()
|
||||
clearPendingCodexAttention()
|
||||
dropPendingTitle()
|
||||
// Why: module-scoped identity survives a live-stream remount; evict only on genuine teardown (isLive() false) so it can't leak per closed pane.
|
||||
if (!options.isLive()) {
|
||||
const remainingCoordinators = (coordinatorCountByPaneKey.get(options.paneKey) ?? 1) - 1
|
||||
if (remainingCoordinators > 0) {
|
||||
coordinatorCountByPaneKey.set(options.paneKey, remainingCoordinators)
|
||||
} else {
|
||||
coordinatorCountByPaneKey.delete(options.paneKey)
|
||||
}
|
||||
// Why: one pane can have hook and mounted coordinators; a remount disposal must not erase replay state still owned by its live sibling.
|
||||
if (remainingCoordinators <= 0 && !options.isLive()) {
|
||||
lastCompletionIdentityByPaneKey.delete(options.paneKey)
|
||||
handledTurnCompletedAtByPaneKey.delete(options.paneKey)
|
||||
pendingStampedTailByPaneKey.delete(options.paneKey)
|
||||
@@ -1063,6 +1077,7 @@ export function resetAgentCompletionCoordinatorIdentitiesForTest(): void {
|
||||
lastCompletionIdentityByPaneKey.clear()
|
||||
handledTurnCompletedAtByPaneKey.clear()
|
||||
pendingStampedTailByPaneKey.clear()
|
||||
coordinatorCountByPaneKey.clear()
|
||||
}
|
||||
|
||||
export function getAgentCompletionCoordinatorIdentityCountForTest(): number {
|
||||
|
||||
Reference in New Issue
Block a user