Prechádzať zdrojové kódy

test(snapshot): isolate persistent tool artifacts

Tianyi Cui 2 mesiacov pred
rodič
commit
d8b70df00c

+ 35 - 21
examples/jsonrpc-agent/tests/sdk.snapshot.ts

@@ -11,7 +11,7 @@
 
 import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
-import { delimiter, isAbsolute, join } from 'node:path'
+import { basename, delimiter, join } from 'node:path'
 import { fileURLToPath } from 'node:url'
 import { describe, expect, it } from 'vitest'
 import {
@@ -55,7 +55,7 @@ interface SdkScenario {
   children: number
   /** Optional scenario-specific live and replay compositions. */
   configs?: { live: string; replay: string }
-  /** Files whose final contents are part of the scenario contract. */
+  /** Cwd-relative files whose final contents are part of the scenario contract. */
   expectedFiles?: Readonly<Record<string, string>>
 }
 
@@ -80,13 +80,11 @@ const SCENARIOS: SdkScenario[] = [
   },
   {
     name: 'persistent-tools',
-    prompt: 'Prove that bash state persists, then create and edit note.txt.',
+    prompt: 'Prove that bash state persists, then create and edit the exact file {{cwd}}/note.txt.',
     sessionId: 'persistent-tools-snapshot',
     children: 0,
     configs: { live: persistentToolsLiveConfig, replay: persistentToolsReplayConfig },
-    // Replay returns recorded tool arguments verbatim, so this cross-platform
-    // POSIX fixture uses one stable absolute path and cleans it around the run.
-    expectedFiles: { '/tmp/dsh-persistent-tools-snapshot-note.txt': 'beta\n' },
+    expectedFiles: { 'note.txt': 'beta\n' },
   },
 ]
 
@@ -96,6 +94,10 @@ interface PersistedLog {
   readonly header: Record<string, unknown>
 }
 
+interface MissingFile {
+  readonly missing: true
+}
+
 async function jsonlFiles(dir: string): Promise<string[]> {
   const entries = await readdir(dir, { recursive: true })
   return entries.filter(entry => entry.endsWith('.jsonl')).map(entry => join(dir, entry)).sort()
@@ -125,6 +127,25 @@ function contextOfContents(contents: readonly string[]): NormalizeContext {
   }
 }
 
+async function hydrateReplayFixtures(scenario: SdkScenario, cwd: string): Promise<string[]> {
+  const root = join(cwd, '.replay-fixtures')
+  await mkdir(root, { recursive: true })
+  return Promise.all(fixtureFiles(scenario).map(async (source) => {
+    const destination = join(root, basename(source))
+    await writeFile(destination, (await readFile(source, 'utf8')).replaceAll('{{cwd}}', cwd))
+    return destination
+  }))
+}
+
+async function readExpectedFile(path: string): Promise<string | MissingFile> {
+  try {
+    return await readFile(path, 'utf8')
+  } catch (error: unknown) {
+    if (error instanceof Error && (error as NodeJS.ErrnoException).code === 'ENOENT') return { missing: true }
+    throw error
+  }
+}
+
 /**
  * Normalize the SDK-visible notification stream: embedded `session.event`
  * envelopes get the session-log treatment (times zeroed, headers tokenized),
@@ -163,24 +184,18 @@ async function runScenario(scenario: SdkScenario): Promise<{
   result: TurnResult
   notifications: HarnessNotification[]
   logs: PersistedLog[]
-  observedFiles: Record<string, string>
+  observedFiles: Record<string, string | MissingFile>
   cwd: string
 }> {
   const cwd = await mkdtemp(join(tmpdir(), `sdk-snapshot-${scenario.name}-`))
   const sessionsRoot = join(cwd, '.sessions')
-  const scenarioDir = join(snapshotsDir, scenario.name)
-  const expectedFilePaths = Object.keys(scenario.expectedFiles ?? {}).map(path =>
-    isAbsolute(path) ? path : join(cwd, path))
-  await Promise.all(expectedFilePaths.map(async path => rm(path, { force: true })))
+  const replayFixtures = recording ? [] : await hydrateReplayFixtures(scenario, cwd)
   const launch = resolveExampleLaunch({
     srcBin: runtimeBin,
     configArgs: [],
     tsconfigPath: repoTsconfig,
   })
-  const childFixtures = Array.from(
-    { length: scenario.children },
-    (_, index) => join(scenarioDir, `session.${index + 1}.jsonl`),
-  )
+  const [parentFixture, ...childFixtures] = replayFixtures
   const env: Record<string, string> = {
     ...Object.fromEntries(Object.entries(process.env).filter(([, value]) => value !== undefined)) as Record<string, string>,
     ...Object.fromEntries(Object.entries(launch.env).filter(([, value]) => value !== undefined)) as Record<string, string>,
@@ -191,8 +206,8 @@ async function runScenario(scenario: SdkScenario): Promise<{
     DSH_CWD: cwd,
     DSH_SNAPSHOT: mode,
     NODE_OPTIONS: [process.env.NODE_OPTIONS, '--disable-warning=ExperimentalWarning'].filter(Boolean).join(' '),
-    ...recording ? {} : {
-      DSH_SNAPSHOT_FILE: join(scenarioDir, 'session.jsonl'),
+    ...parentFixture === undefined ? {} : {
+      DSH_SNAPSHOT_FILE: parentFixture,
       ...childFixtures.length > 0 ? { DSH_SNAPSHOT_CHILD_FILES: childFixtures.join(delimiter) } : {},
     },
   }
@@ -211,22 +226,21 @@ async function runScenario(scenario: SdkScenario): Promise<{
   })
   try {
     const notifications: HarnessNotification[] = []
-    const result = await harness.run(scenario.prompt, {
+    const result = await harness.run(scenario.prompt.replaceAll('{{cwd}}', cwd), {
       sessionId: scenario.sessionId,
       onNotification: (notification) => { notifications.push(notification) },
     })
     await harness.close()
     const logs = await persistedLogs(sessionsRoot)
     const observedFiles = Object.fromEntries(await Promise.all(
-      Object.keys(scenario.expectedFiles ?? {}).map(async (path): Promise<[string, string]> => [
+      Object.keys(scenario.expectedFiles ?? {}).map(async (path): Promise<[string, string | MissingFile]> => [
         path,
-        await readFile(isAbsolute(path) ? path : join(cwd, path), 'utf8'),
+        await readExpectedFile(join(cwd, path)),
       ]),
     ))
     return { result, notifications, logs, observedFiles, cwd }
   } finally {
     await harness.close()
-    await Promise.all(expectedFilePaths.map(async path => rm(path, { force: true })))
     await rm(cwd, { recursive: true, force: true })
   }
 }

+ 11 - 11
examples/jsonrpc-agent/tests/snapshots/persistent-tools/notifications.expected.jsonl

@@ -1,5 +1,5 @@
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Prove that bash state persists, then create and edit note.txt."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Prove that bash state persists, then create and edit the exact file {{cwd}}/note.txt."}],"source":{"kind":"user"},"role":"user","id":"{{sessionId}}"},"surfaceOp":"append"}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"session/title","seq":2,"time":0,"data":{"title":"Prove that bash state persists,","messageSeqs":[1],"source":{"kind":"fallback"}}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":3,"time":0,"data":{"turn":1,"step":1}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"request/header","seq":4,"time":0,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}}}
@@ -24,23 +24,23 @@
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":23,"time":0,"data":{"turn":1,"step":2}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":24,"time":0,"data":{"turn":1,"step":3}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":25,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":26,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-create","name":"str_replace_editor","argumentsDelta":"{\"command\":\"create\",\"path\":\"/tmp/dsh-{{sessionId}}-note.txt\",\"file_text\":\"alpha\\n\"}"}}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":27,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"/tmp/dsh-{{sessionId}}-note.txt\",\"file_text\":\"alpha\\n\"}"}}}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":26,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-create","name":"str_replace_editor","argumentsDelta":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"alpha\\n\"}"}}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":27,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"alpha\\n\"}"}}}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":28,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":29,"time":0,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":30,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"/tmp/dsh-{{sessionId}}-note.txt\",\"file_text\":\"alpha\\n\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[25,26,27,28,29],"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":31,"time":0,"data":{"turn":1,"step":3,"callId":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"/tmp/dsh-{{sessionId}}-note.txt\",\"file_text\":\"alpha\\n\"}"}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":32,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"editor-create"},"content":[{"type":"tool-result","toolCallId":"editor-create","content":[{"type":"text","text":"New file created successfully at: /tmp/dsh-{{sessionId}}-note.txt"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[31],"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":30,"time":0,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"alpha\\n\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[25,26,27,28,29],"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":31,"time":0,"data":{"turn":1,"step":3,"callId":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"alpha\\n\"}"}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":32,"time":0,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"editor-create"},"content":[{"type":"tool-result","toolCallId":"editor-create","content":[{"type":"text","text":"New file created successfully at: {{cwd}}/note.txt"}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[31],"surfaceOp":"append"}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":33,"time":0,"data":{"turn":1,"step":3}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":34,"time":0,"data":{"turn":1,"step":4}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":35,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":36,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-replace","name":"str_replace_editor","argumentsDelta":"{\"command\":\"str_replace\",\"path\":\"/tmp/dsh-{{sessionId}}-note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":37,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"/tmp/dsh-{{sessionId}}-note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":36,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-replace","name":"str_replace_editor","argumentsDelta":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":37,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":38,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":39,"time":0,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":40,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"/tmp/dsh-{{sessionId}}-note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[35,36,37,38,39],"surfaceOp":"append"}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":41,"time":0,"data":{"turn":1,"step":4,"callId":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"/tmp/dsh-{{sessionId}}-note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}}
-{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":42,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"editor-replace"},"content":[{"type":"tool-result","toolCallId":"editor-replace","content":[{"type":"text","text":"The file /tmp/dsh-{{sessionId}}-note.txt has been edited successfully."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[41],"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/message","seq":40,"time":0,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"{{sessionId}}"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[35,36,37,38,39],"surfaceOp":"append"}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/call","seq":41,"time":0,"data":{"turn":1,"step":4,"callId":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}}
+{"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"tool/result","seq":42,"time":0,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"editor-replace"},"content":[{"type":"tool-result","toolCallId":"editor-replace","content":[{"type":"text","text":"The file {{cwd}}/note.txt has been edited successfully."}],"isError":false}],"role":"user","id":"{{sessionId}}"}},"sourceEventSeqs":[41],"surfaceOp":"append"}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/end","seq":43,"time":0,"data":{"turn":1,"step":4}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"step/start","seq":44,"time":0,"data":{"turn":1,"step":5}}}}
 {"method":"session.event","params":{"sessionId":"{{sessionId}}","event":{"type":"assistant/chunk","seq":45,"time":0,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}}}

+ 11 - 11
examples/jsonrpc-agent/tests/snapshots/persistent-tools/session.jsonl

@@ -1,6 +1,6 @@
 {"type":"session","version":0,"id":"persistent-tools-snapshot","createdAt":1785331618309,"cwd":"{{cwd}}","delegationDepth":0}
 {"type":"turn/start","seq":0,"time":1785331618311,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
-{"type":"user/message","seq":1,"time":1785331618311,"data":{"content":[{"type":"text","text":"Prove that bash state persists, then create and edit note.txt."}],"source":{"kind":"user"},"role":"user","id":"d0534fe8-a74b-4fcf-913f-d78e36f486bb"},"surfaceOp":"append"}
+{"type":"user/message","seq":1,"time":1785331618311,"data":{"content":[{"type":"text","text":"Prove that bash state persists, then create and edit the exact file {{cwd}}/note.txt."}],"source":{"kind":"user"},"role":"user","id":"d0534fe8-a74b-4fcf-913f-d78e36f486bb"},"surfaceOp":"append"}
 {"type":"session/title","seq":2,"time":1785331618312,"data":{"title":"Prove that bash state persists,","messageSeqs":[1],"source":{"kind":"fallback"}}}
 {"type":"step/start","seq":3,"time":1785331618312,"data":{"turn":1,"step":1}}
 {"type":"request/header","seq":4,"time":1785331618313,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
@@ -25,23 +25,23 @@
 {"type":"step/end","seq":23,"time":1785331618759,"data":{"turn":1,"step":2}}
 {"type":"step/start","seq":24,"time":1785331618759,"data":{"turn":1,"step":3}}
 {"type":"assistant/chunk","seq":25,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
-{"type":"assistant/chunk","seq":26,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-create","name":"str_replace_editor","argumentsDelta":"{\"command\":\"create\",\"path\":\"/tmp/dsh-persistent-tools-snapshot-note.txt\",\"file_text\":\"alpha\\n\"}"}}}
-{"type":"assistant/chunk","seq":27,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"/tmp/dsh-persistent-tools-snapshot-note.txt\",\"file_text\":\"alpha\\n\"}"}}}}
+{"type":"assistant/chunk","seq":26,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-create","name":"str_replace_editor","argumentsDelta":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"alpha\\n\"}"}}}
+{"type":"assistant/chunk","seq":27,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"alpha\\n\"}"}}}}
 {"type":"assistant/chunk","seq":28,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
 {"type":"assistant/chunk","seq":29,"time":1785331618762,"data":{"turn":1,"step":3,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
-{"type":"assistant/message","seq":30,"time":1785331618762,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"/tmp/dsh-persistent-tools-snapshot-note.txt\",\"file_text\":\"alpha\\n\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"6407aec3-f75c-427a-8783-a61bd99327bb"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[25,26,27,28,29],"surfaceOp":"append"}
-{"type":"tool/call","seq":31,"time":1785331618762,"data":{"turn":1,"step":3,"callId":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"/tmp/dsh-persistent-tools-snapshot-note.txt\",\"file_text\":\"alpha\\n\"}"}}
-{"type":"tool/result","seq":32,"time":1785331618782,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"editor-create"},"content":[{"type":"tool-result","toolCallId":"editor-create","content":[{"type":"text","text":"New file created successfully at: /tmp/dsh-persistent-tools-snapshot-note.txt"}],"isError":false}],"role":"user","id":"121833da-381d-492e-9d6c-82eaa9694ef1"}},"sourceEventSeqs":[31],"surfaceOp":"append"}
+{"type":"assistant/message","seq":30,"time":1785331618762,"data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"alpha\\n\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"6407aec3-f75c-427a-8783-a61bd99327bb"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[25,26,27,28,29],"surfaceOp":"append"}
+{"type":"tool/call","seq":31,"time":1785331618762,"data":{"turn":1,"step":3,"callId":"editor-create","name":"str_replace_editor","arguments":"{\"command\":\"create\",\"path\":\"{{cwd}}/note.txt\",\"file_text\":\"alpha\\n\"}"}}
+{"type":"tool/result","seq":32,"time":1785331618782,"data":{"turn":1,"step":3,"message":{"source":{"kind":"tool","callId":"editor-create"},"content":[{"type":"tool-result","toolCallId":"editor-create","content":[{"type":"text","text":"New file created successfully at: {{cwd}}/note.txt"}],"isError":false}],"role":"user","id":"121833da-381d-492e-9d6c-82eaa9694ef1"}},"sourceEventSeqs":[31],"surfaceOp":"append"}
 {"type":"step/end","seq":33,"time":1785331618782,"data":{"turn":1,"step":3}}
 {"type":"step/start","seq":34,"time":1785331618782,"data":{"turn":1,"step":4}}
 {"type":"assistant/chunk","seq":35,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
-{"type":"assistant/chunk","seq":36,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-replace","name":"str_replace_editor","argumentsDelta":"{\"command\":\"str_replace\",\"path\":\"/tmp/dsh-persistent-tools-snapshot-note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}
-{"type":"assistant/chunk","seq":37,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"/tmp/dsh-persistent-tools-snapshot-note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}}
+{"type":"assistant/chunk","seq":36,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"tool-call-delta","index":0,"id":"editor-replace","name":"str_replace_editor","argumentsDelta":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}
+{"type":"assistant/chunk","seq":37,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}}}
 {"type":"assistant/chunk","seq":38,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}}}
 {"type":"assistant/chunk","seq":39,"time":1785331618784,"data":{"turn":1,"step":4,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
-{"type":"assistant/message","seq":40,"time":1785331618784,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"/tmp/dsh-persistent-tools-snapshot-note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"1cf1d34c-faee-464d-bdd7-413ba7233e23"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[35,36,37,38,39],"surfaceOp":"append"}
-{"type":"tool/call","seq":41,"time":1785331618784,"data":{"turn":1,"step":4,"callId":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"/tmp/dsh-persistent-tools-snapshot-note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}
-{"type":"tool/result","seq":42,"time":1785331618799,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"editor-replace"},"content":[{"type":"tool-result","toolCallId":"editor-replace","content":[{"type":"text","text":"The file /tmp/dsh-persistent-tools-snapshot-note.txt has been edited successfully."}],"isError":false}],"role":"user","id":"c88746c2-208d-46aa-8c3d-79ccc88c7f6d"}},"sourceEventSeqs":[41],"surfaceOp":"append"}
+{"type":"assistant/message","seq":40,"time":1785331618784,"data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"1cf1d34c-faee-464d-bdd7-413ba7233e23"},"usage":{"inputTokens":3,"outputTokens":3}},"sourceEventSeqs":[35,36,37,38,39],"surfaceOp":"append"}
+{"type":"tool/call","seq":41,"time":1785331618784,"data":{"turn":1,"step":4,"callId":"editor-replace","name":"str_replace_editor","arguments":"{\"command\":\"str_replace\",\"path\":\"{{cwd}}/note.txt\",\"old_str\":\"alpha\",\"new_str\":\"beta\"}"}}
+{"type":"tool/result","seq":42,"time":1785331618799,"data":{"turn":1,"step":4,"message":{"source":{"kind":"tool","callId":"editor-replace"},"content":[{"type":"tool-result","toolCallId":"editor-replace","content":[{"type":"text","text":"The file {{cwd}}/note.txt has been edited successfully."}],"isError":false}],"role":"user","id":"c88746c2-208d-46aa-8c3d-79ccc88c7f6d"}},"sourceEventSeqs":[41],"surfaceOp":"append"}
 {"type":"step/end","seq":43,"time":1785331618799,"data":{"turn":1,"step":4}}
 {"type":"step/start","seq":44,"time":1785331618799,"data":{"turn":1,"step":5}}
 {"type":"assistant/chunk","seq":45,"time":1785331618801,"data":{"turn":1,"step":5,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}