@deepseek-ai/dsh-session-snapshot 0.1.6-alpha.2 → 0.1.7-alpha.1

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
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
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: 51aaff9af7dfce83ed032b42ce0bb471aa384002
6
- README.zh.md: ad136b19a75170c130bbb872ba02bb88e010bb02
5
+ README.md: 2bc7b9eaf9ebd8e7a2fa357e60eddd594d21d9a7
6
+ README.zh.md: ea26aa50d9dc835c8f234ad224afff9f5a33483e
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
 
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
 
package/lib/index.js CHANGED
@@ -1163,6 +1163,7 @@ const SYSTEM = "{{system}}";
1163
1163
  const TOOLS = "{{tools}}";
1164
1164
  const EVENT_TIME = "{{eventTime}}";
1165
1165
  const EVENT_OMITTED_BYTES = "{{eventOmittedBytes}}";
1166
+ const SOURCE_SESSION_FORMAT = "{{sourceSessionFormatVersion}}";
1166
1167
  const PACKED_CHUNK_ROW_TYPES$1 = new Set([
1167
1168
  "text-chunks",
1168
1169
  "reasoning-chunks",
@@ -1448,32 +1449,43 @@ function normalizeSessionSnapshot(rawLog, ctx, options = {}) {
1448
1449
  }
1449
1450
  /**
1450
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.
1451
1453
  * @param rawLogs - primary-first persisted or projected session JSONL.
1452
1454
  * @param ctx - generated cwd spellings and other volatile run facts.
1453
- * @param options - separator controls; relationship-preserving identity mode is mandatory.
1454
- * @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.
1455
1457
  */
1456
1458
  function normalizeSessionSnapshots(rawLogs, ctx, options = {}) {
1457
- return redactSessionSnapshotIds(rawLogs.map((log) => hasSessionFormatVersion(log) ? prepareSessionSnapshotFixtureForComparison(log) : log).map(normalizeSessionFormatMetadata)).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, {
1458
1464
  ...ctx,
1459
1465
  sessionIds: []
1460
1466
  }, {
1461
- ...options,
1467
+ ...normalizeOptions,
1462
1468
  identityMode: "preserve"
1463
1469
  }))));
1464
1470
  }
1465
1471
  /**
1466
1472
  * Omit the artifact header generation after official migration for comparison.
1467
- * 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.
1468
1475
  * @param rawLog - Session records or events as compact JSON lines.
1469
- * @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.
1470
1478
  */
1471
- function normalizeSessionFormatMetadata(rawLog) {
1479
+ function normalizeSessionFormatMetadata(rawLog, sourceVersion) {
1472
1480
  return rawLog.split("\n").map((line) => {
1473
1481
  if (line.trim().length === 0) return line;
1474
1482
  const record = JSON.parse(line);
1475
- if (record.type !== "session" || !Object.hasOwn(record, "version")) return line;
1476
- 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;
1477
1489
  return JSON.stringify(record);
1478
1490
  }).join("\n");
1479
1491
  }
@@ -1531,7 +1543,8 @@ function scrubModelRequestBulk(rawLog) {
1531
1543
  /**
1532
1544
  * Project a persisted session log while tokenizing prompt text and schema
1533
1545
  * bulk. Each non-empty line is parsed at most once; the session header stays
1534
- * byte-identical. Body records omit their persistence-only envelopes.
1546
+ * byte-identical. Body records omit their persistence-only envelopes and expand
1547
+ * source-event ranges without changing reference order.
1535
1548
  *
1536
1549
  * @param rawLog - persisted or already-projected session JSONL.
1537
1550
  * @returns committed snapshot JSONL with prompt text and tool schemas tokenized.
@@ -1547,6 +1560,7 @@ function scrubSessionSnapshot(rawLog) {
1547
1560
  return line;
1548
1561
  }
1549
1562
  omitFixtureEnvelope(record);
1563
+ if (Object.hasOwn(record, "sourceEventSeqs")) record.sourceEventSeqs = decodeSeqRanges(record.sourceEventSeqs);
1550
1564
  normalizeFeedbackClocks(record);
1551
1565
  return JSON.stringify(record);
1552
1566
  }).join("\n");
@@ -2183,6 +2197,7 @@ function surfaceEventMessage(record) {
2183
2197
  message = data;
2184
2198
  break;
2185
2199
  case "system/message":
2200
+ case "developer/message":
2186
2201
  case "assistant/message":
2187
2202
  case "tool/result":
2188
2203
  message = data.message;
@@ -2352,6 +2367,28 @@ function refreshFixtureReplacements(logs, fixtures) {
2352
2367
  }
2353
2368
  return replacements;
2354
2369
  }
2370
+ /**
2371
+ * Check raw parent/child clocks before normalization, or preserve their equality during refresh.
2372
+ * @param logs - one scenario's parent and child logs with shared Session ids.
2373
+ * @param policy - validate live output, or align refreshed catalogs to the retained child headers.
2374
+ * @returns logs with only conflicting catalog creation times replaced under preserve-headers.
2375
+ */
2376
+ function reconcileCatalogCreationTimes(logs, policy) {
2377
+ const headers = new Map(logs.map((log) => {
2378
+ const header = parseJsonlRecords(log)[0];
2379
+ return [header?.["id"], header];
2380
+ }));
2381
+ return logs.map((log) => log.split("\n").map((line) => {
2382
+ if (line.length === 0) return line;
2383
+ const record = JSON.parse(line);
2384
+ if (record.type !== "subagent/catalog" || !isRecord(record.data)) return line;
2385
+ const child = headers.get(record.data.childId);
2386
+ if (child === void 0 || record.data.childCreatedAt === child.createdAt) return line;
2387
+ 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)}`);
2388
+ record.data.childCreatedAt = child.createdAt;
2389
+ return JSON.stringify(record);
2390
+ }).join("\n"));
2391
+ }
2355
2392
  function preserveFixtureVolatiles(record, existing) {
2356
2393
  if (existing === void 0 || existing.type !== record.type) return;
2357
2394
  if (record.type === "session") {
@@ -2612,6 +2649,7 @@ function defineAcpSnapshotSuite(options) {
2612
2649
  ...scenario.workspaceParent !== void 0 ? { workspaceParent: scenario.workspaceParent } : {},
2613
2650
  ...scenario.configPath !== void 0 ? { configPath: scenario.configPath } : {}
2614
2651
  });
2652
+ reconcileCatalogCreationTimes(result.sessionLogs.map((log) => log.content), "validate");
2615
2653
  for (const log of result.sessionLogs) expect(unknownToolCallIds(log.content), `session ${log.id}: snapshot scenarios must not accept UNKNOWN_TOOL`).toEqual([]);
2616
2654
  const ctx = {
2617
2655
  sessionIds: [...result.sessionId !== void 0 ? [result.sessionId] : [], ...result.sessionLogs.map((l) => l.id)],
@@ -2631,7 +2669,7 @@ function defineAcpSnapshotSuite(options) {
2631
2669
  return readFile(join(dir, file), "utf8");
2632
2670
  }));
2633
2671
  const refreshReplacements = REFRESHING ? refreshFixtureReplacements(result.sessionLogs, existingFixtures) : [];
2634
- 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));
2672
+ 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));
2635
2673
  await Promise.all(outputFixtures.map((fixture, index) => writeFile(join(dir, outputFixtureFiles[index]), fixture)));
2636
2674
  fixtureFiles = outputFixtureFiles;
2637
2675
  if (scenario.pinsHeader === true) {
@@ -2678,8 +2716,8 @@ function defineAcpSnapshotSuite(options) {
2678
2716
  sessionIds: fixtureContexts.flatMap((context) => context.sessionIds),
2679
2717
  cwd: fixtureContexts[0].cwd
2680
2718
  };
2681
- const actualSnapshots = normalizeSessionSnapshots(harvested, ctx);
2682
- const expectedSnapshots = normalizeSessionSnapshots(fixtures, fixtureCtx);
2719
+ const actualSnapshots = normalizeSessionSnapshots(harvested, ctx, { nativeWriterOutput: true });
2720
+ const expectedSnapshots = normalizeSessionSnapshots(fixtures, fixtureCtx, { nativeWriterOutput: true });
2683
2721
  for (const [index, actual] of actualSnapshots.entries()) expect(actual, `${fixtureFiles[index]} mismatch`).toEqual(expectedSnapshots[index]);
2684
2722
  }
2685
2723
  /* v8 ignore next -- construction guarantees the pin exists; a miss would fail the one-header assertion loudly. */
@@ -2848,4 +2886,4 @@ function defineAcpSnapshotSuite(options) {
2848
2886
  });
2849
2887
  }
2850
2888
  //#endregion
2851
- 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, redactSessionSnapshotIds, refreshFixtureReplacements, restorePinnedToolSchemas, runScenario, scrubModelRequestBulk, scrubSessionSnapshot, scrubSystemPrompts, scrubToolSchemas, sessionFixtureFiles, sessionFixtureName, sessionFixtureNames, sessionHeaderVersion, snapshotSpillRoot, stabilizeFixtureMessageIds, stabilizeRefreshLog, systemPromptPrecedesRequests, tokenizeSessionFixtureCwd, writerSnapshotName, writesCurrentSessionFixtures };
2889
+ 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 };
@@ -22,6 +22,6 @@ export { launchAcpTestAgent, materializeProfilePatch, type AcpTestLaunchOptions,
22
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
@@ -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 normalizeSessionFormatMetadata(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
@@ -135,7 +143,8 @@ export declare function scrubModelRequestBulk(rawLog: string): string;
135
143
  /**
136
144
  * Project a persisted session log while tokenizing prompt text and schema
137
145
  * bulk. Each non-empty line is parsed at most once; the session header stays
138
- * byte-identical. Body records omit their persistence-only envelopes.
146
+ * byte-identical. Body records omit their persistence-only envelopes and expand
147
+ * source-event ranges without changing reference order.
139
148
  *
140
149
  * @param rawLog - persisted or already-projected session JSONL.
141
150
  * @returns committed snapshot JSONL with prompt text and tool schemas tokenized.
@@ -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": "@deepseek-ai/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.6-alpha.2",
4
+ "version": "0.1.7-alpha.1",
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
- "@deepseek-ai/dsh-llm-replay": "^0.1.6-alpha.2",
35
- "@deepseek-ai/dsh-loader-smoke": "0.1.6-alpha.2",
36
- "@deepseek-ai/dsh-session-format": "^0.1.6-alpha.2"
33
+ "@deepseek-ai/cordis-plugin-include": "1.0.8",
34
+ "@deepseek-ai/dsh-llm-replay": "^0.1.7-alpha.1",
35
+ "@deepseek-ai/dsh-loader-smoke": "0.1.7-alpha.1",
36
+ "@deepseek-ai/dsh-session-format": "^0.1.7-alpha.1"
37
37
  },
38
38
  "peerDependencies": {
39
- "@deepseek-ai/cordis": "^4.0.2",
40
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
41
- "@deepseek-ai/dsh-http-proxy": "^0.1.6-alpha.2"
39
+ "@deepseek-ai/cordis": "^4.0.3",
40
+ "@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
41
+ "@deepseek-ai/dsh-http-proxy": "^0.1.7-alpha.1"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/js-yaml": "^4.0.9",
45
- "@deepseek-ai/cordis": "^4.0.2",
46
- "@deepseek-ai/dsh-agent": "^0.1.6-alpha.2",
47
- "@deepseek-ai/dsh-agent-loop": "^0.1.6-alpha.2",
48
- "@deepseek-ai/dsh-compaction": "^0.1.6-alpha.2",
49
- "@deepseek-ai/dsh-llm": "^0.1.6-alpha.2",
50
- "@deepseek-ai/dsh-sandbox": "^0.1.6-alpha.2",
51
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
52
- "@deepseek-ai/dsh-subagent": "^0.1.6-alpha.2",
53
- "@deepseek-ai/dsh-tools": "^0.1.6-alpha.2",
54
- "@deepseek-ai/dsh-user-questions": "^0.1.6-alpha.2",
55
- "@deepseek-ai/dsh-http-proxy": "^0.1.6-alpha.2",
56
- "@deepseek-ai/dsh-sandbox-policy": "^0.1.6-alpha.2"
45
+ "@deepseek-ai/cordis": "^4.0.3",
46
+ "@deepseek-ai/dsh-agent": "^0.1.7-alpha.1",
47
+ "@deepseek-ai/dsh-compaction": "^0.1.7-alpha.1",
48
+ "@deepseek-ai/dsh-agent-loop": "^0.1.7-alpha.1",
49
+ "@deepseek-ai/dsh-sandbox": "^0.1.7-alpha.1",
50
+ "@deepseek-ai/dsh-sandbox-policy": "^0.1.7-alpha.1",
51
+ "@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
52
+ "@deepseek-ai/dsh-subagent": "^0.1.7-alpha.1",
53
+ "@deepseek-ai/dsh-tools": "^0.1.7-alpha.1",
54
+ "@deepseek-ai/dsh-user-questions": "^0.1.7-alpha.1",
55
+ "@deepseek-ai/dsh-http-proxy": "^0.1.7-alpha.1",
56
+ "@deepseek-ai/dsh-llm": "^0.1.7-alpha.1"
57
57
  }
58
58
  }