import { mkdirSync, mkdtempSync, rmSync } from 'node:fs' import { createHash } from 'node:crypto' import { canonicalJson } from '../shared/agent-protocol/canonical' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it } from 'vitest' import { REMOTE_SEMANTIC_TRANSCRIPT_LIMITS } from '../shared/remote-agent-contracts' import { SemanticPromptStore } from './semantic-prompt-store' const temporary: string[] = [] function store(options: { maximumEventsPerPrompt?: number maximumTranscriptBytes?: number maximumRetainedAcknowledgedOperations?: number } = {}): SemanticPromptStore { const root = mkdtempSync(join(tmpdir(), 'goodbuddy-semantic-prompt-')) temporary.push(root) mkdirSync(join(root, 'state'), { mode: 0o700 }) return new SemanticPromptStore(join(root, 'state', 'prompts.sqlite'), { now: () => 1_000, ...options }) } afterEach(() => { for (const path of temporary.splice(0)) { rmSync(path, { recursive: true, force: true }) } }) describe('SemanticPromptStore', () => { it('preserves legacy preparation digests and replay identity instead of accepting a mode-free resubmission', () => { const first = store() const identity = { bindingId: 'legacy-binding', operationId: 'legacy-operation', requestId: 'legacy-operation', controllerId: 'controller-1', promptSequence: 0 } const digest = (value: unknown) => `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}` const preparation = { ...identity, preparationDigest: digest({ ...identity, workMode: 'ask' }) } const start = { bindingId: identity.bindingId, operationId: identity.operationId, startDigest: digest({ prompt: 'original' }), sessionId: 'legacy-session' } first.prepare(preparation) first.begin(start) const payload = { update: { sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: 'original' } } } first.append({ ...identity, kind: 'session-update', payload }) first.close() const reopened = new SemanticPromptStore(join(temporary.at(-1)!, 'state', 'prompts.sqlite')) try { expect(reopened.prepare(preparation)).toEqual({ created: false }) expect(() => reopened.prepare({ ...preparation, preparationDigest: digest(identity) })).toThrow(/different preparation/) expect(reopened.findStarted({ ...identity, startDigest: start.startDigest })).toMatchObject({ state: 'outcome-unknown', sessionId: 'legacy-session' }) const page = reopened.page({ ...identity, afterSequence: '0', limit: 10 }) expect(page.events[0]).toMatchObject({ sequence: '1', payload }) expect(page.acknowledgedSequence).toBe('0') expect(reopened.acknowledge({ ...identity, acknowledgedSequence: page.latestSequence })) .toMatchObject({ acknowledgedSequence: page.latestSequence }) } finally { reopened.close() } }) it('replays unacknowledged output and prunes it after durable acknowledgement', () => { const first = store() first.prepare({ bindingId: 'binding-1', operationId: 'operation-1', requestId: 'operation-1', controllerId: 'controller-1', preparationDigest: `sha256:${'a'.repeat(64)}`, promptSequence: 0 }) first.begin({ bindingId: 'binding-1', operationId: 'operation-1', startDigest: `sha256:${'b'.repeat(64)}`, sessionId: 'session-1' }) first.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'session-update', payload: { update: { sessionUpdate: 'agent_message_chunk' } } }) first.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'prompt-terminal', payload: { status: 'completed', response: { stopReason: 'end_turn' } }, terminalState: 'completed' }) expect( first.page({ bindingId: 'binding-1', operationId: 'operation-1', controllerId: 'controller-1', afterSequence: '1', limit: 10 }).events.map((event) => event.sequence) ).toEqual(['2']) expect( first.acknowledge({ bindingId: 'binding-1', operationId: 'operation-1', controllerId: 'controller-1', acknowledgedSequence: '2' }) ).toMatchObject({ acknowledgedSequence: '2' }) const path = (first as unknown as { close(): void }) path.close() // Reopen the durable authority as a brand-new Desktop-facing owner. const root = temporary[0]! const second = new SemanticPromptStore( join(root, 'state', 'prompts.sqlite') ) expect( second.attach('binding-1', 'operation-1', 'controller-1') ).toMatchObject({ state: 'completed', sessionId: 'session-1', latestSemanticSequence: '2' }) expect( second.page({ bindingId: 'binding-1', operationId: 'operation-1', controllerId: 'controller-1', afterSequence: '0', limit: 10 }) ).toMatchObject({ events: [], acknowledgedSequence: '2', latestSequence: '2', state: 'completed' }) second.close() }) it('is idempotent for one canonical operation and rejects divergent starts', () => { const prompts = store() const preparation = { bindingId: 'binding-1', operationId: 'operation-1', requestId: 'operation-1', controllerId: 'controller-1', preparationDigest: `sha256:${'a'.repeat(64)}`, promptSequence: 0 } expect(prompts.prepare(preparation)).toEqual({ created: true }) expect(prompts.prepare(preparation)).toEqual({ created: false }) const start = { bindingId: 'binding-1', operationId: 'operation-1', startDigest: `sha256:${'b'.repeat(64)}`, sessionId: 'session-1' } expect(prompts.begin(start).created).toBe(true) expect(prompts.begin(start).created).toBe(false) expect( prompts.findStarted({ bindingId: 'binding-1', operationId: 'operation-1', controllerId: 'controller-1', startDigest: start.startDigest }) ).toMatchObject({ state: 'running', sessionId: 'session-1' }) expect(() => prompts.begin({ ...start, startDigest: `sha256:${'c'.repeat(64)}` }) ).toThrow(/conflicts/u) prompts.close() }) it('rejects transcript events beyond the per-event bound', () => { const prompts = store() prompts.prepare({ bindingId: 'binding-1', operationId: 'operation-1', requestId: 'operation-1', controllerId: 'controller-1', preparationDigest: `sha256:${'a'.repeat(64)}`, promptSequence: 0 }) prompts.begin({ bindingId: 'binding-1', operationId: 'operation-1', startDigest: `sha256:${'b'.repeat(64)}`, sessionId: 'session-1' }) expect(() => prompts.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'session-update', payload: { text: 'x'.repeat(1024 * 1024) } }) ).toThrow(/byte limit/u) prompts.close() }) it('reserves event-count capacity for durable terminal evidence', () => { const prompts = store({ maximumEventsPerPrompt: 3 }) prompts.prepare({ bindingId: 'binding-1', operationId: 'operation-1', requestId: 'operation-1', controllerId: 'controller-1', preparationDigest: `sha256:${'a'.repeat(64)}`, promptSequence: 0 }) prompts.begin({ bindingId: 'binding-1', operationId: 'operation-1', startDigest: `sha256:${'b'.repeat(64)}`, sessionId: 'session-1' }) for (let index = 0; index < 2; index += 1) { prompts.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'session-update', payload: { index } }) } expect(() => prompts.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'session-update', payload: { index: 3 } }) ).toThrow(/quota/u) expect( prompts.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'prompt-terminal', payload: { status: 'completed' }, terminalState: 'completed' }) ).toMatchObject({ sequence: '3', kind: 'prompt-terminal' }) prompts.close() }) it('reserves byte capacity for terminal evidence across restart', () => { const first = store({ maximumTranscriptBytes: REMOTE_SEMANTIC_TRANSCRIPT_LIMITS.maximumEventBytes + 256 }) first.prepare({ bindingId: 'binding-1', operationId: 'operation-1', requestId: 'operation-1', controllerId: 'controller-1', preparationDigest: `sha256:${'a'.repeat(64)}`, promptSequence: 0 }) first.begin({ bindingId: 'binding-1', operationId: 'operation-1', startDigest: `sha256:${'b'.repeat(64)}`, sessionId: 'session-1' }) expect(() => first.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'session-update', payload: { text: 'x'.repeat(512) } }) ).toThrow(/quota/u) first.close() const reopened = new SemanticPromptStore( join(temporary[0]!, 'state', 'prompts.sqlite'), { maximumTranscriptBytes: REMOTE_SEMANTIC_TRANSCRIPT_LIMITS.maximumEventBytes + 256 } ) expect( reopened.attach('binding-1', 'operation-1', 'controller-1') ).toMatchObject({ state: 'outcome-unknown' }) reopened.close() }) it('keeps the first terminal transition authoritative', () => { const prompts = store() prompts.prepare({ bindingId: 'binding-1', operationId: 'operation-1', requestId: 'operation-1', controllerId: 'controller-1', preparationDigest: `sha256:${'a'.repeat(64)}`, promptSequence: 0 }) prompts.begin({ bindingId: 'binding-1', operationId: 'operation-1', startDigest: `sha256:${'b'.repeat(64)}`, sessionId: 'session-1' }) prompts.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'prompt-terminal', payload: { status: 'cancelled' }, terminalState: 'cancelled' }) expect(() => prompts.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'prompt-terminal', payload: { status: 'completed' }, terminalState: 'completed' }) ).toThrow(/terminal evidence/u) expect( prompts.attach('binding-1', 'operation-1', 'controller-1') ).toMatchObject({ state: 'cancelled' }) prompts.close() }) it('marks nonterminal operations outcome-unknown after Agent restart', () => { const first = store() first.prepare({ bindingId: 'binding-1', operationId: 'operation-1', requestId: 'operation-1', controllerId: 'controller-1', preparationDigest: `sha256:${'a'.repeat(64)}`, promptSequence: 0 }) first.begin({ bindingId: 'binding-1', operationId: 'operation-1', startDigest: `sha256:${'b'.repeat(64)}`, sessionId: 'session-1' }) first.close() const second = new SemanticPromptStore( join(temporary[0]!, 'state', 'prompts.sqlite') ) expect( second.attach('binding-1', 'operation-1', 'controller-1') ).toMatchObject({ state: 'outcome-unknown' }) expect( second.page({ bindingId: 'binding-1', operationId: 'operation-1', controllerId: 'controller-1', afterSequence: '0', limit: 10 }).events ).toEqual([ expect.objectContaining({ kind: 'prompt-terminal', payload: expect.objectContaining({ status: 'outcome-unknown' }) }) ]) second.close() }) it('bounds transcript pages by aggregate control-frame bytes', () => { const prompts = store() prompts.prepare({ bindingId: 'binding-1', operationId: 'operation-1', requestId: 'operation-1', controllerId: 'controller-1', preparationDigest: `sha256:${'a'.repeat(64)}`, promptSequence: 0 }) prompts.begin({ bindingId: 'binding-1', operationId: 'operation-1', startDigest: `sha256:${'b'.repeat(64)}`, sessionId: 'session-1' }) for (let index = 0; index < 2; index += 1) { prompts.append({ bindingId: 'binding-1', operationId: 'operation-1', kind: 'session-update', payload: { text: 'x'.repeat(400 * 1024), index } }) } const first = prompts.page({ bindingId: 'binding-1', operationId: 'operation-1', controllerId: 'controller-1', afterSequence: '0', limit: 10 }) expect(first.events).toHaveLength(1) expect(first.hasMore).toBe(true) expect(Buffer.byteLength(JSON.stringify(first), 'utf8')).toBeLessThan( 768 * 1024 ) prompts.close() }) it('prunes acknowledged terminal operations while the daemon remains running', () => { const prompts = store({ maximumRetainedAcknowledgedOperations: 2 }) for (let index = 1; index <= 3; index += 1) { const operationId = `operation-${index}` prompts.prepare({ bindingId: 'binding-1', operationId, requestId: operationId, controllerId: 'controller-1', preparationDigest: `sha256:${String(index).repeat(64)}`, promptSequence: index }) prompts.begin({ bindingId: 'binding-1', operationId, startDigest: `sha256:${'b'.repeat(64)}`, sessionId: `session-${index}` }) prompts.append({ bindingId: 'binding-1', operationId, kind: 'prompt-terminal', payload: { status: 'completed', response: { stopReason: 'end_turn' } }, terminalState: 'completed' }) prompts.acknowledge({ bindingId: 'binding-1', operationId, controllerId: 'controller-1', acknowledgedSequence: '1' }) } expect(() => prompts.attach('binding-1', 'operation-1', 'controller-1') ).toThrow(/does not exist/u) expect( prompts.attach('binding-1', 'operation-2', 'controller-1') ).toMatchObject({ state: 'completed' }) expect( prompts.attach('binding-1', 'operation-3', 'controller-1') ).toMatchObject({ state: 'completed' }) prompts.close() }) })