mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 10:08:13 +00:00
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
This commit is contained in:
@@ -1,111 +0,0 @@
|
||||
import { mkdirSync, mkdtempSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import Database from '../sqlite/sync-database'
|
||||
import { readOpenCodeTranscriptPage } from '../native-chat/transcript-opencode-sqlite-query'
|
||||
import {
|
||||
openCodeStoredUserMessagesReader,
|
||||
type OpenCodeTranscriptPageReader
|
||||
} from './opencode-acp-stored-messages'
|
||||
|
||||
const roots: string[] = []
|
||||
afterEach(() => {
|
||||
for (const root of roots.splice(0)) {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
/** An `opencode.db` under a fresh XDG data home, built by `schema`; returns that data home. */
|
||||
function dataHomeWith(schema: string): string {
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca-opencode-stored-'))
|
||||
roots.push(root)
|
||||
mkdirSync(join(root, 'opencode'))
|
||||
const db = new Database(join(root, 'opencode', 'opencode.db'))
|
||||
db.exec(schema)
|
||||
db.close()
|
||||
return root
|
||||
}
|
||||
|
||||
/** The bounded reader in-process, standing in for its worker. */
|
||||
const readInProcess: OpenCodeTranscriptPageReader = async (args) => readOpenCodeTranscriptPage(args)
|
||||
|
||||
function readFrom(dataHome: string) {
|
||||
return openCodeStoredUserMessagesReader(readInProcess)({
|
||||
env: { HOME: dataHome, USERPROFILE: dataHome, XDG_DATA_HOME: dataHome },
|
||||
providerSessionId: 'ses_1',
|
||||
signal: new AbortController().signal
|
||||
})
|
||||
}
|
||||
|
||||
const OPENCODE_2_TABLES = `CREATE TABLE session_v2 (id TEXT PRIMARY KEY);
|
||||
CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT, type TEXT, seq INTEGER,
|
||||
data TEXT, time_created INTEGER, time_updated INTEGER);`
|
||||
|
||||
describe('OpenCode stored user messages', () => {
|
||||
it("reads the database under the chat's own home, and only its user messages", async () => {
|
||||
const readPage = vi.fn<OpenCodeTranscriptPageReader>(async () => ({
|
||||
items: [
|
||||
{
|
||||
rowid: 1,
|
||||
fingerprint: 'a',
|
||||
message: { id: 'msg_1', role: 'user', blocks: [], timestamp: 5, source: 'transcript' }
|
||||
},
|
||||
{
|
||||
rowid: 2,
|
||||
fingerprint: 'b',
|
||||
message: {
|
||||
id: 'msg_2',
|
||||
role: 'assistant',
|
||||
blocks: [],
|
||||
timestamp: 6,
|
||||
source: 'transcript'
|
||||
}
|
||||
}
|
||||
],
|
||||
hasMore: false,
|
||||
beforeMessageRowId: 1
|
||||
}))
|
||||
const home = join('/', 'child-home')
|
||||
const read = openCodeStoredUserMessagesReader(readPage)
|
||||
await expect(
|
||||
read({
|
||||
env: { HOME: home, USERPROFILE: home },
|
||||
providerSessionId: 'ses_1',
|
||||
signal: new AbortController().signal
|
||||
})
|
||||
).resolves.toEqual([{ id: 'msg_1', blocks: [], createdAt: 5 }])
|
||||
expect(readPage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
dbPath: join(home, '.local', 'share', 'opencode', 'opencode.db'),
|
||||
sessionId: 'ses_1'
|
||||
}),
|
||||
expect.anything()
|
||||
)
|
||||
})
|
||||
|
||||
it('reads a 2.x session from its live tables, not the frozen 1.x copy an upgrade left', async () => {
|
||||
const dataHome = dataHomeWith(`${OPENCODE_2_TABLES}
|
||||
CREATE TABLE session (id TEXT PRIMARY KEY);
|
||||
CREATE TABLE message (id TEXT, session_id TEXT, time_created INTEGER, time_updated INTEGER,
|
||||
data TEXT);
|
||||
CREATE TABLE part (id TEXT, message_id TEXT, session_id TEXT, time_updated INTEGER, data TEXT);
|
||||
INSERT INTO session VALUES ('ses_1');
|
||||
INSERT INTO message VALUES ('old', 'ses_1', 1, 1, '{"role":"user"}');
|
||||
INSERT INTO part VALUES ('p', 'old', 'ses_1', 1, '{"type":"text","text":"frozen"}');
|
||||
INSERT INTO session_v2 VALUES ('ses_1');
|
||||
INSERT INTO session_message VALUES
|
||||
('u1', 'ses_1', 'user', 1, '{"text":"live prompt"}', 50, 50),
|
||||
('a1', 'ses_1', 'assistant', 2, '{"content":[{"type":"text","text":"reply"}]}', 60, 60);`)
|
||||
await expect(readFrom(dataHome)).resolves.toEqual([
|
||||
{ id: 'opencode:u1', blocks: [{ type: 'text', text: 'live prompt' }], createdAt: 50 }
|
||||
])
|
||||
})
|
||||
|
||||
it('finds nothing in a 2.x database whose message table it does not know', async () => {
|
||||
const dataHome = dataHomeWith(`CREATE TABLE session_v2 (id TEXT PRIMARY KEY);
|
||||
CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT, body TEXT);
|
||||
INSERT INTO session_v2 VALUES ('ses_1');`)
|
||||
await expect(readFrom(dataHome)).resolves.toBeNull()
|
||||
})
|
||||
})
|
||||
@@ -1,38 +0,0 @@
|
||||
import { homedir } from 'node:os'
|
||||
import { OPENCODE_TRANSCRIPT_MAX_WINDOW } from '../../shared/opencode-transcript-page-limit'
|
||||
import type { AcpStoredUserMessagesReader } from '../acp/acp-recovery-history'
|
||||
import { resolveOpenCodeDatabasePath } from './opencode-data-directory'
|
||||
import type { readOpenCodeTranscriptPageViaWorker } from '../ai-vault/session-scanner-opencode-sqlite-worker-spawn'
|
||||
|
||||
export type OpenCodeTranscriptPageReader = typeof readOpenCodeTranscriptPageViaWorker
|
||||
|
||||
/** The latest user messages of an OpenCode session, read from the database its pinned account
|
||||
* selects, through the existing bounded reader: 2.x's `session_v2` rows first, else 1.x's. */
|
||||
export function openCodeStoredUserMessagesReader(
|
||||
readPage?: OpenCodeTranscriptPageReader
|
||||
): AcpStoredUserMessagesReader {
|
||||
return async ({ env, providerSessionId, signal }) => {
|
||||
// The child's own home, which its account may name, not this process's.
|
||||
const home = (process.platform === 'win32' ? env.USERPROFILE : env.HOME) || homedir()
|
||||
const dbPath = resolveOpenCodeDatabasePath(env, home)
|
||||
if (!dbPath) {
|
||||
return null
|
||||
}
|
||||
const read =
|
||||
readPage ??
|
||||
(await import('../ai-vault/session-scanner-opencode-sqlite-worker-spawn'))
|
||||
.readOpenCodeTranscriptPageViaWorker
|
||||
const page = await read(
|
||||
{ dbPath, sessionId: providerSessionId, limit: OPENCODE_TRANSCRIPT_MAX_WINDOW },
|
||||
signal
|
||||
)
|
||||
if (!page) {
|
||||
return null
|
||||
}
|
||||
return page.items.flatMap(({ message }) =>
|
||||
message.role === 'user' && message.timestamp !== null
|
||||
? [{ id: message.id, blocks: message.blocks, createdAt: message.timestamp }]
|
||||
: []
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user