mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 00:03:46 +00:00
fix(agent-hooks): keep a displaced completion unread in the sidebar badge (STA-3386)
Review round 3: sidebar-badge mode counts only the live entry, so a session boundary landing on an unacknowledged completion silently dropped the sidebar badge while the agent-events count kept it. Count the displaced completion from history for boundary rows, and pin the behavior with countActivityUnread tests.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { AgentStatusEntry } from '../../../../shared/agent-status-types'
|
||||
import { countActivityUnread } from './useActivityUnreadCount'
|
||||
|
||||
const PANE = 'tab-1:11111111-1111-4111-8111-111111111111'
|
||||
|
||||
function makeEntry(overrides: Partial<AgentStatusEntry>): AgentStatusEntry {
|
||||
return {
|
||||
state: 'done',
|
||||
prompt: '',
|
||||
updatedAt: 2_000,
|
||||
stateStartedAt: 2_000,
|
||||
paneKey: PANE,
|
||||
agentType: 'claude',
|
||||
stateHistory: [],
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
function makeSource(entry: AgentStatusEntry, ackAt = 0) {
|
||||
return {
|
||||
acknowledgedAgentsByPaneKey: { [PANE]: ackAt },
|
||||
agentStatusByPaneKey: { [PANE]: entry },
|
||||
migrationUnsupportedByPtyId: {},
|
||||
retainedAgentsByPaneKey: {},
|
||||
worktreesByRepo: {}
|
||||
}
|
||||
}
|
||||
|
||||
describe('countActivityUnread session-boundary rows (STA-3386)', () => {
|
||||
it('does not count a session-boundary done as unread in either mode', () => {
|
||||
const source = makeSource(makeEntry({ sessionBoundary: true }))
|
||||
expect(countActivityUnread(source, 'sidebar-badge')).toBe(0)
|
||||
expect(countActivityUnread(source, 'agent-events')).toBe(0)
|
||||
})
|
||||
|
||||
it('keeps counting a real completion displaced into history by a session boundary', () => {
|
||||
// Why: agent finished (unacknowledged), then the user resumed the session — the
|
||||
// boundary row replaces the live done but the finish must stay unread in both badges.
|
||||
const source = makeSource(
|
||||
makeEntry({
|
||||
sessionBoundary: true,
|
||||
stateHistory: [{ state: 'done', prompt: 'fix bug', startedAt: 1_000 }]
|
||||
})
|
||||
)
|
||||
expect(countActivityUnread(source, 'sidebar-badge')).toBe(1)
|
||||
expect(countActivityUnread(source, 'agent-events')).toBe(1)
|
||||
})
|
||||
|
||||
it('stops counting the displaced completion once acknowledged', () => {
|
||||
const source = makeSource(
|
||||
makeEntry({
|
||||
sessionBoundary: true,
|
||||
stateHistory: [{ state: 'done', prompt: 'fix bug', startedAt: 1_000 }]
|
||||
}),
|
||||
1_500
|
||||
)
|
||||
expect(countActivityUnread(source, 'sidebar-badge')).toBe(0)
|
||||
expect(countActivityUnread(source, 'agent-events')).toBe(0)
|
||||
})
|
||||
|
||||
it('still counts an ordinary unacknowledged done in sidebar-badge mode', () => {
|
||||
const source = makeSource(makeEntry({}))
|
||||
expect(countActivityUnread(source, 'sidebar-badge')).toBe(1)
|
||||
})
|
||||
})
|
||||
@@ -60,14 +60,22 @@ export function countActivityUnread(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Why: a session-boundary done is an idle connect (STA-3386), not an event to read;
|
||||
// history never contains one (the status slice skips the push), so only the live entry needs the gate.
|
||||
// Why: a session-boundary done is an idle connect (STA-3386), not an event to read.
|
||||
// History never contains a boundary, but it DOES keep the real completion a boundary
|
||||
// displaced (the slice pushes it on done→done), so sidebar-badge mode — which skips the
|
||||
// history loop above — must still count that displaced completion or the badge silently
|
||||
// drops an unacknowledged finish the moment its session is resumed.
|
||||
if (
|
||||
isUnreadAgentState(entry.state) &&
|
||||
entry.sessionBoundary !== true &&
|
||||
ackAt < entry.stateStartedAt
|
||||
) {
|
||||
count += 1
|
||||
} else if (mode === 'sidebar-badge' && entry.state === 'done' && entry.sessionBoundary) {
|
||||
const displaced = entry.stateHistory.at(-1)
|
||||
if (displaced && isUnreadAgentState(displaced.state) && ackAt < displaced.startedAt) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user