| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607 |
- import { readFile, readdir, writeFile } from 'node:fs/promises'
- import { createServer } from 'node:http'
- import type { IncomingMessage, ServerResponse } from 'node:http'
- import { delimiter, dirname, join } from 'node:path'
- import { fileURLToPath } from 'node:url'
- import {
- normalizeSessionLog,
- normalizeStdout,
- refreshFixtureReplacements,
- scrubRequestHeaders,
- stabilizeRefreshLog,
- tokenizeSessionFixtureCwd,
- type HarvestedLog,
- type NormalizeContext,
- } from '@deepseek-ai/dsh-acp-snapshot'
- import { LOADER_SMOKE_TEST_TIMEOUT_MS, runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
- import { describe, expect, it } from 'vitest'
- const snapshotsDir = join(dirname(fileURLToPath(import.meta.url)), 'snapshots')
- const advancedScenarioDir = join(snapshotsDir, 'advanced-toolchain')
- const advancedSessionFixture = join(advancedScenarioDir, 'session.jsonl')
- const advancedStreamExpected = join(advancedScenarioDir, 'stream-json.expected.jsonl')
- const advancedConfigPath = fileURLToPath(new URL('../advanced.cordis.snapshot.yml', import.meta.url))
- const ptyScenarioDir = join(snapshotsDir, 'pty-tools')
- const ptySessionFixture = join(ptyScenarioDir, 'session.jsonl')
- const ptyStreamExpected = join(ptyScenarioDir, 'stream-json.expected.jsonl')
- const ptyConfigPath = fileURLToPath(new URL('../pty.cordis.snapshot.yml', import.meta.url))
- const goalScenarioDir = join(snapshotsDir, 'goal-tools')
- const goalConfigPath = fileURLToPath(new URL('../goal.cordis.snapshot.yml', import.meta.url))
- const retryScenarioDir = join(snapshotsDir, 'provider-retry')
- const retryConfigPath = fileURLToPath(new URL('../retry.cordis.snapshot.yml', import.meta.url))
- const credentialsScenarioDir = join(snapshotsDir, 'missing-credential')
- const credentialsConfigPath = fileURLToPath(new URL('../credentials.cordis.snapshot.yml', import.meta.url))
- const ralphScenarioDir = join(snapshotsDir, 'ralph-loop')
- const ralphConfigPath = fileURLToPath(new URL('../ralph.cordis.snapshot.yml', import.meta.url))
- const startupFailureConfigPath = fileURLToPath(new URL('./fixtures/startup-activation-error/cordis.yml', import.meta.url))
- const startupFailureExpected = join(snapshotsDir, 'startup-activation-error', 'stderr.expected.txt')
- 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 reasoningConfigPath = fileURLToPath(new URL('./fixtures/cli.cordis.yml', import.meta.url))
- const deepseekDefaultsConfigPath = fileURLToPath(new URL('./fixtures/deepseek-defaults.cordis.yml', import.meta.url))
- const refreshing = process.env.DSH_SNAPSHOT === 'refresh'
- interface JsonObject {
- [key: string]: unknown
- }
- interface PersistedLog {
- readonly content: string
- readonly header: JsonObject
- }
- interface DeepSeekDefaultsServer {
- readonly url: string
- readonly requests: JsonObject[]
- close(): Promise<void>
- }
- /** Serve one deterministic DeepSeek-compatible response while retaining its request body. */
- async function deepseekDefaultsServer(): Promise<DeepSeekDefaultsServer> {
- const requests: JsonObject[] = []
- const server = createServer((request: IncomingMessage, response: ServerResponse) => {
- let body = ''
- request.setEncoding('utf8')
- request.on('data', (chunk: string) => { body += chunk })
- request.on('end', () => {
- requests.push(JSON.parse(body) as JsonObject)
- response.writeHead(200, { 'content-type': 'text/event-stream' })
- response.end([
- 'data: {"choices":[{"delta":{"content":"DEFAULTS_OK"}}]}',
- 'data: {"choices":[{"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":3,"completion_tokens":1}}',
- 'data: [DONE]',
- '',
- ].join('\n\n'))
- })
- })
- await new Promise<void>(resolve => server.listen(0, '127.0.0.1', resolve))
- const address = server.address()
- if (address === null || typeof address === 'string') throw new Error('DeepSeek defaults snapshot server has no port')
- return {
- url: `http://127.0.0.1:${address.port}`,
- requests,
- close: () => new Promise(resolve => server.close(() => { resolve() })),
- }
- }
- function parseJsonl(content: string): JsonObject[] {
- return content.split('\n')
- .filter(line => line.trim().length > 0)
- .map(line => JSON.parse(line) as JsonObject)
- }
- function contextFromLogs(contents: readonly string[]): NormalizeContext {
- const headers = contents.map(content => parseJsonl(content)[0])
- return {
- sessionIds: headers.flatMap(header => typeof header?.id === 'string' ? [header.id] : []),
- cwd: typeof headers[0]?.cwd === 'string' ? headers[0].cwd : '\0no-cwd\0',
- }
- }
- function normalizeHeadlessStream(rawStdout: string, cwd: string): string {
- const records = parseJsonl(rawStdout)
- if (records.length === 0) throw new Error('headless snapshot emitted no stream-json records')
- const final = records.at(-1)
- if (final?.type !== 'result') throw new Error('headless snapshot did not end with a result record')
- if (records.slice(0, -1).some(record => record.type !== 'session_event')) {
- throw new Error('headless snapshot emitted a non-event record before its result')
- }
- const sessionIds = [...new Set(records.flatMap(record => typeof record.sessionId === 'string' ? [record.sessionId] : []))]
- if (sessionIds.length !== 1) throw new Error(`headless snapshot streamed ${sessionIds.length} main session ids`)
- const context: NormalizeContext = { sessionIds, cwd }
- const events = records.slice(0, -1).map((record) => {
- if (record.event === null || typeof record.event !== 'object' || Array.isArray(record.event)) {
- throw new Error('headless snapshot emitted an invalid session event')
- }
- return record.event as JsonObject
- })
- const normalizedEvents = parseJsonl(scrubRequestHeaders(normalizeSessionLog(
- `${events.map(event => JSON.stringify(event)).join('\n')}\n`,
- context,
- )))
- const normalizedRecords = records.map((record, index) => index < normalizedEvents.length
- ? { ...record, event: normalizedEvents[index] }
- : record)
- return normalizeStdout(`${normalizedRecords.map(record => JSON.stringify(record)).join('\n')}\n`, context)
- }
- /** Zero durable goal timestamps inside both metadata records and rendered XML JSON. */
- function normalizeGoalTimestamps(value: unknown): unknown {
- if (typeof value === 'string') {
- return value.replace(/("(?:createdAt|updatedAt|clearedAt)":)\d+/g, '$10')
- }
- if (Array.isArray(value)) return value.map(normalizeGoalTimestamps)
- if (value !== null && typeof value === 'object') {
- return Object.fromEntries(Object.entries(value).map(([key, item]) => [
- key,
- ['createdAt', 'updatedAt', 'clearedAt'].includes(key) && typeof item === 'number'
- ? 0
- : normalizeGoalTimestamps(item),
- ]))
- }
- return value
- }
- /** Normalize the stream's durable goal timestamps after the shared scrubbers. */
- function normalizeGoalStream(rawStdout: string, cwd: string): string {
- return parseJsonl(normalizeHeadlessStream(rawStdout, cwd))
- .map(record => JSON.stringify(normalizeGoalTimestamps(record)))
- .join('\n') + '\n'
- }
- async function scenarioPrompt(dir: string, label: string): Promise<string> {
- const input = JSON.parse(await readFile(join(dir, 'input.json'), 'utf8')) as {
- steps?: { op?: unknown; text?: unknown }[]
- }
- const prompt = input.steps?.find(step => step.op === 'prompt')?.text
- if (typeof prompt !== 'string') throw new Error(`${label} input has no prompt step`)
- return prompt
- }
- async function persistedLogs(cwd: string): Promise<PersistedLog[]> {
- const root = join(cwd, '.sessions')
- const files = (await readdir(root, { recursive: true })).filter(file => file.endsWith('.jsonl'))
- return Promise.all(files.map(async (file) => {
- const content = await readFile(join(root, file), 'utf8')
- return { content, header: parseJsonl(content)[0] ?? {} }
- }))
- }
- describe('headless stream-json snapshots', () => {
- it('prints the original Loader activation error through the assembled one-shot app', async () => {
- const result = await runLoaderSmoke({
- label: 'headless startup activation error snapshot',
- tempDirPrefix: 'headless-snapshot-startup-error-',
- binScript,
- configPath: startupFailureConfigPath,
- binArgs: ['--config', startupFailureConfigPath, '--output-format', 'stream-json', 'unreachable task'],
- tsconfigPath,
- expectedExitCode: 1,
- })
- expect(result.stdout).toBe('')
- await expect(result.stderr).toMatchFileSnapshot(startupFailureExpected)
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- it('retries a transient provider failure through the one-shot app', async () => {
- const prompt = await scenarioPrompt(retryScenarioDir, 'provider-retry')
- const streamExpected = join(retryScenarioDir, 'stream-json.expected.jsonl')
- let runCwd = ''
- const result = await runLoaderSmoke({
- label: 'provider retry headless stream-json snapshot',
- tempDirPrefix: 'headless-snapshot-provider-retry-',
- binScript,
- configPath: retryConfigPath,
- binArgs: ['--config', retryConfigPath, '--output-format', 'stream-json', prompt],
- tsconfigPath,
- env: {
- DSH_SNAPSHOT: 'replay',
- NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
- },
- prepare: (cwd) => { runCwd = cwd },
- inspect: async (cwd) => {
- const logs = await persistedLogs(cwd)
- expect(logs).toHaveLength(1)
- const records = parseJsonl(logs[0]?.content ?? '')
- const retries = records.filter(record => record.type === 'llm/retry')
- expect(retries).toHaveLength(1)
- expect(retries[0]?.data).toMatchObject({
- provider: 'deepseek-official',
- mode: 'normal',
- policyKey: '["normal",1,["RATE_LIMIT"],1,1,0]',
- retry: 1,
- maxRetries: 1,
- delayMs: 1,
- failure: { message: 'snapshot transient failure', code: 'RATE_LIMIT', status: 429 },
- })
- },
- })
- expect(result.stderr).toBe('')
- const normalized = normalizeHeadlessStream(result.stdout, runCwd)
- if (refreshing) await writeFile(streamExpected, normalized)
- expect(normalized).toBe(await readFile(streamExpected, 'utf8'))
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- it('logs actionable missing-credential guidance through the one-shot app', async () => {
- const streamExpected = join(credentialsScenarioDir, 'stream-json.expected.jsonl')
- let runCwd = ''
- const result = await runLoaderSmoke({
- label: 'missing-credential headless stream-json snapshot',
- tempDirPrefix: 'headless-snapshot-missing-credential-',
- binScript,
- configPath: credentialsConfigPath,
- binArgs: ['--config', credentialsConfigPath, '--output-format', 'stream-json', 'say pong'],
- tsconfigPath,
- env: {
- // First-run posture: no key in the environment, none under ./.dsh.
- DEEPSEEK_API_KEY: '',
- DEEPSEEK_BASE_URL: '',
- NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
- },
- prepare: (cwd) => { runCwd = cwd },
- })
- expect(result.stderr).toBe('')
- const normalized = normalizeHeadlessStream(result.stdout, runCwd)
- if (refreshing) await writeFile(streamExpected, normalized)
- expect(normalized).toBe(await readFile(streamExpected, 'utf8'))
- // The durable failure leads with the credential store — the path that
- // keeps the secret out of configuration files — and offers a literal key last.
- expect(normalized).toContain(
- 'store DEEPSEEK_API_KEY through the credentials service (the web Models page writes it),',
- )
- expect(normalized).toContain('as a last resort')
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- it('logs the model default and a dynamic next-step reasoning effort', async () => {
- const result = await runLoaderSmoke({
- label: 'reasoning effort headless stream-json snapshot',
- tempDirPrefix: 'headless-snapshot-reasoning-effort-',
- binScript,
- configPath: reasoningConfigPath,
- binArgs: ['--config', reasoningConfigPath, '--output-format', 'stream-json', 'prove dynamic reasoning effort'],
- tsconfigPath,
- })
- expect(result.stderr).toBe('')
- const headers = parseJsonl(result.stdout)
- .map(record => record.event)
- .filter((event): event is JsonObject => (
- event !== null
- && typeof event === 'object'
- && !Array.isArray(event)
- && 'type' in event
- && event.type === 'request/header'
- ))
- .map((event) => {
- const data = event.data as JsonObject
- return (data.header as JsonObject).config
- })
- expect(headers).toMatchInlineSnapshot(`
- [
- {
- "model": "cli-mock",
- "provider": "cli-mock",
- "reasoningEffort": "high",
- },
- {
- "model": "cli-mock",
- "provider": "cli-mock",
- "reasoningEffort": "off",
- },
- ]
- `)
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- it('logs and sends the DeepSeek adapter maxTokens default through the one-shot app', async () => {
- const server = await deepseekDefaultsServer()
- try {
- const result = await runLoaderSmoke({
- label: 'DeepSeek adapter defaults headless stream-json snapshot',
- tempDirPrefix: 'headless-snapshot-deepseek-defaults-',
- binScript,
- configPath: deepseekDefaultsConfigPath,
- binArgs: [
- '--config',
- deepseekDefaultsConfigPath,
- '--output-format',
- 'stream-json',
- 'return the deterministic response',
- ],
- tsconfigPath,
- env: {
- DSH_SNAPSHOT_BASE_URL: server.url,
- NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
- },
- })
- expect(result.stderr).toBe('')
- expect(server.requests).toHaveLength(1)
- expect(server.requests[0]?.max_tokens).toBe(256_000)
- const header = (parseJsonl(result.stdout)
- .map(record => record.event)
- .find((event): event is JsonObject => (
- event !== null
- && typeof event === 'object'
- && !Array.isArray(event)
- && 'type' in event
- && event.type === 'request/header'
- ))?.data as JsonObject | undefined)?.header as JsonObject | undefined
- expect(header?.config).toMatchInlineSnapshot(`
- {
- "maxTokens": 256000,
- "model": "deepseek-v4-flash",
- "provider": "deepseek-official",
- "reasoningEffort": "off",
- }
- `)
- expect(header?.adapterDefaults).toEqual({
- maxTokens: true,
- reasoningEffort: true,
- })
- } finally {
- await server.close()
- }
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- it('replays the advanced toolchain through the one-shot app', async () => {
- const prompt = await scenarioPrompt(advancedScenarioDir, 'advanced-toolchain')
- const fixtureFiles = [
- advancedSessionFixture,
- join(advancedScenarioDir, 'session.1.jsonl'),
- join(advancedScenarioDir, 'session.2.jsonl'),
- ]
- let expectedSessions = await Promise.all(fixtureFiles.map(file => readFile(file, 'utf8')))
- let runCwd = ''
- const result = await runLoaderSmoke({
- label: 'advanced headless stream-json snapshot',
- tempDirPrefix: 'headless-snapshot-advanced-',
- binScript,
- configPath: advancedConfigPath,
- binArgs: ['--config', advancedConfigPath, '--output-format', 'stream-json', prompt],
- tsconfigPath,
- env: {
- DSH_SNAPSHOT: 'replay',
- DSH_SNAPSHOT_FILE: advancedSessionFixture,
- DSH_SNAPSHOT_CHILD_FILES: [
- join(advancedScenarioDir, 'session.1.jsonl'),
- join(advancedScenarioDir, 'session.2.jsonl'),
- ].join(delimiter),
- NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
- },
- prepare: (cwd) => { runCwd = cwd },
- inspect: async (cwd) => {
- const logs = await persistedLogs(cwd)
- expect(logs).toHaveLength(3)
- const parents = logs.filter(log => typeof log.header.parentSession !== 'string')
- expect(parents).toHaveLength(1)
- const parent = parents[0]
- if (parent === undefined) throw new Error('headless snapshot did not persist its main session')
- const children = logs.filter(log => typeof log.header.parentSession === 'string')
- .sort((left, right) => Number(left.header.createdAt) - Number(right.header.createdAt))
- const actualSessions = [parent, ...children]
- const actualContext = contextFromLogs(actualSessions.map(log => log.content))
- if (refreshing) {
- const harvested = actualSessions.map((log): HarvestedLog => ({
- id: String(log.header.id),
- createdAt: Number(log.header.createdAt),
- ...typeof log.header.parentSession === 'string'
- ? { parentSession: log.header.parentSession }
- : {},
- content: log.content,
- }))
- const replacements = refreshFixtureReplacements(harvested, expectedSessions)
- expectedSessions = await Promise.all(actualSessions.map(async (actual, index) => {
- const existing = expectedSessions[index]
- const file = fixtureFiles[index]
- if (existing === undefined || file === undefined) {
- throw new Error(`headless snapshot has no fixture for persisted log ${index}`)
- }
- const stable = tokenizeSessionFixtureCwd(
- stabilizeRefreshLog(actual.content, existing, replacements, actualContext),
- )
- await writeFile(file, stable)
- return stable
- }))
- }
- const expectedContext = contextFromLogs(expectedSessions)
- for (const [index, actual] of actualSessions.entries()) {
- const expected = expectedSessions[index]
- if (expected === undefined) throw new Error(`headless snapshot has no fixture for persisted log ${index}`)
- expect(scrubRequestHeaders(normalizeSessionLog(actual.content, actualContext)))
- .toBe(scrubRequestHeaders(normalizeSessionLog(expected, expectedContext)))
- }
- },
- })
- expect(result.stderr).toBe('')
- const normalized = normalizeHeadlessStream(result.stdout, runCwd)
- if (refreshing) await writeFile(advancedStreamExpected, normalized)
- expect(normalized).toBe(await readFile(advancedStreamExpected, 'utf8'))
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- it('replays persisted goal tools through the one-shot app', async () => {
- const prompt = await scenarioPrompt(goalScenarioDir, 'goal-tools')
- const streamExpected = join(goalScenarioDir, 'stream-json.expected.jsonl')
- let runCwd = ''
- const result = await runLoaderSmoke({
- label: 'goal tools headless stream-json snapshot',
- tempDirPrefix: 'headless-snapshot-goal-tools-',
- binScript,
- configPath: goalConfigPath,
- binArgs: ['--config', goalConfigPath, '--output-format', 'stream-json', prompt],
- tsconfigPath,
- env: {
- DSH_SNAPSHOT: 'replay',
- DSH_SNAPSHOT_FILE: join(goalScenarioDir, 'session.jsonl'),
- DSH_SNAPSHOT_OVERRIDE: join(goalScenarioDir, 'replay.override.json'),
- NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
- },
- prepare: (cwd) => { runCwd = cwd },
- inspect: async (cwd) => {
- const logs = await persistedLogs(cwd)
- expect(logs).toHaveLength(1)
- const records = parseJsonl(logs[0]?.content ?? '')
- const calls = records.filter(record => record.type === 'tool/call')
- .map(record => (record.data as JsonObject | undefined)?.name)
- expect(calls).toEqual(['update_goal', 'create_goal', 'get_goal'])
- const probeResult = records.find((record) => {
- if (record.type !== 'tool/result') return false
- const data = record.data as JsonObject | undefined
- const message = data?.message as JsonObject | undefined
- const source = message?.source as JsonObject | undefined
- return source?.callId === 'call_goal_probe'
- })
- const probeData = probeResult?.data as JsonObject | undefined
- const probeMessage = probeData?.message as JsonObject | undefined
- const probeContent = probeMessage?.content as JsonObject[] | undefined
- expect(probeContent?.[0]?.isError).toBe(true)
- expect((probeData?.error as JsonObject | undefined)?.code).toBe('GOAL_NOT_FOUND')
- const goalChanges = records.filter(record => record.type === 'goal/change')
- expect(goalChanges).toHaveLength(1)
- const data = goalChanges[0]?.data as JsonObject | undefined
- const goal = data?.goal as JsonObject | undefined
- expect(data?.operation).toBe('create')
- expect(goal).toMatchObject({
- objective: 'Finish the headless goal-tool snapshot proof',
- phase: 'active',
- maxGoalRounds: 7,
- })
- },
- })
- expect(result.stderr).toBe('')
- const normalized = normalizeGoalStream(result.stdout, runCwd)
- if (refreshing) await writeFile(streamExpected, normalized)
- expect(normalized).toBe(await readFile(streamExpected, 'utf8'))
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- it('replays two fresh Ralph rounds through the one-shot app', async () => {
- const prompt = await scenarioPrompt(ralphScenarioDir, 'ralph-loop')
- const streamExpected = join(ralphScenarioDir, 'stream-json.expected.jsonl')
- let runCwd = ''
- const result = await runLoaderSmoke({
- label: 'Ralph loop headless stream-json snapshot',
- tempDirPrefix: 'headless-snapshot-ralph-loop-',
- binScript,
- configPath: ralphConfigPath,
- binArgs: ['--config', ralphConfigPath, '--output-format', 'stream-json', prompt],
- tsconfigPath,
- env: {
- DSH_SNAPSHOT: 'replay',
- DSH_SNAPSHOT_FILE: join(ralphScenarioDir, 'session.jsonl'),
- DSH_SNAPSHOT_OVERRIDE: join(ralphScenarioDir, 'replay.override.json'),
- DSH_SNAPSHOT_CHILD_FILES: [
- join(ralphScenarioDir, 'session.1.jsonl'),
- join(ralphScenarioDir, 'session.2.jsonl'),
- ].join(delimiter),
- NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
- },
- prepare: (cwd) => { runCwd = cwd },
- inspect: async (cwd) => {
- const logs = await persistedLogs(cwd)
- expect(logs).toHaveLength(3)
- const parent = logs.find(log => typeof log.header.parentSession !== 'string')
- if (parent === undefined) throw new Error('Ralph snapshot did not persist its parent session')
- const parentId = parent.header.id
- expect(typeof parentId).toBe('string')
- const children = logs.filter(log => typeof log.header.parentSession === 'string')
- .sort((left, right) => Number(left.header.createdAt) - Number(right.header.createdAt))
- expect(children).toHaveLength(2)
- expect(children.map(child => child.header.parentSession)).toEqual([parentId, parentId])
- expect(children.map(child => child.header.cwd)).toEqual([parent.header.cwd, parent.header.cwd])
- expect(parent.header.delegationDepth).toBe(0)
- expect(children.map(child => child.header.delegationDepth)).toEqual([1, 1])
- expect(children.map(child => child.header.seedLength)).toEqual([undefined, undefined])
- expect(new Set(children.map(child => child.header.id)).size).toBe(2)
- const parentRecords = parseJsonl(parent.content)
- const parentCalls = parentRecords.filter(record => record.type === 'tool/call')
- expect(parentCalls.map(record => (record.data as JsonObject | undefined)?.name)).toEqual(['ralph'])
- const parentResult = parentRecords.find(record => record.type === 'tool/result')
- const parentResultData = parentResult?.data as JsonObject | undefined
- const parentMessage = parentResultData?.message as JsonObject | undefined
- const parentContent = parentMessage?.content as JsonObject[] | undefined
- expect(parentContent?.[0]?.isError).toBe(false)
- expect(JSON.stringify(parentContent?.[0]?.content)).toContain('reported completion after 2 rounds')
- const childRecords = children.map(child => parseJsonl(child.content))
- const childPrompts = childRecords.map((records) => {
- const message = records.find(record => record.type === 'user/message')
- return JSON.stringify((message?.data as JsonObject | undefined)?.content)
- })
- expect(childPrompts[0]).toContain('Ralph round: 1 of 2.')
- expect(childPrompts[0]).toContain('(none — this is the first round)')
- expect(childPrompts[0]).not.toContain('ROUND_ONE_HANDOFF')
- expect(childPrompts[1]).toContain('Ralph round: 2 of 2.')
- expect(childPrompts[1]).toContain('ROUND_ONE_HANDOFF')
- for (const childPrompt of childPrompts) {
- expect(childPrompt).toContain('Prove two fresh Ralph rounds through the shipped headless app.')
- expect(childPrompt).not.toContain('Run a two-round fresh-agent Ralph loop')
- }
- for (const records of childRecords) {
- const calls = records.filter(record => record.type === 'tool/call')
- expect(calls.map(record => (record.data as JsonObject | undefined)?.name))
- .toEqual(['structured_output'])
- }
- },
- })
- expect(result.stderr).toBe('')
- const normalized = normalizeHeadlessStream(result.stdout, runCwd)
- if (refreshing) await writeFile(streamExpected, normalized)
- expect(normalized).toBe(await readFile(streamExpected, 'utf8'))
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- it('replays persistent PTY tools through the one-shot app', async () => {
- const input = JSON.parse(await readFile(join(ptyScenarioDir, 'input.json'), 'utf8')) as {
- steps?: { op?: unknown; text?: unknown }[]
- }
- const prompt = input.steps?.find(step => step.op === 'prompt')?.text
- if (typeof prompt !== 'string') throw new Error('pty-tools input has no prompt step')
- let expectedSession = await readFile(ptySessionFixture, 'utf8')
- let runCwd = ''
- const result = await runLoaderSmoke({
- label: 'headless persistent PTY snapshot',
- tempDirPrefix: 'headless-snapshot-pty-',
- binScript,
- configPath: ptyConfigPath,
- binArgs: ['--config', ptyConfigPath, '--output-format', 'stream-json', prompt],
- tsconfigPath,
- env: {
- DSH_SNAPSHOT: 'replay',
- DSH_SNAPSHOT_FILE: ptySessionFixture,
- NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
- },
- prepare: (cwd) => { runCwd = cwd },
- inspect: async (cwd) => {
- const logs = await persistedLogs(cwd)
- expect(logs).toHaveLength(1)
- const actual = logs[0]
- if (actual === undefined) throw new Error('headless PTY snapshot did not persist its session')
- const actualContext = contextFromLogs([actual.content])
- if (refreshing) {
- const harvested: HarvestedLog = {
- id: String(actual.header.id),
- createdAt: Number(actual.header.createdAt),
- content: actual.content,
- }
- const replacements = refreshFixtureReplacements([harvested], [expectedSession])
- expectedSession = tokenizeSessionFixtureCwd(
- stabilizeRefreshLog(actual.content, expectedSession, replacements, actualContext),
- )
- await writeFile(ptySessionFixture, expectedSession)
- }
- const expectedContext = contextFromLogs([expectedSession])
- expect(scrubRequestHeaders(normalizeSessionLog(actual.content, actualContext)))
- .toBe(scrubRequestHeaders(normalizeSessionLog(expectedSession, expectedContext)))
- },
- })
- expect(result.stderr).toBe('')
- const normalized = normalizeHeadlessStream(result.stdout, runCwd)
- if (refreshing) await writeFile(ptyStreamExpected, normalized)
- expect(normalized).toBe(await readFile(ptyStreamExpected, 'utf8'))
- }, LOADER_SMOKE_TEST_TIMEOUT_MS)
- })
|