@deepseek-ai/dsh-session-snapshot 0.2.0-rc.2 → 0.2.1-alpha.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 +8 -8
- package/README.md +3 -4
- package/README.zh.md +3 -4
- package/lib/index.js +85 -58
- package/lib/types/harness.d.ts +5 -3
- package/package.json +23 -22
package/README.i18n.yaml
CHANGED
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
en: 594ad1f3e6f02054
|
|
19
19
|
zh: 7ca4f565976704be
|
|
20
20
|
/deepseek-ai-dsh-session-snapshot/use-this-package/writing-a-snapshot-suite:
|
|
21
|
-
en:
|
|
22
|
-
zh:
|
|
21
|
+
en: 57ff7c89e7696ee8
|
|
22
|
+
zh: b885ac1b4edc8c56
|
|
23
23
|
/deepseek-ai-dsh-session-snapshot/use-this-package/record-replay-and-refresh:
|
|
24
|
-
en:
|
|
25
|
-
zh:
|
|
24
|
+
en: 2bea67c686715289
|
|
25
|
+
zh: aecc6ea7b4ae5623
|
|
26
26
|
/deepseek-ai-dsh-session-snapshot/use-this-package/pinning-request-headers-and-system-prompts:
|
|
27
27
|
en: 6b11d5111e1a3222
|
|
28
28
|
zh: de4d3889b1a5ebb4
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
en: 64666ea2894acc4a
|
|
31
31
|
zh: 0e78df32bcfd3633
|
|
32
32
|
/deepseek-ai-dsh-session-snapshot/use-this-package/what-can-go-wrong:
|
|
33
|
-
en:
|
|
34
|
-
zh:
|
|
33
|
+
en: 0b03592947ca43d6
|
|
34
|
+
zh: b14a64238c4b5f80
|
|
35
35
|
/deepseek-ai-dsh-session-snapshot/understand-the-implementation:
|
|
36
36
|
en: 18ff76b440fe40da
|
|
37
37
|
zh: 4dbf2eb5f9ee98a2
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
en: 8771607f15c4f4e1
|
|
40
40
|
zh: 7c2cf395f704717c
|
|
41
41
|
/deepseek-ai-dsh-session-snapshot/understand-the-implementation/source-map:
|
|
42
|
-
en:
|
|
43
|
-
zh:
|
|
42
|
+
en: 1510b3b09c3bd553
|
|
43
|
+
zh: 1931fba0891f7d65
|
|
44
44
|
/deepseek-ai-dsh-session-snapshot/understand-the-implementation/data-flow:
|
|
45
45
|
en: a71c8ab2e78798c5
|
|
46
46
|
zh: 419cebed1c023fbf
|
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ 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. Without that declaration, record and refresh write the current generation; replay
|
|
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 independently accepts the retained V3 baseline or the current generation for each parent/child role under the [corpus policy](../../../snapshots/AGENTS.md). An explicit `sessionFormat.version` requires every selected role to match that version. 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
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
|
|
|
@@ -80,7 +80,7 @@ Headless/ACP and SDK adapters compare raw catalog child-creation times with harv
|
|
|
80
80
|
|
|
81
81
|
### Record, replay, and refresh
|
|
82
82
|
|
|
83
|
-
`pnpm run test:snapshot:record` calls the live LLM and writes the harvested current generation under its canonical versioned filename. Record and refresh never rename or delete a completed generation, including generations of a child role absent from a later run; reviewed source-tree curation removes a predecessor only after the same role has a verified current replacement. Scenarios with an explicit `sessionFormat` remain read-only in record mode. `pnpm run test:snapshot:refresh` stays keyless, runs the selected highest replay input, and writes stdout, owned prompt and tool-schema sidecars, and a fresh current-generation comparable Session output; retained historical scenarios write the separate writer-output oracles instead of a canonical current-format replay generation. Each composition owner keeps its replay patch beside its live patch; top-level `snapshots/` owns Session-driven scenarios, while other expected outputs stay beside their owning package. [`dsh-llm-replay`](../llm-replay/README.md) serves the recorded streams selected through `DSH_SNAPSHOT_*` environment values.
|
|
83
|
+
`pnpm run test:snapshot:record` calls the live LLM and writes the harvested current generation under its canonical versioned filename. Record and refresh never rename or delete a completed generation, including generations of a child role absent from a later run; reviewed source-tree curation removes a predecessor only after the same role has a verified current replacement. Scenarios with an explicit `sessionFormat` remain read-only in record mode. `pnpm run test:snapshot:refresh` stays keyless, runs the selected highest replay input, and writes stdout, owned prompt and tool-schema sidecars, and a fresh current-generation comparable Session output; retained historical scenarios write the separate writer-output oracles instead of a canonical current-format replay generation. Headless and SDK scenarios may add a local `runtime.cordis.yml` patch for execution fixtures. Each composition owner keeps its replay patch beside its live patch; top-level `snapshots/` owns Session-driven scenarios, while other expected outputs stay beside their owning package. [`dsh-llm-replay`](../llm-replay/README.md) serves the recorded streams selected through `DSH_SNAPSHOT_*` environment values.
|
|
84
84
|
|
|
85
85
|
### Pinning request headers and system prompts
|
|
86
86
|
|
|
@@ -92,7 +92,7 @@ A scenario requiring a non-Windows host declares `posixOnly`, which skips its ru
|
|
|
92
92
|
|
|
93
93
|
### What can go wrong
|
|
94
94
|
|
|
95
|
-
- **A
|
|
95
|
+
- **A durable wait times out** — `waitForTurnStart`, `waitForTurnEnd`, `waitForSubagentTurnEnd`, `waitForGoalPhase`, `waitForInboxMessage`, `waitForTitleAfterTurnEnd`, and `waitForEventAfterTurnEnd` identify the session or child and deadline even when the first log harvest exceeds that deadline. Child waits also name the requested turn. All retain the underlying failure as the error cause. `waitForTurnStart` rejects malformed turn records immediately.
|
|
96
96
|
- **A fixture guard rejects the committed files** — orphan scenario dirs, missing files, multiple pins for one header class, duplicate sidecar content, unscrubbed prompt text or tool schemas, a `request/header` with no preceding `system/message`, and malformed pinning headers all fail the suite before comparisons run.
|
|
97
97
|
- **The session harvest needs raw JSONL mode** — snapshot configs set the JSONL backend's `compression: 'none'`; compressed JSONL has no snapshot-harvest path.
|
|
98
98
|
- **Built mode needs current artifacts** — run `pnpm run build` before selecting `DSH_EXAMPLE_MODE=lib`; source mode remains the zero-build path.
|
|
@@ -124,7 +124,6 @@ The shared core owns manifests, generation-qualified role selection, workspace s
|
|
|
124
124
|
| [`src/workspace.ts`](src/workspace.ts) | Scenario workspace setup and complete expected-state comparison |
|
|
125
125
|
| [`src/suite.ts`](src/suite.ts) | Scenario-table suite factory, fixture guards, record/refresh write-back |
|
|
126
126
|
| [`src/index.ts`](src/index.ts) | Package entry re-exporting the four layers |
|
|
127
|
-
| — | No runtime invariant companion is published; this test-support package owns no production event stream or mutable data; consuming test suites exercise its behavior. |
|
|
128
127
|
|
|
129
128
|
### Data flow
|
|
130
129
|
|
package/README.zh.md
CHANGED
|
@@ -68,7 +68,7 @@ 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。未声明时,record 与 refresh 写入当前 generation;replay
|
|
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)对每个 parent/child 角色独立接受保留的 V3 基线或当前 generation。显式声明 `sessionFormat.version` 时,每个选定角色都必须匹配该版本。manifest 还会指名场景、随附 profile、组合/header 类别、录制来源,以及已完成 Session 无法重建的 replay、平台、权限、环境、workspace 或输入事实。存储保护检查每个选定 parent 与 child 角色的工具结果和可移植路径。提示词/schema 擦除、消息身份及提示词先于请求的顺序检查适用于当前 generation;保留的前代维持其历史表示。适配器注册预期输出、Session 日志与可选 `workspace.expected/` 比较;保护会拒绝遗留目录、缺失角色、非规范名称、绝对路径、格式错误的 manifest 与平台专用分隔符。
|
|
72
72
|
|
|
73
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
|
|
|
@@ -80,7 +80,7 @@ Headless/ACP 与 SDK 适配器在规范化之前,将原始目录中的子创
|
|
|
80
80
|
|
|
81
81
|
### 录制、回放与刷新
|
|
82
82
|
|
|
83
|
-
`pnpm run test:snapshot:record` 调用在线 LLM(大语言模型),并在规范具名版本文件下写入收集到的当前 generation。record 与 refresh 绝不重命名或删除已完成的 generation,即使后续运行不再产生某个 child 角色也一样;受审阅的源树整理只有在同角色存在已验证的当前替代文件后才移除前代。显式声明 `sessionFormat` 的场景在录制模式下保持只读。`pnpm run test:snapshot:refresh` 保持无密钥,运行选定的最高 replay 输入,并写入 stdout、各 pin 自有的提示词与工具 schema 伴随文件,以及新鲜当前 generation 的可比较 Session 输出;保留历史输入的场景写入单独的 writer 输出比较基准,而非规范当前格式 replay
|
|
83
|
+
`pnpm run test:snapshot:record` 调用在线 LLM(大语言模型),并在规范具名版本文件下写入收集到的当前 generation。record 与 refresh 绝不重命名或删除已完成的 generation,即使后续运行不再产生某个 child 角色也一样;受审阅的源树整理只有在同角色存在已验证的当前替代文件后才移除前代。显式声明 `sessionFormat` 的场景在录制模式下保持只读。`pnpm run test:snapshot:refresh` 保持无密钥,运行选定的最高 replay 输入,并写入 stdout、各 pin 自有的提示词与工具 schema 伴随文件,以及新鲜当前 generation 的可比较 Session 输出;保留历史输入的场景写入单独的 writer 输出比较基准,而非规范当前格式 replay 代际。Headless 与 SDK 场景可通过本地 `runtime.cordis.yml` patch 添加执行 fixture。每个组合 owner 把 replay patch 放在 live patch 旁;顶层 `snapshots/` 拥有 Session 驱动场景,其他预期输出留在其 package owner 旁。[`dsh-llm-replay`](../llm-replay/README.zh.md) 提供通过 `DSH_SNAPSHOT_*` 环境值选择的已记录流。
|
|
84
84
|
|
|
85
85
|
### 固定请求 header 与系统提示
|
|
86
86
|
|
|
@@ -92,7 +92,7 @@ Headless/ACP 与 SDK 适配器在规范化之前,将原始目录中的子创
|
|
|
92
92
|
|
|
93
93
|
### 可能出什么问题
|
|
94
94
|
|
|
95
|
-
-
|
|
95
|
+
- **持久记录等待超时**——即使首次日志收集就超过期限,`waitForTurnStart`、`waitForTurnEnd`、`waitForSubagentTurnEnd`、`waitForGoalPhase`、`waitForInboxMessage`、`waitForTitleAfterTurnEnd` 和 `waitForEventAfterTurnEnd` 也会指出会话或子会话及等待期限。子会话等待还会指出目标轮次。这些等待都通过错误的 cause 保留底层失败。`waitForTurnStart` 会立即拒绝格式错误的轮次记录。
|
|
96
96
|
- **fixture 保护拒绝已提交文件**——遗留场景目录、缺失文件、一个 header 类别包含多个 pin、重复的伴随文件内容、未擦除的提示文本或工具 schema、没有前置 `system/message` 的 `request/header`,以及格式错误的 pin header 都会在比较运行前使套件失败。
|
|
97
97
|
- **会话收集需要原始 JSONL mode**——快照配置使用 JSONL 后端的 `compression: 'none'`;压缩 JSONL 没有快照收集路径。
|
|
98
98
|
- **构建 mode 需要当前产物**——选择 `DSH_EXAMPLE_MODE=lib` 前先运行 `pnpm run build`;源 mode 仍是零构建路径。
|
|
@@ -124,7 +124,6 @@ Headless/ACP 与 SDK 适配器在规范化之前,将原始目录中的子创
|
|
|
124
124
|
| [`src/workspace.ts`](src/workspace.ts) | 场景 workspace 设置与完整预期状态比较 |
|
|
125
125
|
| [`src/suite.ts`](src/suite.ts) | 场景表套件工厂、fixture 保护、录制/刷新回写 |
|
|
126
126
|
| [`src/index.ts`](src/index.ts) | 再导出四个层的包入口 |
|
|
127
|
-
| — | 不发布运行时不变式伴生入口;该测试支持包不拥有任何生产事件流或可变数据;消费它的测试套件会检验该工具包。 |
|
|
128
127
|
|
|
129
128
|
### 数据流
|
|
130
129
|
|
package/lib/index.js
CHANGED
|
@@ -672,7 +672,7 @@ function captureExpectedWorkspaceSnapshot(root) {
|
|
|
672
672
|
* shutdown flush. The pure normalizers in ./normalize.ts turn the captured
|
|
673
673
|
* stdout frames and the session-log events into stable, snapshot-able text.
|
|
674
674
|
*
|
|
675
|
-
* See
|
|
675
|
+
* See packages/test-support/session-snapshot/README.md.
|
|
676
676
|
*
|
|
677
677
|
* @module @deepseek-ai/dsh-session-snapshot/harness
|
|
678
678
|
*/
|
|
@@ -958,23 +958,25 @@ async function runStep(client, step, cwd, waitForUpdate, getSessionId, setSessio
|
|
|
958
958
|
/** Wait until persistence exposes an open turn for the selected session. */
|
|
959
959
|
async function waitForPersistedTurnStart(root, sessionId, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS, minimumTurn) {
|
|
960
960
|
let invalidRecord;
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
openTurn
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
})
|
|
961
|
+
const message = `snapshot-harness: session "${sessionId}" did not persist ${minimumTurn === void 0 ? "turn/start" : `turn/start at or beyond turn ${minimumTurn}`} within ${timeoutMs}ms`;
|
|
962
|
+
try {
|
|
963
|
+
await vi.waitFor(async () => {
|
|
964
|
+
const log = (await harvestSessionLogs(root)).find((candidate) => candidate.id === sessionId);
|
|
965
|
+
let openTurn;
|
|
966
|
+
try {
|
|
967
|
+
openTurn = log === void 0 ? void 0 : latestOpenTurn(log.content);
|
|
968
|
+
} catch (error) {
|
|
969
|
+
invalidRecord = { error };
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (openTurn === void 0 || minimumTurn !== void 0 && openTurn < minimumTurn) throw new Error(message);
|
|
973
|
+
}, {
|
|
974
|
+
interval: WAIT_POLL_INTERVAL_MS,
|
|
975
|
+
timeout: timeoutMs
|
|
976
|
+
});
|
|
977
|
+
} catch (cause) {
|
|
978
|
+
throw new Error(message, { cause });
|
|
979
|
+
}
|
|
978
980
|
if (invalidRecord !== void 0) throw invalidRecord.error;
|
|
979
981
|
}
|
|
980
982
|
/**
|
|
@@ -984,13 +986,18 @@ async function waitForPersistedTurnStart(root, sessionId, timeoutMs = DEFAULT_WA
|
|
|
984
986
|
* keep subprocess disposal from changing an `aborted` turn into `disposed`.
|
|
985
987
|
*/
|
|
986
988
|
async function waitForPersistedTurnEnd(root, sessionId, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
989
|
+
const message = `snapshot-harness: session "${sessionId}" did not persist turn/end within ${timeoutMs}ms`;
|
|
990
|
+
try {
|
|
991
|
+
await vi.waitFor(async () => {
|
|
992
|
+
const log = (await harvestSessionLogs(root)).find((candidate) => candidate.id === sessionId);
|
|
993
|
+
if (log === void 0 || !latestTurnIsClosed(log.content)) throw new Error(message);
|
|
994
|
+
}, {
|
|
995
|
+
interval: WAIT_POLL_INTERVAL_MS,
|
|
996
|
+
timeout: timeoutMs
|
|
997
|
+
});
|
|
998
|
+
} catch (cause) {
|
|
999
|
+
throw new Error(message, { cause });
|
|
1000
|
+
}
|
|
994
1001
|
}
|
|
995
1002
|
/**
|
|
996
1003
|
* Wait until the Nth harvested child Session closes a model work turn.
|
|
@@ -1023,28 +1030,38 @@ function hasClosedTurn(content, turn) {
|
|
|
1023
1030
|
}
|
|
1024
1031
|
/** Wait until the latest durable goal snapshot reaches one phase. */
|
|
1025
1032
|
async function waitForPersistedGoalPhase(root, sessionId, phase, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1033
|
+
const message = `snapshot-harness: session "${sessionId}" did not persist goal phase "${phase}" within ${timeoutMs}ms`;
|
|
1034
|
+
try {
|
|
1035
|
+
await vi.waitFor(async () => {
|
|
1036
|
+
if (!(((await harvestSessionLogs(root)).find((log) => log.id === sessionId)?.content)?.split("\n").filter(Boolean).some((line) => {
|
|
1037
|
+
const event = JSON.parse(line);
|
|
1038
|
+
return event.type === "goal/change" && event.data?.goal?.phase === phase;
|
|
1039
|
+
}) ?? false)) throw new Error(message);
|
|
1040
|
+
}, {
|
|
1041
|
+
interval: WAIT_POLL_INTERVAL_MS,
|
|
1042
|
+
timeout: timeoutMs
|
|
1043
|
+
});
|
|
1044
|
+
} catch (cause) {
|
|
1045
|
+
throw new Error(message, { cause });
|
|
1046
|
+
}
|
|
1035
1047
|
}
|
|
1036
1048
|
/** Wait until an inserted inbox message contains scenario-owned text. */
|
|
1037
1049
|
async function waitForPersistedInboxMessage(root, sessionId, text, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1050
|
+
const message = `snapshot-harness: session "${sessionId}" did not persist expected inbox message within ${timeoutMs}ms`;
|
|
1051
|
+
try {
|
|
1052
|
+
await vi.waitFor(async () => {
|
|
1053
|
+
if (!((await harvestSessionLogs(root)).find((candidate) => candidate.id === sessionId)?.content.split("\n").some((line) => {
|
|
1054
|
+
if (line.length === 0) return false;
|
|
1055
|
+
const record = JSON.parse(line);
|
|
1056
|
+
return record.type === "agent/inbox/spliced" && record.data?.inserted?.some((message) => message.content?.some((block) => block.type === "text" && typeof block.text === "string" && block.text.includes(text))) === true;
|
|
1057
|
+
}) ?? false)) throw new Error(message);
|
|
1058
|
+
}, {
|
|
1059
|
+
interval: WAIT_POLL_INTERVAL_MS,
|
|
1060
|
+
timeout: timeoutMs
|
|
1061
|
+
});
|
|
1062
|
+
} catch (cause) {
|
|
1063
|
+
throw new Error(message, { cause });
|
|
1064
|
+
}
|
|
1048
1065
|
}
|
|
1049
1066
|
/** Whether a child log contains model work after its own descriptor event. */
|
|
1050
1067
|
function hasRequestHeaderAfterDescriptor(content) {
|
|
@@ -1054,23 +1071,33 @@ function hasRequestHeaderAfterDescriptor(content) {
|
|
|
1054
1071
|
}
|
|
1055
1072
|
/** Wait until a complete provider or fallback title record follows the latest closed turn. */
|
|
1056
1073
|
async function waitForPersistedTitleAfterTurnEnd(root, sessionId, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1074
|
+
const message = `snapshot-harness: session "${sessionId}" did not persist session/title after turn/end within ${timeoutMs}ms`;
|
|
1075
|
+
try {
|
|
1076
|
+
await vi.waitFor(async () => {
|
|
1077
|
+
const log = (await harvestSessionLogs(root)).find((candidate) => candidate.id === sessionId);
|
|
1078
|
+
if (log === void 0 || !latestTitleFollowsTurnEnd(log.content)) throw new Error(message);
|
|
1079
|
+
}, {
|
|
1080
|
+
interval: WAIT_POLL_INTERVAL_MS,
|
|
1081
|
+
timeout: timeoutMs
|
|
1082
|
+
});
|
|
1083
|
+
} catch (cause) {
|
|
1084
|
+
throw new Error(message, { cause });
|
|
1085
|
+
}
|
|
1064
1086
|
}
|
|
1065
1087
|
/** Wait until a complete record of `type` follows the latest closed turn. */
|
|
1066
1088
|
async function waitForPersistedEventAfterTurnEnd(root, sessionId, type, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1089
|
+
const message = `snapshot-harness: session "${sessionId}" did not persist ${type} after turn/end within ${timeoutMs}ms`;
|
|
1090
|
+
try {
|
|
1091
|
+
await vi.waitFor(async () => {
|
|
1092
|
+
const log = (await harvestSessionLogs(root)).find((candidate) => candidate.id === sessionId);
|
|
1093
|
+
if (log === void 0 || !latestEventFollowsTurnEnd(log.content, type)) throw new Error(message);
|
|
1094
|
+
}, {
|
|
1095
|
+
interval: WAIT_POLL_INTERVAL_MS,
|
|
1096
|
+
timeout: timeoutMs
|
|
1097
|
+
});
|
|
1098
|
+
} catch (cause) {
|
|
1099
|
+
throw new Error(message, { cause });
|
|
1100
|
+
}
|
|
1074
1101
|
}
|
|
1075
1102
|
/** Wait for a cwd-relative marker proving an external action reached readiness. */
|
|
1076
1103
|
async function waitForWorkspaceFile(cwd, path, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
|
package/lib/types/harness.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* shutdown flush. The pure normalizers in ./normalize.ts turn the captured
|
|
12
12
|
* stdout frames and the session-log events into stable, snapshot-able text.
|
|
13
13
|
*
|
|
14
|
-
* See
|
|
14
|
+
* See packages/test-support/session-snapshot/README.md.
|
|
15
15
|
*
|
|
16
16
|
* @module @deepseek-ai/dsh-session-snapshot/harness
|
|
17
17
|
*/
|
|
@@ -39,12 +39,14 @@ export type { AgentUnderTest } from './launcher.ts';
|
|
|
39
39
|
* `waitForInboxMessage` waits for inserted inbox text containing a scenario marker.
|
|
40
40
|
* `waitForSubagentTurnEnd` waits until one background child has persisted a
|
|
41
41
|
* closed model-work turn after its own descriptor; child progress has no ACP
|
|
42
|
-
* update to wait on.
|
|
43
|
-
* the first log read is still pending; the underlying failure is retained as cause.
|
|
42
|
+
* update to wait on.
|
|
44
43
|
* `waitForTitleAfterTurnEnd` additionally waits for a later durable title.
|
|
45
44
|
* `waitForEventAfterTurnEnd` waits until a complete record of the given event
|
|
46
45
|
* type follows the latest closed turn — for scenarios whose asserted state
|
|
47
46
|
* (e.g. a goal pause) is appended only after cancellation reaches idle.
|
|
47
|
+
* Session-log wait timeouts identify the session or child and deadline even
|
|
48
|
+
* if the first log read is still pending; they retain the underlying failure
|
|
49
|
+
* as cause. Child waits also name the requested turn.
|
|
48
50
|
* A standalone `cancel` may also wait for a cwd-relative readiness marker.
|
|
49
51
|
* All wait timeouts default to 10s.
|
|
50
52
|
*/
|
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.2.
|
|
4
|
+
"version": "0.2.1-alpha.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -28,31 +28,32 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@agentclientprotocol/sdk": "1.4.0",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"@deepseek-ai/
|
|
34
|
-
"@deepseek-ai/dsh-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
31
|
+
"@deepseek-ai/cordis-plugin-include": "~1.0.10-alpha.1",
|
|
32
|
+
"@deepseek-ai/dsh-llm-replay": "0.2.1-alpha.2",
|
|
33
|
+
"@deepseek-ai/dsh-loader-smoke": "0.2.1-alpha.2",
|
|
34
|
+
"@deepseek-ai/dsh-session-format": "0.2.1-alpha.2",
|
|
35
|
+
"js-yaml": "^4.3.2",
|
|
36
|
+
"vitest": "^4.1.11"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@deepseek-ai/cordis": "~4.0.
|
|
40
|
-
"@deepseek-ai/dsh-session": "0.2.
|
|
41
|
-
"@deepseek-ai/dsh-http-proxy": "0.2.
|
|
39
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1",
|
|
40
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.2",
|
|
41
|
+
"@deepseek-ai/dsh-http-proxy": "0.2.1-alpha.2"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
+
"@deepseek-ai/cordis": "~4.0.5-alpha.1",
|
|
45
|
+
"@deepseek-ai/dsh-agent": "0.2.1-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-agent-loop": "0.2.1-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-compaction": "0.2.1-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-fs": "0.2.1-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-llm": "0.2.1-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-sandbox": "0.2.1-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.2.1-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-session": "0.2.1-alpha.2",
|
|
53
|
+
"@deepseek-ai/dsh-subagent": "0.2.1-alpha.2",
|
|
54
|
+
"@deepseek-ai/dsh-tools": "0.2.1-alpha.2",
|
|
55
|
+
"@deepseek-ai/dsh-user-questions": "0.2.1-alpha.2",
|
|
44
56
|
"@types/js-yaml": "^4.0.9",
|
|
45
|
-
"@deepseek-ai/
|
|
46
|
-
"@deepseek-ai/dsh-agent": "0.2.0-rc.2",
|
|
47
|
-
"@deepseek-ai/dsh-agent-loop": "0.2.0-rc.2",
|
|
48
|
-
"@deepseek-ai/dsh-compaction": "0.2.0-rc.2",
|
|
49
|
-
"@deepseek-ai/dsh-llm": "0.2.0-rc.2",
|
|
50
|
-
"@deepseek-ai/dsh-sandbox": "0.2.0-rc.2",
|
|
51
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.2.0-rc.2",
|
|
52
|
-
"@deepseek-ai/dsh-session": "0.2.0-rc.2",
|
|
53
|
-
"@deepseek-ai/dsh-subagent": "0.2.0-rc.2",
|
|
54
|
-
"@deepseek-ai/dsh-tools": "0.2.0-rc.2",
|
|
55
|
-
"@deepseek-ai/dsh-user-questions": "0.2.0-rc.2",
|
|
56
|
-
"@deepseek-ai/dsh-http-proxy": "0.2.0-rc.2"
|
|
57
|
+
"@deepseek-ai/dsh-http-proxy": "0.2.1-alpha.2"
|
|
57
58
|
}
|
|
58
59
|
}
|