mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
fix(agent-status): invalidate background presentation mirrors
This commit is contained in:
@@ -35,7 +35,8 @@ describe('createHookStatusSessionTabsInvalidator', () => {
|
||||
['agentType', { agentType: 'codex' }],
|
||||
['toolName', { toolName: 'Bash' }],
|
||||
['interactivePrompt', { interactivePrompt: '{"questions":[]}' }],
|
||||
['interrupted', { interrupted: true }]
|
||||
['interrupted', { interrupted: true }],
|
||||
['backgroundOnly', { backgroundOnly: true }]
|
||||
])('invalidates when %s changes', (_field, payload) => {
|
||||
const changed = createHookStatusSessionTabsInvalidator()
|
||||
changed(working())
|
||||
|
||||
@@ -28,7 +28,8 @@ export function createHookStatusSessionTabsInvalidator(): {
|
||||
(previous.agentType ?? null) !== (next.agentType ?? null) ||
|
||||
(previous.toolName ?? null) !== (next.toolName ?? null) ||
|
||||
(previous.interactivePrompt ?? null) !== (next.interactivePrompt ?? null) ||
|
||||
(previous.interrupted ?? false) !== (next.interrupted ?? false)
|
||||
(previous.interrupted ?? false) !== (next.interrupted ?? false) ||
|
||||
(previous.backgroundOnly ?? false) !== (next.backgroundOnly ?? false)
|
||||
)
|
||||
}
|
||||
// Why: a cleared pane must re-arm, else the memo swallows the first event of the
|
||||
|
||||
@@ -19258,13 +19258,25 @@ describe('OrcaRuntimeService', () => {
|
||||
|
||||
runtime.onPtyData(
|
||||
'hook-only-pty',
|
||||
'\x1b]9999;{"state":"waiting","prompt":"fix the tests","agentType":"opencode"}\x07',
|
||||
'\x1b]9999;{"state":"working","prompt":"fix the tests","agentType":"opencode","backgroundOnly":true}\x07',
|
||||
101
|
||||
)
|
||||
|
||||
await waitForMobileSessionTabsEvents(events, 2)
|
||||
expect(events).toHaveLength(2)
|
||||
expect(events[1]?.tabs[0]?.type === 'terminal' && events[1].tabs[0].agentStatus).toEqual(
|
||||
expect.objectContaining({ state: 'working', backgroundOnly: true })
|
||||
)
|
||||
|
||||
runtime.onPtyData(
|
||||
'hook-only-pty',
|
||||
'\x1b]9999;{"state":"waiting","prompt":"fix the tests","agentType":"opencode"}\x07',
|
||||
102
|
||||
)
|
||||
|
||||
await waitForMobileSessionTabsEvents(events, 3)
|
||||
expect(events).toHaveLength(3)
|
||||
expect(events[2]?.tabs[0]?.type === 'terminal' && events[2].tabs[0].agentStatus).toEqual(
|
||||
expect.objectContaining({ state: 'waiting' })
|
||||
)
|
||||
|
||||
@@ -36714,7 +36726,8 @@ describe('OrcaRuntimeService', () => {
|
||||
lastAssistantMessage: 'on it',
|
||||
connectionId: null,
|
||||
receivedAt: now,
|
||||
stateStartedAt: now - 100
|
||||
stateStartedAt: now - 100,
|
||||
backgroundOnly: true
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -36772,6 +36785,7 @@ describe('OrcaRuntimeService', () => {
|
||||
taskTitle: 'Dispatch prompt work',
|
||||
displayName: 'Review dispatch prompts and make worker labels distinct',
|
||||
lastAssistantMessage: 'on it',
|
||||
backgroundOnly: true,
|
||||
stateStartedAt: now - 100,
|
||||
updatedAt: now
|
||||
})
|
||||
|
||||
@@ -10928,7 +10928,8 @@ export class OrcaRuntimeService {
|
||||
(previous.payload.agentType ?? null) !== (payload.agentType ?? null) ||
|
||||
(previous.payload.toolName ?? null) !== (payload.toolName ?? null) ||
|
||||
(previous.payload.interactivePrompt ?? null) !== (payload.interactivePrompt ?? null) ||
|
||||
(previous.payload.interrupted ?? false) !== (payload.interrupted ?? false)
|
||||
(previous.payload.interrupted ?? false) !== (payload.interrupted ?? false) ||
|
||||
(previous.payload.backgroundOnly ?? false) !== (payload.backgroundOnly ?? false)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -118,6 +118,19 @@ describe('AgentTerminalDialog', () => {
|
||||
expect(screen.getByText(/Claude · Done/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('labels background-only work without claiming the foreground turn is working', () => {
|
||||
render(
|
||||
<AgentTerminalDialog
|
||||
card={card({ backgroundOnly: true })}
|
||||
onOpenChange={() => {}}
|
||||
onReveal={() => {}}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText(/Claude · Background work/)).toBeInTheDocument()
|
||||
expect(screen.queryByText(/Claude · Working/)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('preserves the execution host when revealing a colliding worktree ID', () => {
|
||||
const onReveal = vi.fn()
|
||||
render(
|
||||
|
||||
@@ -39,6 +39,9 @@ function AgentTerminalFrame({
|
||||
onOpenChange,
|
||||
onReveal
|
||||
}: AgentTerminalFrameProps): React.JSX.Element {
|
||||
const displayState = dashboardCardDisplayState(card)
|
||||
const foregroundState =
|
||||
card.backgroundOnly === true && displayState === 'working' ? 'background' : displayState
|
||||
const reveal = (): void => {
|
||||
onReveal({
|
||||
repoId: card.repoId,
|
||||
@@ -58,8 +61,7 @@ function AgentTerminalFrame({
|
||||
</span>
|
||||
{title}
|
||||
<span className="text-[11px] text-muted-foreground">
|
||||
{formatAgentTypeLabel(card.agentType)} ·{' '}
|
||||
{agentStateLabel(dashboardCardDisplayState(card))}
|
||||
{formatAgentTypeLabel(card.agentType)} · {agentStateLabel(foregroundState)}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -1987,6 +1987,64 @@ describe('applyWebSessionTabsSnapshot', () => {
|
||||
expect(patch.sortEpoch).toBe(1)
|
||||
})
|
||||
|
||||
it('invalidates mirrored presentation caches when only background work remains', () => {
|
||||
const hostPaneKey = makePaneKey('host-tab-1', LEAF_ID)
|
||||
const snapshot = makeSnapshot([
|
||||
{
|
||||
type: 'terminal',
|
||||
id: HOST_SURFACE_ID,
|
||||
title: 'claude [working]',
|
||||
parentTabId: 'host-tab-1',
|
||||
leafId: LEAF_ID,
|
||||
isActive: true,
|
||||
status: 'ready',
|
||||
terminal: 'terminal-1',
|
||||
agentStatus: {
|
||||
state: 'working',
|
||||
prompt: 'run the server',
|
||||
updatedAt: NOW - 100,
|
||||
stateStartedAt: NOW - 1_000,
|
||||
agentType: 'claude',
|
||||
paneKey: hostPaneKey,
|
||||
tabId: 'host-tab-1',
|
||||
worktreeId: WT,
|
||||
stateHistory: []
|
||||
}
|
||||
}
|
||||
])
|
||||
const initial = applyWebSessionTabsSnapshot(
|
||||
makeState(),
|
||||
snapshot,
|
||||
ENV,
|
||||
NOW
|
||||
) as Partial<WebSessionTabsSyncState>
|
||||
const mirroredPaneKey = Object.keys(initial.agentStatusByPaneKey ?? {})[0]!
|
||||
const background = applyWebSessionTabsSnapshot(
|
||||
makeState({ ...initial }),
|
||||
{
|
||||
...snapshot,
|
||||
snapshotVersion: 2,
|
||||
tabs: snapshot.tabs.map((tab) =>
|
||||
tab.type === 'terminal' && tab.agentStatus
|
||||
? {
|
||||
...tab,
|
||||
agentStatus: { ...tab.agentStatus, backgroundOnly: true }
|
||||
}
|
||||
: tab
|
||||
)
|
||||
},
|
||||
ENV,
|
||||
NOW
|
||||
) as Partial<WebSessionTabsSyncState>
|
||||
|
||||
expect(background.agentStatusByPaneKey?.[mirroredPaneKey]).toMatchObject({
|
||||
state: 'working',
|
||||
backgroundOnly: true
|
||||
})
|
||||
expect(background.agentStatusEpoch).toBe(2)
|
||||
expect(background.sortEpoch).toBe(2)
|
||||
})
|
||||
|
||||
it('applies a marker-only host restart degradation to mirrored agent status', () => {
|
||||
const hostPaneKey = makePaneKey('host-tab-1', LEAF_ID)
|
||||
const snapshot = makeSnapshot([
|
||||
|
||||
@@ -1293,9 +1293,11 @@ function buildMirroredAgentStatusPatch(
|
||||
existing?.state === 'done' &&
|
||||
entry.state === 'done' &&
|
||||
agentEntryCompletionAt(existing) !== agentEntryCompletionAt(entry)
|
||||
const backgroundOnlyChanged = existing?.backgroundOnly !== entry.backgroundOnly
|
||||
const entrySortRelevantChange =
|
||||
!existing ||
|
||||
existing.state !== entry.state ||
|
||||
backgroundOnlyChanged ||
|
||||
!isAgentStatusFresh(existing, now) ||
|
||||
entryFreshnessChanged ||
|
||||
entryAttributionChanged ||
|
||||
|
||||
Reference in New Issue
Block a user