mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 00:02:38 +00:00
fix(native-chat): the host-window timer waits only for windows still open
A window that had already closed while the journal was still loading would arm a zero-delay timer that re-armed itself every tick. Only windows still open arm one now; a closed one is settled when the journal loads. Adds a test that a Stop-outrun send comes back once its window closes.
This commit is contained in:
+5
-3
@@ -63,16 +63,18 @@ export function settleStructuredAgentSessionOutboxFromJournal(
|
||||
return entries
|
||||
}
|
||||
|
||||
/** When the soonest host window closes among entries only an owed answer settles, or null. */
|
||||
/** When the soonest host window still open closes among entries only an owed answer settles, or
|
||||
* null. One already closed waits for the journal to load, which re-runs the settlement anyway. */
|
||||
export function nextStructuredAgentSessionHostWindowEnd(
|
||||
entries: readonly StructuredAgentSessionOutboxEntry[]
|
||||
entries: readonly StructuredAgentSessionOutboxEntry[],
|
||||
now: number
|
||||
): number | null {
|
||||
let soonest: number | null = null
|
||||
for (const entry of entries) {
|
||||
const endsAt = structuredAgentSessionEntryAwaitsSettlement(entry)
|
||||
? structuredAgentSessionEntryHostWindowEndsAt(entry)
|
||||
: null
|
||||
if (endsAt !== null && (soonest === null || endsAt < soonest)) {
|
||||
if (endsAt !== null && endsAt >= now && (soonest === null || endsAt < soonest)) {
|
||||
soonest = endsAt
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -11,6 +11,9 @@ import type {
|
||||
AgentJournalSubmission
|
||||
} from '../../../../shared/agent-session-journal-types'
|
||||
import { createStructuredAgentSessionOutboxEntry } from '../../../../shared/structured-agent-session-outbox'
|
||||
import { createStructuredAgentSessionOperationId } from '../../../../shared/structured-agent-session-mutation'
|
||||
import { AGENT_SESSION_MAX_OPERATION_REPLAY_AGE_MS } from '../../../../shared/agent-session-host-authority'
|
||||
import { createBrowserUuid } from '@/lib/browser-uuid'
|
||||
|
||||
type SentParams = { envelope: { clientOperationId: string }; delivery?: string }
|
||||
|
||||
@@ -260,6 +263,26 @@ describe('a Stop answered while the journal is already read through it', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("the host's window for a send a Stop outran", () => {
|
||||
it('hands it back to check the chat once the window closes, though nothing else moved', async () => {
|
||||
const id = createStructuredAgentSessionOperationId(
|
||||
createBrowserUuid,
|
||||
Date.now() - AGENT_SESSION_MAX_OPERATION_REPLAY_AGE_MS + 5_000
|
||||
)
|
||||
seedAttempted({ clientMessageId: id, stoppedBy: { operationId: 'stop-1' } })
|
||||
const view = mount({ fence: 1, submissions: [], journalCursor: { epoch: 'e', sequence: 3 } })
|
||||
expect(view.result.current.outbox).toHaveLength(1)
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(6_000)
|
||||
})
|
||||
expect(view.result.current.outbox).toEqual([])
|
||||
expect(readNativeChatDraftCache(SCOPE)).toBe('follow-up')
|
||||
expect(view.result.current.error).toBe(CHECK_THE_CHAT)
|
||||
expect(mocks.call).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('a send in doubt behind one a Stop outran', () => {
|
||||
it('is the one resent: the queue waits on it, not on the stamped one', async () => {
|
||||
const later = {
|
||||
|
||||
@@ -171,13 +171,13 @@ export function useStructuredAgentSessionOutbox(args: {
|
||||
}
|
||||
// A send only an owed answer settles must still end when the host's window for it closes,
|
||||
// though nothing else moves by then.
|
||||
const windowEnd = nextStructuredAgentSessionHostWindowEnd(entries)
|
||||
const windowEnd = nextStructuredAgentSessionHostWindowEnd(entries, now)
|
||||
if (windowEnd === null) {
|
||||
return
|
||||
}
|
||||
const timer = setTimeout(
|
||||
() => setHostWindowsClosed((count) => count + 1),
|
||||
Math.min(Math.max(windowEnd - now + 1, 0), MAX_TIMER_DELAY_MS)
|
||||
Math.min(windowEnd - now + 1, MAX_TIMER_DELAY_MS)
|
||||
)
|
||||
return () => clearTimeout(timer)
|
||||
}, [
|
||||
|
||||
Reference in New Issue
Block a user