@x1a0f3n9/dsh-session-snapshot 0.1.5-rc.4 → 0.1.7-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -1,6 +1,61 @@
1
- # Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
2
- # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
- # after editing either side, bring the other along and re-record with:
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/test-support/session-snapshot/README.md
5
- README.md: a73ea265516f965bec8d11c41d6eb5bb028c7ac6
6
- README.zh.md: ad136b19a75170c130bbb872ba02bb88e010bb02
5
+ /:
6
+ en: 35f64a68381efbd0
7
+ zh: dd2c508ba263d788
8
+ /x1a0f3n9-dsh-session-snapshot:
9
+ en: 537b3a97673f4504
10
+ zh: 1408982d9112a449
11
+ /x1a0f3n9-dsh-session-snapshot/summary:
12
+ en: 71cd3441e7189ac4
13
+ zh: 874ae7ac5fd231c9
14
+ /x1a0f3n9-dsh-session-snapshot/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /x1a0f3n9-dsh-session-snapshot/use-this-package:
18
+ en: 594ad1f3e6f02054
19
+ zh: 7ca4f565976704be
20
+ /x1a0f3n9-dsh-session-snapshot/use-this-package/writing-a-snapshot-suite:
21
+ en: b753e44790d4df99
22
+ zh: 5547c21757797d9b
23
+ /x1a0f3n9-dsh-session-snapshot/use-this-package/record-replay-and-refresh:
24
+ en: 4c4b1d6a459d2dc6
25
+ zh: 10c1bb216c50fea8
26
+ /x1a0f3n9-dsh-session-snapshot/use-this-package/pinning-request-headers-and-system-prompts:
27
+ en: 6b11d5111e1a3222
28
+ zh: de4d3889b1a5ebb4
29
+ /x1a0f3n9-dsh-session-snapshot/use-this-package/platform-and-composition-variants:
30
+ en: 64666ea2894acc4a
31
+ zh: 0e78df32bcfd3633
32
+ /x1a0f3n9-dsh-session-snapshot/use-this-package/what-can-go-wrong:
33
+ en: f9dc0b285401e01e
34
+ zh: e2b365b082dbaa82
35
+ /x1a0f3n9-dsh-session-snapshot/understand-the-implementation:
36
+ en: 18ff76b440fe40da
37
+ zh: 4dbf2eb5f9ee98a2
38
+ /x1a0f3n9-dsh-session-snapshot/understand-the-implementation/design:
39
+ en: 8771607f15c4f4e1
40
+ zh: 7c2cf395f704717c
41
+ /x1a0f3n9-dsh-session-snapshot/understand-the-implementation/source-map:
42
+ en: 4c3ff235ca8fe261
43
+ zh: 0f03425b926721cc
44
+ /x1a0f3n9-dsh-session-snapshot/understand-the-implementation/data-flow:
45
+ en: a71c8ab2e78798c5
46
+ zh: 419cebed1c023fbf
47
+ /x1a0f3n9-dsh-session-snapshot/further-exploration:
48
+ en: 84279d50373a156a
49
+ zh: fcd600bf386f6efe
50
+ /x1a0f3n9-dsh-session-snapshot/model-experience:
51
+ en: 9f12d94ab5e31ade
52
+ zh: 7c75a17565caca1c
53
+ /x1a0f3n9-dsh-session-snapshot/model-experience/kv-cache-effect:
54
+ en: ca75c51c89c2c9b0
55
+ zh: f57cc48a1b5cbfa9
56
+ /x1a0f3n9-dsh-session-snapshot/known-limitations-and-deferred-work:
57
+ en: 42e871839dddaf6d
58
+ zh: b7ab1d313527612b
59
+ /x1a0f3n9-dsh-session-snapshot/known-limitations-and-deferred-work/dev-note:
60
+ en: e67f633dc6fe7773
61
+ zh: 3bb0c20c93f16f98
package/README.md CHANGED
@@ -68,13 +68,15 @@ defineAcpSnapshotSuite({
68
68
  })
69
69
  ```
70
70
 
71
- Each recorded-session directory carries a closed `snapshot.yml` manifest plus canonical parent and contiguous child roles. Parent filenames are `session[.vN].jsonl`; children are `session.<ordinal>[.vN].jsonl`; v0 omits `.v0`, positive versions use lowercase `.vN`, and every filename agrees with its header. A role may retain older generations, but the harness selects the numerically highest one. An owning manifest may declare `sessionFormat.version` plus one or more closed `coverage` names to keep that historical generation as an explicit migration fixture; absence tracks the current writer. The manifest also names the scenario, shipped profile, composition/header class, recording source, and only the replay, platform, permission, environment, workspace, or input facts the completed Session cannot reconstruct. Storage guards check tool results and portable paths in every selected parent and child role. Prompt/schema scrubbing, message identity, and prompt-before-request ordering apply to current generations; retained predecessors keep their historical representation. The adapter registers expected-output, Session-log, and optional `workspace.expected/` comparisons; guards reject orphan directories, missing roles, noncanonical names, absolute paths, malformed manifests, and platform-specific separators.
71
+ Each recorded-session directory carries a closed `snapshot.yml` manifest plus canonical parent and contiguous child roles. Parent filenames are `session[.vN].jsonl`; children are `session.<ordinal>[.vN].jsonl`; v0 omits `.v0`, positive versions use lowercase `.vN`, and every filename agrees with its header. A role may retain older generations, but the harness selects the numerically highest one. An owning manifest may declare `sessionFormat.version` plus one or more closed `coverage` names to keep that historical generation as an explicit migration fixture. Without that declaration, record and refresh write the current generation; replay also accepts the retained V3 baseline under the [corpus policy](../../../snapshots/AGENTS.md). The manifest also names the scenario, shipped profile, composition/header class, recording source, and only the replay, platform, permission, environment, workspace, or input facts the completed Session cannot reconstruct. Storage guards check tool results and portable paths in every selected parent and child role. Prompt/schema scrubbing, message identity, and prompt-before-request ordering apply to current generations; retained predecessors keep their historical representation. The adapter registers expected-output, Session-log, and optional `workspace.expected/` comparisons; guards reject orphan directories, missing roles, noncanonical names, absolute paths, malformed manifests, and platform-specific separators.
72
72
 
73
- `normalizeSessionSnapshot` retains the complete Session header and event payloads but omits top-level `seq`/`time` envelopes from committed fixtures after normalizing paths and scrubbing system-prompt text and tool schemas; it also normalizes embedded stream clocks and historical packed-row `seq0`/`time0` envelopes, and catalog child-creation clocks. Event order and source-event references are preserved. Replay synthesizes the top-level envelopes in memory, while runtime persistence continues to write complete logs. Multi-session comparison validates expected and harvested logs through the strict build-static Session format catalog before identity redaction and normalization; source filenames cannot alter format validation. A retained historical replay input is not the native current-format writer-output oracle: structural migration preserves request meaning but can produce a different event layout. Normalization preserves unexpected request-header fields, including `system`, so regressions remain visible. Versionless protocol-adapter unit fixtures remain outside the released Session format corpus. Fixtures in the [current writer format](../../../docs/session-format-status.md) use one row per event; retained v0/v1 fixtures may use canonical packed rows. The [temporary repository migrator](../../../scripts/migrate-packed-session-fixtures.ts) (`pnpm run migrate:packed-session-fixtures`) rewrites older historical layouts, and its [removal proposal](../../../.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md) owns its deletion.
73
+ `normalizeSessionSnapshot` retains the complete Session header and event payloads but omits top-level `seq`/`time` envelopes from committed fixtures after normalizing paths and scrubbing system-prompt text and tool schemas; it also normalizes embedded stream clocks and historical packed-row `seq0`/`time0` envelopes, and catalog child-creation clocks. Event order and source-event reference order are preserved. Record and refresh write top-level `sourceEventSeqs` as expanded arrays. Replay synthesizes the top-level envelopes in memory, while runtime persistence continues to write complete logs. Multi-session comparison validates expected and harvested logs through the strict build-static Session format catalog before identity redaction and normalization; source filenames cannot alter format validation. A retained historical replay input is not the native current-format writer-output oracle: structural migration preserves request meaning but can produce a different event layout. Normalization preserves unexpected request-header fields, including `system`, so regressions remain visible. Versionless protocol-adapter unit fixtures remain outside the released Session format corpus. Fixtures in the [current writer format](../../../docs/session-format-status.md) use one row per event; retained v0/v1 fixtures may use canonical packed rows. The [temporary repository migrator](../../../scripts/migrate-packed-session-fixtures.ts) (`pnpm run migrate:packed-session-fixtures`) rewrites older historical layouts, and its [removal proposal](../../../.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md) owns its deletion.
74
74
 
75
75
  Spill scenarios save through the real local provider under a private temporary root. Their fixture adapter exposes fixed-length logical locators and maps only this run’s saved locators back to real files for retrieval, preserving preview budgets without writing into a shared logical path. Known snapshot spill paths normalize to stable locator tokens, including paths quoted inside JSON omission notices with JSON-escaped Windows separators. Refresh extraction preserves the matched serialized path spelling for literal replacement. Normalization changes only the locator: saved byte lengths and omission counts remain comparison evidence.
76
76
 
77
- Retained historical scenarios keep their canonical Session files unchanged and selected for replay, with no newer canonical sibling in the pinned directory. Their exact normalized native current-format output is recorded separately in `writer.expected.jsonl` for the parent and `writer.<ordinal>.expected.jsonl` for children; these are output oracles, not replay generations. Retained SDK scenarios use `notifications.current.expected.jsonl` for current protocol output. Comparisons neither project current events back into a historical format nor strip structural differences. Independent migration tests verify the official transform rather than treating native writer layout as its expected event sequence.
77
+ Retained historical scenarios keep their canonical Session files unchanged and selected for replay, with no newer canonical sibling in the pinned directory. Their exact normalized native writer output is recorded separately in `writer.expected.jsonl` for the parent and `writer.<ordinal>.expected.jsonl` for children; these are output oracles, not replay generations. Output oracles may retain an older supported generation: comparison restores them through the strict catalog, while harvested logs must use the checkout writer. Fresh-writer golden comparisons select `nativeWriterOutput` to tokenize only delivery markers qualified by each input’s original writer generation after strict restoration. Other delivery generations and captured-source fields remain numeric; source-versus-migrated-artifact comparisons preserve exact generations by default. Recording and refresh never store these comparison tokens. Retained SDK scenarios use `notifications.current.expected.jsonl` for current protocol output. Comparisons neither project current events back into a historical format nor strip structural differences. Independent migration tests verify the official transform rather than treating native writer layout as its expected event sequence.
78
+
79
+ Headless/ACP and SDK adapters compare raw catalog child-creation times with harvested child headers before normalization. Refresh preserves the chosen child header time in every matching catalog entry, including inherited copies, so retained header clocks cannot diverge from freshly written catalog clocks. Committed historical inconsistencies remain unchanged migration-refusal evidence; normalizing their clocks does not establish successful migration.
78
80
 
79
81
  ### Record, replay, and refresh
80
82
 
@@ -82,7 +84,7 @@ Retained historical scenarios keep their canonical Session files unchanged and s
82
84
 
83
85
  ### Pinning request headers and system prompts
84
86
 
85
- A pin owns its generated `system-prompt.expected.md` or `tool-schemas.expected.json` sidecar by default; `systemPromptSource` and `toolSchemasSource` name another pin when the complete corresponding sequence is identical, so each distinct version is committed once. The system prompt is surface node 0, logged as a `system/message` event before the step's first `request/header`; every fixture stores its text block as `"text":"{{system}}"` and the prompt sidecar keeps the full text. The pin's `request/header` events store `"tools":"{{tools}}"` while retaining config and reason, and the structured schema sidecar keeps the full catalogs. A child Session whose own scope composes a different request declares it per fixture index with `pinsChildToolSchemas` and `pinsChildSystemPrompts`. A scenario that changes the request header mid-run declares `expectedHeaderChanges`; a scenario whose prompt changes mid-run — replacing node 0, or appending after the cached history on an `in-history` route — declares `expectedPromptChanges`, and each change adds a `<!-- system/message change N -->` section to the prompt sidecar. The manifest spells these `header.changes` and `header.promptChanges`.
87
+ A pin owns its generated `system-prompt.expected.md` or `tool-schemas.expected.json` sidecar by default; `systemPromptSource` and `toolSchemasSource` name another pin when the complete corresponding sequence is identical, so each distinct version is committed once. The system prompt is surface node 0, logged as a `system/message` event before the step's first `request/header`; every fixture stores its text block as `"text":"{{system}}"` and the prompt sidecar keeps the full text. The pin's `request/header` events store `"tools":"{{tools}}"` while retaining config and reason, and the structured schema sidecar keeps the full catalogs. Logs containing developer messages retain ordered tool names in each header instead of the token so historical addition references remain verifiable. A child Session whose own scope composes a different request declares it per fixture index with `pinsChildToolSchemas` and `pinsChildSystemPrompts`. A scenario that changes the request header mid-run declares `expectedHeaderChanges`; a scenario whose prompt changes mid-run — replacing node 0, or appending after the cached history on an `in-history` route — declares `expectedPromptChanges`, and each change adds a `<!-- system/message change N -->` section to the prompt sidecar. The manifest spells these `header.changes` and `header.promptChanges`.
86
88
 
87
89
  ### Platform and composition variants
88
90
 
@@ -107,7 +109,7 @@ This section explains the design of the kit; the observable behavior is fully co
107
109
 
108
110
  ### Design
109
111
 
110
- The shared core owns manifests, generation-qualified role selection, workspace setup/comparison, typed identity mapping, normalizers, and fixture invariants. The ACP adapter adds four composable layers: launcher, scenario harness, normalizers, and suite factory. `launchAcpTestAgent` boots a source profile under tsx or a built `lib` profile under plain Node, connects the SDK client over a raw-byte stdout tee, collects Session updates and stderr, fails closed on unhandled permission requests, and owns shutdown. `runScenario` drives ACP JSON-RPC stdio and harvests the numerically highest persisted raw JSONL generation for every Session directory. The pure normalizers replace cwd paths and typed identities with stable tokens, zero times, expand physical provenance ranges, and scrub system-prompt text and tool-schema bulk. `defineAcpSnapshotSuite` registers comparisons, generation-qualified fixture write-back, and the live uniformity guard.
112
+ The shared core owns manifests, generation-qualified role selection, workspace setup/comparison, typed identity mapping, normalizers, and fixture invariants. The ACP adapter adds four composable layers: launcher, scenario harness, normalizers, and suite factory. `launchAcpTestAgent` boots a source profile under tsx or a built `lib` profile under plain Node, connects the SDK client over a raw-byte stdout tee, collects Session updates and stderr, fails closed on unhandled permission requests, and owns shutdown. `runScenario` drives ACP JSON-RPC stdio and harvests the numerically highest persisted raw JSONL generation for every Session directory. The pure normalizers replace cwd paths and typed identities with stable tokens, zero times, expand physical source-event ranges, and scrub system-prompt text and tool-schema bulk. `defineAcpSnapshotSuite` registers comparisons, generation-qualified fixture write-back, and the live uniformity guard.
111
113
 
112
114
  ### Source map
113
115
 
package/README.zh.md CHANGED
@@ -68,13 +68,15 @@ defineAcpSnapshotSuite({
68
68
  })
69
69
  ```
70
70
 
71
- 每个已记录 Session 目录携带封闭的 `snapshot.yml` manifest,以及规范 parent 与连续 child 角色。parent 文件名是 `session[.vN].jsonl`;child 是 `session.<ordinal>[.vN].jsonl`;v0 省略 `.v0`,正版本使用小写 `.vN`,且每个文件名与其 header 一致。一个角色可以保留旧 generation,但 harness 会选择数值最高的一项。拥有 fixture 的 manifest 可以声明 `sessionFormat.version` 与一个或多个封闭 `coverage` 名称,把该历史 generation 保留为显式迁移 fixture;省略此字段时跟随当前 writer。manifest 还会指名场景、随附 profile、组合/header 类别、录制来源,以及已完成 Session 无法重建的 replay、平台、权限、环境、workspace 或输入事实。存储保护检查每个选定 parent 与 child 角色的工具结果和可移植路径。提示词/schema 擦除、消息身份及提示词先于请求的顺序检查适用于当前 generation;保留的前代维持其历史表示。适配器注册预期输出、Session 日志与可选 `workspace.expected/` 比较;保护会拒绝遗留目录、缺失角色、非规范名称、绝对路径、格式错误的 manifest 与平台专用分隔符。
71
+ 每个已记录 Session 目录携带封闭的 `snapshot.yml` manifest,以及规范 parent 与连续 child 角色。parent 文件名是 `session[.vN].jsonl`;child 是 `session.<ordinal>[.vN].jsonl`;v0 省略 `.v0`,正版本使用小写 `.vN`,且每个文件名与其 header 一致。一个角色可以保留旧 generation,但 harness 会选择数值最高的一项。拥有 fixture 的 manifest 可以声明 `sessionFormat.version` 与一个或多个封闭 `coverage` 名称,把该历史 generation 保留为显式迁移 fixture。未声明时,record 与 refresh 写入当前 generation;replay 也按[语料策略](../../../snapshots/AGENTS.md)接受保留的 V3 基线。manifest 还会指名场景、随附 profile、组合/header 类别、录制来源,以及已完成 Session 无法重建的 replay、平台、权限、环境、workspace 或输入事实。存储保护检查每个选定 parent 与 child 角色的工具结果和可移植路径。提示词/schema 擦除、消息身份及提示词先于请求的顺序检查适用于当前 generation;保留的前代维持其历史表示。适配器注册预期输出、Session 日志与可选 `workspace.expected/` 比较;保护会拒绝遗留目录、缺失角色、非规范名称、绝对路径、格式错误的 manifest 与平台专用分隔符。
72
72
 
73
- `normalizeSessionSnapshot` 在规范化路径并擦除系统提示文本与工具 schema 后,会保留完整 Session header 与事件 payload,但从已提交 fixture 中省略顶层 `seq`/`time` envelope;它还会规范化嵌入式 stream clock 与历史 packed-row 的 `seq0`/`time0` envelope 与 catalog child 创建时钟。事件顺序与来源事件引用保持不变。Replay 只在内存中合成顶层 envelope,而运行时持久化仍写入完整日志。多 Session 比较会先通过严格的构建期静态 Session 格式目录校验预期日志与收集日志,再进行身份脱敏与规范化;来源文件名不能改变格式校验。保留的历史 replay 输入不是原生当前格式 writer 输出的比较基准:结构迁移保留请求含义,但可以产生不同的事件布局。归一化保留意外的 request-header 字段(包括 `system`),使回归保持可见。无版本的协议适配器单元测试 fixture 不属于已发布 Session 格式语料。[当前写入器格式](../../../docs/session-format-status.zh.md)的 fixture 每个事件占一行;保留的 v0/v1 fixture 可以使用规范 packed row。[临时仓库迁移器](../../../scripts/migrate-packed-session-fixtures.ts)(`pnpm run migrate:packed-session-fixtures`)会改写更旧的历史布局,由其[移除提案](../../../.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.zh.md)负责删除该迁移器。
73
+ `normalizeSessionSnapshot` 在规范化路径并擦除系统提示文本与工具 schema 后,会保留完整 Session header 与事件 payload,但从已提交 fixture 中省略顶层 `seq`/`time` envelope;它还会规范化嵌入式 stream clock 与历史 packed-row 的 `seq0`/`time0` envelope 与 catalog child 创建时钟。事件顺序与来源事件引用的顺序保持不变。录制与刷新会将顶层 `sourceEventSeqs` 以展开数组的形式写入 fixture。Replay 只在内存中合成顶层 envelope,而运行时持久化仍写入完整日志。多 Session 比较会先通过严格的构建期静态 Session 格式目录校验预期日志与收集日志,再进行身份脱敏与规范化;来源文件名不能改变格式校验。保留的历史 replay 输入不是原生当前格式 writer 输出的比较基准:结构迁移保留请求含义,但可以产生不同的事件布局。归一化保留意外的 request-header 字段(包括 `system`),使回归保持可见。无版本的协议适配器单元测试 fixture 不属于已发布 Session 格式语料。[当前写入器格式](../../../docs/session-format-status.zh.md)的 fixture 每个事件占一行;保留的 v0/v1 fixture 可以使用规范 packed row。[临时仓库迁移器](../../../scripts/migrate-packed-session-fixtures.ts)(`pnpm run migrate:packed-session-fixtures`)会改写更旧的历史布局,由其[移除提案](../../../.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.zh.md)负责删除该迁移器。
74
74
 
75
75
  spill 场景通过真实本地提供方保存到私有临时根目录。fixture 适配器提供固定长度的逻辑定位符,并仅将本次运行已保存的定位符映射回实际文件以供检索,在不写入共享逻辑路径的情况下保留预览预算。已知的快照 spill 路径会规范化为稳定的定位符 token,包括 JSON 省略通知中带引号、使用 JSON 转义 Windows 分隔符的路径。刷新提取会保留匹配路径的序列化写法,以便进行字面替换。规范化只改变定位符:保存字节数与省略计数仍作为比较证据。
76
76
 
77
- 保留历史输入的场景保持规范 Session 文件不变,并继续选择它们进行回放;固定历史版本的目录中没有更新的规范同角色文件。其精确的规范化原生当前格式输出单独记录在父会话的 `writer.expected.jsonl` 和子会话的 `writer.<ordinal>.expected.jsonl` 中;这些是输出比较基准,而非 replay 代际。保留历史输入的 SDK 场景使用 `notifications.current.expected.jsonl` 记录当前协议输出。比较既不将当前事件反向投影为历史格式,也不剥除结构差异。独立迁移测试验证正式转换,而不把原生 writer 布局当作其预期事件序列。
77
+ 保留历史输入的场景保持规范 Session 文件不变,并继续选择它们进行回放;固定历史版本的目录中没有更新的规范同角色文件。其精确的规范化原生 writer 输出单独记录在父会话的 `writer.expected.jsonl` 和子会话的 `writer.<ordinal>.expected.jsonl` 中;这些是输出比较基准,而非 replay 代际。输出比较基准可以保留较旧的受支持代际:比较通过严格 catalog 恢复它们,而收集的日志必须使用当前检出版本的 writer。新 writer 输出的 golden 比较选择 `nativeWriterOutput`,仅在严格恢复后,将版本限定为各输入原始 writer 代际的 delivery 标记转换为 token。其他 delivery 代际与 captured-source 字段保持数值;源文件与已迁移产物的比较默认保留精确代际。record 与 refresh 从不存储这些比较 token。保留历史输入的 SDK 场景使用 `notifications.current.expected.jsonl` 记录当前协议输出。比较既不将当前事件反向投影为历史格式,也不剥除结构差异。独立迁移测试验证正式转换,而不把原生 writer 布局当作其预期事件序列。
78
+
79
+ Headless/ACP 与 SDK 适配器在规范化之前,将原始目录中的子创建时间与采集到的子 header 比较。刷新会在每条匹配的目录记录中保留选定的子 header 时间,包括继承副本,防止保留的 header 时钟与新写入的目录时钟分歧。已提交的历史不一致数据保持不变,作为迁移拒绝证据;将其时钟规范化不能证明迁移成功。
78
80
 
79
81
  ### 录制、回放与刷新
80
82
 
@@ -82,7 +84,7 @@ spill 场景通过真实本地提供方保存到私有临时根目录。fixture
82
84
 
83
85
  ### 固定请求 header 与系统提示
84
86
 
85
- 每个 pin 默认拥有其生成的 `system-prompt.expected.md` 或 `tool-schemas.expected.json` 伴随文件;当完整的对应序列相同时,`systemPromptSource` 与 `toolSchemasSource` 指定另一个 pin 作为来源,因此每个不同版本只提交一次。系统提示是 surface 节点 0,作为 `system/message` 事件记录在该步骤第一个 `request/header` 之前;每个 fixture 把其文本块存储为 `"text":"{{system}}"`,提示词伴随文件保留完整文本。该 pin 的 `request/header` 事件存储 `"tools":"{{tools}}"`,同时保留配置与原因,结构化 schema 伴随文件保留完整目录。自身作用域组合出不同请求的 child Session 按 fixture 索引以 `pinsChildToolSchemas` 与 `pinsChildSystemPrompts` 单独声明。运行中改变请求 header 的场景声明 `expectedHeaderChanges`;运行中提示词发生变化的场景——替换节点 0,或在 `in-history` 路由上追加到已缓存历史之后——声明 `expectedPromptChanges`,每次变化在提示词伴随文件中增加一个 `<!-- system/message change N -->` 小节。manifest 中对应字段为 `header.changes` 与 `header.promptChanges`。
87
+ 每个 pin 默认拥有其生成的 `system-prompt.expected.md` 或 `tool-schemas.expected.json` 伴随文件;当完整的对应序列相同时,`systemPromptSource` 与 `toolSchemasSource` 指定另一个 pin 作为来源,因此每个不同版本只提交一次。系统提示是 surface 节点 0,作为 `system/message` 事件记录在该步骤第一个 `request/header` 之前;每个 fixture 把其文本块存储为 `"text":"{{system}}"`,提示词伴随文件保留完整文本。该 pin 的 `request/header` 事件存储 `"tools":"{{tools}}"`,同时保留配置与原因,结构化 schema 伴随文件保留完整目录。包含 developer 消息的日志会在各 header 中保留有序工具名,而不使用 token,以便继续校验历史工具添加引用。自身作用域组合出不同请求的 child Session 按 fixture 索引以 `pinsChildToolSchemas` 与 `pinsChildSystemPrompts` 单独声明。运行中改变请求 header 的场景声明 `expectedHeaderChanges`;运行中提示词发生变化的场景——替换节点 0,或在 `in-history` 路由上追加到已缓存历史之后——声明 `expectedPromptChanges`,每次变化在提示词伴随文件中增加一个 `<!-- system/message change N -->` 小节。manifest 中对应字段为 `header.changes` 与 `header.promptChanges`。
86
88
 
87
89
  ### 平台与组合变体
88
90
 
package/lib/index.js CHANGED
@@ -313,7 +313,7 @@ function profileArgs(profile, basePatch, selectedPatch, snapshotMode, cwd) {
313
313
  return [
314
314
  "--profile",
315
315
  profile,
316
- ...patches.map((file, index) => materializeProfilePatch(file, cwd, materializedDir, index)).flatMap((file) => ["--patch", file])
316
+ ...patches.map((file, index) => materializeProfilePatch(file, cwd, profile, materializedDir, index)).flatMap((file) => ["--patch", file])
317
317
  ];
318
318
  }
319
319
  /** Derive the replay-only sibling patch selected by the former app-bin swap. */
@@ -336,13 +336,13 @@ function packageDirFromPatch(source, packageName) {
336
336
  }
337
337
  /**
338
338
  * Install an authored patch's resolvable bare package into the temporary
339
- * profile fallback. This mirrors `dsh plugin` while retaining the bare entry
340
- * name and package provenance used by request metadata.
339
+ * profile. This mirrors `dsh plugin` while retaining the bare entry
340
+ * name and package identity used by request metadata.
341
341
  */
342
- function linkProfilePackage(source, cwd, packageName) {
342
+ function linkProfilePackage(source, cwd, profile, packageName) {
343
343
  const packageDir = packageDirFromPatch(source, packageName);
344
344
  if (packageDir === void 0) return;
345
- const link = join(cwd, ".dsh", "profiles", "node_modules", packageName);
345
+ const link = join(cwd, ".dsh", "profiles", profile, "node_modules", packageName);
346
346
  mkdirSync(dirname(link), { recursive: true });
347
347
  if (existsSync(link)) {
348
348
  if (realpathSync(link) !== packageDir) throw new Error(`snapshot profile package ${packageName} resolves to two directories`);
@@ -354,19 +354,20 @@ function linkProfilePackage(source, cwd, packageName) {
354
354
  /**
355
355
  * Copy one authored patch into the launch cwd with relative plugin names made absolute.
356
356
  * @param source - authored profile patch path.
357
- * @param cwd - isolated process cwd whose profile fallback receives package links.
357
+ * @param cwd - isolated process cwd whose profile receives package links.
358
+ * @param profile - profile whose local package lookup receives the test links.
358
359
  * @param targetDir - existing directory that owns the materialized patch.
359
360
  * @param index - stable patch ordinal used in the output filename.
360
361
  * @returns absolute materialized patch path.
361
362
  */
362
- function materializeProfilePatch(source, cwd, targetDir, index) {
363
+ function materializeProfilePatch(source, cwd, profile, targetDir, index) {
363
364
  const parsed = yaml.load(readFileSync(source, "utf8"), { schema: entryListSchema });
364
365
  if (!Array.isArray(parsed)) throw new Error(`snapshot profile patch must be a top-level array: ${source}`);
365
366
  const patches = parsed;
366
367
  const baseDir = dirname(source);
367
368
  const resolveName = (value) => {
368
369
  const packageName = barePackageName(value);
369
- if (packageName !== void 0) linkProfilePackage(source, cwd, packageName);
370
+ if (packageName !== void 0) linkProfilePackage(source, cwd, profile, packageName);
370
371
  return value.startsWith("./") || value.startsWith("../") ? pathToFileURL(resolve(baseDir, value)).href : value;
371
372
  };
372
373
  const visitEntry = (entry) => {
@@ -1162,6 +1163,7 @@ const SYSTEM = "{{system}}";
1162
1163
  const TOOLS = "{{tools}}";
1163
1164
  const EVENT_TIME = "{{eventTime}}";
1164
1165
  const EVENT_OMITTED_BYTES = "{{eventOmittedBytes}}";
1166
+ const SOURCE_SESSION_FORMAT = "{{sourceSessionFormatVersion}}";
1165
1167
  const PACKED_CHUNK_ROW_TYPES$1 = new Set([
1166
1168
  "text-chunks",
1167
1169
  "reasoning-chunks",
@@ -1447,32 +1449,43 @@ function normalizeSessionSnapshot(rawLog, ctx, options = {}) {
1447
1449
  }
1448
1450
  /**
1449
1451
  * Normalize one scenario's primary and child logs with shared typed identity redaction.
1452
+ * Native-writer comparison strictly restores each input before tokenizing its own delivery generation.
1450
1453
  * @param rawLogs - primary-first persisted or projected session JSONL.
1451
1454
  * @param ctx - generated cwd spellings and other volatile run facts.
1452
- * @param options - separator controls; relationship-preserving identity mode is mandatory.
1453
- * @returns normalized session fixtures in input order.
1455
+ * @param options - separator and native-writer comparison controls; identity relationships are preserved.
1456
+ * @returns comparison-only Session records in input order; not persistence or fixture write-back input.
1454
1457
  */
1455
1458
  function normalizeSessionSnapshots(rawLogs, ctx, options = {}) {
1456
- return redactSessionSnapshotIds(rawLogs.map((log) => hasSessionFormatVersion(log) ? prepareSessionSnapshotFixtureForComparison(log) : log).map(normalizeSessionFormatProvenance)).map((log) => projectSessionSnapshot(scrubSessionSnapshot(normalizeSessionLog(log, {
1459
+ const { nativeWriterOutput, ...normalizeOptions } = options;
1460
+ return redactSessionSnapshotIds(rawLogs.map((log) => {
1461
+ if (!hasSessionFormatVersion(log)) return normalizeSessionFormatMetadata(log);
1462
+ return normalizeSessionFormatMetadata(prepareSessionSnapshotFixtureForComparison(log), nativeWriterOutput ? sessionHeaderVersion(log, "source Session snapshot") : void 0);
1463
+ })).map((log) => projectSessionSnapshot(scrubSessionSnapshot(normalizeSessionLog(log, {
1457
1464
  ...ctx,
1458
1465
  sessionIds: []
1459
1466
  }, {
1460
- ...options,
1467
+ ...normalizeOptions,
1461
1468
  identityMode: "preserve"
1462
1469
  }))));
1463
1470
  }
1464
1471
  /**
1465
1472
  * Omit the artifact header generation after official migration for comparison.
1466
- * Delivery and captured-source generations retain their opaque recorded values.
1473
+ * An explicit source version tokenizes only delivery markers for that original input generation.
1474
+ * Other delivery generations and every captured-source generation retain their recorded values.
1467
1475
  * @param rawLog - Session records or events as compact JSON lines.
1468
- * @returns the same records with only the Session header version omitted.
1476
+ * @param sourceVersion - original generation of strictly validated native writer output; otherwise omit.
1477
+ * @returns comparison-only records with the Session header version omitted and native delivery qualifiers tokenized.
1469
1478
  */
1470
- function normalizeSessionFormatProvenance(rawLog) {
1479
+ function normalizeSessionFormatMetadata(rawLog, sourceVersion) {
1471
1480
  return rawLog.split("\n").map((line) => {
1472
1481
  if (line.trim().length === 0) return line;
1473
1482
  const record = JSON.parse(line);
1474
- if (record.type !== "session" || !Object.hasOwn(record, "version")) return line;
1475
- delete record.version;
1483
+ if (record.type === "session" && Object.hasOwn(record, "version")) delete record.version;
1484
+ else if (sourceVersion !== void 0 && record.type === "session-log-deepseek/delivery-accepted") {
1485
+ const data = record.data;
1486
+ if (data?.sessionFormatVersion !== sourceVersion) return line;
1487
+ data.sessionFormatVersion = SOURCE_SESSION_FORMAT;
1488
+ } else return line;
1476
1489
  return JSON.stringify(record);
1477
1490
  }).join("\n");
1478
1491
  }
@@ -1499,7 +1512,9 @@ function scrubSystemPrompts(rawLog) {
1499
1512
  }
1500
1513
  /**
1501
1514
  * Replace tool schemas in full request-header snapshots with `{{tools}}`
1502
- * tokens while retaining field presence. System-prompt text stays verbatim so
1515
+ * tokens while retaining field presence. Logs containing developer messages
1516
+ * retain tool names so historical addition references remain verifiable.
1517
+ * System-prompt text stays verbatim so
1503
1518
  * pinning fixtures can move only schema bulk into their dedicated JSON
1504
1519
  * sidecar. Lines without a tool payload pass through byte-for-byte; the
1505
1520
  * transform is idempotent.
@@ -1530,7 +1545,8 @@ function scrubModelRequestBulk(rawLog) {
1530
1545
  /**
1531
1546
  * Project a persisted session log while tokenizing prompt text and schema
1532
1547
  * bulk. Each non-empty line is parsed at most once; the session header stays
1533
- * byte-identical. Body records omit their persistence-only envelopes.
1548
+ * byte-identical. Body records omit their persistence-only envelopes and expand
1549
+ * source-event ranges without changing reference order.
1534
1550
  *
1535
1551
  * @param rawLog - persisted or already-projected session JSONL.
1536
1552
  * @returns committed snapshot JSONL with prompt text and tool schemas tokenized.
@@ -1546,6 +1562,7 @@ function scrubSessionSnapshot(rawLog) {
1546
1562
  return line;
1547
1563
  }
1548
1564
  omitFixtureEnvelope(record);
1565
+ if (Object.hasOwn(record, "sourceEventSeqs")) record.sourceEventSeqs = decodeSeqRanges(record.sourceEventSeqs);
1549
1566
  normalizeFeedbackClocks(record);
1550
1567
  return JSON.stringify(record);
1551
1568
  }).join("\n");
@@ -1568,9 +1585,12 @@ function systemPromptBlock(data) {
1568
1585
  }
1569
1586
  /** Transform the selected model-request payloads. */
1570
1587
  function scrubModelRequestContent(rawLog, options) {
1571
- return rawLog.split("\n").map((line) => {
1572
- if (line.trim().length === 0) return line;
1573
- const record = JSON.parse(line);
1588
+ const lines = rawLog.split("\n");
1589
+ const records = lines.map((line) => line.trim().length === 0 ? void 0 : JSON.parse(line));
1590
+ const retainToolNames = records.some((record) => record?.type === "developer/message");
1591
+ return lines.map((line, index) => {
1592
+ const record = records[index];
1593
+ if (record === void 0) return line;
1574
1594
  const data = record.data;
1575
1595
  if (data === null || typeof data !== "object") return line;
1576
1596
  if (options.system === true && record.type === "system/message") {
@@ -1582,7 +1602,7 @@ function scrubModelRequestContent(rawLog, options) {
1582
1602
  if (options.tools === true && record.type === "request/header") {
1583
1603
  const header = data.header;
1584
1604
  if (header === null || typeof header !== "object" || !("tools" in header)) return line;
1585
- header.tools = TOOLS;
1605
+ header.tools = retainToolNames && Array.isArray(header.tools) ? header.tools.map((tool) => typeof tool === "string" ? tool : tool.name) : TOOLS;
1586
1606
  return JSON.stringify(record);
1587
1607
  }
1588
1608
  return line;
@@ -1621,7 +1641,8 @@ const SESSION_FORMAT_COVERAGE = new Set([
1621
1641
  "packed-row",
1622
1642
  "retry-failure",
1623
1643
  "shipped-profile",
1624
- "adjacent-migration"
1644
+ "adjacent-migration",
1645
+ "retired-tools"
1625
1646
  ]);
1626
1647
  const NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1627
1648
  function record(value, label) {
@@ -2089,13 +2110,18 @@ function parseToolSchemasSnapshot(snapshot) {
2089
2110
  /**
2090
2111
  * Restore one sidecar schema set into a tokenized pinned header.
2091
2112
  *
2092
- * @param header The parsed request header carrying `tools: "{{tools}}"`.
2113
+ * @param header The parsed request header carrying a tools token or ordered tool names matching the sidecar.
2093
2114
  * @param schemas The complete schemas for this full header snapshot.
2094
2115
  * @returns A copy of the header with its complete schemas restored.
2095
2116
  */
2096
2117
  function restorePinnedToolSchemas(header, schemas) {
2097
2118
  if (header === null || typeof header !== "object" || Array.isArray(header)) throw new Error("acp-snapshot: pinned request header must be an object");
2098
- if (header.tools !== TOOLS_TOKEN) throw new Error(`acp-snapshot: pinned request header tools must equal ${TOOLS_TOKEN}`);
2119
+ const tools = header.tools;
2120
+ const namesMatch = Array.isArray(tools) && tools.length === schemas.length && tools.every((name, index) => {
2121
+ const schema = schemas[index];
2122
+ return typeof name === "string" && schema !== null && typeof schema === "object" && "name" in schema && schema.name === name;
2123
+ });
2124
+ if (tools !== TOOLS_TOKEN && !namesMatch) throw new Error(`acp-snapshot: pinned request header tools must equal ${TOOLS_TOKEN} or the ordered sidecar tool names`);
2099
2125
  return {
2100
2126
  ...header,
2101
2127
  tools: schemas
@@ -2181,6 +2207,7 @@ function surfaceEventMessage(record) {
2181
2207
  message = data;
2182
2208
  break;
2183
2209
  case "system/message":
2210
+ case "developer/message":
2184
2211
  case "assistant/message":
2185
2212
  case "tool/result":
2186
2213
  message = data.message;
@@ -2350,6 +2377,28 @@ function refreshFixtureReplacements(logs, fixtures) {
2350
2377
  }
2351
2378
  return replacements;
2352
2379
  }
2380
+ /**
2381
+ * Check raw parent/child clocks before normalization, or preserve their equality during refresh.
2382
+ * @param logs - one scenario's parent and child logs with shared Session ids.
2383
+ * @param policy - validate live output, or align refreshed catalogs to the retained child headers.
2384
+ * @returns logs with only conflicting catalog creation times replaced under preserve-headers.
2385
+ */
2386
+ function reconcileCatalogCreationTimes(logs, policy) {
2387
+ const headers = new Map(logs.map((log) => {
2388
+ const header = parseJsonlRecords(log)[0];
2389
+ return [header?.["id"], header];
2390
+ }));
2391
+ return logs.map((log) => log.split("\n").map((line) => {
2392
+ if (line.length === 0) return line;
2393
+ const record = JSON.parse(line);
2394
+ if (record.type !== "subagent/catalog" || !isRecord(record.data)) return line;
2395
+ const child = headers.get(record.data.childId);
2396
+ if (child === void 0 || record.data.childCreatedAt === child.createdAt) return line;
2397
+ if (policy === "validate") throw new Error(`catalog child ${String(record.data.childId)} creation time ${String(record.data.childCreatedAt)} disagrees with child header ${String(child.createdAt)}`);
2398
+ record.data.childCreatedAt = child.createdAt;
2399
+ return JSON.stringify(record);
2400
+ }).join("\n"));
2401
+ }
2353
2402
  function preserveFixtureVolatiles(record, existing) {
2354
2403
  if (existing === void 0 || existing.type !== record.type) return;
2355
2404
  if (record.type === "session") {
@@ -2610,6 +2659,7 @@ function defineAcpSnapshotSuite(options) {
2610
2659
  ...scenario.workspaceParent !== void 0 ? { workspaceParent: scenario.workspaceParent } : {},
2611
2660
  ...scenario.configPath !== void 0 ? { configPath: scenario.configPath } : {}
2612
2661
  });
2662
+ reconcileCatalogCreationTimes(result.sessionLogs.map((log) => log.content), "validate");
2613
2663
  for (const log of result.sessionLogs) expect(unknownToolCallIds(log.content), `session ${log.id}: snapshot scenarios must not accept UNKNOWN_TOOL`).toEqual([]);
2614
2664
  const ctx = {
2615
2665
  sessionIds: [...result.sessionId !== void 0 ? [result.sessionId] : [], ...result.sessionLogs.map((l) => l.id)],
@@ -2629,7 +2679,7 @@ function defineAcpSnapshotSuite(options) {
2629
2679
  return readFile(join(dir, file), "utf8");
2630
2680
  }));
2631
2681
  const refreshReplacements = REFRESHING ? refreshFixtureReplacements(result.sessionLogs, existingFixtures) : [];
2632
- const outputFixtures = redactSessionSnapshotIds(stabilizeFixtureMessageIds(REFRESHING ? result.sessionLogs.map((log, index) => scrubSessionSnapshot(portableFixture(stabilizeRefreshLog(log.content, existingFixtures[index], refreshReplacements, ctx)))) : result.sessionLogs.map((log) => scrubSessionSnapshot(portableFixture(log.content))), existingFixtures));
2682
+ const outputFixtures = redactSessionSnapshotIds(stabilizeFixtureMessageIds(reconcileCatalogCreationTimes(REFRESHING ? result.sessionLogs.map((log, index) => scrubSessionSnapshot(portableFixture(stabilizeRefreshLog(log.content, existingFixtures[index], refreshReplacements, ctx)))) : result.sessionLogs.map((log) => scrubSessionSnapshot(portableFixture(log.content))), "preserve-headers"), existingFixtures));
2633
2683
  await Promise.all(outputFixtures.map((fixture, index) => writeFile(join(dir, outputFixtureFiles[index]), fixture)));
2634
2684
  fixtureFiles = outputFixtureFiles;
2635
2685
  if (scenario.pinsHeader === true) {
@@ -2676,8 +2726,8 @@ function defineAcpSnapshotSuite(options) {
2676
2726
  sessionIds: fixtureContexts.flatMap((context) => context.sessionIds),
2677
2727
  cwd: fixtureContexts[0].cwd
2678
2728
  };
2679
- const actualSnapshots = normalizeSessionSnapshots(harvested, ctx);
2680
- const expectedSnapshots = normalizeSessionSnapshots(fixtures, fixtureCtx);
2729
+ const actualSnapshots = normalizeSessionSnapshots(harvested, ctx, { nativeWriterOutput: true });
2730
+ const expectedSnapshots = normalizeSessionSnapshots(fixtures, fixtureCtx, { nativeWriterOutput: true });
2681
2731
  for (const [index, actual] of actualSnapshots.entries()) expect(actual, `${fixtureFiles[index]} mismatch`).toEqual(expectedSnapshots[index]);
2682
2732
  }
2683
2733
  /* v8 ignore next -- construction guarantees the pin exists; a miss would fail the one-header assertion loudly. */
@@ -2846,4 +2896,4 @@ function defineAcpSnapshotSuite(options) {
2846
2896
  });
2847
2897
  }
2848
2898
  //#endregion
2849
- export { EMPTY_WORKSPACE_MARKER, assertPersistedSessionVersion, assertSessionFixtureVersion, captureExpectedWorkspaceSnapshot, captureWorkspaceSnapshot, defineAcpSnapshotSuite, extractSnapshotSpillPaths, fixtureContext, formatSystemPromptSnapshot, formatToolSchemasSnapshot, headerChangeCount, latestPersistedSessionPaths, launchAcpTestAgent, materializeProfilePatch, normalizeSessionFormatProvenance, normalizeSessionLog, normalizeSessionSnapshot, normalizeSessionSnapshots, normalizeStdout, normalizedHeaders, normalizedSystemPrompts, normalizedToolSchemas, parsePersistedSessionFilename, parseSessionFixtureName, parseSnapshotManifest, parseSystemPromptSnapshot, parseToolSchemasSnapshot, persistedSessionFilename, redactSessionSnapshotIds, refreshFixtureReplacements, restorePinnedToolSchemas, runScenario, scrubModelRequestBulk, scrubSessionSnapshot, scrubSystemPrompts, scrubToolSchemas, sessionFixtureFiles, sessionFixtureName, sessionFixtureNames, sessionHeaderVersion, snapshotSpillRoot, stabilizeFixtureMessageIds, stabilizeRefreshLog, systemPromptPrecedesRequests, tokenizeSessionFixtureCwd, writerSnapshotName, writesCurrentSessionFixtures };
2899
+ export { EMPTY_WORKSPACE_MARKER, assertPersistedSessionVersion, assertSessionFixtureVersion, captureExpectedWorkspaceSnapshot, captureWorkspaceSnapshot, defineAcpSnapshotSuite, extractSnapshotSpillPaths, fixtureContext, formatSystemPromptSnapshot, formatToolSchemasSnapshot, headerChangeCount, latestPersistedSessionPaths, launchAcpTestAgent, materializeProfilePatch, normalizeSessionFormatMetadata, normalizeSessionLog, normalizeSessionSnapshot, normalizeSessionSnapshots, normalizeStdout, normalizedHeaders, normalizedSystemPrompts, normalizedToolSchemas, parsePersistedSessionFilename, parseSessionFixtureName, parseSnapshotManifest, parseSystemPromptSnapshot, parseToolSchemasSnapshot, persistedSessionFilename, reconcileCatalogCreationTimes, redactSessionSnapshotIds, refreshFixtureReplacements, restorePinnedToolSchemas, runScenario, scrubModelRequestBulk, scrubSessionSnapshot, scrubSystemPrompts, scrubToolSchemas, sessionFixtureFiles, sessionFixtureName, sessionFixtureNames, sessionHeaderVersion, snapshotSpillRoot, stabilizeFixtureMessageIds, stabilizeRefreshLog, systemPromptPrecedesRequests, tokenizeSessionFixtureCwd, writerSnapshotName, writesCurrentSessionFixtures };
@@ -19,9 +19,9 @@
19
19
  export { redactSessionSnapshotIds, } from './identity.ts';
20
20
  export { runScenario, snapshotSpillRoot, type HarvestedLog, type InputScript, type InputStep, type PermissionAnswer, type RunOptions, type RunResult, } from './harness.ts';
21
21
  export { launchAcpTestAgent, materializeProfilePatch, type AcpTestLaunchOptions, type AgentUnderTest, type LaunchedAcpTestAgent, } from './launcher.ts';
22
- export { extractSnapshotSpillPaths, normalizeSessionFormatProvenance, normalizeSessionLog, normalizeSessionSnapshot, normalizeSessionSnapshots, normalizeStdout, scrubModelRequestBulk, scrubSessionSnapshot, scrubSystemPrompts, scrubToolSchemas, tokenizeSessionFixtureCwd, type CwdPathMode, type NormalizeContext, type NormalizeOptions, } from './normalize.ts';
22
+ export { extractSnapshotSpillPaths, normalizeSessionFormatMetadata, normalizeSessionLog, normalizeSessionSnapshot, normalizeSessionSnapshots, normalizeStdout, scrubModelRequestBulk, scrubSessionSnapshot, scrubSystemPrompts, scrubToolSchemas, tokenizeSessionFixtureCwd, type CwdPathMode, type NormalizeContext, type NormalizeOptions, } from './normalize.ts';
23
23
  export { parseSnapshotManifest, writesCurrentSessionFixtures, type SnapshotHeaderManifest, type SnapshotInputAttachment, type SnapshotInputManifest, type SnapshotManifest, type SnapshotPermission, type SnapshotPlatform, type SnapshotProfile, type SnapshotRecording, type SnapshotReplayManifest, type SnapshotSessionReference, type SnapshotSessionFormatCoverage, type SnapshotSessionFormatManifest, type SnapshotSessionWriteMode, type SnapshotWorkspaceManifest, } from './manifest.ts';
24
24
  export { assertPersistedSessionVersion, assertSessionFixtureVersion, latestPersistedSessionPaths, parsePersistedSessionFilename, parseSessionFixtureName, persistedSessionFilename, sessionFixtureFiles, sessionFixtureName, sessionFixtureNames, sessionHeaderVersion, writerSnapshotName, type PersistedSessionFile, type SessionFixtureFile, } from './session-files.ts';
25
- export { formatSystemPromptSnapshot, formatToolSchemasSnapshot, fixtureContext, headerChangeCount, defineAcpSnapshotSuite, normalizedHeaders, normalizedSystemPrompts, normalizedToolSchemas, parseSystemPromptSnapshot, parseToolSchemasSnapshot, refreshFixtureReplacements, restorePinnedToolSchemas, stabilizeFixtureMessageIds, stabilizeRefreshLog, systemPromptPrecedesRequests, type Scenario, type SnapshotSuiteOptions, } from './suite.ts';
25
+ export { formatSystemPromptSnapshot, formatToolSchemasSnapshot, fixtureContext, headerChangeCount, defineAcpSnapshotSuite, normalizedHeaders, normalizedSystemPrompts, normalizedToolSchemas, parseSystemPromptSnapshot, parseToolSchemasSnapshot, refreshFixtureReplacements, reconcileCatalogCreationTimes, restorePinnedToolSchemas, stabilizeFixtureMessageIds, stabilizeRefreshLog, systemPromptPrecedesRequests, type Scenario, type SnapshotSuiteOptions, } from './suite.ts';
26
26
  export { captureExpectedWorkspaceSnapshot, captureWorkspaceSnapshot, EMPTY_WORKSPACE_MARKER, type CaptureWorkspaceSnapshotOptions, type WorkspaceBinaryFileSnapshot, type WorkspaceEmptyDirectorySnapshot, type WorkspaceSnapshotEntry, type WorkspaceSymlinkSnapshot, type WorkspaceTextFileSnapshot, } from './workspace.ts';
27
27
  //# sourceMappingURL=index.d.ts.map
@@ -75,10 +75,11 @@ export declare function launchAcpTestAgent(options: AcpTestLaunchOptions): Launc
75
75
  /**
76
76
  * Copy one authored patch into the launch cwd with relative plugin names made absolute.
77
77
  * @param source - authored profile patch path.
78
- * @param cwd - isolated process cwd whose profile fallback receives package links.
78
+ * @param cwd - isolated process cwd whose profile receives package links.
79
+ * @param profile - profile whose local package lookup receives the test links.
79
80
  * @param targetDir - existing directory that owns the materialized patch.
80
81
  * @param index - stable patch ordinal used in the output filename.
81
82
  * @returns absolute materialized patch path.
82
83
  */
83
- export declare function materializeProfilePatch(source: string, cwd: string, targetDir: string, index: number): string;
84
+ export declare function materializeProfilePatch(source: string, cwd: string, profile: string, targetDir: string, index: number): string;
84
85
  //# sourceMappingURL=launcher.d.ts.map
@@ -61,13 +61,13 @@ export interface SnapshotSessionReference {
61
61
  /** Repository-relative POSIX path to the owning scenario's selected parent Session fixture. */
62
62
  source: string;
63
63
  }
64
- /** Historical-format behavior one retained scenario permanently exercises. */
65
- export type SnapshotSessionFormatCoverage = 'multi-hop' | 'packed-row' | 'retry-failure' | 'shipped-profile' | 'adjacent-migration';
64
+ /** Historical format or retired capability one retained scenario permanently exercises. */
65
+ export type SnapshotSessionFormatCoverage = 'multi-hop' | 'packed-row' | 'retry-failure' | 'shipped-profile' | 'adjacent-migration' | 'retired-tools';
66
66
  /** Explicit historical generation retained by an owning scenario. */
67
67
  export interface SnapshotSessionFormatManifest {
68
68
  /** Selected fixture generation; absent manifest metadata tracks the current writer. */
69
69
  readonly version: number;
70
- /** Migration behaviors that require this historical fixture. */
70
+ /** Migration or retired-tool behavior that requires this immutable fixture. */
71
71
  readonly coverage: readonly SnapshotSessionFormatCoverage[];
72
72
  }
73
73
  /** Declarative ownership metadata stored beside a recorded session. */
@@ -30,6 +30,11 @@ export interface NormalizeOptions {
30
30
  /** Keep already-redacted typed ids and arbitrary UUID-like prose unchanged. */
31
31
  identityMode?: 'legacy' | 'preserve';
32
32
  }
33
+ /** Comparison controls for complete primary and child Session logs. */
34
+ export interface SessionSnapshotComparisonOptions extends Omit<NormalizeOptions, 'identityMode'> {
35
+ /** Inputs are captured native writer output, rather than source-versus-migrated-artifact comparisons. */
36
+ nativeWriterOutput?: true;
37
+ }
33
38
  /**
34
39
  * Store one generated workspace as `{{cwd}}` while retaining every other
35
40
  * session value. The caller opts in only for workspaces created under a
@@ -85,19 +90,22 @@ export declare function normalizeSessionLog(rawLog: string, ctx: NormalizeContex
85
90
  export declare function normalizeSessionSnapshot(rawLog: string, ctx: NormalizeContext, options?: NormalizeOptions): string;
86
91
  /**
87
92
  * Normalize one scenario's primary and child logs with shared typed identity redaction.
93
+ * Native-writer comparison strictly restores each input before tokenizing its own delivery generation.
88
94
  * @param rawLogs - primary-first persisted or projected session JSONL.
89
95
  * @param ctx - generated cwd spellings and other volatile run facts.
90
- * @param options - separator controls; relationship-preserving identity mode is mandatory.
91
- * @returns normalized session fixtures in input order.
96
+ * @param options - separator and native-writer comparison controls; identity relationships are preserved.
97
+ * @returns comparison-only Session records in input order; not persistence or fixture write-back input.
92
98
  */
93
- export declare function normalizeSessionSnapshots(rawLogs: readonly string[], ctx: NormalizeContext, options?: Omit<NormalizeOptions, 'identityMode'>): string[];
99
+ export declare function normalizeSessionSnapshots(rawLogs: readonly string[], ctx: NormalizeContext, options?: SessionSnapshotComparisonOptions): string[];
94
100
  /**
95
101
  * Omit the artifact header generation after official migration for comparison.
96
- * Delivery and captured-source generations retain their opaque recorded values.
102
+ * An explicit source version tokenizes only delivery markers for that original input generation.
103
+ * Other delivery generations and every captured-source generation retain their recorded values.
97
104
  * @param rawLog - Session records or events as compact JSON lines.
98
- * @returns the same records with only the Session header version omitted.
105
+ * @param sourceVersion - original generation of strictly validated native writer output; otherwise omit.
106
+ * @returns comparison-only records with the Session header version omitted and native delivery qualifiers tokenized.
99
107
  */
100
- export declare function normalizeSessionFormatProvenance(rawLog: string): string;
108
+ export declare function normalizeSessionFormatMetadata(rawLog: string, sourceVersion?: number): string;
101
109
  /**
102
110
  * Replace the rendered prompt text of every `system/message` event with the
103
111
  * `{{system}}` token. The text block keeps its position and type, so the
@@ -111,7 +119,9 @@ export declare function normalizeSessionFormatProvenance(rawLog: string): string
111
119
  export declare function scrubSystemPrompts(rawLog: string): string;
112
120
  /**
113
121
  * Replace tool schemas in full request-header snapshots with `{{tools}}`
114
- * tokens while retaining field presence. System-prompt text stays verbatim so
122
+ * tokens while retaining field presence. Logs containing developer messages
123
+ * retain tool names so historical addition references remain verifiable.
124
+ * System-prompt text stays verbatim so
115
125
  * pinning fixtures can move only schema bulk into their dedicated JSON
116
126
  * sidecar. Lines without a tool payload pass through byte-for-byte; the
117
127
  * transform is idempotent.
@@ -135,7 +145,8 @@ export declare function scrubModelRequestBulk(rawLog: string): string;
135
145
  /**
136
146
  * Project a persisted session log while tokenizing prompt text and schema
137
147
  * bulk. Each non-empty line is parsed at most once; the session header stays
138
- * byte-identical. Body records omit their persistence-only envelopes.
148
+ * byte-identical. Body records omit their persistence-only envelopes and expand
149
+ * source-event ranges without changing reference order.
139
150
  *
140
151
  * @param rawLog - persisted or already-projected session JSONL.
141
152
  * @returns committed snapshot JSONL with prompt text and tool schemas tokenized.
@@ -312,7 +312,7 @@ export declare function parseToolSchemasSnapshot(snapshot: string): ToolSchemasS
312
312
  /**
313
313
  * Restore one sidecar schema set into a tokenized pinned header.
314
314
  *
315
- * @param header The parsed request header carrying `tools: "{{tools}}"`.
315
+ * @param header The parsed request header carrying a tools token or ordered tool names matching the sidecar.
316
316
  * @param schemas The complete schemas for this full header snapshot.
317
317
  * @returns A copy of the header with its complete schemas restored.
318
318
  */
@@ -386,6 +386,13 @@ export declare function unknownToolCallIds(rawLog: string): string[];
386
386
  * @returns Literal replacements from fresh values to the fixture's existing values.
387
387
  */
388
388
  export declare function refreshFixtureReplacements(logs: HarvestedLog[], fixtures: string[]): FixtureReplacement[];
389
+ /**
390
+ * Check raw parent/child clocks before normalization, or preserve their equality during refresh.
391
+ * @param logs - one scenario's parent and child logs with shared Session ids.
392
+ * @param policy - validate live output, or align refreshed catalogs to the retained child headers.
393
+ * @returns logs with only conflicting catalog creation times replaced under preserve-headers.
394
+ */
395
+ export declare function reconcileCatalogCreationTimes(logs: readonly string[], policy: 'validate' | 'preserve-headers'): string[];
389
396
  /**
390
397
  * Rewrite a fresh replay-produced log so repeated refreshes do not churn
391
398
  * volatile fixture fields. Meaningful event payloads come from `fresh`; the
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@x1a0f3n9/dsh-session-snapshot",
3
3
  "description": "Session-log snapshot core with an ACP protocol adapter, expected-output normalization, and fixture invariants",
4
- "version": "0.1.5-rc.4",
4
+ "version": "0.1.7-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -30,29 +30,29 @@
30
30
  "@agentclientprotocol/sdk": "1.4.0",
31
31
  "js-yaml": "^4.2.0",
32
32
  "vitest": "^4.1.8",
33
- "@deepseek-ai/cordis-plugin-include": "1.0.7",
34
- "@x1a0f3n9/dsh-llm-replay": "^0.1.5-rc.4",
35
- "@x1a0f3n9/dsh-loader-smoke": "0.1.5-rc.4",
36
- "@x1a0f3n9/dsh-session-format": "^0.1.5-rc.4"
33
+ "@deepseek-ai/cordis-plugin-include": "~1.0.9",
34
+ "@x1a0f3n9/dsh-llm-replay": "0.1.7-rc.2",
35
+ "@x1a0f3n9/dsh-loader-smoke": "0.1.7-rc.2",
36
+ "@x1a0f3n9/dsh-session-format": "0.1.7-rc.2"
37
37
  },
38
38
  "peerDependencies": {
39
- "@deepseek-ai/cordis": "^4.0.2",
40
- "@x1a0f3n9/dsh-session": "^0.1.5-rc.4",
41
- "@x1a0f3n9/dsh-http-proxy": "^0.1.5-rc.4"
39
+ "@deepseek-ai/cordis": "~4.0.4",
40
+ "@x1a0f3n9/dsh-session": "0.1.7-rc.2",
41
+ "@x1a0f3n9/dsh-http-proxy": "0.1.7-rc.2"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/js-yaml": "^4.0.9",
45
- "@deepseek-ai/cordis": "^4.0.2",
46
- "@x1a0f3n9/dsh-agent": "^0.1.5-rc.4",
47
- "@x1a0f3n9/dsh-agent-loop": "^0.1.5-rc.4",
48
- "@x1a0f3n9/dsh-compaction": "^0.1.5-rc.4",
49
- "@x1a0f3n9/dsh-llm": "^0.1.5-rc.4",
50
- "@x1a0f3n9/dsh-sandbox": "^0.1.5-rc.4",
51
- "@x1a0f3n9/dsh-sandbox-policy": "^0.1.5-rc.4",
52
- "@x1a0f3n9/dsh-session": "^0.1.5-rc.4",
53
- "@x1a0f3n9/dsh-subagent": "^0.1.5-rc.4",
54
- "@x1a0f3n9/dsh-tools": "^0.1.5-rc.4",
55
- "@x1a0f3n9/dsh-user-questions": "^0.1.5-rc.4",
56
- "@x1a0f3n9/dsh-http-proxy": "^0.1.5-rc.4"
45
+ "@deepseek-ai/cordis": "~4.0.4",
46
+ "@x1a0f3n9/dsh-agent": "0.1.7-rc.2",
47
+ "@x1a0f3n9/dsh-agent-loop": "0.1.7-rc.2",
48
+ "@x1a0f3n9/dsh-compaction": "0.1.7-rc.2",
49
+ "@x1a0f3n9/dsh-llm": "0.1.7-rc.2",
50
+ "@x1a0f3n9/dsh-sandbox": "0.1.7-rc.2",
51
+ "@x1a0f3n9/dsh-sandbox-policy": "0.1.7-rc.2",
52
+ "@x1a0f3n9/dsh-session": "0.1.7-rc.2",
53
+ "@x1a0f3n9/dsh-subagent": "0.1.7-rc.2",
54
+ "@x1a0f3n9/dsh-tools": "0.1.7-rc.2",
55
+ "@x1a0f3n9/dsh-user-questions": "0.1.7-rc.2",
56
+ "@x1a0f3n9/dsh-http-proxy": "0.1.7-rc.2"
57
57
  }
58
58
  }