mirror of
https://github.com/stablyai/orca.git
synced 2026-11-04 11:07:42 +00:00
* 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
104 lines
4.1 KiB
TypeScript
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()
|
|
}
|
|
})
|