{"_id":"@ai4rpg/dsh-stage-switch","_rev":"4-2da2bd7d992618caec644ec74e0a2189","name":"@ai4rpg/dsh-stage-switch","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@ai4rpg/dsh-stage-switch","version":"0.1.0","license":"MIT","_id":"@ai4rpg/dsh-stage-switch@0.1.0","maintainers":[{"name":"ai4rpg","email":"ai4rpg@gmail.com"}],"homepage":"https://github.com/ai4rpg/stage-switch#readme","bugs":{"url":"https://github.com/ai4rpg/stage-switch/issues"},"dist":{"shasum":"085d6c822eaf09c9f425fa2f77671ad61d785c3b","tarball":"https://registry.npmjs.org/@ai4rpg/dsh-stage-switch/-/dsh-stage-switch-0.1.0.tgz","fileCount":24,"integrity":"sha512-d7nIL3cpab9D/1LISQgkOotocjd1d5/wVGRgoUF8ZsDcsoyBIGN6LYhXnWBTF9QQu5j90zvkGhk6XnOxQ0i9cA==","signatures":[{"sig":"MEYCIQCUMrxChmrc1xkkRrsFL+DOBm+mhAo4brLAsNjHPmMGXQIhAIfWmikCbyYNq/ZpDYZo/JULUkxzb0vXSfxXCvKqxkn+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":184747},"main":"lib/index.js","type":"module","types":"lib/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"978debf31bb7c57b79b88d7d3388065384334776","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","pretest":"node scripts/sync-prompts.mjs","prebuild":"node scripts/sync-prompts.mjs","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"ai4rpg","email":"ai4rpg@gmail.com"},"repository":{"url":"git+https://github.com/ai4rpg/stage-switch.git","type":"git"},"_npmVersion":"11.9.0","description":"Logged per-agent stage collaboration state with eligibility-gated guidance, a handoff document, and a user-reviewed transition","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.0.0","vitest":"^4.0.0","typescript":"^5.9.0","@types/node":"^24.0.0","@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-fs":"^0.1.1-rc.2","@deepseek-ai/dsh-llm":"^0.1.1-rc.2","@deepseek-ai/dsh-agent":"^0.1.1-rc.2","@deepseek-ai/dsh-scope":"^0.1.1-rc.2","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-sandbox":"^0.1.1-rc.2","@deepseek-ai/dsh-session":"^0.1.1-rc.2","@deepseek-ai/dsh-commands":"^0.1.1-rc.2","@deepseek-ai/dsh-agent-loop":"^0.1.1-rc.2","@deepseek-ai/dsh-token-meter":"^0.1.1-rc.2","@deepseek-ai/dsh-system-prompt":"^0.1.1-rc.2","@deepseek-ai/dsh-user-questions":"^0.1.1-rc.2","@deepseek-ai/cordis-plugin-loader":"^1.0.2","@deepseek-ai/cordis-plugin-include":"^1.0.6"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.1","@deepseek-ai/dsh-fs":"^0.1.1-rc.2","@deepseek-ai/dsh-llm":"^0.1.1-rc.2","@deepseek-ai/dsh-agent":"^0.1.1-rc.2","@deepseek-ai/dsh-tools":"^0.1.1-rc.2","@deepseek-ai/dsh-sandbox":"^0.1.1-rc.2","@deepseek-ai/dsh-session":"^0.1.1-rc.2","@deepseek-ai/dsh-commands":"^0.1.1-rc.2","@deepseek-ai/dsh-token-meter":"^0.1.1-rc.2","@deepseek-ai/dsh-system-prompt":"^0.1.1-rc.2","@deepseek-ai/dsh-user-questions":"^0.1.1-rc.2"},"peerDependenciesMeta":{"@deepseek-ai/dsh-sandbox":{"optional":true},"@deepseek-ai/dsh-commands":{"optional":true},"@deepseek-ai/dsh-token-meter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/dsh-stage-switch_0.1.0_1788099163296_0.4280450137972376","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@ai4rpg/dsh-stage-switch","version":"0.1.1","license":"MIT","_id":"@ai4rpg/dsh-stage-switch@0.1.1","maintainers":[{"name":"ai4rpg","email":"ai4rpg@gmail.com"}],"homepage":"https://github.com/ai4rpg/stage-switch#readme","bugs":{"url":"https://github.com/ai4rpg/stage-switch/issues"},"dist":{"shasum":"0a4f7875e2abf3e24c007fb59ed6a943ab64f386","tarball":"https://registry.npmjs.org/@ai4rpg/dsh-stage-switch/-/dsh-stage-switch-0.1.1.tgz","fileCount":24,"integrity":"sha512-d32SjCSfHhj5c9Z81HeZcuTuNIJisYKg07VBlFWjsOEP9P0ShfcuR04TeMS8+eolOsSL3DIx6vgOcpmmzrQWQw==","signatures":[{"sig":"MEYCIQD3wHPt3HqJKZnLEzDEzYsUiCsdfDNnbSiAyYGkWqRZWAIhANPwGDmK0Cifpua7n3ky5h3TQ81Oo9OImrCRSHtelKCH","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":184917},"main":"lib/index.js","type":"module","types":"lib/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"978debf31bb7c57b79b88d7d3388065384334776","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","pretest":"node scripts/sync-prompts.mjs","prebuild":"node scripts/sync-prompts.mjs","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"ai4rpg","email":"ai4rpg@gmail.com"},"repository":{"url":"git+https://github.com/ai4rpg/stage-switch.git","type":"git"},"_npmVersion":"11.9.0","description":"Logged per-agent stage collaboration state with eligibility-gated guidance, a handoff document, and a user-reviewed transition","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.0.0","vitest":"^4.0.0","typescript":"^5.9.0","@types/node":"^24.0.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-fs":"^0.1.5-rc.2","@deepseek-ai/dsh-llm":"^0.1.5-rc.2","@deepseek-ai/dsh-agent":"^0.1.5-rc.2","@deepseek-ai/dsh-scope":"^0.1.5-rc.2","@deepseek-ai/dsh-tools":"^0.1.5-rc.2","@deepseek-ai/dsh-sandbox":"^0.1.5-rc.2","@deepseek-ai/dsh-session":"^0.1.5-rc.2","@deepseek-ai/dsh-commands":"^0.1.5-rc.2","@deepseek-ai/dsh-agent-loop":"^0.1.5-rc.2","@deepseek-ai/dsh-token-meter":"^0.1.5-rc.2","@deepseek-ai/dsh-system-prompt":"^0.1.5-rc.2","@deepseek-ai/dsh-user-questions":"^0.1.5-rc.2","@deepseek-ai/cordis-plugin-loader":"^1.0.3","@deepseek-ai/cordis-plugin-include":"^1.0.7","@deepseek-ai/dsh-session-projection":"^0.1.5-rc.2"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-fs":"^0.1.5-rc.2","@deepseek-ai/dsh-llm":"^0.1.5-rc.2","@deepseek-ai/dsh-agent":"^0.1.5-rc.2","@deepseek-ai/dsh-tools":"^0.1.5-rc.2","@deepseek-ai/dsh-sandbox":"^0.1.5-rc.2","@deepseek-ai/dsh-session":"^0.1.5-rc.2","@deepseek-ai/dsh-commands":"^0.1.5-rc.2","@deepseek-ai/dsh-token-meter":"^0.1.5-rc.2","@deepseek-ai/dsh-system-prompt":"^0.1.5-rc.2","@deepseek-ai/dsh-user-questions":"^0.1.5-rc.2"},"peerDependenciesMeta":{"@deepseek-ai/dsh-sandbox":{"optional":true},"@deepseek-ai/dsh-commands":{"optional":true},"@deepseek-ai/dsh-token-meter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/dsh-stage-switch_0.1.1_1789130818649_0.5717346868169251","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@ai4rpg/dsh-stage-switch","version":"0.1.2","license":"MIT","_id":"@ai4rpg/dsh-stage-switch@0.1.2","maintainers":[{"name":"ai4rpg","email":"ai4rpg@gmail.com"}],"homepage":"https://github.com/ai4rpg/stage-switch#readme","bugs":{"url":"https://github.com/ai4rpg/stage-switch/issues"},"dist":{"shasum":"c5a8d8cde22c2b7d0578dd8794244bf48f7c3b1c","tarball":"https://registry.npmjs.org/@ai4rpg/dsh-stage-switch/-/dsh-stage-switch-0.1.2.tgz","fileCount":24,"integrity":"sha512-9NQS+oMUe0Yo/74/LExghSFa0AT4gi6+R3fhq36mge9ciq5IiOhYzZExramZqf6ee6T0qjpEkno0Ih88mVOFjg==","signatures":[{"sig":"MEQCIHnoSR5a49CLgE9AhceCDvj7itGKVzkjPB1hjWYfxjhwAiBkYNoT8nCAwx6J8oMVX3vBE7nYkCK7axM8Htadr1sciw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":191120},"main":"lib/index.js","type":"module","types":"lib/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"0b6e2d017e822f30d49f2b2536074de0706034ad","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","pretest":"node scripts/sync-prompts.mjs","prebuild":"node scripts/sync-prompts.mjs","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"ai4rpg","email":"ai4rpg@gmail.com"},"repository":{"url":"git+https://github.com/ai4rpg/stage-switch.git","type":"git"},"_npmVersion":"11.9.0","description":"Logged per-agent stage collaboration state with eligibility-gated guidance, a handoff document, and a user-reviewed transition","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.0.0","vitest":"^4.0.0","typescript":"^5.9.0","@types/node":"^24.0.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-fs":"^0.1.5-rc.2","@deepseek-ai/dsh-llm":"^0.1.5-rc.2","@deepseek-ai/dsh-agent":"^0.1.5-rc.2","@deepseek-ai/dsh-scope":"^0.1.5-rc.2","@deepseek-ai/dsh-tools":"^0.1.5-rc.2","@deepseek-ai/dsh-sandbox":"^0.1.5-rc.2","@deepseek-ai/dsh-session":"^0.1.5-rc.2","@deepseek-ai/dsh-commands":"^0.1.5-rc.2","@deepseek-ai/dsh-agent-loop":"^0.1.5-rc.2","@deepseek-ai/dsh-token-meter":"^0.1.5-rc.2","@deepseek-ai/dsh-system-prompt":"^0.1.5-rc.2","@deepseek-ai/dsh-user-questions":"^0.1.5-rc.2","@deepseek-ai/cordis-plugin-loader":"^1.0.3","@deepseek-ai/cordis-plugin-include":"^1.0.7","@deepseek-ai/dsh-session-projection":"^0.1.5-rc.2"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-fs":"^0.1.5-rc.2","@deepseek-ai/dsh-llm":"^0.1.5-rc.2","@deepseek-ai/dsh-agent":"^0.1.5-rc.2","@deepseek-ai/dsh-tools":"^0.1.5-rc.2","@deepseek-ai/dsh-sandbox":"^0.1.5-rc.2","@deepseek-ai/dsh-session":"^0.1.5-rc.2","@deepseek-ai/dsh-commands":"^0.1.5-rc.2","@deepseek-ai/dsh-token-meter":"^0.1.5-rc.2","@deepseek-ai/dsh-system-prompt":"^0.1.5-rc.2","@deepseek-ai/dsh-user-questions":"^0.1.5-rc.2"},"peerDependenciesMeta":{"@deepseek-ai/dsh-sandbox":{"optional":true},"@deepseek-ai/dsh-commands":{"optional":true},"@deepseek-ai/dsh-token-meter":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/dsh-stage-switch_0.1.2_1789445407562_0.980897271839867","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"_id":"@ai4rpg/dsh-stage-switch@0.2.0","bugs":{"url":"https://github.com/ai4rpg/stage-switch/issues"},"dist":{"shasum":"1a01996206a0dd75502becb1ddd491ab62399d08","tarball":"https://registry.npmjs.org/@ai4rpg/dsh-stage-switch/-/dsh-stage-switch-0.2.0.tgz","fileCount":24,"integrity":"sha512-AYaGheZPpbdKfnDA7YY8+vQFp2QFrDQB4evmYp9ju4K7oy8CEyg248kI5IXvricDlaiVZF8Pf1ncy25FtIEqVQ==","signatures":[{"sig":"MEUCIQCpe89u3AnNWIOL8gt5TnwAHGnKMx0m8lTWjTz3F392/QIgWIqBlmzGSY62+QU6cgmODsoKBhc/ferbDQdaWsT58fM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCehIpxnTcU274MBAGdsxVsnRn5AxaXybT6aEOvJfRFKgIgQ6eXl4aFhczeYrZ5j3wW5SyJNlcey/CkJULLJazojDw="}],"unpackedSize":220600},"main":"lib/index.js","name":"@ai4rpg/dsh-stage-switch","type":"module","types":"lib/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"b141f558e21b3b8c4b4f01651923c69e1acfc33f","license":"MIT","scripts":{"test":"vitest run","build":"tsc -p tsconfig.build.json","pretest":"node scripts/sync-prompts.mjs","prebuild":"node scripts/sync-prompts.mjs","typecheck":"tsc --noEmit","test:watch":"vitest"},"version":"0.2.0","_npmUser":{"name":"ai4rpg","email":"ai4rpg@gmail.com"},"homepage":"https://github.com/ai4rpg/stage-switch#readme","repository":{"url":"git+https://github.com/ai4rpg/stage-switch.git","type":"git","directory":"stage-switch"},"_npmVersion":"11.9.0","description":"Logged per-agent stage collaboration state with eligibility-gated guidance, a handoff document, and a user-reviewed transition","directories":{},"maintainers":[{"name":"ai4rpg","email":"ai4rpg@gmail.com"}],"_nodeVersion":"24.14.0","dependencies":{"zod":"^4.4.3"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.0.0","vitest":"^4.0.0","typescript":"^5.9.0","@types/node":"^24.0.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-fs":"^0.2.0-rc.2","@deepseek-ai/dsh-llm":"^0.2.0-rc.2","@deepseek-ai/dsh-agent":"^0.2.0-rc.2","@deepseek-ai/dsh-scope":"^0.2.0-rc.2","@deepseek-ai/dsh-tools":"^0.2.0-rc.2","@deepseek-ai/dsh-sandbox":"^0.2.0-rc.2","@deepseek-ai/dsh-session":"^0.2.0-rc.2","@deepseek-ai/dsh-commands":"^0.2.0-rc.2","@deepseek-ai/dsh-agent-loop":"^0.2.0-rc.2","@deepseek-ai/dsh-token-meter":"^0.2.0-rc.2","@deepseek-ai/dsh-system-prompt":"^0.2.0-rc.2","@deepseek-ai/dsh-user-questions":"^0.2.0-rc.2","@deepseek-ai/cordis-plugin-loader":"^1.0.3","@deepseek-ai/cordis-plugin-include":"^1.0.7","@deepseek-ai/dsh-session-projection":"^0.2.0-rc.2","@deepseek-ai/dsh-session-format-v3-to-v4":"^0.2.0-rc.2"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-fs":"^0.2.0-rc.2","@deepseek-ai/dsh-llm":"^0.2.0-rc.2","@deepseek-ai/dsh-agent":"^0.2.0-rc.2","@deepseek-ai/dsh-tools":"^0.2.0-rc.2","@deepseek-ai/dsh-sandbox":"^0.2.0-rc.2","@deepseek-ai/dsh-session":"^0.2.0-rc.2","@deepseek-ai/dsh-commands":"^0.2.0-rc.2","@deepseek-ai/dsh-token-meter":"^0.2.0-rc.2","@deepseek-ai/dsh-system-prompt":"^0.2.0-rc.2","@deepseek-ai/dsh-user-questions":"^0.2.0-rc.2","@deepseek-ai/dsh-session-projection":"^0.2.0-rc.2"},"peerDependenciesMeta":{"@deepseek-ai/dsh-sandbox":{"optional":true},"@deepseek-ai/dsh-commands":{"optional":true},"@deepseek-ai/dsh-token-meter":{"optional":true}},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-stage-switch_0.2.0_1791023052486_0.867252031713019"}}},"time":{"created":"2026-08-30T14:12:43.166Z","modified":"2026-10-03T10:24:12.724Z","0.1.0":"2026-08-30T14:12:43.448Z","0.1.1":"2026-09-11T12:46:58.794Z","0.1.2":"2026-09-15T04:10:07.701Z","0.2.0":"2026-10-03T10:24:12.582Z"},"bugs":{"url":"https://github.com/ai4rpg/stage-switch/issues"},"license":"MIT","homepage":"https://github.com/ai4rpg/stage-switch#readme","repository":{"url":"git+https://github.com/ai4rpg/stage-switch.git","type":"git","directory":"stage-switch"},"description":"Logged per-agent stage collaboration state with eligibility-gated guidance, a handoff document, and a user-reviewed transition","maintainers":[{"name":"ai4rpg","email":"ai4rpg@gmail.com"}],"readme":"# @ai4rpg/dsh-stage-switch\n\n`@ai4rpg/dsh-stage-switch` 的独立仓库，一个为 DeepSeek Harness agent harness（[deepseek-harness](https://github.com/deepseek-ai/deepseek-harness)）开发的阶段协作插件。设计决策记录见 [docs/DESIGN.zh.md](docs/DESIGN.zh.md)。\n\n[English](README.md) | 中文\n\n按 agent（智能体）分别记录到日志的阶段协作状态：部署方配置的阶段提示词、一个把关切换引导的判定器 seam、写入工作区的交接文档，以及经用户评审的 `goto_stage` 切换——批准后将模型可见历史归档。\n\n## 持久状态\n\n阶段记录搭载在每次阶段进入都会追加的 stage-switch 插件消息上——阶段提示（`Current stage: <name>`）或交接提示（`Stage switched to <name>`），都是官方 harness 事件目录已知的普通 `user/message`。每条记录的 source 为生产者自有：`kind: 'stage-switch'`，`notice` 形态并携带 summary。更早版本写入的记录经 harness 的 V3→V4 会话转换后以 `plugin:stage-switch` 返回；`foldStage` 同时接受两种 kind，因此旧会话 resume 仍落在其记录的阶段上。`foldStage(events)` 返回最后一条记录的阶段，无记录时为 `undefined`；服务在第一条记录之前折叠到配置的 `initial` 阶段（默认第一个阶段），因此 resume、fork 与 compaction 直接从会话日志恢复阶段。UI 通过 `session/event` 观察已提交的切换。\n读取经由服务注册在 `ctx.sessionProjections` 上的 `stage` 会话投影单元（必需注入）：由框架对已提交事件驱动同一套逐事件折叠并做检查点，因此每次读取只花 O(新增事件) 而不是全量扫描日志——harness 已废弃同步的全日志读取器，本包对它们的调用数为零。该单元只存在于 host 侧（无 client wire view）；侧栏客户端包仍自行折叠持久记录。\n`ctx.stage.current(session)` 读取当前阶段。已评审的切换保持 pending，在下一个被接受的 in-turn pre-step 应用，因此当前工具批次保持其阶段上下文，切换由工具结果自身叙述。\n\n## 判定器 seam\n\n`ctx.stage.registerEligibility(predicate)` 注册一个同步的 `(agent) => boolean` 判定器；只要任一已注册判定器对组装中的 agent 返回 true，切换引导上下文就贡献，没有任何注册时永不贡献。判定器必须同步，因为运行时上下文快照同步组装；需要异步信号的部署方应预先计算一个同步标志。注册返回 effect disposer。\n\n## 阶段提示与运行时上下文\n\n当前阶段**绝不是系统提示词 section**：新会话在首个请求收到初始阶段提示，每次切换把新阶段的提示作为对话消息追加——`Current stage: <name>` 加上该阶段的 `instruction`——因此切换阶段不改变任何请求前缀，永不使 provider 的缓存前缀失效。**子代会话跳过初始阶段提示**：父会话已经派发了任务，注入配置的初始阶段（通常是 `route`，其指令让模型调用 `goto_stage`）会让子代死锁——`goto_stage` 的评审在子代上下文里没有用户可应答。子代仍通过正常投影继承 `{{stage_current}}`/`{{stage_targets}}` 和 `stage:policy`（没有判定器时这些投影为空）。`stage:policy`（order 116）是运行时上下文贡献，而非提示词 section：仅在判定器为 true 时渲染配置的 `section` 模板，运行时上下文投影仅在其渲染文本变化时追加快照。模板可引用 `{{stage_current}}` 与 `{{stage_targets}}` 变量；改变它们的阶段切换追加一条快照，仍不破坏缓存前缀。\n\n## `goto_stage` 工具\n\n`goto_stage` 在每个阶段都保持注册，因此切换绝不改变请求工具目录。其执行路径要求调用 agent、一个已配置且不同于当前阶段的目标阶段，以及可用的评审 seam；任何失败都留在当前阶段。\n\n未配置 `minHandoffTokens`、或表面 token 测量值达到阈值时，切换是**完整**的：要求非空的 `handoff` markdown 文档，通过 `ctx.fs` 写入会话 cwd 下的 `<handoffDir>/<会话 id>/<stage>.md`（默认 `handoff/`，按会话分子目录）——按会话分目录让共享同一工作区的会话互不覆盖彼此的交接文档，写入携带会话级 sandbox policy（其工作区根即会话 cwd），受限后端按会话工作区栅栏、与工具层一致，绝不落到部署级进程 cwd 兜底——并以文档作为 `detail` 提交评审。只有恰好一个不带自定义文本的 `Approve` 选择才算同意；任何其他回答都是携带用户反馈的失败调用，被放弃的评审（`ASK_CANCELLED`）会告诉模型留在当前阶段等待。\n\n模型从不自行判断切换是否完整：工具 schema 只携带交接指导的索引（`handoff` 参数说明\"除非工具要求否则省略\"），因此小对话不花任何交接 token。插件在调用时实测——完整切换缺 `handoff` 会被拒绝，拒绝信息里给出要遵循的交接模板，模型带文档重试；轻量切换则永不索取。轻量切换上模型多写的 handoff 会被忽略、不落盘。\n\n低于阈值时切换是**轻量**的：`handoff` 可选、不写任何文件，评审是不带意图或 detail 的通用确认。无论哪种形态，批准都会记录一个静默的 pending 切换，在下一个被接受的 in-turn pre-step 应用。\n\n## `/stage` 命令\n\n组合了 `ctx.commands` 时，本包注册 `/stage [stage|message]`。裸 `/stage` 显示当前阶段与已配置的阶段列表，不触碰状态。`/stage <name>` 是直接的用户切换：校验目标后，在空闲会话上立即应用，或在轮次中排队到下一个被接受的 in-turn pre-step，并追加新阶段的提示（仅当最后一个请求头描述的是另一阶段时才带用户切换通知前缀）。阶段名之后的任何文本都会作为普通用户消息通过 `agent.steer()` 送入切换后阶段的上下文，对标 `/plan <message>`。手动切换是**轻量**切换：永不写交接文档、永不归档历史——用户拥有这次变更，追加的阶段提示会告诉模型新阶段的用途。\n\n## 边界冲刷\n\n下一个被接受的 in-turn pre-step 会追加目标阶段的提示消息——该消息的 notice summary 就是已提交的阶段记录。完整形态下，它先把会话系统提示之后的模型可见 surface 替换为一条提示——`Stage switched to <stage>. The previous conversation was archived. Read the handoff document at <path> before continuing.` 后接新阶段提示——与 compaction checkpoint 使用相同的 surface 替换。append-only 日志为人类转录保留完整历史；只有模型可见 surface 被遮蔽，本次 step 自身的用户消息落在提示之后。替换失败时，已记录的阶段保持不变、切换保持 pending，因此观察不到半应用的切换。\n\n## 配置\n\n```yaml\n- id: stage-switch\n  name: '@ai4rpg/dsh-stage-switch'\n  config:\n    stages:\n      - name: explore\n        instruction: Explore the problem space and present a plan.\n      - name: implement\n        instruction: Implement the approved plan.\n      - name: verify\n        instruction: Verify the implementation with tests.\n    section: |\n      The current stage is complete. Call goto_stage to switch to a later\n      stage, submitting a handoff document with completed work, user\n      requirements, and discussion results.\n    handoffDir: handoff\n    initial: explore\n    minHandoffTokens: 4000\n```\n\n`stages` 必填、非空、名称唯一；名称必须匹配 `[a-z][a-z0-9_-]*`，以保证交接文件名安全。每份交接文档落在会话 cwd 下的 `<handoffDir>/<会话 id>/<stage>.md`——按会话分目录让共享同一工作区的会话互不覆盖彼此的文档。`section` 必填且非空。`handoffDir` 默认 `handoff`；`initial` 默认第一个阶段且必须命名已配置的阶段；`minHandoffTokens` 可选且必须非负；`language` 选择评审弹窗语言——`en`（默认）或 `zh`，即中文评审弹窗词典（见下文）。未知 key 在加载时失败。\n\n配置了 `minHandoffTokens` 时，组合中需要 `@deepseek-ai/dsh-token-meter`；缺失时 `goto_stage` 调用会大声失败。服务的投影读取还需要 `@deepseek-ai/dsh-session-projection`——harness 的 base bundle 已挂载它，因此标准组合天然具备；缺失时激活阶段即大声失败。\n\n## 提示词文案\n\n本包自带的所有用户可见文案——`goto_stage` 工具描述及其参数提示、切换评审对话框（问题、标签、选项描述）、工具结果与呈现卡片文案、边界/交接 notice、`/stage` 命令文案、失败信息——都集中在**一个可编辑事实源** `src/prompts.json`。`src/prompts.ts` 在模块加载时读取它，并叠加在 `src/prompts.defaults.ts`（由 JSON 重新生成的嵌入兜底）之上；没有 JSON 的过期安装（未重装前）会回退到与包历代完全一致的兜底文案。\n\n模板可引用 `{name}` 占位符，由 `formatPrompt` 插值——例如 `review.fullQuestion: \"Approve switching to stage \\\"{stage}\\\" and archiving the conversation?\"`。\n\n**刻意不进** prompts.json 的是 fold 与提示注入路径解析的承载格式：\n\n- 阶段提示正文前缀 `Current stage: <name>`（叙述前缀 *\"The user switched this session to stage …\"* 是可编辑文案）；\n- `source.summary` 的 notice 形态（`Current stage: <name>` / `Stage switched to <name>`）；\n- `stage-review` 问题 id。\n\n编辑流程（在本仓库下运行）：\n\n```sh\nnode scripts/sync-prompts.mjs                 # 从 src/prompts.json 重生成 src/prompts.defaults.ts\nnpm run build                                # prebuild 钩子自动跑 sync-prompts.mjs，再 tsc 编译 lib/\n```\n\n直接手改 `src/prompts.json` 后跑 `npm run build`（`prebuild` 钩子自动重生成 `src/prompts.defaults.ts`）；`npm run test` 也通过 `pretest` 重生成。然后在部署处重装本包。直接手改 `src/prompts.json` 再跑 `node scripts/sync-prompts.mjs` 效果相同。\n\n### 中文评审弹窗文案\n\n本包带一份中文评审弹窗文案覆盖层\n（`src/prompts.zh.json`，8 条键值）；其他用户可见文案（工具描述、提示消息、\n错误信息、命令文本等）保持英文。\n\n**部署声明式路径（推荐）：** 在插件行上设置 `language: zh`——服务在构造时\n解析中文词典，因此该选择存在于组合中（挂载本插件的同一份 patch 里），\n每次重装都保留，且无需任何安装后脚本：\n\n```yaml\n- id: stage-switch\n  name: '@ai4rpg/dsh-stage-switch'\n  config:\n    language: zh\n    # ...stages、section 及其余配置\n```\n\n**文件级路径（旧法）：** 改为合并到已装副本——适用于无法编辑组合的部署：\n\n```sh\nnode node_modules/@ai4rpg/dsh-stage-switch/scripts/apply-zh.mjs\n```\n\n该脚本将中文评审文案深度合并到已装的\n`src/prompts.json` 中。内嵌兜底（`lib/prompts.defaults.js`）不受影响——\n安装包丢失 JSON 文件时仍回退到英文。回退文件级合并：重装包\n（`dsh plugin remove + add`），恢复英文 `src/prompts.json`。**每次重装或升级后\n重新运行该脚本**——或改用配置路径，它没有这一步。\n\n## 模型体验\n\n### 阶段引导交付\n\n#### 模型看到什么\n\n新会话的首个请求把初始阶段提示作为对话消息携带；每次切换把新阶段的提示（`Current stage: <name>` 加上该阶段的 `instruction`）作为又一条消息追加。**子代会话跳过此初始提示**——父会话已经派发了任务，初始阶段（通常是 `route`，其指令让模型调用 `goto_stage`）会让子代死锁，因为 `goto_stage` 的评审在子代上下文里没有用户可应答。系统提示词永不包含当前阶段。当某判定器为 true 时，部署方的 `section` 模板在 order 116 贡献给运行时上下文快照；若它引用 `{{stage_current}}` 或 `{{stage_targets}}`，则组装时插值为当前阶段。\n\n##### 配置示例\n\n```markdown\nCurrent stage: explore\nExplore the problem space and present a plan.\n```\n\n#### Token 影响\n\n初始阶段提示只在首个请求花费一次 instruction 的 token；每个切换后的阶段提示作为对话消息再花费一次。子代会话两者都不花——它跳过初始提示，也不切换阶段。`stage:policy` 仅在判定器为 true 时花费模板的 token，付在运行时上下文快照中而非系统提示词。\n\n#### KV Cache 影响\n\n阶段提示是只追加的对话增长：切换阶段追加一条消息且不改变任何请求前缀，因此 provider 的缓存前缀在每次切换后仍可复用。`stage:policy` 每次请求都重新渲染，但投影仅在其文本变化时追加快照（可切换状态翻转，或改变阶段名变量的切换）；缓存的系统提示词前缀永不被触碰。工具目录永不改变。\n\n### 人工命令\n\n#### 模型看到什么\n\n`/stage` 及其终端结果不进模型历史。`/stage <name>` 切换会追加新阶段的提示，仅在最后一个请求头描述的是另一阶段时带前置的用户切换通知；仅确认当前阶段的切换不追加任何内容。阶段名之后的非空后缀会通过 `agent.steer()` 成为切换后阶段上下文中的一条修剪过的用户文本块。\n\n#### Token 影响\n\n裸 `/stage` 与无后缀的 `/stage <name>` 除追加的阶段提示（及可选切换通知）外不增加历史 token；带后缀的消息与单独提交该文本花费相同的历史 token。\n\n#### KV Cache 影响\n\n阶段提示与可选用户块都是只追加的对话增长；可复用请求前缀保持不变。\n\n### 切换工具\n\n#### 模型看到什么\n\n`goto_stage` schema 在每个阶段都可用；在当前阶段规则之外的执行会带着修正原因失败。已批准的完整切换返回 `{ approved: true, stage, handoffPath }`，并渲染指明文档的确认文本；轻量切换返回 `{ approved: true, stage }`。拒绝仍是携带评审反馈的失败调用，被放弃的评审是点名用户接手的失败调用。\n\n#### Token 影响\n\n稳定 schema 按 ToolRuntime 模式计费，每个交接参数与评审结果留在对话历史中。\n\n#### KV Cache 影响\n\n阶段切换不改变工具目录；交接参数与评审结果正常扩展对话。\n\n## 已知限制与延后工作\n\n- 在下一个被接受的 in-turn pre-step 应用的已评审切换，若进程在此之前退出则会丢失；UI 需要重新应用。\n- 轻量切换（低于 `minHandoffTokens`）不会归档对话，因此之后的完整切换会替换一段更长的累积历史。\n- 被拒绝或被放弃的评审会让已写入的交接文档留在原处；它是用户可删除的可见草稿。\n- 评审在通用问题流程上呈现。DeepSeek Harness monorepo 的 `stage-review` 呈现意图尚未出现在已发布的 `dsh-user-questions` 版本中；发布后，本包的未来版本可以附加它以支持专用 UI。\n- 阶段提示词是引导而非强制；除非部署方另行配置沙箱与审批控制，模型可能在其之外行动。\n","readmeFilename":"README.zh.md"}