Files
orca/tests/e2e/native-chat-rewind-boundary.unit.test.ts
T
Brennan Benson 6059a5f7fa Native chat: a message is never marked not delivered after a crash, and reopening a chat stops reading the transcript (#30580)
* Native chat: never mark a message not delivered after a crash; reopening a chat stops reading the transcript

After a crash or restart, attach read a window of the agent's saved log and marked any
unconfirmed message missing from it as rejected "not delivered". Absence from a log Orca
does not own is not proof: the window's anchor moves every turn (STA-7416) and its text
filters drop tagged prompts (STA-7453), so messages the agent received were marked
undelivered and retyping them duplicated the question. The read also ran on every attach
(STA-7434).

Delete the inference: the provider history window and its adapter hooks (Claude window and
branch proof, OpenCode stored-messages reader), the submission matcher, and the attach step.
A send the crash boundary leaves unconfirmed stays an ordinary sent message; the interrupted
turn still ends through the existing settlement. Explicit live refusals are unchanged.

A queued card whose hand-off a restart left unconfirmed no longer counts as able to return
to the queue, so it stops keeping an idle chat loaded.

* test(native-chat): update restart fixture calls after history removal

* Hide rewind targets without a saved native boundary

* Run rewind boundary regression across host and renderer

* Keep incoming main package-manager lock unchanged

* Test recovered chat consumer outcomes without delivery inference

* Assign the complete cursor in recovered phone regression fixture

* Supply presentation capture in rewind boundary fixture
2026-10-24 00:27:01 -07:00

104 lines
4.1 KiB
TypeScript

// @vitest-environment happy-dom
import { renderHook } from '@testing-library/react'
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, expect, it, vi } from 'vitest'
import {
agentJournalItemKey,
agentJournalSubmissionKey
} from '../../src/shared/agent-session-journal-item-key'
import { EMPTY_STRUCTURED_AGENT_SESSION } from '../../src/shared/structured-agent-session-reducer'
import { AGENT_JOURNAL_THREAD_SCOPE } from '../../src/shared/agent-session-journal-types'
import { codexProviderHandle } from '../../src/shared/agent-session-provider-handle-encoding'
import { createTrackedJournalOpener } from '../../src/main/native-chat/agent-session-journal/journal-host-database-test-support'
import { structuredRewindCut } from '../../src/main/native-chat/agent-session-wire/structured-rewind-cut'
import { useNativeChatRewind } from '../../src/renderer/src/components/native-chat/use-native-chat-rewind'
const IDENTITY = {
sessionId: 'session-1',
workspaceId: 'ws-1',
hostId: 'host-1',
agent: 'codex',
providerHandle: codexProviderHandle('main')
}
const journals = createTrackedJournalOpener()
let root: string
afterEach(async () => {
await journals.closeAll()
await rm(root, { recursive: true, force: true })
})
it('offers a recovered opener only when its saved native alias arrives, retaining prior history', async () => {
root = await mkdtemp(join(tmpdir(), 'orca-rewind-boundary-'))
const options = { identity: IDENTITY, stateDirectory: root }
const journal = await journals.open(options)
const native = (uuid: string) => ({ provider: 'claude' as const, sessionId: 'original', uuid })
const blocks = [{ type: 'text' as const, text: 'prompt' }]
const body = { kind: 'message' as const, role: 'user' as const, blocks }
const attribution = { fence: 1, turnScope: AGENT_JOURNAL_THREAD_SCOPE }
await journal.appendItem(native('earlier'), body, attribution)
await journal.appendItem(native('reply'), { ...body, role: 'assistant' }, attribution)
await journal.appendSubmission({
clientMessageId: 'lost',
payloadFingerprint: 'lost',
body,
fence: 1
})
await journals.closeAll()
const recovered = await journals.open(options)
await recovered.markPendingSubmissionsUnknown(2)
const snapshot = recovered.snapshot()
const target = agentJournalSubmissionKey('lost')
expect(snapshot.submissions.find((row) => row.clientMessageId === 'lost')).toMatchObject({
dispatchState: 'unknown',
recovered: true
})
const state = {
...EMPTY_STRUCTURED_AGENT_SESSION,
...snapshot,
epoch: snapshot.cursor.epoch,
status: 'ready' as const,
fence: 2
}
const props = {
sessionId: IDENTITY.sessionId,
capturePresentation: () => () => true,
state,
support: { supported: true as const },
blocked: false,
send: vi.fn()
}
const view = renderHook((input) => useNativeChatRewind(input), { initialProps: props })
try {
expect(view.result.current.surface?.eligibleItemIds).not.toContain(target)
const cursor = recovered.cursor()
expect(
structuredRewindCut(recovered, { itemId: target, provider: 'claude', nativeId: 'forked' })
).toBeNull()
expect(recovered.cursor()).toEqual(cursor)
view.rerender({ ...props, state: { ...state, submissions: [] } })
expect(view.result.current.surface?.eligibleItemIds).toContain(target)
await recovered.acceptLate({
clientMessageId: 'lost',
state: 'accepted',
providerIdentity: native('genuine'),
fence: 2
})
view.rerender({ ...props, state: { ...state, submissions: recovered.snapshot().submissions } })
expect(view.result.current.surface?.eligibleItemIds).toContain(target)
const cut = structuredRewindCut(recovered, {
itemId: target,
provider: 'claude',
nativeId: 'forked'
})
expect(cut).toMatchObject({ beforeMessage: { sessionId: 'original', uuid: 'genuine' } })
expect(cut?.kept.map(({ identity }) => agentJournalItemKey(identity))).toEqual([
agentJournalItemKey(native('earlier')),
agentJournalItemKey(native('reply'))
])
} finally {
view.unmount()
}
})