mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
Forward paired host completion stamps
This commit is contained in:
@@ -13,6 +13,11 @@ vi.mock('@/hooks/agent-hook-completion-notifications', () => ({
|
||||
|
||||
import { useAppStore } from '@/store'
|
||||
import { createAgentCompletionCoordinator } from '@/components/terminal-pane/agent-completion-coordinator'
|
||||
import {
|
||||
markRendererOwnedAgentStatusWrite,
|
||||
registerRendererOwnedAgentStatusPane,
|
||||
resetRendererOwnedAgentStatusPanesForTests
|
||||
} from '@/components/terminal-pane/renderer-owned-agent-status-registry'
|
||||
import {
|
||||
applyWebSessionTabsSnapshot,
|
||||
applyWebSessionTabsStorePatch,
|
||||
@@ -79,12 +84,14 @@ describe('paired session-tab agent completion notifications', () => {
|
||||
vi.useFakeTimers()
|
||||
mocks.observeAgentHookCompletionForNotification.mockReset()
|
||||
resetWebSessionTabsSnapshotFreshnessForTests()
|
||||
resetRendererOwnedAgentStatusPanesForTests()
|
||||
useAppStore.setState(initialState, true)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
useAppStore.setState(initialState, true)
|
||||
resetWebSessionTabsSnapshotFreshnessForTests()
|
||||
resetRendererOwnedAgentStatusPanesForTests()
|
||||
vi.useRealTimers()
|
||||
})
|
||||
|
||||
@@ -131,6 +138,60 @@ describe('paired session-tab agent completion notifications', () => {
|
||||
expect(dispatchCompletion).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('announces a host-stamped turn while client OSC status owns the pane', () => {
|
||||
const paneKey = makePaneKey(toWebTerminalSurfaceTabId(HOST_TAB_ID), LEAF_ID)
|
||||
registerRendererOwnedAgentStatusPane(paneKey, ENVIRONMENT_ID)
|
||||
markRendererOwnedAgentStatusWrite(paneKey)
|
||||
useAppStore.setState({
|
||||
agentStatusByPaneKey: {
|
||||
[paneKey]: {
|
||||
state: 'working',
|
||||
prompt: 'review the PR',
|
||||
updatedAt: NOW,
|
||||
stateStartedAt: NOW,
|
||||
agentType: 'claude',
|
||||
paneKey,
|
||||
tabId: toWebTerminalSurfaceTabId(HOST_TAB_ID),
|
||||
worktreeId: WORKTREE_ID,
|
||||
stateHistory: []
|
||||
}
|
||||
}
|
||||
})
|
||||
const dispatchCompletion = vi.fn()
|
||||
const coordinator = createAgentCompletionCoordinator({
|
||||
paneKey,
|
||||
getPtyId: () => 'pty-1',
|
||||
getSettings: () => null,
|
||||
inspectProcess: vi.fn(),
|
||||
dispatchCompletion,
|
||||
isLive: () => true
|
||||
})
|
||||
mocks.observeAgentHookCompletionForNotification.mockImplementation(({ payload }) =>
|
||||
coordinator.observeHookStatus(payload)
|
||||
)
|
||||
|
||||
applySnapshot(makeAgentSnapshot(1, NOW - 2_000), true)
|
||||
const turnCompletedAt = NOW + 20_000
|
||||
applySnapshot(makeAgentSnapshot(2, NOW - 1_000, turnCompletedAt), true)
|
||||
applySnapshot(makeAgentSnapshot(3, NOW, turnCompletedAt, 'done'), true)
|
||||
|
||||
expect(useAppStore.getState().agentStatusByPaneKey[paneKey]?.state).toBe('working')
|
||||
expect(mocks.observeAgentHookCompletionForNotification).toHaveBeenCalledTimes(3)
|
||||
expect(dispatchCompletion).toHaveBeenCalledTimes(1)
|
||||
expect(dispatchCompletion).toHaveBeenCalledWith(
|
||||
'claude',
|
||||
expect.objectContaining({
|
||||
source: 'hook',
|
||||
quietedHookDone: false,
|
||||
agentStatus: expect.objectContaining({
|
||||
state: 'done',
|
||||
prompt: 'review the PR',
|
||||
turnCompletedAt
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('suppresses a stamped reconnect tail and its live all-clear', () => {
|
||||
const dispatchCompletion = vi.fn()
|
||||
const coordinator = createAgentCompletionCoordinator({
|
||||
|
||||
@@ -3431,7 +3431,7 @@ export function applyWebSessionTabsStorePatch(
|
||||
useAppStore.setState((state) => {
|
||||
const patch = buildPatch(state)
|
||||
mirroredAgentStatusChanged = patch !== state && Object.hasOwn(patch, 'agentStatusByPaneKey')
|
||||
if (mirroredAgentStatusChanged && agentStatusSnapshots) {
|
||||
if (agentStatusSnapshots) {
|
||||
const nextAgentStatuses = patch.agentStatusByPaneKey ?? state.agentStatusByPaneKey
|
||||
const snapshots = Array.isArray(agentStatusSnapshots)
|
||||
? agentStatusSnapshots
|
||||
@@ -3443,30 +3443,40 @@ export function applyWebSessionTabsStorePatch(
|
||||
}
|
||||
const remapped = remapHostAgentStatus(surface)
|
||||
const accepted = remapped ? nextAgentStatuses[remapped.paneKey] : undefined
|
||||
const turnCompletedAt = remapped
|
||||
? normalizeTurnCompletedAtField(surface.turnCompletedAt, remapped.state)
|
||||
: undefined
|
||||
// Why: client OSC owns display state, but only the host hook stream carries background-turn stamps.
|
||||
const clientOwnedNotification = Boolean(
|
||||
remapped &&
|
||||
isClientAuthoritativeAgentStatusPane(remapped.paneKey) &&
|
||||
(remapped.state === 'working' || turnCompletedAt !== undefined)
|
||||
)
|
||||
if (
|
||||
!remapped ||
|
||||
!accepted ||
|
||||
accepted === state.agentStatusByPaneKey[remapped.paneKey] ||
|
||||
!agentStatusEntryEqual(accepted, remapped)
|
||||
(!clientOwnedNotification &&
|
||||
(!accepted ||
|
||||
accepted === state.agentStatusByPaneKey[remapped.paneKey] ||
|
||||
!agentStatusEntryEqual(accepted, remapped)))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
const turnCompletedAt = normalizeTurnCompletedAtField(
|
||||
surface.turnCompletedAt,
|
||||
accepted.state
|
||||
)
|
||||
if (!allowCompletionNotification && accepted.state !== 'working') {
|
||||
const notificationStatus = clientOwnedNotification ? remapped : accepted
|
||||
if (!notificationStatus) {
|
||||
continue
|
||||
}
|
||||
if (!allowCompletionNotification && notificationStatus.state !== 'working') {
|
||||
continue
|
||||
}
|
||||
acceptedNotificationStatuses.push({
|
||||
paneKey: accepted.paneKey,
|
||||
worktreeId: accepted.worktreeId ?? snapshot.worktree,
|
||||
paneKey: notificationStatus.paneKey,
|
||||
worktreeId: notificationStatus.worktreeId ?? snapshot.worktree,
|
||||
payload: {
|
||||
...pickParsedAgentStatusPayload({
|
||||
...accepted,
|
||||
...notificationStatus,
|
||||
...(turnCompletedAt !== undefined ? { turnCompletedAt } : {})
|
||||
}),
|
||||
stateStartedAt: accepted.stateStartedAt
|
||||
stateStartedAt: notificationStatus.stateStartedAt
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user