{"_id":"@buddhilive/dsh-session-snapshot","name":"@buddhilive/dsh-session-snapshot","dist-tags":{"alpha":"0.1.2-alpha.3","latest":"0.1.2-alpha.3"},"versions":{"0.1.2-alpha.3":{"name":"@buddhilive/dsh-session-snapshot","description":"Session-log snapshot core with an ACP protocol adapter, expected-output normalization, and fixture invariants","version":"0.1.2-alpha.3","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/test-support/session-snapshot"},"type":"module","main":"lib/index.js","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./src/*":"./src/*","./package.json":"./package.json"},"license":"MIT","dependencies":{"@agentclientprotocol/sdk":"1.4.0","js-yaml":"^4.2.0","vitest":"^4.1.8","@deepseek-ai/cordis-plugin-include":"1.0.7","@buddhilive/dsh-loader-smoke":"0.1.2-alpha.3"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@buddhilive/dsh-session":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3"},"devDependencies":{"@types/js-yaml":"^4.0.9","@deepseek-ai/cordis":"^4.0.2","@buddhilive/dsh-agent":"^0.1.2-alpha.3","@buddhilive/dsh-agent-loop":"^0.1.2-alpha.3","@buddhilive/dsh-compaction":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3","@buddhilive/dsh-llm":"^0.1.2-alpha.3","@buddhilive/dsh-sandbox":"^0.1.2-alpha.3","@buddhilive/dsh-sandbox-policy":"^0.1.2-alpha.3","@buddhilive/dsh-session":"^0.1.2-alpha.3","@buddhilive/dsh-subagent":"^0.1.2-alpha.3","@buddhilive/dsh-tools":"^0.1.2-alpha.3","@buddhilive/dsh-user-questions":"^0.1.2-alpha.3"},"_id":"@buddhilive/dsh-session-snapshot@0.1.2-alpha.3","bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","_integrity":"sha512-wO0GbM+9DUt1CICSe71PH6u7yTK5cGF0Qgz4GSmiLdfuNjY03I7BTpU8GGGNjXrnQ9SQ5C5hzJVUQfsXCUTEtQ==","_resolved":"C:\\DevDojo\\Buddhi\\buddhi-ai-harness\\dist\\npm\\buddhilive-dsh-session-snapshot-0.1.2-alpha.3.tgz","_from":"file:C:/DevDojo/Buddhi/buddhi-ai-harness/dist/npm/buddhilive-dsh-session-snapshot-0.1.2-alpha.3.tgz","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-wO0GbM+9DUt1CICSe71PH6u7yTK5cGF0Qgz4GSmiLdfuNjY03I7BTpU8GGGNjXrnQ9SQ5C5hzJVUQfsXCUTEtQ==","shasum":"a382897c2492bbd52bf6e8e7292e3be51a5ee135","tarball":"https://registry.npmjs.org/@buddhilive/dsh-session-snapshot/-/dsh-session-snapshot-0.1.2-alpha.3.tgz","fileCount":16,"unpackedSize":208041,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC/7zUY9rv7Kkb32AXBfG0qSt9D/V/BBDitkg6suyy+MQIgGaeyDe3vrX2hPHFhHja4Ckv3m2gPOI4NTmwU3JSr2Do="}]},"_npmUser":{"name":"buddhilive","email":"visitbudkavin@gmail.com"},"directories":{},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-session-snapshot_0.1.2-alpha.3_1788166711056_0.580723245566009"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-31T08:58:30.902Z","0.1.2-alpha.3":"2026-08-31T08:58:31.189Z","modified":"2026-08-31T08:58:31.427Z"},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"description":"Session-log snapshot core with an ACP protocol adapter, expected-output normalization, and fixture invariants","homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/test-support/session-snapshot"},"bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"license":"MIT","readme":"---\ndescription: \"面向无密钥 profile 测试的 session-log 快照支持：manifest、身份脱敏、规范化、workspace 检查与协议适配器。\"\nkind: \"package-library\"\n---\n\n# @buddhilive/dsh-session-snapshot\n\n[English](README.md) | 中文\n\n## 概述\n\n`dsh-session-snapshot` 提供无密钥已记录会话测试（`pnpm run test:snapshot`）背后的共享支持：封闭 manifest、类型化身份脱敏、规范化、workspace 比较、fixture 保护，以及 headless、SDK、ACP 与 Web owner 使用的协议适配器。ACP 适配器以真实子进程启动被测 profile，驱动确定性输入脚本，并注册完整的录制、回放与刷新套件。每个场景都提交足够证据来证明模型可见输出与文件系统效果，不依赖 agent 自述。包入口会导入 vitest，因此只能在 vitest 运行中使用。\n\n## 目录\n\n- [使用本包](#use-this-package)\n- [理解实现](#understand-the-implementation)\n- [进一步探索](#further-exploration)\n- [模型体验](#model-experience)\n- [已知限制与延期工作](#known-limitations-and-deferred-work)\n- [开发备注](#dev-note)\n\n-----\n\n<a id=\"use-this-package\"></a>\n## 使用本包\n\n本包把随附 profile 场景变成无密钥快照套件：写一张场景表和一个 fixture 目录，调用一次匹配的适配器，工具包就负责启动或组合 profile、驱动场景、比较规范化输出并守护已提交的 fixture。\n\n### 编写快照套件\n\n消费方 `*.snapshot.ts` 就是场景表加一次工厂调用。`AgentUnderTest` 提供绝对 `binScript`、可选 `libBinScript`、`configPath` 与 `tsconfigPath` 路径，因为子进程 cwd 位于仓库之外：\n\n```ts\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport {\n  defineAcpSnapshotSuite,\n  type Scenario,\n  type SnapshotSuiteOptions,\n} from '@buddhilive/dsh-session-snapshot'\n\nfunction snapshotMode(value: string | undefined): SnapshotSuiteOptions['mode'] {\n  switch (value) {\n    case undefined:\n    case '':\n    case 'replay': return 'replay'\n    case 'record': return 'record'\n    case 'refresh': return 'refresh'\n    default: throw new Error(`unknown DSH_SNAPSHOT mode: ${value}`)\n  }\n}\n\nconst SCENARIOS: Scenario[] = [\n  { name: 'text-turn', hasModelTurn: true, recorded: true, pinsHeader: true },\n]\n\ndefineAcpSnapshotSuite({\n  agent: { // absolute paths, resolved from the suite's own location\n    binScript: fileURLToPath(new URL('../../../apps/cli/src/bin.ts', import.meta.url)),\n    configPath: fileURLToPath(new URL('../cordis.yml', import.meta.url)),\n    profile: 'acp',\n    tsconfigPath: fileURLToPath(new URL('../../../tsconfig.json', import.meta.url)),\n  },\n  snapshotsDir: join(dirname(fileURLToPath(import.meta.url)), 'snapshots'),\n  scenarios: SCENARIOS, // exactly one entry per header class sets pinsHeader\n  mode: snapshotMode(process.env.DSH_SNAPSHOT),\n})\n```\n\n每个已记录会话目录携带封闭的 `snapshot.yml` manifest，以及自身的 `session.jsonl` 与连续的 `session.<n>.jsonl` 子会话日志。manifest 指名场景、随附 profile、组合／header 类别、录制来源，以及已完成会话无法重建的 replay、平台、权限、环境、workspace 或输入事实。适配器注册预期输出、会话日志与可选 `workspace.expected/` 比较；保护会拒绝遗留目录、缺失文件、绝对路径、畸形 manifest 与平台专用分隔符。\n\n`normalizeSessionSnapshot` 在规范化路径并清理 request header 后，会保留完整会话 header 与事件 payload，但从已提交 fixture 中省略普通行的 `seq`/`time` 和打包行的 `seq0`/`time0` envelope。回放只在内存中合成这些 envelope，而运行时持久化仍写入完整日志。fixture 使用规范打包行；[临时仓库迁移器](../../../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)负责删除该迁移器。\n\n### 录制、回放与刷新\n\n`pnpm run test:snapshot:record` 调用在线 LLM（大语言模型），并重写已录制的模型 fixture；`pnpm run test:snapshot:refresh` 保持无密钥，运行回放 overlay，并从已提交模型脚本重写 stdout、可比较会话日志预期输出，以及各 pin 自有的提示词与工具 schema 伴随文件。每个组合 owner 把 replay patch 放在 live patch 旁；顶层 `snapshots/` 拥有会话驱动场景，其他预期输出留在其包 owner 旁。[`dsh-llm-replay`](../llm-replay/README.zh.md) 提供通过 `DSH_SNAPSHOT_*` 环境值选择的已记录流。\n\n### 固定请求 header\n\n每个 pin 默认拥有其生成的 `system-prompt.expected.md` 或 `tool-schemas.expected.json` 伴随文件；当完整的对应序列相同时，`systemPromptSource` 与 `toolSchemasSource` 指定另一个 pin 作为来源，因此每个不同版本只提交一次。该 pin 的 `session.jsonl` 存储 `\"system\":\"{{system}}\",\"tools\":\"{{tools}}\"`，同时保留配置、原因与任何模型可见前缀。自身作用域组合出不同请求的子会话按 fixture 索引以 `pinsChildToolSchemas` 与 `pinsChildSystemPrompts` 单独声明。运行中改变请求 header 的场景声明 `expectedHeaderChanges`。\n\n### 平台与组合变体\n\n需要非 Windows 主机的场景声明 `posixOnly`，在 Windows 上跳过运行测试，但 fixture 保护仍在所有平台覆盖其已提交文件；组合需要可用 `pwsh` 的场景声明 `pwshOnly`。当临时目录授权自身待测时，`workspaceParent` 将生成子级 cwd 移出平台临时区域；场景签入的 `workspace/` 会先复制到该子级，随后 `prepareWorkspace` 在 agent 启动前针对生成 cwd 运行。默认生成的 workspace 在会话 fixture 中存储为 `{{cwd}}`，使平台临时根目录与随机 basename 不影响录制。\n\n### 可能出什么问题\n\n- **fixture 保护拒绝已提交文件**——遗留场景目录、缺失文件、一个 header 类别包含多个 pin、重复的伴随文件内容、未擦除的 JSONL header 与格式错误的 pin header 都会在比较运行前使套件失败。\n- **会话收集需要原始 JSONL mode**——快照配置使用 JSONL 后端的 `compression: 'none'`；压缩 JSONL 与 SQLite 组合没有快照收集路径。\n- **构建 mode 需要当前产物**——选择 `DSH_EXAMPLE_MODE=lib` 前先运行 `pnpm run build`；源 mode 仍是零构建路径。\n\n-----\n\n<a id=\"understand-the-implementation\"></a>\n## 理解实现\n\n<details>\n<summary>实现细节——点击展开</summary>\n\n本节解释工具包的设计；可观察行为已在[使用本包](#use-this-package)中完整说明。\n\n### 设计\n\n共享核心拥有 manifest、workspace 设置／比较、类型化身份映射、规范化器与 fixture 不变式。ACP 适配器增加四个可组合层：启动器、场景 harness、规范化器与套件工厂。`launchAcpTestAgent` 在 tsx 下启动源码 profile，或在普通 Node 下启动已构建 `lib` profile，通过原始字节 stdout tee 连接 SDK 客户端，收集会话更新与 stderr，默认拒绝未处理的权限请求，并负责关闭。`runScenario` 驱动 ACP JSON-RPC stdio，并收集每个持久化原始 JSONL 会话日志。纯规范化器把 cwd 路径与类型化身份变为稳定 token，将时间归零、展开物理来源区间，并擦除请求 header bulk。`defineAcpSnapshotSuite` 注册比较、fixture 回写与实时一致性保护。\n\n### 源码地图\n\n| 文件 | 职责 |\n|---|---|\n| [`src/launcher.ts`](src/launcher.ts) | 子进程/客户端启动器与关闭所有权 |\n| [`src/harness.ts`](src/harness.ts) | 脚本化场景驱动与会话日志收集 |\n| [`src/manifest.ts`](src/manifest.ts) | 封闭 `snapshot.yml` schema、收集与归属规则 |\n| [`src/identity.ts`](src/identity.ts) | 跨父子日志的类型化首次出现身份 token 化 |\n| [`src/normalize.ts`](src/normalize.ts) | 纯规范化器与擦除辅助 |\n| [`src/workspace.ts`](src/workspace.ts) | 场景 workspace 设置与完整预期状态比较 |\n| [`src/suite.ts`](src/suite.ts) | 场景表套件工厂、fixture 保护、录制/刷新回写 |\n| [`src/index.ts`](src/index.ts) | 再导出四个层的包入口 |\n| [`src/invariant.ts`](src/invariant.ts) | 不变式伴生插件（无运行时不变式；消费它的测试套件会检验该工具包） |\n\n### 数据流\n\n场景在启动器下运行 agent，通过 harness 向它喂入输入脚本，并捕获 stdout 与持久化日志。规范化器把捕获内容规范化——id 转为首次出现序列、生成 cwd 转为 `{{cwd}}`、header bulk 转为 `{{system}}`/`{{tools}}`——使已录制与本次运行可以结构化比较。随后工厂把规范化 stdout 与重新持久化日志同已提交 fixture 比较，或在录制/刷新模式下回写它们；其保护在任何比较结果被采信之前就拒绝畸形或漂移的 fixture。\n\n</details>\n\n-----\n\n<a id=\"further-exploration\"></a>\n## 进一步探索\n\n当包级约定不够用时阅读以下页面。它们从快照工具包逐步进入模型 fixture 来源、启动机制与要求该层级存在的策略。\n\n- [llm-replay](../llm-replay/README.zh.md)——回放模式消费的无密钥模型 fixture 来源。\n- [loader-smoke](../loader-smoke/README.zh.md)——启动器所依赖的模式感知子进程启动机制。\n- [测试策略](../../../docs/testing.zh.md)——无密钥快照层、其适用时机与 fixture 归属规则。\n- [test-support 组地图](../README.zh.md)——兄弟 harness 与支持包。\n\n-----\n\n<a id=\"model-experience\"></a>\n## 模型体验\n\n无。该测试专用支持会记录、规范化并比较 profile 会话，不会改变 agent 组装的模型请求。\n\n#### KV Cache 影响\n\n无；本包既不组装也不发送提供方请求。\n\n## 已知限制与延期工作\n\n<a id=\"known-limitations-and-deferred-work\"></a>\n\n\n这些限制说明何时需要对该工具包特别小心。它们是当前包约束，不是任务积压。\n\n- **会话收集需要原始 JSONL mode**——`runScenario` 收集持久化 `.jsonl` 日志，因此快照配置使用 JSONL 后端的 `compression: 'none'`；压缩 JSONL 与 SQLite 组合没有快照收集路径。\n- **构建 mode 需要当前产物**——选择 `DSH_EXAMPLE_MODE=lib` 前先运行 `pnpm run build`；源 mode 仍是零构建路径。\n- **ACP 继续覆盖协议行为**——刺激来自 ACP 客户端的取消与权限往返留在该适配器；组装式一次性行为与持久控制行为使用 headless 与 SDK 适配器。\n\n<a id=\"dev-note\"></a>\n### 开发备注\n\n<details>\n<summary>维护者的工作上下文——点击展开</summary>\n\n无。\n\n</details>\n","readmeFilename":"README.zh.md","_rev":"1-914da79060d46d1ee1ee3e7e861e65a5"}