114 lines
4.8 KiB
TypeScript
114 lines
4.8 KiB
TypeScript
import { readFile, writeFile } from 'node:fs/promises'
|
|
import { dirname, join } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import { Context } from 'cordis'
|
|
import { normalizeSessionLog, scrubRequestHeaders, type NormalizeContext } from '@deepseek-ai/dsh-acp-snapshot'
|
|
import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
|
|
import { CallId } from '@deepseek-ai/dsh-llm'
|
|
import SessionStore, { SESSION_FORMAT_VERSION, SessionId, type SessionEvent, type SessionHeader } from '@deepseek-ai/dsh-session'
|
|
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const fixtureDir = join(dirname(fileURLToPath(import.meta.url)), 'semantic-checkpoint-snapshots/tool-outcome-unknown')
|
|
const replayFixture = join(fixtureDir, 'replay.jsonl')
|
|
const replayOverride = join(fixtureDir, 'replay.override.json')
|
|
const sessionExpected = join(fixtureDir, 'session.expected.jsonl')
|
|
const configPath = fileURLToPath(new URL('../semantic-checkpoint.cordis.snapshot.yml', import.meta.url))
|
|
const binScript = fileURLToPath(new URL('../../../packages/examples/cli-demo/src/bin.ts', import.meta.url))
|
|
const tsconfigPath = fileURLToPath(new URL('../../../tsconfig.json', import.meta.url))
|
|
const sessionId = SessionId('semantic-checkpoint-unknown-outcome')
|
|
const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
|
|
const task = 'Continue safely from the interrupted operation.'
|
|
|
|
async function seedInterruptedSession(root: string, cwd: string): Promise<string> {
|
|
const ctx = new Context()
|
|
await ctx.plugin(SessionStore)
|
|
await ctx.plugin(SessionPersistenceJsonl, { root, compression: 'none' })
|
|
const meta: SessionHeader = {
|
|
version: SESSION_FORMAT_VERSION,
|
|
id: sessionId,
|
|
createdAt: 1,
|
|
cwd,
|
|
delegationDepth: 0,
|
|
}
|
|
const events: SessionEvent[] = [
|
|
{ type: 'turn/start', seq: 0, time: 10, data: { turn: 1, trigger: { kind: 'message', source: { kind: 'user' } } } },
|
|
{ type: 'user/message', seq: 1, time: 11, data: { content: [{ type: 'text', text: 'Perform one side-effecting remote mutation.' }], source: { kind: 'user' } }, surfaceOp: 'append' },
|
|
{ type: 'step/start', seq: 2, time: 12, data: { turn: 1, step: 1 } },
|
|
{
|
|
type: 'assistant/message',
|
|
seq: 3,
|
|
time: 13,
|
|
data: {
|
|
turn: 1,
|
|
step: 1,
|
|
content: [{ type: 'tool-call', id: CallId('unknown-outcome-call'), name: 'write_remote', arguments: '{"value":1}' }],
|
|
provenance: { provider: 'deepseek', model: 'deepseek-v4-flash' },
|
|
},
|
|
surfaceOp: 'append',
|
|
},
|
|
{
|
|
type: 'tool/call',
|
|
seq: 4,
|
|
time: 14,
|
|
data: {
|
|
turn: 1,
|
|
step: 1,
|
|
callId: CallId('unknown-outcome-call'),
|
|
name: 'write_remote',
|
|
arguments: '{"value":1}',
|
|
},
|
|
},
|
|
]
|
|
try {
|
|
await ctx.sessionPersistence.create(meta)
|
|
await ctx.sessionPersistence.append(sessionId, events)
|
|
const location = ctx.sessionPersistence.locate(meta)
|
|
if (location === undefined) throw new Error('JSONL backend did not locate the seeded session')
|
|
return location.path
|
|
} finally {
|
|
await ctx.fiber.dispose()
|
|
}
|
|
}
|
|
|
|
describe('semantic checkpoint recovery snapshot', () => {
|
|
it('resumes an unknown tool outcome through the headless stream-json app', async () => {
|
|
let cwd = ''
|
|
let sessionPath = ''
|
|
const result = await runLoaderSmoke({
|
|
label: 'semantic checkpoint headless stream-json snapshot',
|
|
tempDirPrefix: 'dsh-semantic-snapshot-',
|
|
binScript,
|
|
configPath,
|
|
binArgs: ['--config', configPath, '--output-format', 'stream-json', task],
|
|
tsconfigPath,
|
|
env: {
|
|
DSH_SNAPSHOT_FILE: replayFixture,
|
|
DSH_SNAPSHOT_OVERRIDE: replayOverride,
|
|
},
|
|
prepare: async (runCwd) => {
|
|
cwd = runCwd
|
|
sessionPath = await seedInterruptedSession(join(runCwd, '.sessions'), runCwd)
|
|
},
|
|
inspect: async () => {
|
|
const normalization: NormalizeContext = { sessionIds: [sessionId], cwd }
|
|
const session = scrubRequestHeaders(normalizeSessionLog(await readFile(sessionPath, 'utf8'), normalization))
|
|
if (refreshing) await writeFile(sessionExpected, session)
|
|
expect(session).toBe(await readFile(sessionExpected, 'utf8'))
|
|
expect(session).toContain('TOOL_OUTCOME_UNKNOWN')
|
|
expect(session).toContain('Do not retry blindly.')
|
|
},
|
|
})
|
|
|
|
expect(result.stderr).toBe('')
|
|
const records = result.stdout.trimEnd().split('\n').map(line => JSON.parse(line) as Record<string, unknown>)
|
|
expect(records.at(-1)).toMatchObject({
|
|
type: 'result',
|
|
success: true,
|
|
sessionId,
|
|
result: 'I will verify the external state before deciding whether to retry the side-effecting operation.',
|
|
reason: { kind: 'completed' },
|
|
})
|
|
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
|
|
})
|