dsh-plugin-dev-kb 1.0.0

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.
Files changed (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/cordis.patch.yml +12 -0
  4. package/kb/INDEX.md +210 -0
  5. package/kb/README.md +69 -0
  6. package/kb/extra/AGENTS.md +75 -0
  7. package/kb/extra/api-gateway.md +164 -0
  8. package/kb/extra/api-gateway.zh.md +164 -0
  9. package/kb/extra/cookbook/adding-a-vendored-package.md +59 -0
  10. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +59 -0
  11. package/kb/extra/cookbook/maintaining-dsh-code-review.md +64 -0
  12. package/kb/extra/cookbook/maintaining-dsh-code-review.zh.md +64 -0
  13. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
  14. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
  15. package/kb/extra/defensive-patterns.md +33 -0
  16. package/kb/extra/defensive-patterns.zh.md +33 -0
  17. package/kb/extra/development.md +171 -0
  18. package/kb/extra/development.zh.md +171 -0
  19. package/kb/extra/event-producer-consumer.md +76 -0
  20. package/kb/extra/event-producer-consumer.zh.md +78 -0
  21. package/kb/extra/glossary.md +45 -0
  22. package/kb/extra/glossary.zh.md +45 -0
  23. package/kb/extra/graph-atlas.md +24 -0
  24. package/kb/extra/graph-atlas.zh.md +26 -0
  25. package/kb/extra/i18n/README.md +60 -0
  26. package/kb/extra/i18n/README.zh.md +60 -0
  27. package/kb/extra/i18n/style-samples.md +87 -0
  28. package/kb/extra/i18n/terminology.md +214 -0
  29. package/kb/extra/i18n/translation-prompt.md +263 -0
  30. package/kb/extra/i18n/translation-rules.md +69 -0
  31. package/kb/extra/i18n/translation-rules.zh.md +69 -0
  32. package/kb/extra/module-graph.md +1641 -0
  33. package/kb/extra/module-graph.zh.md +1643 -0
  34. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +113 -0
  35. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
  36. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
  37. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
  38. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
  39. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
  40. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
  41. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
  42. package/kb/extra/postmortem/README.md +18 -0
  43. package/kb/extra/postmortem/README.zh.md +18 -0
  44. package/kb/extra/rescope.md +53 -0
  45. package/kb/extra/rescope.zh.md +53 -0
  46. package/kb/extra/subsystems/attachment.md +125 -0
  47. package/kb/extra/subsystems/attachment.zh.md +125 -0
  48. package/kb/extra/subsystems/extensions.md +364 -0
  49. package/kb/extra/subsystems/extensions.zh.md +364 -0
  50. package/kb/extra/subsystems/feedback.md +266 -0
  51. package/kb/extra/subsystems/feedback.zh.md +266 -0
  52. package/kb/extra/testing.md +49 -0
  53. package/kb/extra/testing.zh.md +49 -0
  54. package/kb/extra/web-styling.md +25 -0
  55. package/kb/extra/web-styling.zh.md +25 -0
  56. package/kb/meta/search-index.json +1328 -0
  57. package/kb/meta/site-pages.txt +168 -0
  58. package/kb/meta/source.json +13 -0
  59. package/kb/meta/topics.md +75 -0
  60. package/kb/site/develop/basic/config.md +108 -0
  61. package/kb/site/develop/basic/index.md +146 -0
  62. package/kb/site/develop/basic/publish.md +185 -0
  63. package/kb/site/develop/basic/tool.md +54 -0
  64. package/kb/site/develop/cordis-tutorial/01-first-plugin.md +95 -0
  65. package/kb/site/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  66. package/kb/site/develop/cordis-tutorial/03-services.md +98 -0
  67. package/kb/site/develop/cordis-tutorial/04-events.md +144 -0
  68. package/kb/site/develop/cordis-tutorial/05-config.md +84 -0
  69. package/kb/site/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
  70. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +107 -0
  71. package/kb/site/develop/cordis-tutorial/index.md +62 -0
  72. package/kb/site/develop/framework/events.md +145 -0
  73. package/kb/site/develop/framework/index.md +139 -0
  74. package/kb/site/develop/framework/service.md +152 -0
  75. package/kb/site/develop/practice/index.md +157 -0
  76. package/kb/site/develop/practice/llm-adapter.md +190 -0
  77. package/kb/site/en/develop/basic/config.md +108 -0
  78. package/kb/site/en/develop/basic/index.md +146 -0
  79. package/kb/site/en/develop/basic/publish.md +185 -0
  80. package/kb/site/en/develop/basic/tool.md +54 -0
  81. package/kb/site/en/develop/cordis-tutorial/01-first-plugin.md +95 -0
  82. package/kb/site/en/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  83. package/kb/site/en/develop/cordis-tutorial/03-services.md +98 -0
  84. package/kb/site/en/develop/cordis-tutorial/04-events.md +144 -0
  85. package/kb/site/en/develop/cordis-tutorial/05-config.md +84 -0
  86. package/kb/site/en/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
  87. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +107 -0
  88. package/kb/site/en/develop/cordis-tutorial/index.md +60 -0
  89. package/kb/site/en/develop/framework/events.md +145 -0
  90. package/kb/site/en/develop/framework/index.md +139 -0
  91. package/kb/site/en/develop/framework/service.md +150 -0
  92. package/kb/site/en/develop/practice/index.md +157 -0
  93. package/kb/site/en/develop/practice/llm-adapter.md +190 -0
  94. package/kb/site/en/guide/providers-custom-form.png +0 -0
  95. package/kb/site/en/guide/providers-models-page.png +0 -0
  96. package/kb/site/en/guide/providers.md +100 -0
  97. package/kb/site/en/guide/python-sdk.md +106 -0
  98. package/kb/site/en/guide/quickstart.md +32 -0
  99. package/kb/site/en/index.md +8 -0
  100. package/kb/site/en/reference/agent-lifecycle.md +86 -0
  101. package/kb/site/en/reference/capability-seams.md +475 -0
  102. package/kb/site/en/reference/config-catalog.md +3155 -0
  103. package/kb/site/en/reference/cookbook/adding-a-conversation-node.md +235 -0
  104. package/kb/site/en/reference/cookbook/adding-a-package.md +120 -0
  105. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +102 -0
  106. package/kb/site/en/reference/cookbook/adding-a-tool.md +96 -0
  107. package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +45 -0
  108. package/kb/site/en/reference/cookbook/extension-cookbook.md +131 -0
  109. package/kb/site/en/reference/cordis-api/context.md +368 -0
  110. package/kb/site/en/reference/cordis-api/events.md +211 -0
  111. package/kb/site/en/reference/cordis-api/fiber.md +379 -0
  112. package/kb/site/en/reference/cordis-api/inherited.md +43 -0
  113. package/kb/site/en/reference/cordis-api/registry.md +156 -0
  114. package/kb/site/en/reference/cordis-api/service.md +106 -0
  115. package/kb/site/en/reference/cordis-primer.md +46 -0
  116. package/kb/site/en/reference/index.md +131 -0
  117. package/kb/site/en/reference/persistence-catalog.md +949 -0
  118. package/kb/site/en/reference/subsystems/approval.md +173 -0
  119. package/kb/site/en/reference/subsystems/client-modules.md +121 -0
  120. package/kb/site/en/reference/subsystems/code-runtime.md +194 -0
  121. package/kb/site/en/reference/subsystems/commands.md +190 -0
  122. package/kb/site/en/reference/subsystems/compaction.md +241 -0
  123. package/kb/site/en/reference/subsystems/core.md +1073 -0
  124. package/kb/site/en/reference/subsystems/credentials.md +136 -0
  125. package/kb/site/en/reference/subsystems/filesystem.md +498 -0
  126. package/kb/site/en/reference/subsystems/goal.md +280 -0
  127. package/kb/site/en/reference/subsystems/index.md +58 -0
  128. package/kb/site/en/reference/subsystems/invariants.md +91 -0
  129. package/kb/site/en/reference/subsystems/jobs.md +293 -0
  130. package/kb/site/en/reference/subsystems/llm-streaming.md +920 -0
  131. package/kb/site/en/reference/subsystems/lsp.md +205 -0
  132. package/kb/site/en/reference/subsystems/permission-presets.md +134 -0
  133. package/kb/site/en/reference/subsystems/persistence.md +388 -0
  134. package/kb/site/en/reference/subsystems/plan.md +90 -0
  135. package/kb/site/en/reference/subsystems/sandbox.md +221 -0
  136. package/kb/site/en/reference/subsystems/schedule.md +189 -0
  137. package/kb/site/en/reference/subsystems/scope.md +62 -0
  138. package/kb/site/en/reference/subsystems/session-projection.md +265 -0
  139. package/kb/site/en/reference/subsystems/session-query.md +498 -0
  140. package/kb/site/en/reference/subsystems/session-reference.md +111 -0
  141. package/kb/site/en/reference/subsystems/session-telemetry.md +197 -0
  142. package/kb/site/en/reference/subsystems/session-title.md +207 -0
  143. package/kb/site/en/reference/subsystems/session.md +852 -0
  144. package/kb/site/en/reference/subsystems/settings.md +313 -0
  145. package/kb/site/en/reference/subsystems/shell.md +306 -0
  146. package/kb/site/en/reference/subsystems/skills.md +334 -0
  147. package/kb/site/en/reference/subsystems/spill.md +120 -0
  148. package/kb/site/en/reference/subsystems/storage.md +232 -0
  149. package/kb/site/en/reference/subsystems/subagent.md +737 -0
  150. package/kb/site/en/reference/subsystems/subprocess.md +327 -0
  151. package/kb/site/en/reference/subsystems/system-prompt.md +210 -0
  152. package/kb/site/en/reference/subsystems/terminal.md +187 -0
  153. package/kb/site/en/reference/subsystems/token-meter.md +93 -0
  154. package/kb/site/en/reference/subsystems/tools.md +723 -0
  155. package/kb/site/en/reference/subsystems/typert.md +339 -0
  156. package/kb/site/en/reference/subsystems/user-questions.md +181 -0
  157. package/kb/site/en/reference/subsystems/web-server.md +111 -0
  158. package/kb/site/en/reference/subsystems/web.md +202 -0
  159. package/kb/site/en/reference/subsystems/workflow.md +281 -0
  160. package/kb/site/en/reference/subsystems/workspace.md +231 -0
  161. package/kb/site/en/reference/tool-catalog.md +1877 -0
  162. package/kb/site/en/reference/tool-execution-pipeline.md +66 -0
  163. package/kb/site/guide/providers-custom-form.zh.png +0 -0
  164. package/kb/site/guide/providers-models-page.zh.png +0 -0
  165. package/kb/site/guide/providers.md +100 -0
  166. package/kb/site/guide/python-sdk.md +106 -0
  167. package/kb/site/guide/quickstart.md +32 -0
  168. package/kb/site/index.md +8 -0
  169. package/kb/site/reference/agent-lifecycle.md +86 -0
  170. package/kb/site/reference/capability-seams.md +475 -0
  171. package/kb/site/reference/config-catalog.md +3154 -0
  172. package/kb/site/reference/cookbook/adding-a-conversation-node.md +235 -0
  173. package/kb/site/reference/cookbook/adding-a-package.md +120 -0
  174. package/kb/site/reference/cookbook/adding-a-settings-card.md +102 -0
  175. package/kb/site/reference/cookbook/adding-a-tool.md +98 -0
  176. package/kb/site/reference/cookbook/adding-an-llm-adapter.md +45 -0
  177. package/kb/site/reference/cookbook/extension-cookbook.md +133 -0
  178. package/kb/site/reference/cordis-api/context.md +368 -0
  179. package/kb/site/reference/cordis-api/events.md +211 -0
  180. package/kb/site/reference/cordis-api/fiber.md +379 -0
  181. package/kb/site/reference/cordis-api/inherited.md +43 -0
  182. package/kb/site/reference/cordis-api/registry.md +156 -0
  183. package/kb/site/reference/cordis-api/service.md +106 -0
  184. package/kb/site/reference/cordis-primer.md +52 -0
  185. package/kb/site/reference/index.md +135 -0
  186. package/kb/site/reference/persistence-catalog.md +949 -0
  187. package/kb/site/reference/subsystems/approval.md +173 -0
  188. package/kb/site/reference/subsystems/client-modules.md +121 -0
  189. package/kb/site/reference/subsystems/code-runtime.md +194 -0
  190. package/kb/site/reference/subsystems/commands.md +190 -0
  191. package/kb/site/reference/subsystems/compaction.md +241 -0
  192. package/kb/site/reference/subsystems/core.md +1081 -0
  193. package/kb/site/reference/subsystems/credentials.md +136 -0
  194. package/kb/site/reference/subsystems/filesystem.md +498 -0
  195. package/kb/site/reference/subsystems/goal.md +280 -0
  196. package/kb/site/reference/subsystems/index.md +58 -0
  197. package/kb/site/reference/subsystems/invariants.md +91 -0
  198. package/kb/site/reference/subsystems/jobs.md +293 -0
  199. package/kb/site/reference/subsystems/llm-streaming.md +926 -0
  200. package/kb/site/reference/subsystems/lsp.md +205 -0
  201. package/kb/site/reference/subsystems/permission-presets.md +134 -0
  202. package/kb/site/reference/subsystems/persistence.md +388 -0
  203. package/kb/site/reference/subsystems/plan.md +90 -0
  204. package/kb/site/reference/subsystems/sandbox.md +221 -0
  205. package/kb/site/reference/subsystems/schedule.md +189 -0
  206. package/kb/site/reference/subsystems/scope.md +62 -0
  207. package/kb/site/reference/subsystems/session-projection.md +265 -0
  208. package/kb/site/reference/subsystems/session-query.md +498 -0
  209. package/kb/site/reference/subsystems/session-reference.md +111 -0
  210. package/kb/site/reference/subsystems/session-telemetry.md +197 -0
  211. package/kb/site/reference/subsystems/session-title.md +207 -0
  212. package/kb/site/reference/subsystems/session.md +854 -0
  213. package/kb/site/reference/subsystems/settings.md +313 -0
  214. package/kb/site/reference/subsystems/shell.md +306 -0
  215. package/kb/site/reference/subsystems/skills.md +334 -0
  216. package/kb/site/reference/subsystems/spill.md +120 -0
  217. package/kb/site/reference/subsystems/storage.md +232 -0
  218. package/kb/site/reference/subsystems/subagent.md +739 -0
  219. package/kb/site/reference/subsystems/subprocess.md +327 -0
  220. package/kb/site/reference/subsystems/system-prompt.md +210 -0
  221. package/kb/site/reference/subsystems/terminal.md +187 -0
  222. package/kb/site/reference/subsystems/token-meter.md +93 -0
  223. package/kb/site/reference/subsystems/tools.md +723 -0
  224. package/kb/site/reference/subsystems/typert.md +339 -0
  225. package/kb/site/reference/subsystems/user-questions.md +181 -0
  226. package/kb/site/reference/subsystems/web-server.md +111 -0
  227. package/kb/site/reference/subsystems/web.md +202 -0
  228. package/kb/site/reference/subsystems/workflow.md +281 -0
  229. package/kb/site/reference/subsystems/workspace.md +231 -0
  230. package/kb/site/reference/tool-catalog.md +1880 -0
  231. package/kb/site/reference/tool-execution-pipeline.md +66 -0
  232. package/package.json +40 -0
  233. package/scripts/rebuild-index.mjs +88 -0
  234. package/skills/dsh-plugin-dev-kb.md +66 -0
@@ -0,0 +1,388 @@
1
+ ---
2
+ editSource: "docs/subsystems/persistence.zh.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # 会话持久化
7
+
8
+ 事件日志的**持久性 seam**。[session.md](./session.md) 描述了内存中的 `Session`:仅追加的 `SessionEvent` 日志即为真源。本页描述如何使该日志持久化:抽象的 `SessionPersistence` 服务、它的后端、flush 检查点、崩溃恢复,以及随日志一同存储的元数据头。日志承载的事件词汇在生成的[持久化日志事件目录](../persistence-catalog.md)中逐项列举。
9
+
10
+ 该 seam 是一个[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.md):一个抽象服务([dsh-session-persistence](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence),`ctx.sessionPersistence`)在现有 `SessionEvent` 上定义 locate/create/append、可复用的 Session 准备流程、逻辑 load/inspect、物理后缀读取,以及轻量的 list/snapshot 观察——**没有平行的持久化事件类型**——以及两个实现同一约定的可互换后端。见 [session-persistence Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-14-session-persistence.md)。
11
+
12
+ ## flush 检查点
13
+
14
+ `session/event` 是一个*同步*通知;持久化插件会将事件复制到逐会话控制器,而不阻塞生产方。第一个待处理事件会开启固定批处理窗口,后续事件会加入但不会重置截止时间。窗口到期后会启动一个持久化批次;该次写入期间接纳的事件会获得自己的截止时间,并形成后续批次。`session/flush` 会取消等待并排空至完全停稳,因此循环仍将其用作在领取下一个普通轮次之前的顺序与错误观察检查点。后台写入被拒绝时会保留对应事件并暂停自动重试;新事件会开启新的固定窗口,而显式 flush 会立即重试,并通过 `agent/error` 和 logger 报告失败,绝不会把失败记录成已关闭轮次之后的会话事件。dispose(资源释放)会执行同样的最终排空。配置的最大值只限制有意的批处理等待,不限制事件循环调度或后端完成持久化的延迟([决策](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md))。
15
+
16
+ ## 崩溃恢复保留被中断的轮次
17
+
18
+ 后端重新加载一个在轮次中途崩溃的日志时,会发现一个已打开的 `turn/start` 却没有 `turn/end`。它**不会**截断日志:在长周期任务中,单个轮次可能非常庞大(许多步骤、大量工具输出),而这些事件在崩溃前已被持久追加。后端改为用一个合成的 `turn/end { reason: { kind: 'interrupted' } }` 关闭这个遗留轮次,在不改变其前后任何独立事件的情况下配平被中断的执行。`interrupted` 是唯一一个不由循环发出的 `TurnEndReason`(见 [session.md](./session.md#why-a-turn-ended-turnendreasonmap))。
19
+
20
+ 修复仅适用于冷会话。对于活跃 id,`SessionPersistence.load(id)` 会等待权威内存快照完成持久化,并且只在日志平衡时返回;若活跃轮次仍未闭合,则拒绝操作,而不是添加合成的中断边界。HMR(热模块替换)会接管活跃前缀,而不会关闭其中正在进行的轮次。
21
+
22
+ `SessionPersistence.inspect(id)` 会构造一个不可变的逻辑 Session,但不发布它,也不写入恢复内容。冷检查会在内存中配平中断的轮次,同时保持撕裂的物理尾部不变;检查已处于活跃状态的 Session 则借用其当前不可变快照,因此可能包含未闭合的轮次。使用协调器的实现会在有界 LRU 中保留这个精确的冷未发布 Session,因此重复历史读取与后续 `prepare(id)` 可复用同一次读取、解压、验证、冻结及 Session 构造。`prepare(id)` 会预留该 Session、提交待处理修复并返回可 dispose 的发布句柄;`load(id)` 使用相同机制提交修复,但不会发布 Session。该生命周期由 [Session 准备阶段决策](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-05-session-preparation.md)定义。
23
+
24
+ ## `SessionLocation`——可选的逐会话产物目标
25
+
26
+ `SessionPersistence.locate(meta)` 会同步解析一个归后端所有的独立产物,而不会读取、创建或 flush 它。JSONL 返回其项目/会话目录内 transcript(文本记录)的绝对路径;SQLite 因各会话共享一个数据库而返回 `undefined`。因此,返回的路径可能指向尚不存在的文件,或指向还不包含当前尚未 flush 轮次的文件;它是位置提示,不是授权或新鲜度保证。
27
+
28
+ ```ts type-equiv
29
+ /**
30
+ * A backend-resolved, per-session local artifact location. The path is an
31
+ * absolute target path and can name an artifact that has not materialized yet.
32
+ * Consumers must treat it as a location hint, never as an authorization token.
33
+ */
34
+ interface SessionLocation {
35
+ /** Backend-specific artifact kind, for example `jsonl`. */
36
+ readonly kind: string
37
+ /** Absolute path to this session's backend-owned artifact. */
38
+ readonly path: string
39
+ }
40
+ ```
41
+
42
+ <a id="sessionheader--metadata-beside-the-log"></a>
43
+
44
+ ## `SessionHeader`:日志旁的元数据
45
+
46
+ 每个会话的元数据与事件日志**分开**存储:格式版本、cwd、血统与 seed 边界是存储层关注点而非对话事件,因此不进入 `SessionEventMap`,也不会到达 `deriveMessages()`。header 通过 `session.header` 附加到 `Session` 上。
47
+
48
+ 源码:[`packages/core/session/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/session/src/types.ts)
49
+
50
+ ```ts type-equiv
51
+ /**
52
+ * Immutable validated storage metadata, kept outside the conversation event log.
53
+ */
54
+ interface SessionHeader {
55
+ /**
56
+ * On-disk format version, stamped from {@link SESSION_FORMAT_VERSION} when the
57
+ * session is created. A persistence backend rejects any other version on load
58
+ * (no migration — see the constant).
59
+ */
60
+ readonly version: number
61
+ /** The session's id (mirrors the {@link Session}'s id). */
62
+ readonly id: SessionId
63
+ /** Non-negative safe-integer Unix epoch milliseconds when the session was created. */
64
+ readonly createdAt: number
65
+ /** Absolute working directory the session was created in (if any). */
66
+ readonly cwd?: string
67
+ /** The session this one was forked from (seed lineage), if any. */
68
+ readonly parentSession?: SessionId
69
+ /**
70
+ * How many leading events were inherited through a seed. Persisting this
71
+ * boundary lets resume and replay distinguish parent history from child work.
72
+ */
73
+ readonly seedLength?: number
74
+ /**
75
+ * Coarse product classification for a session created as a subagent child.
76
+ * This is presentation metadata, not proof that the child is continuable.
77
+ */
78
+ readonly origin?: 'subagent'
79
+ /**
80
+ * Delegation depth: absent (zero) for a top-level session, parent depth + 1
81
+ * for a subagent child. Persisted so a recursion budget survives restart and
82
+ * resume — a runtime-only depth would reset a resumed child to top-level.
83
+ */
84
+ readonly delegationDepth?: number
85
+ /**
86
+ * Id of the agent preset this session's agent was composed from, when the
87
+ * deployment composes per session. Durable because the preset decides the
88
+ * session's tools and prompt: a resume that restored a different composition
89
+ * would replay history the model can no longer act on.
90
+ */
91
+ readonly agentPreset?: string
92
+ }
93
+ ```
94
+
95
+ ## 格式拒绝:本构建无法可靠读取的日志
96
+
97
+ 后端用 `SessionFormatUnsupportedError` 拒绝无法可靠解读的日志,它与 `SessionPersistenceCorruptionError` 区分,因为数据没有损坏。header 的 `version` 比 `SESSION_FORMAT_VERSION` 新时,消息说明方向("由更新的 harness 写入,请升级 harness 后打开");比它旧时说明本构建没有升级路径。经过 legacy 形状归一化后,本构建生成词汇表(`KNOWN_SESSION_EVENT_TYPES`,由 `gen-persistence-catalog` 生成)之外的事件类型同样被拒绝,除非该事件的信封带 `ignorable: true`:静默跳过一个不认识的必需事件可能改变日志其余部分的解读方式。后端为每个会话保留独立文件时,消息附上原始日志路径,被拒绝的文本仍然可读。JSONL 后端直接从原始 header 行拒绝外来版本,先于当前 header 形状校验和任何事件行解码,因此结构完全不同的未来格式仍会报告升级方向,绝不会报"损坏";SQLite 则先由自己的 `SCHEMA_VERSION` pragma 把关整个文件的结构。设计理由与推迟建设的升级器链见 [session-log 版本机制 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-10-session-log-version-mechanism.md)。
98
+
99
+ ## `CreateSessionOptions`:seed 与元数据
100
+
101
+ 通过 store 创建 `Session` 时会接收 `seed`(初始回放或 fork 历史)与 `meta`(store 整合进 `SessionHeader` 的存储层字段)。store 填充 `version`/`id` 并为 `createdAt` 提供默认值;调用方可以提供已校验的绝对 `cwd`、`parentSession` 谱系、`seedLength` 种子边界、可选的粗粒度 `origin`、`delegationDepth`、用于组装该 agent(智能体)的 `agentPreset` 以及已有的 `createdAt`。`origin: 'subagent'` 让产品导航能够隐藏重复的 child 行;它不证明描述符有效,也不证明 child 可以恢复。
102
+
103
+ ```ts type-equiv
104
+ /**
105
+ * Options for creating a {@link Session} via the store. `seed` replays/forks
106
+ * an existing event log; `meta` carries the caller-supplied storage fields the
107
+ * store folds into a {@link SessionHeader}.
108
+ */
109
+ interface CreateSessionOptions {
110
+ /** Initial replay or fork history supplied at construction. */
111
+ readonly seed?: readonly SessionEvent[]
112
+ /**
113
+ * Storage metadata read once before publication. `seedLength` is explicit
114
+ * because a resumed seed contains the full stored log, not only its inherited prefix.
115
+ */
116
+ readonly meta?: {
117
+ readonly cwd?: string
118
+ readonly parentSession?: SessionId
119
+ readonly createdAt?: number
120
+ readonly seedLength?: number
121
+ readonly origin?: 'subagent'
122
+ readonly delegationDepth?: number
123
+ readonly agentPreset?: string
124
+ }
125
+ }
126
+ ```
127
+
128
+ 因此,回放/fork 的调用方式为 `ctx.sessions.create(id, { seed: seedEvents })`;将一个*持久化*会话恢复为活跃 agent 的调用方式为 `ctx.agents.resume({ resumeSessionId })`。
129
+
130
+ ## `SessionRawArtifact`——逐字存储工件文本
131
+
132
+ 后端为单个会话自持的工件文本,与其持久化写入的字节逐字一致(按物理编码解码)。`readRaw` 返回它而不从解析后事件重建,因此后端特定的序列化(chunk 打包、键序、换行)得以保留。Consumer 须先检查 `supportsRawArtifacts`:`false` 表示后端不提供此能力(如 SQLite),而 `readRaw(...) === undefined` 表示受支持的后端没有该会话的已实体化工件。
133
+
134
+ ```ts type-equiv
135
+ /** A backend's own raw artifact text for one session, verbatim. */
136
+ interface SessionRawArtifact {
137
+ /** The session header parsed from the artifact's own first line. */
138
+ readonly meta: SessionHeader
139
+ /** The artifact's base filename on disk, without any physical encoding suffix. */
140
+ readonly filename: string
141
+ /** The artifact's full text content, decoded from the backend's physical encoding. */
142
+ readonly content: string
143
+ }
144
+ ```
145
+
146
+ ## 准备与恢复所有权
147
+
148
+ `SessionStore.prepare()` 接收普通创建选项,或通过 `RestoredSessionOptions` 转移所有权的全新的持久化对象图。恢复分支会就地验证并冻结转移来的 header 与事件,因此调用方不得保留可变别名。`SessionPreparation` 随后持有该精确的未发布 Session,直至发布或回滚;dispose 是同步且幂等的。持久化检查只暴露 `SessionInspection`,即从同一个已准备 Session 借用的不可变逻辑视图。
149
+
150
+ ```ts type-equiv
151
+ /**
152
+ * Fresh storage values transferred to {@link SessionStore.prepare} without a
153
+ * second serialization copy. Callers retain no mutable aliases.
154
+ */
155
+ interface RestoredSessionOptions {
156
+ /** Fresh detached storage events to validate and freeze in place. */
157
+ readonly seed: SessionEvent[]
158
+ /** Fresh detached storage metadata to validate and freeze in place. */
159
+ readonly meta: SessionHeader
160
+ /** Select the persistence ownership-transfer path. */
161
+ readonly seedSource: 'persistence'
162
+ }
163
+ ```
164
+
165
+ ```ts type-equiv
166
+ /** Inputs accepted while constructing an unpublished Session. */
167
+ type PrepareSessionOptions =
168
+ | (CreateSessionOptions & { readonly seedSource?: undefined })
169
+ | RestoredSessionOptions
170
+ ```
171
+
172
+ ```ts type-equiv
173
+ /** Options for a preparation whose provider retains unpublished state. */
174
+ interface SessionPreparationOptions {
175
+ /** Release provider-owned state when the Session was not published. */
176
+ readonly release?: () => void
177
+ }
178
+ ```
179
+
180
+ ```ts public-api
181
+ /**
182
+ * One exact unpublished Session and the provider state that keeps it usable.
183
+ * Disposal is synchronous and idempotent. Providers decide whether release
184
+ * returns the Session to a cache or discards it; publication may consume that
185
+ * state before disposal, making the callback a no-op.
186
+ */
187
+ declare class SessionPreparation implements Disposable {
188
+ /** The exact Session to use for setup and publication. */
189
+ readonly session: Session;
190
+ /**
191
+ * Wrap an unpublished Session in one preparation lifetime.
192
+ * @param session - exact unpublished Session.
193
+ * @param options - optional provider release behavior.
194
+ * @returns a preparation disposed after publication or rollback.
195
+ */
196
+ static create(session: Session, options?: SessionPreparationOptions): SessionPreparation;
197
+ /** Release provider state once when this preparation leaves its caller. */
198
+ [Symbol.dispose](): void;
199
+ }
200
+ ```
201
+
202
+ ```ts type-equiv
203
+ /** Immutable logical session prepared from persistence or a live owner. */
204
+ interface SessionInspection {
205
+ /** Validated immutable session metadata. */
206
+ readonly meta: SessionHeader
207
+ /** Validated contiguous logical event log. */
208
+ readonly events: readonly SessionEvent[]
209
+ }
210
+ ```
211
+
212
+ ## 轻量源修订号
213
+
214
+ 派生状态的消费方会在加载完整事件日志之前比较一个低开销的不透明修订号。其表示由持久化后端拥有,并随 append 或会修改数据的 load 修复以事务方式改变;调用方仅比较修订号是否相等。
215
+
216
+ ```ts type-equiv
217
+ /**
218
+ * Backend-owned token that identifies both one storage source and one revision
219
+ * of a persisted session log.
220
+ */
221
+ type SessionPersistenceRevision = Branded<'SessionPersistenceRevision'>
222
+ ```
223
+
224
+ ```ts type-equiv
225
+ /** Lightweight immutable source identity returned without loading a full log. */
226
+ interface SessionPersistenceSnapshot {
227
+ /** Detached metadata for one materialized session. */
228
+ header: SessionHeader
229
+ /** Opaque source-qualified token that changes whenever this stored log changes. */
230
+ revision: SessionPersistenceRevision
231
+ }
232
+ ```
233
+
234
+ ## 后端
235
+
236
+ 两者都实现同一个抽象 `SessionPersistence`(在 `SessionEvent` 上执行 locate/create/append/prepare/load/inspect/readFrom/list/listSnapshots,观察方法可选支持取消),并通过共享的 `runPersistenceContract` 套件:
237
+
238
+ - **[dsh-session-persistence-jsonl](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-jsonl)**——每个会话一份仅追加的逻辑 JSONL 日志,默认存储为带 checksum 的连续 Zstandard frame,也可配置为原始行;支持崩溃安全的原子写入、被中断轮次的恢复以及读取/回放路径。
239
+ - **[dsh-session-persistence-sqlite](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/session/session-persistence-sqlite)**:基于 `node:sqlite`,每个 `SessionEvent` 一行。行字段 `(session_id, seq, type, time, data, source_event_seqs, surface_op)` 与事件 1:1 映射(包含可选的 surface 元数据),因此没有需要保持同步的并行持久化 schema。
240
+
241
+ <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
242
+
243
+ <a id="cordis-surface"></a>
244
+
245
+ ## Cordis API
246
+
247
+ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
248
+
249
+ <a id="ctxsessionpersistence--sessionpersistence-abstract-seam"></a>
250
+
251
+ ### `ctx.sessionPersistence` — `SessionPersistence` (abstract seam)
252
+
253
+ Durable append-only session storage. Implementations preserve contiguous, losslessly JSON-serializable events; append resolves only after durability, and load balances a complete interrupted tail without rewriting committed events.
254
+
255
+ ```ts cordis-catalog
256
+ /**
257
+ * Resolve this backend's independent local artifact for a session without
258
+ * reading, creating, flushing, or otherwise materializing it. Backends such
259
+ * as SQLite that do not own one artifact per session return `undefined`.
260
+ * @param meta - the immutable session header whose artifact is requested.
261
+ * @returns the backend-specific absolute location, when one exists.
262
+ */
263
+ abstract locate(meta: SessionHeader): SessionLocation | undefined
264
+
265
+ /**
266
+ * Read a session's backend-owned artifact text verbatim — the exact durable
267
+ * bytes the backend wrote (decoded from its physical encoding, e.g. a
268
+ * decompressed JSONL). The returned `content` is the raw text, not a
269
+ * reconstruction from parsed events, so it preserves backend-specific
270
+ * serialization (chunk packing, key order, line breaks). Callers first test
271
+ * {@link supportsRawArtifacts}; `undefined` then means only that the requested
272
+ * session has no materialized artifact.
273
+ * @param _id - the persisted session to read (unused by the default: no
274
+ * per-session artifact).
275
+ * @param signal - optional cancellation for backend read work.
276
+ * @returns the raw artifact plus its parsed header, or `undefined` when the
277
+ * session is absent.
278
+ * @throws when this backend does not expose per-session raw artifacts.
279
+ */
280
+ readRaw(_id: SessionId, signal?: AbortSignal): Promise<SessionRawArtifact | undefined>
281
+
282
+ /**
283
+ * Register a new session's metadata. A backend MAY defer the physical write
284
+ * until the first {@link append} (lazy materialization), in which case a
285
+ * created-but-never-appended session is absent from {@link list}
286
+ * — abandoned sessions leave nothing behind.
287
+ * @param meta - the immutable header (id, version, cwd, lineage) to record.
288
+ */
289
+ abstract create(meta: SessionHeader): Promise<void>
290
+
291
+ /**
292
+ * Durably persist a batch of events. Honors the append-only and contiguous-
293
+ * seq contracts: the first event's `seq` MUST equal the stored next-seq
294
+ * (after `load` has durably closed any interrupted turn). Rejects non-JSON-
295
+ * serializable `event.data` with an error naming the offending event type.
296
+ * @param id - the session the batch belongs to.
297
+ * @param events - the contiguous batch to persist, in seq order.
298
+ */
299
+ abstract append(id: SessionId, events: readonly SessionEvent[]): Promise<void>
300
+
301
+ /**
302
+ * Prepare the exact unpublished Session used by resume. Implementations may
303
+ * reuse object graphs retained by an earlier {@link inspect} after confirming
304
+ * their durable revision is still current; disposal releases an unpublished
305
+ * reservation. Revision retries require the durable log to remain unchanged
306
+ * for one read/check round trip; continuous external writers may delay completion.
307
+ * @param id - persisted session to prepare.
308
+ * @param signal - optional cancellation for preparation work.
309
+ * @returns one owned unpublished Session preparation.
310
+ */
311
+ async prepare(id: SessionId, signal?: AbortSignal): Promise<SessionPreparation>
312
+
313
+ /**
314
+ * Load an immutable balanced logical view and commit any required cold
315
+ * recovery. A complete interrupted final turn is preserved and durably
316
+ * closed with missing tool errors plus any open step and turn boundaries;
317
+ * only a torn final record is discarded. Unknown versions and corruption in
318
+ * the committed prefix reject. Implementations MUST NOT crash-repair an
319
+ * identity still bound to a live Session: a balanced live log may return as a
320
+ * durable snapshot, while an open live turn rejects. Returned values may be
321
+ * shared with immutable live or prepared state and must not be mutated.
322
+ * Revision-based implementations may wait for one stable read/check round trip.
323
+ * @param id - the persisted session to reload.
324
+ * @returns the header and a log ending on a balanced `turn/end`.
325
+ */
326
+ abstract load(id: SessionId): Promise<SessionInspection>
327
+
328
+ /**
329
+ * Inspect an immutable logical session without committing recovery or
330
+ * publishing it. A cold complete interrupted turn receives synthetic closers
331
+ * in memory and a torn physical tail remains untouched. An already-live
332
+ * Session instead yields its current immutable snapshot, which may contain an
333
+ * open turn and its `session/end-seed` boundary. Coordinator-backed
334
+ * implementations retain the exact cold unpublished Session for bounded
335
+ * reuse by a later {@link prepare}. A stale ready source is reloaded; a source
336
+ * already committing or reserved for resume remains exclusive, and inspection
337
+ * may borrow its immutable view. Callers borrow only the immutable header and
338
+ * log. Continuous external writers may delay revision convergence.
339
+ * @param id - the persisted session to inspect.
340
+ * @param signal - optional cancellation for queued and backend read work.
341
+ * @returns the validated header and current logical event log.
342
+ */
343
+ abstract inspect(id: SessionId, signal?: AbortSignal): Promise<SessionInspection>
344
+
345
+ /**
346
+ * Read the stored events from `fromSeq` onward — the read-from-seq
347
+ * primitive for read models that resume from a watermark (e.g. a persisted
348
+ * projection cache folding only the tail past its checkpoint). Unlike
349
+ * {@link inspect}, it is a detached physical suffix read: no preparation
350
+ * cache, torn-tail truncation, synthetic closers, or coordinator-state
351
+ * publication. Only events from the valid contiguous stored prefix are
352
+ * returned, so a torn fragment never reaches the caller. `fromSeq` at or
353
+ * beyond the stored prefix returns an empty event list (never an error).
354
+ * Backends whose medium can seek by seq
355
+ * (SQLite) read only the suffix; sequential media (JSONL, both encodings)
356
+ * still parse the whole artifact and skip forward — the primitive bounds
357
+ * what is RETURNED and refolded, not every backend's physical read.
358
+ * @param id - the persisted session to read.
359
+ * @param fromSeq - first event seq to include; a non-negative safe integer.
360
+ * @param signal - optional cancellation for queued and backend read work.
361
+ * @returns the header and the stored events with `seq >= fromSeq`.
362
+ */
363
+ abstract readFrom(id: SessionId, fromSeq: number, signal?: AbortSignal): Promise<{ meta: SessionHeader; events: SessionEvent[] }>
364
+
365
+ /**
366
+ * Lightweight listing from metadata, without a full-log parse.
367
+ * @param signal - optional cancellation for backend listing work.
368
+ * @returns one header per materialized session.
369
+ */
370
+ abstract list(signal?: AbortSignal): Promise<SessionHeader[]>
371
+
372
+ /**
373
+ * List materialized sessions with cheap per-log change tokens.
374
+ *
375
+ * Repeated observations of an unchanged log return the same revision. A
376
+ * successful mutating {@link load} repair changes the next listed revision.
377
+ * Revisions also distinguish independently backed stores so backend-local
378
+ * counters cannot compare equal across different persistence sources.
379
+ * @param signal - optional cancellation for backend snapshot-listing work.
380
+ * @returns one header and opaque revision per materialized session without loading full logs.
381
+ */
382
+ abstract listSnapshots(signal?: AbortSignal): Promise<SessionPersistenceSnapshot[]>
383
+ ```
384
+
385
+ Types: [SessionEvent](./session.md) · [SessionId](./core.md)
386
+
387
+ Source: [`packages/session/session-persistence/src/index.ts:84`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/session/session-persistence/src/index.ts)
388
+ <!-- END GENERATED cordis-surface -->
@@ -0,0 +1,90 @@
1
+ ---
2
+ editSource: "docs/subsystems/plan.zh.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # 计划模式
7
+
8
+ 计划模式是 [dsh-plan-mode](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/plan/plan-mode) 拥有的、记录到日志的逐 agent(智能体)协作状态(`ctx.planMode`,`PlanModeController`):激活期间,每个模型请求都会包含一段部署持有的指引。计划模式是**软性指引**。[沙箱模式](./sandbox.md)与[审批策略](./approval.md)分别强制限制;两者都不读写计划状态,因此部署需要分别配置它们。该包是可选项,agent loop(智能体循环)不依赖它。它贡献 `plan:policy` 提示词段落,并注册 `exit_plan_mode` 工具和 `/plan` 命令。[设计说明](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/simplification/2026-07-22-plan-specific-collaboration-state.md)负责决策依据;[包 README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/plan/plan-mode/README.md)负责模型体验与限制细节。
9
+
10
+ 源码:[`packages/plan/plan-mode/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/plan/plan-mode/src/index.ts)
11
+
12
+ ## 已记录状态与恢复
13
+
14
+ `plan/mode`(`{ active: boolean }`)是仅记日志、整值替换的[会话事件](./session.md):持久且可回放,绝不进入模型 transcript(文本记录)。`foldPlanMode(events, end?)` 返回前缀中最后一条已记录值,没有时返回 `false`:生效状态始终是会话日志的纯折叠,因此恢复、fork 与压缩(compaction)无需实时镜像即可将其复原,UI 通过 `session/event` 观察已提交的切换。完整事件声明见[持久化日志事件目录](../persistence-catalog.md)。
15
+
16
+ ## 待生效选择与 pre-step 追加
17
+
18
+ 由于每个会话事件都位于轮次之内,用户选择会保持待生效状态,直到下一个被接受的轮内 pre-step 在派生请求之前追加该选择,无论该 pre-step 位于哪个轮次。选择不会强制续行,因此在某轮最后一个被接受的 pre-step 之后作出的选择会在之后的轮次追加。`set(agent, active)` 记录待生效选择(目标值与已记录或已在等待的状态相同时不做任何事),`get(agent)` 返回 `{ active: boolean; pending?: boolean }`:用于组装当前步骤的已记录状态,以及等待追加的已选状态。
19
+
20
+ agent 运行时,唯一的追加点是前置(prepend)注册的 `agent/pre-step` 监听器。它会观察每个候选请求步骤,包括第 1 轮第 1 步和请求恢复重试;它先调用下游监听器,只在下游接受该步骤后追加。提示词准入发生在轮次开启之前,无法追加 `plan/mode`,因此在提示词处作出的选择由它开启的轮次内第一个被接受的 pre-step 追加。追加失败不能阻塞轮次,且该选择会继续等待之后被接受的轮内 pre-step。追加用户选择时还会记录一条插件来源的 `user/message` 通知,但仅当最后记录的请求头描述的是另一种状态时才记录,因此模型恰好在上下文变化时收到通知,且绝不重复。在某轮最后一个被接受的 pre-step 之后作出的选择只存在于进程内;如果进程在另一个被接受的轮内 pre-step 之前退出,该选择会丢失([README 限制](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/plan/plan-mode/README.md#known-limitations-and-deferred-work))。
21
+
22
+ ## 配置
23
+
24
+ ```ts type-equiv
25
+ /** Deployment-owned plan guidance. */
26
+ interface PlanModeConfig {
27
+ /** Guidance rendered as the `plan:policy` prompt section while plan mode is active. */
28
+ section: string
29
+ }
30
+ ```
31
+
32
+ `section` 缺失、为空白或不是字符串,以及任何未知键,都会在插件加载时失败,而不是被忽略。计划模式激活期间,确切的 `section` 文本以 order 50 渲染为 `plan:policy` [系统提示词段落](./system-prompt.md);未激活的计划模式不贡献任何文本。
33
+
34
+ ## 退出工具与 `/plan` 命令
35
+
36
+ [`exit_plan_mode`](../tool-catalog.md#deepseek-aidsh-plan-mode) 在计划模式未激活时仍保持注册,因此进入或离开计划模式只改变提示词段落,绝不改变请求的工具目录;在计划模式之外执行会失败。在计划模式中,它要求一份以 `#` 标题开头的完整 markdown 计划,并通过[用户交互 seam](./user-questions.md) 呈交评审。批准返回 `{ approved: true }`,并记录一个静默(不叙述)的待生效退出,由下一个被接受的轮内 pre-step 追加。因此,计划指引在 assistant 当前这批工具调用的剩余部分继续生效,而工具结果本身会报告这次转换。「继续规划」则是一次携带用户反馈的失败调用,模型据此修订并再次呈交;评审期间交互通道缺失或服务重载同样使调用失败,而不是静默离开计划模式。
37
+
38
+ 当 [`ctx.commands`](./commands.md) 被组合时,插件注册 `/plan [off|message]`:单独的 `/plan` 选择计划模式;任何其他非空消息先选择计划模式,再通过 `agent.steer()` 提交该文本,使其在计划指引下成为下一步骤的普通已记录用户消息;确切参数 `off` 选择未激活,这还会在待生效条目被追加并对请求可见之前将其取消。
39
+
40
+ ## 服务
41
+
42
+ `ctx.planMode` 拥有已记录的计划状态,在步骤开始时应用并叙述选中的状态,还拥有 `plan:policy` 段落、`/plan` 命令和稳定注册的退出工具;`get`/`set` 签名见生成的[服务目录](#ctxplanmode--planmodecontroller)。
43
+
44
+ <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
45
+
46
+ <a id="cordis-surface"></a>
47
+
48
+ ## Cordis API
49
+
50
+ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
51
+
52
+ <a id="ctxplanmode--planmodecontroller"></a>
53
+
54
+ ### `ctx.planMode` — `PlanModeController`
55
+
56
+ `ctx.planMode`: owns logged plan state, applies and narrates selected state at step start, the `plan:policy` section, the `/plan` command, and the stable exit tool. UIs observe committed flips through `session/event`; there is no live mirror.
57
+
58
+ ```ts cordis-catalog
59
+ /**
60
+ * Read the logged plan state and any selected state awaiting the next
61
+ * accepted in-turn pre-step.
62
+ *
63
+ * @param agent The agent to read.
64
+ * @returns Current logged state plus a pending selection, when present.
65
+ */
66
+ get(agent: Agent): { active: boolean; pending?: boolean }
67
+
68
+ /**
69
+ * Select whether plan mode should be active. Between turns the method
70
+ * appends the change immediately because no in-turn pre-step will run until
71
+ * another prompt starts a turn. The open-turn fold is the idle signal:
72
+ * agent status stays `running` through post-turn checkpointing, when no
73
+ * further in-turn pre-step runs. During an open turn the selection remains
74
+ * pending until the next accepted in-turn pre-step. Repeated selection of
75
+ * the current or already-pending state is a no-op.
76
+ *
77
+ * @param agent The agent to switch.
78
+ * @param active Whether plan mode should be active.
79
+ * @returns what happened: `committed` (logged now), `queued` (awaiting the
80
+ * next accepted in-turn pre-step), `cancelled` (an opposite pending selection
81
+ * was cleared; the logged state already matches), or `noop` (already in that
82
+ * state).
83
+ */
84
+ set(agent: Agent, active: boolean): 'committed' | 'queued' | 'cancelled' | 'noop'
85
+ ```
86
+
87
+ Types: [Agent](./core.md)
88
+
89
+ Source: [`packages/plan/plan-mode/src/index.ts:184`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/plan/plan-mode/src/index.ts)
90
+ <!-- END GENERATED cordis-surface -->