mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 08:03:37 +00:00
Seed startup replay and release stale fallback dedupe
This commit is contained in:
@@ -1574,6 +1574,70 @@ describe('agent completion coordinator', () => {
|
||||
expect(dispatchCompletion).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it.each(['pty-first', 'host-first'] as const)(
|
||||
'releases stamped fallback dedupe when the next turn arrives %s',
|
||||
(workingOrder) => {
|
||||
const dispatchCompletion = vi.fn()
|
||||
const localCoordinator = createAgentCompletionCoordinator({
|
||||
paneKey: 'tab-1:leaf-1',
|
||||
statusLane: 'pty',
|
||||
getPtyId: () => 'pty-1',
|
||||
getSettings: () => null,
|
||||
inspectProcess: vi.fn(),
|
||||
dispatchCompletion,
|
||||
isLive: () => true
|
||||
})
|
||||
const hostCoordinator = createAgentCompletionCoordinator({
|
||||
paneKey: 'tab-1:leaf-1',
|
||||
statusLane: 'hook',
|
||||
getPtyId: () => 'pty-1',
|
||||
getSettings: () => null,
|
||||
inspectProcess: vi.fn(),
|
||||
dispatchCompletion,
|
||||
isLive: () => true
|
||||
})
|
||||
const observeHostNextTurn = () =>
|
||||
hostCoordinator.observeHookStatus({
|
||||
state: 'working',
|
||||
prompt: 'second turn',
|
||||
agentType: 'claude',
|
||||
stateStartedAt: 2_000
|
||||
})
|
||||
|
||||
localCoordinator.observeHookStatus({
|
||||
state: 'working',
|
||||
prompt: 'first turn',
|
||||
agentType: 'claude',
|
||||
stateStartedAt: 5_000
|
||||
})
|
||||
hostCoordinator.observeHookStatus({
|
||||
state: 'working',
|
||||
prompt: 'first turn',
|
||||
agentType: 'claude',
|
||||
stateStartedAt: 2_000
|
||||
})
|
||||
hostCoordinator.observeHookStatus({
|
||||
state: 'working',
|
||||
prompt: 'first turn',
|
||||
agentType: 'claude',
|
||||
stateStartedAt: 2_000,
|
||||
turnCompletedAt: 2_500
|
||||
})
|
||||
expect(dispatchCompletion).toHaveBeenCalledTimes(1)
|
||||
|
||||
if (workingOrder === 'pty-first') {
|
||||
localCoordinator.observeTitleWorking()
|
||||
observeHostNextTurn()
|
||||
} else {
|
||||
observeHostNextTurn()
|
||||
localCoordinator.observeTitleWorking()
|
||||
}
|
||||
localCoordinator.observeClassifiedTitleCompletion('Claude done')
|
||||
|
||||
expect(dispatchCompletion).toHaveBeenCalledTimes(2)
|
||||
}
|
||||
)
|
||||
|
||||
it.each(['before', 'after'] as const)(
|
||||
'keeps sibling tail replay suppression when the pane remounts %s its all-clear',
|
||||
(remountOrder) => {
|
||||
|
||||
@@ -1035,6 +1035,12 @@ export function createAgentCompletionCoordinator(
|
||||
const pendingStampedTail = pendingStampedTailByPaneKey.get(options.paneKey)
|
||||
if (pendingStampedTail?.originLane === coordinatorLane) {
|
||||
pendingStampedTailByPaneKey.delete(options.paneKey)
|
||||
if (
|
||||
lastCompletionIdentityByPaneKey.get(options.paneKey)?.lastTurnCompletedAtNotified ===
|
||||
pendingStampedTail.turnCompletedAt
|
||||
) {
|
||||
lastCompletionIdentityByPaneKey.delete(options.paneKey)
|
||||
}
|
||||
}
|
||||
recordWorkingBoundary(payload.stateStartedAt)
|
||||
clearPendingHookDone()
|
||||
|
||||
@@ -281,12 +281,18 @@ export function observeAgentHookCompletionForNotification({
|
||||
payload: AgentCompletionStatusSnapshot
|
||||
seedOnly?: boolean
|
||||
}): void {
|
||||
pruneClosedPaneCoordinators()
|
||||
if (!paneCanReceiveHookCompletion(paneKey)) {
|
||||
return
|
||||
// Why: replay seeds already passed indexed snapshot ownership; re-resolving every row makes startup batches quadratic.
|
||||
if (seedOnly !== true) {
|
||||
pruneClosedPaneCoordinators()
|
||||
if (!paneCanReceiveHookCompletion(paneKey)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const trackingEnabled = syncAgentHookCompletionNotificationSettings()
|
||||
const trackingEnabled =
|
||||
seedOnly === true
|
||||
? isAgentTaskCompleteTrackingEnabled()
|
||||
: syncAgentHookCompletionNotificationSettings()
|
||||
|
||||
let entry = coordinatorsByPaneKey.get(paneKey)
|
||||
if (!entry || entry.worktreeId !== worktreeId) {
|
||||
|
||||
@@ -9160,6 +9160,7 @@ describe('useIpcEvents agent status snapshot integration', () => {
|
||||
|
||||
it('queues replayed startup snapshots until the pane hydrates', async () => {
|
||||
const setAgentStatus = vi.fn()
|
||||
const observeAgentHookCompletionForNotification = vi.fn()
|
||||
const subscribeListenerRef: { current: StoreSubscribeListener | null } = { current: null }
|
||||
let resolveSnapshot!: (entries: AgentStatusSetData[]) => void
|
||||
const getSnapshot = vi.fn(
|
||||
@@ -9190,6 +9191,12 @@ describe('useIpcEvents agent status snapshot integration', () => {
|
||||
getState: () => storeState
|
||||
}
|
||||
}))
|
||||
vi.doMock('./agent-hook-completion-notifications', () => ({
|
||||
observeAgentHookCompletionForNotification,
|
||||
resetAgentHookCompletionNotificationCoordinators: vi.fn(),
|
||||
syncAgentHookCompletionNotificationSettings: vi.fn(),
|
||||
syncAgentHookCompletionNotificationsForStoreUpdate: vi.fn()
|
||||
}))
|
||||
stubAuxiliaryModules()
|
||||
vi.stubGlobal(
|
||||
'window',
|
||||
@@ -9215,6 +9222,7 @@ describe('useIpcEvents agent status snapshot integration', () => {
|
||||
agentType: 'codex',
|
||||
toolName: 'Bash',
|
||||
toolInput: 'pnpm test',
|
||||
turnCompletedAt: 1_699_999_999_500,
|
||||
terminalHandle: 'term-future',
|
||||
receivedAt: 1_700_000_000_000,
|
||||
stateStartedAt: 1_699_999_999_000
|
||||
@@ -9254,6 +9262,17 @@ describe('useIpcEvents agent status snapshot integration', () => {
|
||||
expectWorktreeRouting('wt-1'),
|
||||
undefined
|
||||
)
|
||||
expect(observeAgentHookCompletionForNotification).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
paneKey: FUTURE_PANE_KEY,
|
||||
worktreeId: 'wt-1',
|
||||
seedOnly: true,
|
||||
payload: expect.objectContaining({
|
||||
state: 'working',
|
||||
turnCompletedAt: 1_699_999_999_500
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('suppresses notifications for replayed done snapshots after pane hydration', async () => {
|
||||
|
||||
@@ -3386,7 +3386,7 @@ export function useIpcEvents(): void {
|
||||
: undefined
|
||||
}
|
||||
const applyPostCommitNotification = (): void => {
|
||||
if (options?.replay !== true && statusWorktreeId) {
|
||||
if (statusWorktreeId && (options?.replay !== true || resolvedPayload.state === 'working')) {
|
||||
// Why: local Codex/Claude hooks arrive via this main-process IPC path, not the PTY OSC fallback, so task-complete notifications must observe accepted hook state here too.
|
||||
const notificationPayload =
|
||||
typeof data.stateStartedAt === 'number'
|
||||
@@ -3395,7 +3395,8 @@ export function useIpcEvents(): void {
|
||||
observeAgentHookCompletionForNotification({
|
||||
paneKey,
|
||||
worktreeId: statusWorktreeId,
|
||||
payload: notificationPayload
|
||||
payload: notificationPayload,
|
||||
...(options?.replay === true ? { seedOnly: true } : {})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user