소스 검색

test(snapshot): reconcile V3 fixtures with isolated master replay

Tianyi Cui 3 주 전
부모
커밋
154b2a3cd6

+ 67 - 0
scripts/session-query-spill-command.spec.ts

@@ -0,0 +1,67 @@
+import { Context } from '@deepseek-ai/cordis'
+import { LocalSpillStore } from '@deepseek-ai/dsh-spill-local'
+import { LocalFileSystem } from '@deepseek-ai/dsh-fs-local'
+import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local'
+import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
+import { SessionId } from '@deepseek-ai/dsh-session'
+import { ToolCallId } from '@deepseek-ai/dsh-llm'
+import { mkdtemp, rm } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join, resolve } from 'node:path'
+import { expect, it } from 'vitest'
+import * as locators from './snapshot-spill-locators.ts'
+import * as commands from '../snapshots/session/session-query-spill/resolve-spill-command.mjs'
+
+it.skipIf(process.platform === 'win32')('resolves the exact spill verifier, retains real failures, and restores the shell', async () => {
+  const root = await mkdtemp(join(tmpdir(), "query-spill-'quoted-"))
+  const ctx = new Context()
+  const disposers: (() => Promise<void>)[] = []
+  try {
+    for (const fiber of [
+      ctx.plugin(LocalSpillStore, { root, cleanupPeriodDays: 0 }),
+      ctx.plugin(LocalFileSystem, { cwd: root }),
+      ctx.plugin(LocalSubprocessRuntime),
+      ctx.plugin(LocalBashExecutor),
+    ]) {
+      disposers.push(() => fiber.dispose())
+      await fiber
+    }
+    const logical = resolve('/tmp/dsh-acp-snap-query-verifier')
+    const mapping = ctx.plugin(locators, { root, locatorRoot: logical })
+    disposers.push(() => mapping.dispose())
+    await mapping
+    const saved = await ctx.spillStore.saveText({
+      owner: { sessionId: SessionId('query-verifier') },
+      source: { kind: 'tool', toolName: 'session_event_read', callId: ToolCallId('query'), label: 'result' },
+      suggestedName: 'session_event_read.txt', content: 'request/header session_event_search',
+    })
+    const adapter = ctx.plugin(commands)
+    disposers.push(() => adapter.dispose())
+    await adapter
+    const command = 'file="' + saved.locator + '"' + "; grep -Fq request/header \"$file\" && grep -Fq session_event_search \"$file\" && printf 'SPILL_CANONICAL_OK\\n'"
+    const spec = ctx.shell.resolve({ command })
+    const output = await ctx.shell.run(spec)
+    expect(output.timedOut).toBe(false)
+    expect(output.exitCode).toBe(0)
+    expect(output.stdout.text).toBe('SPILL_CANONICAL_OK\n')
+    expect(spec.command).toBe(command)
+    const other = await ctx.shell.run(ctx.shell.resolve({ command: 'printf ordinary; exit 7' }))
+    expect(other.exitCode).toBe(7)
+    expect(other.stdout.text).toBe('ordinary')
+    const unmatched = await ctx.shell.run(ctx.shell.resolve({ command: command + '; exit 9' }))
+    expect(unmatched.exitCode).toBe(9)
+    expect(unmatched.stderr.text).toContain('No such file')
+    const physical = ctx.fs.processPath(await ctx.fs.resolve(saved.locator))
+    await rm(physical)
+    const missing = await ctx.shell.run(spec)
+    expect(missing.exitCode).not.toBe(0)
+    expect(missing.stdout.text).toBe('')
+    await adapter.dispose()
+    const restored = await ctx.shell.run(spec)
+    expect(restored.exitCode).not.toBe(0)
+    expect(restored.stderr.text).toContain(saved.locator)
+  } finally {
+    for (const dispose of disposers.reverse()) await dispose()
+    await rm(root, { recursive: true, force: true })
+  }
+})

+ 7 - 5
snapshots/session/headless.snapshot.ts

@@ -4,7 +4,7 @@ import { cp, copyFile, mkdir, mkdtemp, readFile, readdir, rm, utimes, writeFile
 import { existsSync } from 'node:fs'
 import { spawnSync } from 'node:child_process'
 import { tmpdir } from 'node:os'
-import { basename, delimiter, dirname, join } from 'node:path'
+import { basename, delimiter, dirname, join, relative, sep } from 'node:path'
 import { fileURLToPath } from 'node:url'
 import { describe, expect, it } from 'vitest'
 import ts from 'typescript'
@@ -539,7 +539,7 @@ function pinOf(scenario: HeadlessScenario): HeadlessScenario {
 }
 
 /** Require successful verification and the complete canonical event before refresh can write a fixture. */
-async function verifySessionQuerySpill(log: string): Promise<void> {
+async function verifySessionQuerySpill(log: string, spillRoot: string, locatorRoot: string): Promise<void> {
   const events = parseSessionLog(log)
   const results = events.flatMap(event => event.type === 'tool/result'
     ? event.data.message.content.filter(block => block.type === 'tool-result')
@@ -555,7 +555,8 @@ async function verifySessionQuerySpill(log: string): Promise<void> {
   const locator = preview?.match(/Full formatted result stored at: (.+-session_event_read\.txt)\. Use read/)
   expect(locator).not.toBeNull()
   expect(locator?.[1]).toBeDefined()
-  const full = await readFile(locator![1]!, 'utf8')
+  expect(locator![1]!.startsWith(locatorRoot + sep)).toBe(true)
+  const full = await readFile(join(spillRoot, relative(locatorRoot, locator![1]!)), 'utf8')
   const json = full.match(/```json\n([\s\S]+)\n```/)
   expect(json).not.toBeNull()
   const header = events.find(event => event.type === 'request/header')
@@ -871,6 +872,7 @@ describe('headless recorded-session snapshots', () => {
       let initialWorkspace: WorkspaceSnapshotEntry[] | undefined
       let finalWorkspace: WorkspaceSnapshotEntry[] | undefined
       const spillRoot = await mkdtemp(join(tmpdir(), 'acp-snap-spill-'))
+      const locatorRoot = snapshotSpillRoot(join(scenario.dir, fixtureFiles[0] as string))
       let result: Awaited<ReturnType<typeof runLoaderSmoke>>
       try {
         result = await runLoaderSmoke({
@@ -894,7 +896,7 @@ describe('headless recorded-session snapshots', () => {
             DSH_SNAPSHOT_PROVIDER: model.provider,
             DSH_SNAPSHOT_MODEL: model.model,
             DSH_SNAPSHOT_SPILL_ROOT: spillRoot,
-            DSH_SNAPSHOT_SPILL_LOCATOR_ROOT: snapshotSpillRoot(join(scenario.dir, fixtureFiles[0] as string)),
+            DSH_SNAPSHOT_SPILL_LOCATOR_ROOT: locatorRoot,
             DSH_SNAPSHOT_FILE: join(scenario.dir, fixtureFiles[0] as string),
             ...(replaying && fixtureFiles.length > 1
               ? { DSH_SNAPSHOT_CHILD_FILES: fixtureFiles.slice(1).map(file => join(scenario.dir, file)).join(delimiter) }
@@ -925,7 +927,7 @@ describe('headless recorded-session snapshots', () => {
           inspect: async (cwd) => {
             actualLogs = await persistedSessions(cwd)
             if (scenario.name === 'session-query-spill') {
-              await verifySessionQuerySpill(actualLogs[0]!.content)
+              await verifySessionQuerySpill(actualLogs[0]!.content, spillRoot, locatorRoot)
             }
             finalWorkspace = await captureWorkspaceSnapshot(cwd, {
               ignoredRootEntries: RUNTIME_WORKSPACE_ENTRIES,

+ 2 - 6
snapshots/session/session-query-spill/cordis.snapshot.yml

@@ -67,9 +67,5 @@
         locatorRoot: !!js process.env.DSH_SNAPSHOT_SPILL_LOCATOR_ROOT
 
 - insert:
-    - id: snapshot-shell-path
-      name: ../../../scripts/snapshot-shell-path.ts
-      config:
-        command: "file=$(find /tmp/dsh-acp-snap-035d1d054 -name '*-session_event_read.txt' -type f); grep -q request/header \"$file\" && grep -q session_event_search \"$file\" && echo SPILL_CANONICAL_OK"
-        recordedPath: /tmp/dsh-acp-snap-035d1d054
-        livePath: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT
+    - id: query-spill-verification-path
+      name: ./resolve-spill-command.mjs

+ 4 - 0
snapshots/session/session-query-spill/cordis.yml

@@ -27,3 +27,7 @@
       config:
         root: !!js process.env.DSH_SNAPSHOT_SPILL_ROOT
         locatorRoot: !!js process.env.DSH_SNAPSHOT_SPILL_LOCATOR_ROOT
+
+- insert:
+    - id: query-spill-verification-path
+      name: ./resolve-spill-command.mjs

+ 21 - 0
snapshots/session/session-query-spill/resolve-spill-command.mjs

@@ -0,0 +1,21 @@
+/** Resolve the exact query-spill verification command through this run's filesystem locator map. */
+export const name = 'query-spill-verification-path'
+export const inject = ['shell', 'fs']
+
+/** Translate the physical command only; tool logging and approval keep the model-visible locator. */
+export function apply(ctx) {
+  const shell = ctx.shell
+  const fs = ctx.fs
+  const run = shell.run
+  const suffix = "; grep -Fq request/header \"$file\" && grep -Fq session_event_search \"$file\" && printf 'SPILL_CANONICAL_OK\\n'"
+  ctx.effect(() => {
+    shell.run = async (spec) => {
+      const match = /^file="([^"]+)"/.exec(spec.command)
+      if (match === null || spec.command !== match[0] + suffix) return run.call(shell, spec)
+      const target = await fs.resolve(match[1])
+      const path = fs.processPath(target).replaceAll("'", "'\"'\"'")
+      return run.call(shell, { ...spec, command: "file='" + path + "'" + suffix })
+    }
+    return () => { shell.run = run }
+  })
+}

+ 5 - 8
snapshots/session/subagent-acp-diagnostic/session.v3.jsonl

@@ -12,21 +12,18 @@
 {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-pro"},"tools":"{{tools}}"},"reason":"initial"}}
 {"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-pro"}}
 {"type":"session/title","data":{"title":"Observe the ACP diagnostic twice","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_acp_foreground","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP foreground failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1788699433176,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788699433176,"index":0,"dt":[],"id":"call_acp_foreground","name":"subagent_acp","args":["{\"description\":\"Observe ACP foreground failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":false}"]},{"type":"chunk","time":1788699433176,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_acp_foreground","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP foreground failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":false}"}}},{"type":"chunk","time":1788699433176,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1788699433176,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_acp_foreground","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP foreground failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1788845521188,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788845521188,"index":0,"dt":[],"id":"call_acp_foreground","name":"subagent_acp","args":["{\"description\":\"Observe ACP foreground failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":false}"]},{"type":"chunk","time":1788845521188,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_acp_foreground","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP foreground failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":false}"}}},{"type":"chunk","time":1788845521188,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1788845521188,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
 {"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_acp_foreground","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP foreground failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":false}"}}
 {"type":"tool/result","data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_acp_foreground"},"content":[{"type":"tool-result","toolCallId":"call_acp_foreground","content":[{"type":"text","text":"Error: subagent run was cancelled\nDiagnostic: ACP unattended decision (policy: reject; request: execute; decision: denied)"}],"isError":true}],"role":"user","id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
 {"type":"step/end","data":{"turn":1,"step":1}}
 {"type":"step/start","data":{"turn":1,"step":2}}
-{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_acp_background","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP background failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1788699433338,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788699433338,"index":0,"dt":[],"id":"call_acp_background","name":"subagent_acp","args":["{\"description\":\"Observe ACP background failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":true}"]},{"type":"chunk","time":1788699433338,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_acp_background","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP background failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":true}"}}},{"type":"chunk","time":1788699433338,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1788699433338,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_acp_background","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP background failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":true}"},{"type":"tool-call","id":"call_acp_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1788845521335,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788845521335,"index":0,"dt":[],"id":"call_acp_background","name":"subagent_acp","args":["{\"description\":\"Observe ACP background failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":true}"]},{"type":"chunk","time":1788845521335,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_acp_background","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP background failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":true}"}}},{"type":"chunk","time":1788845521335,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788845521335,"index":1,"dt":[],"id":"call_acp_output","name":"job_output","args":["{\"job_id\":\"subagent-1\",\"wait\":true}"]},{"type":"chunk","time":1788845521335,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_acp_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}}},{"type":"chunk","time":1788845521335,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1788845521335,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
 {"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_acp_background","name":"subagent_acp","arguments":"{\"description\":\"Observe ACP background failure\",\"prompt\":\"Return the scripted ACP failure.\",\"run_in_background\":true}"}}
 {"type":"tool/result","data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_acp_background"},"content":[{"type":"tool-result","toolCallId":"call_acp_background","content":[{"type":"text","text":"started background subagent job subagent-1"}],"isError":false}],"role":"user","id":"{{message:7}}"}},"sourceEventSeqs":[19],"surfaceOp":"append"}
+{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_acp_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}}
+{"type":"tool/result","data":{"turn":1,"step":2,"message":{"source":{"kind":"tool","callId":"call_acp_output"},"content":[{"type":"tool-result","toolCallId":"call_acp_output","content":[{"type":"text","text":"(no new output)\n[status: failed, aborted; diagnostic: ACP unattended decision (policy: reject; request: execute; decision: denied)]"}],"isError":false}],"role":"user","id":"{{message:8}}"}},"sourceEventSeqs":[21],"surfaceOp":"append"}
 {"type":"step/end","data":{"turn":1,"step":2}}
 {"type":"step/start","data":{"turn":1,"step":3}}
-{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_acp_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:8}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1788699433364,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1788699433364,"index":0,"dt":[],"id":"call_acp_output","name":"job_output","args":["{\"job_id\":\"subagent-1\",\"wait\":true}"]},{"type":"chunk","time":1788699433364,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_acp_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}}},{"type":"chunk","time":1788699433364,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1788699433364,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
-{"type":"tool/call","data":{"turn":1,"step":3,"callId":"call_acp_output","name":"job_output","arguments":"{\"job_id\":\"subagent-1\",\"wait\":true}"}}
-{"type":"tool/result","data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"call_acp_output"},"content":[{"type":"tool-result","toolCallId":"call_acp_output","content":[{"type":"text","text":"(no new output)\n[status: failed, aborted; diagnostic: ACP unattended decision (policy: reject; request: execute; decision: denied)]"}],"isError":false}],"role":"user","id":"{{message:9}}"}},"sourceEventSeqs":[24],"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_OBSERVED_ACP_DIAGNOSTIC"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:9}}"},"usage":{"inputTokens":10,"outputTokens":2},"stream":[{"type":"chunk","time":1788845521472,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1788845521472,"index":0,"dt":[],"texts":["PARENT_OBSERVED_ACP_DIAGNOSTIC"]},{"type":"chunk","time":1788845521472,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PARENT_OBSERVED_ACP_DIAGNOSTIC"}}},{"type":"chunk","time":1788845521472,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}},{"type":"chunk","time":1788845521472,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
 {"type":"step/end","data":{"turn":1,"step":3}}
-{"type":"step/start","data":{"turn":1,"step":4}}
-{"type":"assistant/message","data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_OBSERVED_ACP_DIAGNOSTIC"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:10}}"},"usage":{"inputTokens":10,"outputTokens":2},"stream":[{"type":"chunk","time":1788699433483,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1788699433483,"index":0,"dt":[],"texts":["PARENT_OBSERVED_ACP_DIAGNOSTIC"]},{"type":"chunk","time":1788699433483,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PARENT_OBSERVED_ACP_DIAGNOSTIC"}}},{"type":"chunk","time":1788699433483,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}},{"type":"chunk","time":1788699433483,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
-{"type":"step/end","data":{"turn":1,"step":4}}
 {"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}