dsh-plugin-dev-kb 1.0.8 → 1.1.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 (188) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.en.md +6 -6
  3. package/README.md +6 -6
  4. package/kb/INDEX.md +21 -5
  5. package/kb/README.md +11 -10
  6. package/kb/extra/AGENTS.md +4 -4
  7. package/kb/extra/cookbook/adding-a-remote-api.md +197 -0
  8. package/kb/extra/cookbook/adding-a-remote-api.zh.md +197 -0
  9. package/kb/extra/cookbook/adding-a-vendored-package.md +2 -2
  10. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +2 -2
  11. package/kb/extra/deepseek-llm-api-wire-extensions.md +163 -0
  12. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +163 -0
  13. package/kb/extra/development.md +8 -14
  14. package/kb/extra/development.zh.md +8 -14
  15. package/kb/extra/event-producer-consumer.md +55 -48
  16. package/kb/extra/event-producer-consumer.zh.md +58 -51
  17. package/kb/extra/glossary.md +1 -1
  18. package/kb/extra/glossary.zh.md +1 -1
  19. package/kb/extra/graph-atlas.md +0 -2
  20. package/kb/extra/graph-atlas.zh.md +0 -2
  21. package/kb/extra/i18n/README.md +4 -4
  22. package/kb/extra/i18n/README.zh.md +4 -4
  23. package/kb/extra/i18n/style-samples.md +2 -2
  24. package/kb/extra/module-graph.md +646 -926
  25. package/kb/extra/module-graph.zh.md +648 -928
  26. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +2 -2
  27. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +2 -2
  28. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +2 -2
  29. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +2 -2
  30. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +2 -2
  31. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +2 -2
  32. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +1 -1
  33. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +1 -1
  34. package/kb/extra/rescope.md +2 -2
  35. package/kb/extra/rescope.zh.md +2 -2
  36. package/kb/extra/subsystems/agent-team.md +28 -4
  37. package/kb/extra/subsystems/agent-team.zh.md +28 -4
  38. package/kb/extra/subsystems/attachment.md +168 -7
  39. package/kb/extra/subsystems/attachment.zh.md +168 -7
  40. package/kb/extra/subsystems/extensions.md +18 -0
  41. package/kb/extra/subsystems/extensions.zh.md +18 -0
  42. package/kb/extra/subsystems/feedback.md +4 -4
  43. package/kb/extra/subsystems/feedback.zh.md +4 -4
  44. package/kb/extra/subsystems/todo.md +32 -0
  45. package/kb/extra/subsystems/todo.zh.md +32 -0
  46. package/kb/extra/subsystems/webhook.md +70 -0
  47. package/kb/extra/subsystems/webhook.zh.md +70 -0
  48. package/kb/extra/testing.md +15 -10
  49. package/kb/extra/testing.zh.md +13 -8
  50. package/kb/extra/web-styling.md +4 -0
  51. package/kb/extra/web-styling.zh.md +4 -0
  52. package/kb/meta/search-index.json +309 -177
  53. package/kb/meta/site-pages.txt +183 -167
  54. package/kb/meta/source.json +5 -5
  55. package/kb/meta/topics.md +14 -6
  56. package/kb/site/develop/basic/publish.md +2 -2
  57. package/kb/site/develop/basic/tool.md +1 -1
  58. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +5 -4
  59. package/kb/site/develop/framework/events.md +1 -1
  60. package/kb/site/develop/practice/dynamic-cordis.md +17 -0
  61. package/kb/site/develop/practice/llm-adapter.md +4 -3
  62. package/kb/site/en/develop/basic/publish.md +2 -2
  63. package/kb/site/en/develop/basic/tool.md +1 -1
  64. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +5 -4
  65. package/kb/site/en/develop/framework/events.md +1 -1
  66. package/kb/site/en/develop/practice/dynamic-cordis.md +17 -0
  67. package/kb/site/en/develop/practice/llm-adapter.md +4 -3
  68. package/kb/site/en/guide/github-review.md +104 -0
  69. package/kb/site/en/guide/mcp-memory.md +103 -0
  70. package/kb/site/en/guide/network-proxy.md +87 -0
  71. package/kb/site/en/guide/providers.md +70 -17
  72. package/kb/site/en/guide/python-sdk.md +80 -34
  73. package/kb/site/en/guide/schedule.md +23 -0
  74. package/kb/site/en/reference/agent-lifecycle.md +6 -4
  75. package/kb/{extra → site/en/reference}/api-gateway.md +12 -10
  76. package/kb/site/en/reference/capability-seams.md +128 -73
  77. package/kb/site/en/reference/config-catalog.md +481 -360
  78. package/kb/site/en/reference/cookbook/adding-a-package.md +3 -4
  79. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +12 -10
  80. package/kb/site/en/reference/cookbook/adding-a-tool.md +11 -4
  81. package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +1 -1
  82. package/kb/site/en/reference/cookbook/extension-cookbook.md +20 -17
  83. package/kb/site/en/reference/cordis-api/inherited.md +1 -1
  84. package/kb/site/en/reference/cordis-primer.md +2 -1
  85. package/kb/site/en/reference/index.md +30 -11
  86. package/kb/site/en/reference/persistence-catalog.md +148 -80
  87. package/kb/site/en/reference/subsystems/approval.md +10 -10
  88. package/kb/site/en/reference/subsystems/client-modules.md +58 -16
  89. package/kb/site/en/reference/subsystems/code-runtime.md +10 -6
  90. package/kb/site/en/reference/subsystems/commands.md +25 -16
  91. package/kb/site/en/reference/subsystems/compaction.md +11 -11
  92. package/kb/site/en/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +50 -24
  93. package/kb/site/en/reference/subsystems/core.md +156 -17
  94. package/kb/site/en/reference/subsystems/credentials.md +44 -3
  95. package/kb/site/en/reference/subsystems/filesystem.md +12 -2
  96. package/kb/site/en/reference/subsystems/goal.md +1 -1
  97. package/kb/site/en/reference/subsystems/index.md +7 -2
  98. package/kb/site/en/reference/subsystems/jobs.md +1 -1
  99. package/kb/site/en/reference/subsystems/llm-streaming.md +154 -12
  100. package/kb/site/en/reference/subsystems/permission-presets.md +6 -6
  101. package/kb/site/en/reference/subsystems/persistence.md +185 -175
  102. package/kb/site/en/reference/subsystems/plan.md +2 -2
  103. package/kb/site/en/reference/subsystems/sandbox.md +2 -0
  104. package/kb/site/en/reference/subsystems/schedule.md +9 -3
  105. package/kb/site/en/reference/subsystems/session-projection.md +115 -48
  106. package/kb/site/en/reference/subsystems/session-query.md +28 -14
  107. package/kb/site/en/reference/subsystems/session-reference.md +53 -8
  108. package/kb/site/en/reference/subsystems/session-telemetry.md +8 -8
  109. package/kb/site/en/reference/subsystems/session-title.md +6 -6
  110. package/kb/site/en/reference/subsystems/session.md +401 -99
  111. package/kb/site/en/reference/subsystems/settings.md +101 -6
  112. package/kb/site/en/reference/subsystems/skills.md +23 -0
  113. package/kb/site/en/reference/subsystems/slots.md +178 -0
  114. package/kb/site/en/reference/subsystems/spill.md +2 -2
  115. package/kb/site/en/reference/subsystems/storage.md +34 -3
  116. package/kb/site/en/reference/subsystems/subagent.md +122 -109
  117. package/kb/site/en/reference/subsystems/system-prompt.md +17 -4
  118. package/kb/site/en/reference/subsystems/token-meter.md +27 -12
  119. package/kb/site/en/reference/subsystems/tools.md +39 -39
  120. package/kb/site/en/reference/subsystems/typert.md +62 -55
  121. package/kb/site/en/reference/subsystems/user-questions.md +33 -33
  122. package/kb/site/en/reference/subsystems/web-client.md +98 -0
  123. package/kb/site/en/reference/subsystems/web-server.md +11 -5
  124. package/kb/site/en/reference/subsystems/web.md +7 -1
  125. package/kb/site/en/reference/subsystems/workspace.md +102 -9
  126. package/kb/site/en/reference/tool-catalog.md +86 -82
  127. package/kb/site/en/reference/tool-execution-pipeline.md +1 -1
  128. package/kb/site/guide/github-review.md +104 -0
  129. package/kb/site/guide/mcp-memory.md +103 -0
  130. package/kb/site/guide/network-proxy.md +87 -0
  131. package/kb/site/guide/providers.md +70 -17
  132. package/kb/site/guide/python-sdk.md +87 -41
  133. package/kb/site/guide/schedule.md +23 -0
  134. package/kb/site/reference/agent-lifecycle.md +6 -4
  135. package/kb/{extra/api-gateway.zh.md → site/reference/api-gateway.md} +12 -10
  136. package/kb/site/reference/capability-seams.md +128 -73
  137. package/kb/site/reference/config-catalog.md +481 -360
  138. package/kb/site/reference/cookbook/adding-a-package.md +3 -4
  139. package/kb/site/reference/cookbook/adding-a-settings-card.md +12 -10
  140. package/kb/site/reference/cookbook/adding-a-tool.md +11 -4
  141. package/kb/site/reference/cookbook/adding-an-llm-adapter.md +1 -1
  142. package/kb/site/reference/cookbook/extension-cookbook.md +20 -17
  143. package/kb/site/reference/cordis-api/inherited.md +1 -1
  144. package/kb/site/reference/cordis-primer.md +2 -1
  145. package/kb/site/reference/index.md +30 -11
  146. package/kb/site/reference/persistence-catalog.md +148 -80
  147. package/kb/site/reference/subsystems/approval.md +10 -10
  148. package/kb/site/reference/subsystems/client-modules.md +58 -16
  149. package/kb/site/reference/subsystems/code-runtime.md +10 -6
  150. package/kb/site/reference/subsystems/commands.md +25 -16
  151. package/kb/site/reference/subsystems/compaction.md +11 -11
  152. package/kb/site/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +50 -24
  153. package/kb/site/reference/subsystems/core.md +156 -17
  154. package/kb/site/reference/subsystems/credentials.md +44 -3
  155. package/kb/site/reference/subsystems/filesystem.md +12 -2
  156. package/kb/site/reference/subsystems/goal.md +1 -1
  157. package/kb/site/reference/subsystems/index.md +7 -2
  158. package/kb/site/reference/subsystems/jobs.md +1 -1
  159. package/kb/site/reference/subsystems/llm-streaming.md +154 -12
  160. package/kb/site/reference/subsystems/permission-presets.md +5 -5
  161. package/kb/site/reference/subsystems/persistence.md +184 -174
  162. package/kb/site/reference/subsystems/plan.md +2 -2
  163. package/kb/site/reference/subsystems/schedule.md +9 -3
  164. package/kb/site/reference/subsystems/session-projection.md +115 -48
  165. package/kb/site/reference/subsystems/session-query.md +28 -14
  166. package/kb/site/reference/subsystems/session-reference.md +53 -8
  167. package/kb/site/reference/subsystems/session-telemetry.md +8 -8
  168. package/kb/site/reference/subsystems/session-title.md +6 -6
  169. package/kb/site/reference/subsystems/session.md +401 -99
  170. package/kb/site/reference/subsystems/settings.md +101 -6
  171. package/kb/site/reference/subsystems/skills.md +23 -0
  172. package/kb/site/reference/subsystems/slots.md +178 -0
  173. package/kb/site/reference/subsystems/spill.md +2 -2
  174. package/kb/site/reference/subsystems/storage.md +34 -3
  175. package/kb/site/reference/subsystems/subagent.md +122 -109
  176. package/kb/site/reference/subsystems/system-prompt.md +17 -4
  177. package/kb/site/reference/subsystems/token-meter.md +27 -12
  178. package/kb/site/reference/subsystems/tools.md +39 -39
  179. package/kb/site/reference/subsystems/typert.md +62 -55
  180. package/kb/site/reference/subsystems/user-questions.md +33 -33
  181. package/kb/site/reference/subsystems/web-client.md +98 -0
  182. package/kb/site/reference/subsystems/web-server.md +11 -5
  183. package/kb/site/reference/subsystems/web.md +7 -1
  184. package/kb/site/reference/subsystems/workspace.md +102 -9
  185. package/kb/site/reference/tool-catalog.md +85 -81
  186. package/kb/site/reference/tool-execution-pipeline.md +1 -1
  187. package/package.json +2 -2
  188. package/skills/dsh-plugin-dev-kb.md +8 -6
@@ -33,7 +33,7 @@ type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable'
33
33
 
34
34
  ## 按会话策略
35
35
 
36
- `ApprovalPolicy` 决定在交互式应答者运行之前发生什么。`ask` 委托给组合的应答者链,链的无应答默认值为 `unavailable`;`never` 确定性地返回 `rejected`,不分发任何应答者。生效值为会话日志中最后一条 `approval/policy` 事件,回退到服务配置。`setApprovalPolicy(session, policy)` 是唯一的写入路径,因此回放能重建覆盖值。
36
+ `ApprovalPolicy` 决定在交互式应答者运行之前发生什么。`ask` 委托给组合的应答者链,链的无应答默认值为 `unavailable`;`never` 确定性地返回 `rejected`,不分发任何应答者。生效值为会话日志中最后一条 `approval/policy` 事件,回退到服务配置。消费方通过 `ctx.approval.effectivePolicy(session)` 读取;`setApprovalPolicy(session, policy)` 是唯一的写入路径,因此回放能重建覆盖值。
37
37
 
38
38
  ```ts type-equiv
39
39
  /**
@@ -60,7 +60,7 @@ type ApprovalPolicy = 'ask' | 'never'
60
60
  * Readonly same-process permission question. `callId` links to an already
61
61
  * presented tool call, so arguments are not duplicated here.
62
62
  */
63
- interface ApprovalRequest {
63
+ interface ApprovalRequest extends ApprovalRequestEvent {
64
64
  /**
65
65
  * The agent on whose behalf the question is asked. Routes the question (a
66
66
  * UI answerer only answers for agents it owns) and receives the audit
@@ -73,7 +73,7 @@ interface ApprovalRequest {
73
73
  * The exact tool call being decided, when the asker has one — lets a UI
74
74
  * attach the prompt to the tool call it already streamed.
75
75
  */
76
- readonly callId?: CallId
76
+ readonly callId?: ToolCallId
77
77
  /** The asker's human-readable explanation of WHY it is asking. */
78
78
  readonly reason?: string
79
79
  /**
@@ -154,20 +154,20 @@ Source: [`packages/interaction/user-approval/src/index.ts`](https://github.com/d
154
154
 
155
155
  #### `approval/request` — waterfall
156
156
 
157
- Ask composed answerers for one decision. Return an outcome to claim the request or call `next()`; failure yields the fail-closed default. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
157
+ Ask composed answerers for one decision. Return an outcome to claim the request or call `next()` to delegate. Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
158
158
 
159
159
  ```ts cordis-catalog
160
160
  /**
161
161
  * Ask composed answerers for one decision. Return an outcome to claim the
162
- * request or call `next()`; failure yields the fail-closed default.
163
- * Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
164
- * @param req - the pending decision (agent, tool identity, reason, signal).
162
+ * request or call `next()` to delegate. Scope-filtered dispatch
163
+ * (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
164
+ * @param req - pending approval request.
165
165
  * @mode waterfall
166
166
  */
167
- 'approval/request'(this: Scoped<ApprovalService>, req: ApprovalRequest, next: () => Promise<ApprovalOutcome>): Promise<ApprovalOutcome>
167
+ 'approval/request'( this: Scoped<Agent>, req: ApprovalRequestEvent, next: () => Promise<ApprovalOutcome>, ): Promise<ApprovalOutcome>
168
168
  ```
169
169
 
170
- Types: [Scoped](./scope.md)
170
+ Types: [Agent](./core.md) · [Scoped](./scope.md)
171
171
 
172
- Source: [`packages/interaction/user-approval/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/interaction/user-approval/src/index.ts)
172
+ Source: [`packages/interaction/user-approval/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/interaction/user-approval/src/types.ts)
173
173
  <!-- END GENERATED cordis-surface -->
@@ -5,32 +5,31 @@ outline: [2,3]
5
5
 
6
6
  # Client 模块
7
7
 
8
- Web 插件表:[dsh-client-modules](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModules`(`ClientModuleRegistry`)形式提供。它扫描宿主 Loader 的 entry,找出声明了 `dsh.client` 的包,组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins/<id>/client.js` 提供各个 bundle,并以启动 manifest(元数据清单)行回应每次 index 注入收集——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/host/webserver) 的消费方:[web-server.md](./web-server.md) 所述的载体提供本服务注册的前缀路由与其回应的 `webserver/index-inject` 事件。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/modules/README.zh.md)中,不在本页。
8
+ Web 插件表:[dsh-client-modules](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/modules) 中 client 模块系统的 Node 半,以 `ctx.clientModules`(`ClientModuleRegistry`)形式提供。它扫描宿主 Loader 的 entry,找出声明了 `dsh.client` 的包,组合出 `window.__DSH_BOOT__` entry 图,在 `/plugins` 下提供带版本的单资源或多资源 combo 脚本,并以启动协议行回应每次 index 注入收集——这是同一个服务的四个面。它是 Web GUI 栈的一项可选能力,不属于 agent loop(智能体循环)主干,并且是 [dsh-host-webserver](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/host/webserver) 的消费方:[web-server.md](./web-server.md) 所述的载体提供本服务注册的前缀路由与其回应的 `webserver/index-inject` 事件。同一个包的浏览器半(`ctx.modules`,即拉取并物化这些 bundle 的 lazy CJS 模块表)属于内核机件,记录在[包 README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/modules/README.zh.md)中,不在本页。
9
9
 
10
10
  源码:[`packages/client/modules/src/client/manifest.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/modules/src/client/manifest.ts)
11
11
 
12
12
  ## wire
13
13
 
14
- 图是 Node 半与浏览器半之间协议层的唯一真源:宿主从扫描到的包组合出 `WebBootEntry` 行,把图发布为一条 `global` 注入行、渲染在后续 script 行之前(`globalThis["__DSH_BOOT__"]`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素),壳则在启动任何东西之前先解析它。没有有效 manifest 的页面无法启动——浏览器侧的解析器在图缺失或畸形时大声抛错。
14
+ 图是 Node 半与浏览器半之间协议层的唯一真源。宿主从扫描到的包组合出 `WebBootEntry` 行与 `WebBootBatch` 描述,随后在 Vite entry 之前向结构化 index 注入表贡献 registration facade、application preload、bootstrap 脚本与图全局量。`global` 行渲染为 `globalThis["__DSH_BOOT__"]`,其中 `<` 已转义,插件可控的字符串因此无法逃出 script 元素。没有有效 manifest 的页面无法启动:浏览器解析器会拒绝畸形 row 或批次、未知成员,以及未恰好归属一个初始 combo 描述的 entry。
15
15
 
16
16
  ```ts type-equiv
17
17
  /**
18
18
  * One composed client entry pushed by the host (a graph row). Wire
19
19
  * single source: the host node half (package root) produces this same shape.
20
- * `immediately` marks stage-one prefetch; `inject` is informational graph
21
- * metadata (the authoritative edges live in each package's `dsh.client`
22
- * declaration and reach fibers through entry creation). `external` carries
23
- * module-graph edges: unlike `inject`, they constrain code arrival because
24
- * `require` is synchronous (see {@link WebBootGraph.entries}).
20
+ * `immediately` marks stage-one prefetch. `inject` names package rows whose
21
+ * factories must arrive before this row materializes, while Cordis separately
22
+ * uses the same package edges to compose entries. `external` carries exact
23
+ * non-inject module requests (see {@link WebBootGraph.entries}).
25
24
  */
26
25
  interface WebBootEntry {
27
26
  /** Entry name == package name. */
28
27
  id: string
29
- /** Bundle endpoint, '/plugins/<id>/client.js?rev=<rev>'. */
28
+ /** Revisioned single-resource combo endpoint used by HMR. */
30
29
  url: string
31
- /** Bundle content hash (cache-busting consistency anchor). */
30
+ /** Opaque plugin-artifact revision used for HMR cache busting. */
32
31
  rev: string
33
- /** Package-name dependency edges, informational (preflight display / HMR diffing). */
32
+ /** Package-name dependency edges used for factory arrival and plugin composition. */
34
33
  inject?: string[]
35
34
  /** Stage-one prefetch mark: load the script for factory registration during module-face boot. */
36
35
  immediately?: boolean
@@ -39,6 +38,25 @@ interface WebBootEntry {
39
38
  }
40
39
  ```
41
40
 
41
+ ```ts type-equiv
42
+ /** Initial scheduling phase for one content-addressed combo script. */
43
+ type WebBootBatchPhase = 'bootstrap' | 'application'
44
+ ```
45
+
46
+ ```ts type-equiv
47
+ /** One initial combo script; a scheduling phase may span several descriptors. */
48
+ interface WebBootBatch {
49
+ /** Parser-blocking bootstrap or preloaded application scheduling. */
50
+ phase: WebBootBatchPhase
51
+ /** Content-addressed combo script endpoint. */
52
+ url: string
53
+ /** Revision over the combined plugin script bytes and indexed source map. */
54
+ rev: string
55
+ /** Graph entry ids whose factories the script registers, in execution order. */
56
+ entries: string[]
57
+ }
58
+ ```
59
+
42
60
  ```ts type-equiv
43
61
  /** The composed client entry graph the host injects as `window.__DSH_BOOT__`. */
44
62
  interface WebBootGraph {
@@ -50,28 +68,42 @@ interface WebBootGraph {
50
68
  * unrelated and remains owned by fiber service waiting.
51
69
  */
52
70
  entries: WebBootEntry[]
71
+ /** Initial combo descriptors; every entry belongs to exactly one descriptor. */
72
+ batches: WebBootBatch[]
53
73
  }
54
74
  ```
55
75
 
56
- 每一行的 `rev` 是该 bundle 的内容哈希,并作为使缓存失效的查询参数附在 URL 上;图的 `rev` 对组合后的各行做哈希,因此任何一行的变化都会改变它。`immediately` 标记第一阶段预取档位(在模块面启动期间 fetch 并执行,只做登记);惰性行在首次 import 时才拉取。
76
+ 每个初始 row 的 `rev` 都是不透明的进程 nonce 加序号,因此组合图时不会哈希每个插件产物。HMR 观察到变化后,该 row 的 revision 才改为新 bundle 及其可用 sourcemap 的哈希。初始描述把 row 划入 bootstrap 与 application 两个调度阶段,每个阶段都可以包含多条描述。URL 只含有序 package 资源列表与 revision,阶段名不会进入路由。图组合保持 row 顺序,并在 map 形式 URL 超过 3 KiB 前贪心切分。启动 combo revision 对合并后的插件脚本字节与 indexed sourcemap 求哈希,图 revision 则对 row 与描述一并求哈希。`immediately` 标记第一阶段的 registration barrier;同一 combo 中的 row 共享脚本传输,不同 combo 则独立加载。
57
77
 
58
78
  ## 扫描
59
79
 
60
- 包加入这张表的方式,是在自己的 package.json 中声明 `dsh.client`(`platform: 'web'`、可选的 `inject` 边、可选的 `immediately`),并在 `exports["./client"]` 导出构建好的 bundle。包解析锚定在配置树的 `ctx.baseUrl`——即 cordis.yml 所在目录,该目录的包把每个被组合的插件声明为依赖——这一锚点未设置时,构造即抛错。
80
+ 包加入这张表的方式,是在自己的 package.json 中声明 `dsh.client`(`platform: 'web'`、可选的 `inject` 边、可选的 `immediately`),并在 `exports["./client"]` 导出构建好的 bundle。每个 live row 都从自己的 Loader specifier 与所属 tree `baseUrl` 解析;若 `loader.internal.resolveSync` 可用,则使用 Host face import 所用的同一个实现。最近归属的 package manifest 提供浏览器模块 id,因此相对 source 与 built overlay 仍保留包身份。若不同的 active Loader source 解析到同一包名,组合会失败;一个来源卸载后,仍存活的来源无需重启 fiber 即可提供该 row。
61
81
 
62
82
  扫描是单包增量的;不存在全量重扫代码路径。fiber 构造或 dispose(资源释放)时的每次 cordis `internal/plugin` 发射都把该 fiber 的 entry 名标脏,一次微任务 flush 把每个脏名与实时 loader entry 对账。激活趟以全部当前 entry 灌入同一个脏集合并同步 flush,因此初扫与稳态共享一条实现——但失败姿态相反。激活时,已加载 entry 中的畸形声明或缺失 bundle 会聚合为一个大声的 `AggregateError`,列出每个损坏的包:该 fiber 进入 FAILED,由启动的大声失败 sweep 上报。稳态下,损坏的包只记录一条警告,且不得殃及其他包。
63
83
 
64
- 包元数据——包括「非 client 包」这一否定结论——按名缓存且永不过期:插件集合的变更在重启后生效。fiber 重启原样复用其行与 rev;bundle 内容变更只经 `rebuilt()` 到达图。
84
+ 包元数据——包括「非 client 包」这一否定结论——按 Loader specifier 与所属 tree base URL 缓存至重启。同一来源的 fiber 重启会原样复用其 row 与 rev;bundle 内容变更只经 `rebuilt()` 到达图。
65
85
 
66
86
  ## bundle 路由与 index 注入
67
87
 
68
- `GET`/`HEAD /plugins/<id>/client.js` `no-cache` 从磁盘提供已注册的 bundle(锚定一致性的是 rev 查询参数,而非 HTTP 缓存);其他方法返回 405。未知 id——或已注册、但 bundle 因尚未构建而不可读的行——回应一个大声的 404,因此不可读 bundle 不会表现为成功的 JavaScript 响应。注入行在每次 index 渲染时携带当前图,因此刷新页面总是针对实时组合启动。
88
+ `GET`/`HEAD /plugins/??<package-a>/client.js,<package-b>/client.js&rev=<rev>` 提供精确生成的 combo 脚本;单资源请求采用同一形式,也是 HMR 路径。其绝对 `sourceMappingURL` 平行改写每个资源后缀,得到 `/plugins/??<package-a>/client.js.map,<package-b>/client.js.map&rev=<rev>`。即使只有一个资源,map 仍采用 Indexed Source Map v3。组件有自带 map 时直接用于对应 section;没有时则获得 identity section,其 `sourcesContent` 是构建后 bundle,source 名取打包后的 `sourceURL` 或插件路由。每条启动请求 URL UTF-8 字节计算都不超过 3 KiB;切分按更长的 map 形式计算。所有 application URL 都会预加载,所有 bootstrap URL 都会在图全局量与 Vite entry 之前执行。所有已发布响应都使用长期 immutable 缓存。未知或被修改的资源列表、缺少 revision 及陈旧 revision 都返回 404,绝不提供其他字节,也不会让 SPA fallback 把 HTML 当作 JavaScript 返回;其他方法返回 405。注入行在每次 index 渲染时携带当前图,因此重新加载总是基于实时组合启动。
69
89
 
70
90
  ## 服务
71
91
 
72
- `ClientModuleRegistry`(`ctx.clientModules`,定义于 [`packages/client/modules/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/modules/src/index.ts))暴露读取面与重建面;签名见生成的[服务目录](#ctxclientmodules--clientmoduleregistry)。`graph()` 返回当前组合出的图(两次变更之间是同一个稳定对象),`clientPath(id)` 返回该 bundle 的绝对路径。`rebuilt(id)` 是 bundle 内容到达图的唯一入口:它对文件重新哈希,只有 rev 真正变化才会重新组合图并发出通知。`onRebuilt` 按发生变化的 bundle 逐个触发并携带新 rev;`onGraphChanged` 在任何一次重新组合了图的 flush 之后触发(行的增删,或 rebuilt 带来的 rev 变化),并采用拉取模型——监听器自行重读 `graph()`。两条通知路径都会兜住监听器异常,因此一个抛错的订阅者既不能让后续订阅者被跳过,也不能杀死触发这次 flush 的一方。
92
+ ```ts type-equiv
93
+ /** Filesystem baseline captured before a client artifact snapshot is read. */
94
+ interface ClientArtifactBaseline {
95
+ /** Absolute path of the client bundle. */
96
+ readonly path: string
97
+ /** Bundle modification time in milliseconds. */
98
+ readonly mtimeMs: number
99
+ /** Bundle size in bytes. */
100
+ readonly size: number
101
+ }
102
+ ```
103
+
104
+ `ClientModuleRegistry`(`ctx.clientModules`,定义于 [`packages/client/modules/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/modules/src/index.ts))暴露读取面与重建面;签名见生成的[服务目录](#ctxclientmodules--clientmoduleregistry)。`graph()` 返回当前组合出的图(两次变更之间是同一个稳定对象),`clientPath(id)` 返回 bundle 的绝对路径,`artifactBaseline(id)` 返回读取当前快照前捕获的 bundle stat 值。`rebuilt(id)` 是变化后的 bundle 内容到达图的唯一入口:它把 bundle 与当前 source map 一起重新哈希,只有 rev 真正变化才会重新组合图并发出通知。`onRebuilt` 按发生变化的 bundle 逐个触发并携带新 rev;`onGraphChanged` 在任何一次重新组合了图的 flush 之后触发(行的增删,或 rebuilt 带来的 rev 变化),并采用拉取模型——监听器自行重读 `graph()`。两条通知路径都会兜住监听器异常,因此一个抛错的订阅者既不能让后续订阅者被跳过,也不能杀死触发这次 flush 的一方。
73
105
 
74
- 开发环境下,[dsh-client-hmr](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/hmr/README.zh.md) 是注册表的监视驱动:它的 Node 半从同步取得的基线出发,对图中每一行的 bundle 做 stat 轮询,变化时调用 `rebuilt(id)`,经 `onGraphChanged` 重新同步监视集合,并通过 SSE(Server-Sent Events)把 rev 变化广播给浏览器半。生产环境的图完全不含 HMR(热模块替换)行;模块宿主自身从不监视文件。
106
+ 开发环境下,[dsh-client-hmr](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/hmr/README.zh.md) 是注册表的监视驱动:它的 Node 半从 module host 读文件前记录的基线出发,对图中每一行的 bundle 做 stat 轮询,只为变化或标脏的 row 调用 `rebuilt(id)`,经 `onGraphChanged` 重新同步监视集合,并通过 SSE(Server-Sent Events)把 rev 变化广播给浏览器半。仅 source map 变化不会触发重载;bundle 变化时,当前 map 会一起进入快照。生产环境的图完全不含 HMR(热模块替换)行;module host 自身从不监视文件。
75
107
 
76
108
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
77
109
 
@@ -101,6 +133,16 @@ graph(): WebBootGraph
101
133
  */
102
134
  clientPath(id: string): string | undefined
103
135
 
136
+ /**
137
+ * Filesystem baseline captured before an entry's current bytes were read.
138
+ * HMR compares it with the live files when installing a watch, so a write
139
+ * between startup composition and watch installation cannot disappear into
140
+ * the watcher's initial state.
141
+ * @param id - entry id (package name).
142
+ * @returns the path and baseline, or undefined for an unknown id.
143
+ */
144
+ artifactBaseline(id: string): ClientArtifactBaseline | undefined
145
+
104
146
  /**
105
147
  * Re-hash one bundle (the HMR watch's registration hook — the only entry
106
148
  * point through which bundle content changes reach the graph).
@@ -5,7 +5,7 @@ outline: [2,3]
5
5
 
6
6
  # 代码运行时
7
7
 
8
- 代码执行 seam 是一个[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md):其 Service Definition([dsh-code-runtime](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/code-runtime/code-runtime),`ctx.codeRuntime`)使用宿主提供的异步绑定运行一段模型编写的程序,并报告其打印内容与返回值。代码执行是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](./core.md) 中。各后端的执行基底与源语言不同,这两项均为服务上的只读描述符;worker-thread Service Provider 与工具注册表 Consumer 的约定见 [Code Mode 基础设计](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-15-code-mode.zh.md) 和[类型化返回约定](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-07-20-code-mode-typed-tool-returns.zh.md)。
8
+ 代码执行 seam 是一个[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-13-capability-seams.zh.md):其 Service Definition([dsh-code-runtime](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/code-runtime/code-runtime),`ctx.codeRuntime`)使用宿主提供的异步绑定运行一段模型编写的程序,并报告其打印内容与返回值。代码执行是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇定义在此而非 [core.md](./core.md) 中。各后端的执行基底与源语言不同,这两项均为服务上的只读描述符;worker-thread Service Provider 与工具注册表 Consumer 的约定见 [PTC mode 基础设计](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-06-15-ptc.zh.md) 和[类型化返回约定](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-07-20-ptc-typed-tool-returns.zh.md)。
9
9
 
10
10
  源码:[`packages/code-runtime/code-runtime/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/types.ts)
11
11
 
@@ -55,7 +55,11 @@ interface CodeRunResult {
55
55
  * rendered string; a failed or value-less run leaves this absent.
56
56
  */
57
57
  value?: CodeJsonValue
58
- /** Text the program emitted, in order, bounded only as part of the outer result. */
58
+ /**
59
+ * Captured text. Each source channel preserves emission order; interleaving
60
+ * across independent channels is backend-dependent. Bounded only as part of
61
+ * the outer result.
62
+ */
59
63
  logs: string[]
60
64
  /** Present iff the run failed; see {@link CodeRunFailure} for the taxonomy. */
61
65
  error?: CodeRunFailure
@@ -64,7 +68,7 @@ interface CodeRunResult {
64
68
 
65
69
  ## 绑定:宿主函数作为程序全局变量
66
70
 
67
- 每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(Code Mode Consumer 传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道 Consumer 的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞):
71
+ 每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(PTC mode Consumer 传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道 Consumer 的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞):
68
72
 
69
73
  ```ts type-equiv
70
74
  /**
@@ -72,7 +76,7 @@ interface CodeRunResult {
72
76
  * injects a real error constructor under `name`; rejected member calls become
73
77
  * its instances and expose the exact member name through
74
78
  * `memberNameProperty`. Both strings are runtime data rather than knowledge
75
- * of a particular consumer such as Code Mode.
79
+ * of a particular consumer such as PTC mode.
76
80
  */
77
81
  interface CodeBindingErrorClass {
78
82
  /** Constructor global and resulting `Error.name`; same portable identifier rule as {@link CodeBindingNamespace.global}. */
@@ -134,7 +138,7 @@ type CodeBindingFunction = (args: unknown) => Promise<CodeJsonValue>
134
138
 
135
139
  ## 捕获的输出与失败分类体系
136
140
 
137
- 日志是按发出顺序排列的纯字符串。运行时捕获程序的 console 与流输出,但通道和 console 方法的元数据不属于 seam,因为 Consumer 只渲染文本。实现会对序列化后的外层日志数组,以及完成值或失败消息的组合载荷设置上限;固定的结果封装语法与 Consumer 展示空白不计入这份可变载荷计量。超限会显式失败,而不会在值中插入替代内容。
141
+ 日志是纯字符串。每个来源通道保留自身的发出顺序;由于通道元数据不属于 seam,相互独立的通道如何交错由后端决定。运行时捕获程序的 console 与流输出,Consumer 只渲染文本。实现会对序列化后的外层日志数组,以及完成值或失败消息的组合载荷设置上限;固定的结果封装语法与 Consumer 展示空白不计入这份可变载荷计量。超限会显式失败,而不会在值中插入替代内容。
138
142
 
139
143
  失败类型是**正交的结果,独立报告**(见 [defensive-patterns](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/defensive-patterns.zh.md)):预算耗尽不是异常,中止不是超时,基底崩溃(如 OOM)也不是二者中的任何一个:
140
144
 
@@ -161,7 +165,7 @@ interface CodeRunFailure {
161
165
 
162
166
  ## 服务
163
167
 
164
- `CodeRuntime`(`ctx.codeRuntime`,抽象服务,定义于 [`packages/code-runtime/code-runtime/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/index.ts))由 `run(request)` 加两个只读描述符组成:`language`(程序必须使用的语言,已知值为 `'typescript'` 与 `'python'`,即 `dsh-tools` 能呈现的那些,其中只有 `'typescript'` 有已发布的后端;生成语言相关展示的 Consumer 据此切换,遇到无法展示的语言时应显式报错)和 `isolation`(执行基底,`'worker-thread'`、`'process'`、`'container'`;仅为诊断标签,**不构成安全承诺**)。实现必须保证各次运行彼此隔离(无跨运行状态),并在 dispose(资源释放)时等待系统完全停稳:teardown 要等到所有进行中的运行均已终止并结算后才完成。
168
+ `CodeRuntime`(`ctx.codeRuntime`,抽象服务,定义于 [`packages/code-runtime/code-runtime/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/code-runtime/code-runtime/src/index.ts))由 `run(request)` 加两个只读描述符组成:`language`(程序必须使用的语言,已知值为 `'typescript'` 与 `'python'`,即 `dsh-tools` 能呈现的那些,TypeScript 后端已发布、Python 后端为实验性且私有(未发布);生成语言相关展示的 Consumer 据此切换,遇到无法展示的语言时应显式报错)和 `isolation`(执行基底,`'worker-thread'`、`'process'`、`'container'`;仅为诊断标签,**不构成安全承诺**)。实现必须保证各次运行彼此隔离(无跨运行状态),并在 dispose(资源释放)时等待系统完全停稳:teardown 要等到所有进行中的运行均已终止并结算后才完成。
165
169
 
166
170
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
167
171
 
@@ -11,7 +11,7 @@ outline: [2,3]
11
11
 
12
12
  ## 输入元数据
13
13
 
14
- 该服务公开一个可选的非结构化输入描述符:提示文本加图片接受标志。命令的可用性由插件组合决定:每个消费注册表的适配器都会看到全部生效定义。
14
+ 该服务公开一个可选的非结构化输入描述符:提示文本加附件接受标志。命令的可用性由插件组合决定:每个消费注册表的适配器都会看到全部生效定义。
15
15
 
16
16
  ```ts type-equiv
17
17
  /** Immutable metadata for a command's optional unstructured input. */
@@ -19,13 +19,13 @@ interface CommandInputDescriptor {
19
19
  /** Placeholder shown before the user supplies free-form input. */
20
20
  readonly hint: string
21
21
  /**
22
- * Whether composer image attachments may accompany an invocation. Absent or
23
- * false = the executor rejects an invocation carrying images and capable
22
+ * Whether composer attachments may accompany an invocation. Absent or
23
+ * false = the executor rejects an invocation carrying attachments and capable
24
24
  * composers refuse the submission before dispatch. A declaring command's
25
25
  * handler receives the admitted durable blocks and owns every further
26
26
  * grammar decision, including rejecting sub-commands that cannot use them.
27
27
  */
28
- readonly images?: boolean
28
+ readonly attachments?: boolean
29
29
  }
30
30
  ```
31
31
 
@@ -67,13 +67,13 @@ interface CommandInvocation {
67
67
  /** Exact text following the registered command name, including separator whitespace. */
68
68
  readonly rawInput: string
69
69
  /**
70
- * Durably admitted image blocks accompanying this invocation, in submission
71
- * order; empty unless the definition declares `input.images`. The handler
70
+ * Durably admitted image and file blocks accompanying this invocation, in submission
71
+ * order; empty unless the definition declares `input.attachments`. The handler
72
72
  * owns their model-visible use — the registry never schedules them itself —
73
73
  * and a handler whose grammar cannot use them in this invocation returns an
74
74
  * error so the dispatching composer retains the originals.
75
75
  */
76
- readonly attachments: readonly ImageBlock[]
76
+ readonly attachments: readonly (ImageBlock | FileBlock)[]
77
77
  /** Cancellation signal owned by the dispatching UI request. */
78
78
  readonly signal: AbortSignal
79
79
  }
@@ -86,7 +86,7 @@ type CommandResult =
86
86
  readonly kind: 'success'
87
87
  readonly text?: string
88
88
  /** Earlier authoritative domain event that owns a richer presentation. */
89
- readonly sourceEventSeq?: number
89
+ readonly sourceEventSeq?: SessionSeq
90
90
  }
91
91
  | { readonly kind: 'error'; readonly text: string }
92
92
  ```
@@ -141,6 +141,13 @@ Human-command registry. Plain-context definitions are global; definitions regist
141
141
  */
142
142
  register(definition: CommandDefinition): () => void
143
143
 
144
+ /**
145
+ * Register the sole authority that resolves staged file receipts for command submissions.
146
+ * @param resolver - Session-aware receipt resolver.
147
+ * @returns disposer that removes this exact resolver.
148
+ */
149
+ registerFileReceiptResolver(resolver: CommandFileReceiptResolver): () => void
150
+
144
151
  /**
145
152
  * List the effective immutable command descriptors for one agent.
146
153
  * @param agent - exact receiving agent and scoped-layer key.
@@ -169,23 +176,25 @@ find(agent: Agent, name: string): CommandDefinition | undefined
169
176
  * handler-failure path is contained so the handler's own error stays the
170
177
  * reported failure.
171
178
  *
172
- * Image admission is enforced here, not in the composer: images sent to a
173
- * command that does not declare `input.images`, an absent attachment store,
174
- * and an exceeded attachment limit each settle as an error result before
175
- * the handler runs, and a rejected batch publishes no durable object.
179
+ * Attachment admission is enforced here, not in the composer: attachments sent to a
180
+ * command that does not declare `input.attachments`, an absent attachment store,
181
+ * and an exceeded image limit each settle as an error result before
182
+ * the handler runs. Validation rejection starts no attachment writes;
183
+ * a storage failure can leave only unreachable content-addressed objects
184
+ * for deferred collection.
176
185
  *
177
186
  * @param agent - exact receiving agent.
178
187
  * @param line - complete slash-command line.
179
- * @param images - base64-encoded composer images accompanying the line, in
180
- * submission order; empty for a plain invocation.
188
+ * @param submittedAttachments - encoded images and staged file receipts accompanying the line,
189
+ * in submission order; empty for a plain invocation.
181
190
  * @param signal - cancellation signal owned by the UI request.
182
191
  * @returns the settled execution (result + lifecycle pairing id), or
183
192
  * `undefined` when syntax or name does not resolve.
184
193
  */
185
- @Remote async execute( agent: Agent, line: string, images: readonly EncodedImageAttachment[], signal: AbortSignal, ): Promise<CommandExecution | undefined>
194
+ @Remote async execute( agent: Agent, line: string, submittedAttachments: readonly CommandSubmitAttachment[], signal: AbortSignal, ): Promise<CommandExecution | undefined>
186
195
  ```
187
196
 
188
- Types: [Agent](./core.md) · [EncodedImageAttachment](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/attachment.zh.md)
197
+ Types: [Agent](./core.md)
189
198
 
190
199
  Source: [`packages/interaction/commands/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/interaction/commands/src/index.ts)
191
200
 
@@ -37,11 +37,11 @@ interface CompactionResult {
37
37
  /** Human command that initiated this compaction, when it was manual. */
38
38
  sourceCommandId?: CommandId
39
39
  /** The seq of the appended `compaction/start` event. */
40
- startSeq: number
40
+ startSeq: SessionSeq
41
41
  /** The seq of the appended `compaction/summary` event. */
42
- summarySeq: number
42
+ summarySeq: SessionSeq
43
43
  /** The seq of the appended `compaction/end` event. */
44
- endSeq: number
44
+ endSeq: SessionSeq
45
45
  /** The summary content blocks produced by the backend. */
46
46
  summary: ContentBlock[]
47
47
  /**
@@ -52,9 +52,9 @@ interface CompactionResult {
52
52
  * can be GREATER than `end`. {@link CompactionResult.shadowedSeqs} is the
53
53
  * authoritative set of shadowed nodes, in surface order.
54
54
  */
55
- shadowedRange: { start: number; end: number }
55
+ shadowedRange: { start: SessionSeq; end: SessionSeq }
56
56
  /** The seqs of all shadowed surface nodes, in surface order. */
57
- shadowedSeqs: number[]
57
+ shadowedSeqs: SessionSeq[]
58
58
  /** Estimated token count of the shadowed content. */
59
59
  shadowedTokenCount: number
60
60
  }
@@ -86,7 +86,7 @@ type ManualCompactionErrorCode =
86
86
 
87
87
  `changed` 和 `summary` 保持会话表层不变,但仍会闭合失败尝试并将其持久化到日志。`commit` 可能发生在部分变更之后;`persistence` 表示内存中的标记对已闭合,但 flush 失败。取消独立于这些失败,并在完成必要清理后抛出原始 abort 原因。
88
88
 
89
- 压力压缩在串行 `agent/pre-step` 中运行,先于请求推导。一旦压力或规范化溢出满足条件,compaction-basic 会在选择范围前调用可选的 [`ctx.toolResultPruner`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/compaction/compaction-tool-result-pruner/README.zh.md),再通过 `ctx.tokenMeter` 重新测量,并且可以在不生成摘要的情况下推进 surface。失败请求的恢复在失败的步骤关闭后通过 `agent/request-error` 运行;仅当 surface replacement generation 前进时才返回重试动作,即便后续摘要工作在剪枝后抛异常亦如此;取消仍然优先。区域边界保持工具调用/结果配对,但不保持整个轮次,因此一个过大轮次中较早关闭的步骤可以被压缩。`dsh-compaction-basic` 拥有阈值、保留尾部策略、溢出上限与失败处理。
89
+ 压力压缩在 `agent/pre-step` waterfall(瀑布式事件)中运行,先于请求推导。一旦压力或规范化溢出满足条件,compaction-basic 会在选择范围前调用可选的 [`ctx.toolResultPruner`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/compaction/compaction-tool-result-pruner/README.zh.md),再通过 `ctx.tokenMeter` 重新测量,并且可以在不生成摘要的情况下推进 surface。失败请求的恢复在失败的步骤关闭后通过 `agent/request-error` 运行;仅当 surface replacement generation 前进时才返回重试动作,即便后续摘要工作在剪枝后抛异常亦如此;取消仍然优先。区域边界保持工具调用/结果配对,但不保持整个轮次,因此一个过大轮次中较早关闭的步骤可以被压缩。`dsh-compaction-basic` 拥有阈值、保留尾部策略、溢出上限与失败处理。
90
90
 
91
91
  该 Service Definition 导出 `toolPairingBalancedBefore(session, seq)` 与 `toolPairingBalancedAfter(session, seq)`,用于检查 seq 之前与之后的工具调用/结果配对。两者都会验证当前 surface 成员关系,并拒绝缺失的 seq 与遗留结果;[包约定](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/compaction/compaction/README.zh.md#tool-pairing-boundaries)定义其缓存行为。
92
92
 
@@ -98,11 +98,11 @@ type ManualCompactionErrorCode =
98
98
  /** Cited source event and size accounting for one landed surface replacement. */
99
99
  interface PrunedEntry {
100
100
  /** Full-fidelity tool-result event shadowed by the replacement. */
101
- readonly originalSeq: number
101
+ readonly originalSeq: SessionSeq
102
102
  /** Newly appended pruned tool-result event. */
103
- readonly replacementSeq: number
103
+ readonly replacementSeq: SessionSeq
104
104
  /** Tool call shared by the original and replacement. */
105
- readonly callId: CallId
105
+ readonly callId: ToolCallId
106
106
  /** Original text size in Unicode code points. */
107
107
  readonly charsBefore: number
108
108
  /** Replacement text size in Unicode code points. */
@@ -190,10 +190,10 @@ abstract compactNow( agent: ManualCompactAgentContext, signal: AbortSignal, sour
190
190
  * @throws when compaction is active or the range is missing, reversed, or unbalanced.
191
191
  * @returns the appended event seqs, summary, replaced range, and token accounting.
192
192
  */
193
- abstract compactRegion( start: number, end: number, agent: CompactionAgentContext, signal?: AbortSignal, ): Promise<CompactionResult>
193
+ abstract compactRegion( start: SessionSeq, end: SessionSeq, agent: CompactionAgentContext, signal?: AbortSignal, ): Promise<CompactionResult>
194
194
  ```
195
195
 
196
- Types: [CommandId](./commands.md)
196
+ Types: [CommandId](./commands.md) · [SessionSeq](./session.md)
197
197
 
198
198
  Source: [`packages/compaction/compaction/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/compaction/compaction/src/index.ts)
199
199
 
@@ -1,14 +1,35 @@
1
1
  ---
2
- editSource: "docs/cookbook/adding-a-conversation-node.zh.md"
2
+ editSource: "docs/subsystems/conversation.zh.md"
3
+ outline: [2,3]
3
4
  ---
4
5
 
5
- # 添加 Web Client Conversation Node
6
+ # Conversation 组装
6
7
 
7
- 本教程为 Web Client Chat 视图添加一行由业务自行拥有的内容。完成后的插件会把一个持久 Session 事件族关联成一个 Context,增量构造业务 State,发布类型化 Step 数据,再渲染 keyed Chat Node;整个过程不扫描 Session 窗口或其他已渲染节点。本教程假设 Host 已经记录这些事件,且该 Client 插件已组装进 Web bundle;Host 侧外部 UITrajectory 等额外视图目标不在本文范围内。
8
+ Conversation Client `SessionEventLikeEntry` window 与浏览器 view 之间的 target-neutral assembly 层。[`ui-conversation`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/README.zh.md)拥有 event view registry、每个 `SessionBinding` 对应的 identity-stable binding、Turn/Step Location、增量 Context assembly、target source、共享 shell 与输入编排。[`ui-chat`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/README.zh.md)[`ui-trajectory`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-trajectory/README.zh.md)等 target 包拥有各自的 Definition、最终 snapshot 与渲染。
8
9
 
9
- [Conversation Node 组装决策](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md)记录完整的引擎模型和设计理由;本文只说明实现路径。
10
+ 本文定义数据模型与业务自有 Conversation node 的扩展路径。[Web Client 架构](./web-client.md)说明该子系统在 Client model 与 Slots 之间的位置;[Conversation Node 组装决策](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-08-09-client-conversation-node-assembly.zh.md)记录其设计理由。
10
11
 
11
- ## 1. 设计可回放的事件族
12
+ ## 数据模型与所有权
13
+
14
+ Session Controller 拥有连续的已加载逻辑 event window。每个 `SessionEventLikeEntry` 要么是表示一个持久事件的 `{ type: 'event', event: SessionEvent }`,要么是表示一个 Client-only `assistant/live-chunk` 呈现的 `{ type: 'transient', event: AssistantLiveChunkEvent }`;两种内部 event 都公开 `type`、`seq`、`time` 与 `data`。`ui-conversation` 把这些 entry 直接交给 assembler,不另开 history stream。每个 Session 对应一个 `ConversationNodeAssembler`,它应用所有已注册 Definition,并为每个已注册 view target 发布独立 source。
15
+
16
+ | 概念 | Owner 与用途 |
17
+ |---|---|
18
+ | Event Definition | 业务包一次匹配一个持久 event 或 Client-only 瞬态 event,以稳定 `(kind, id)` 关联输入、折叠确定性 State,并可选择 materialize 一个 target node。 |
19
+ | Context | Engine 为一个 `(kind, id)` 拥有的有序 Match 与当前 State。一个瞬态 event 只占一个 update Match;只有 update 的证据可以保持 pending,直到分页补齐其唯一持久 start。 |
20
+ | Location | Engine 根据持久 boundary event 推导的 Session、Turn 或 Step 坐标。Definition 可以向一个 Turn 或 Step 发布类型化数据。 |
21
+ | View Definition | Target 包为每个 Session 创建一个增量 builder,并拥有该 target 的最终 snapshot 类型。 |
22
+ | View | Chat 或 Trajectory 等 Slot entry 只读取自身 target snapshot,并渲染 target 自有 node。 |
23
+
24
+ Chat 与 Trajectory 可以识别同一个持久 event family,但各自保留自己的 Definition State 与最终 node payload。共享的 target-neutral 机制只包括 identity routing、有序 replay、Location data、predecessor dependency 与 publication cadence。
25
+
26
+ ## Target 激活
27
+
28
+ 每个 Session 都保留单调增长的 active target 集合。创建或读取 target source 不会激活它。shell 会显式激活持久化选择或新选择的 View,其他消费者则通过 target source 的首个订阅激活 target。首次激活会创建该 target 的 builder,并从当前按 target 索引的 Context 调用一次 `replace()`。后续 flush 对每个 active target 调用 `apply()`,取消订阅不会移除 target。
29
+
30
+ shell 拥有 View 选择,并在 binding 创建、被选为 current 或 View roster 变化时,于渲染前解析已注册的偏好 View 或 Chat fallback。assembler 只接收解析后的 target id,不自行选择 Chat 或其他默认 target。第三方 View 使用相同的选择与激活操作。
31
+
32
+ ## 可回放 event family
12
33
 
13
34
  编写 Definition 前先选定稳定的业务 id。构成同一个 Node 的每条事件都必须携带该 id,或只凭自身 payload 独立推导出该 id;Client 绝不能把 update 猜测为属于“最近一个未完成”的 Context。
14
35
 
@@ -24,18 +45,21 @@ editSource: "docs/cookbook/adding-a-conversation-node.zh.md"
24
45
 
25
46
  系统支持增量事件。如果生产方能以较低成本发出 whole-value checkpoint,应优先采用,因为 start 位于已加载窗口之外时它仍可直接使用。每条 delta 都必须携带稳定 id,并且按照日志 `seq` 升序回放时能够确定性地产生 State;它不能依赖只存在于实时内存中的状态。如果当前历史窗口只有 update,Assembler 会保留一个 pending Context,并在更早分页补齐 start 前不构造 State。如果产品必须在 start 尚未加载时渲染,terminal 或 checkpoint 事件就必须携带足够的完整 fallback 状态,让 Definition 能直接构造结果;不要通过扫描无关事件恢复它。
26
47
 
27
- ## 2. 实现 Definition 与类型化 Chat payload
48
+ 实时 Assistant delta 作为 Client-only `assistant/live-chunk` update 到达。重连 baseline 会把活跃的进程内紧凑 stream 展开为相同的瞬态 event,持久 `assistant/message` 与 `assistant/attempt` event 则嵌入完整紧凑 stream 供历史回放。瞬态 event 只能充当 update;`start()` 只接收标准 `SessionEvent`。消费 Assistant 输出的 Definition 在同一组 `match()` 与 `update()` 方法里处理 live chunk 与持久 settlement,其他 Definition 直接返回 `null`,无需展开 stream。
49
+
50
+ ## Definition 与类型化 Chat payload
28
51
 
29
52
  为了完整展示关联关系,下面把生产方声明和 Client 贡献写在同一个代码块里。实际的包族中,branded id 与 `SessionEventMap` 声明留在事件生产方,Definition、Chat data 合并与 renderer 留在 Client 插件。
30
53
 
31
54
  ```ts ignore-check
32
55
  import { createElement } from 'react'
56
+ import type { Context as ClientContext } from '@deepseek-ai/cordis'
33
57
  import type { Branded } from '@deepseek-ai/dsh-brand'
34
58
  import type {
35
- ClientContext, ConversationLocation, ConversationNodeContext,
59
+ ConversationLocation, ConversationNodeContext,
36
60
  ConversationNodeDefinition,
37
- } from '@deepseek-ai/dsh-client-runtime/client'
38
- import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
61
+ } from '@deepseek-ai/dsh-client-ui-conversation/client'
62
+ import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-chat/client'
39
63
 
40
64
  type ReviewId = Branded<'ReviewId'>
41
65
 
@@ -90,13 +114,13 @@ interface ReviewChatData {
90
114
  readonly summary?: string
91
115
  }
92
116
 
93
- declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
117
+ declare module '@deepseek-ai/dsh-client-ui-chat/client' {
94
118
  interface ChatNodeDataMap {
95
119
  'review-job': ReviewChatData
96
120
  }
97
121
  }
98
122
 
99
- declare module '@deepseek-ai/dsh-client-runtime/client' {
123
+ declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
100
124
  interface ConversationStepDataMap {
101
125
  'review-job': ReviewChatData
102
126
  }
@@ -184,10 +208,10 @@ function ReviewNodeView({ node }: ChatNodeViewProps<'review-job'>) {
184
208
  return createElement('p', null, text)
185
209
  }
186
210
 
187
- export const inject = ['conversationEvents', 'slots']
211
+ export const inject = ['uiConversation', 'slots']
188
212
 
189
213
  export function apply(ctx: ClientContext): void {
190
- ctx.conversationEvents.register(reviewDefinition)
214
+ ctx.uiConversation.events.register(reviewDefinition)
191
215
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
192
216
  name: 'conversation.chat.node',
193
217
  key: 'review-job',
@@ -195,33 +219,33 @@ export function apply(ctx: ClientContext): void {
195
219
  }
196
220
  ```
197
221
 
198
- `match(event)` 是身份提取器,不是 fold:它只能收到当前事件,并返回 Definition 内部 id 与生命周期角色。命中后,Assembler 通过 `(kind, id)` 定位 Context,再调用一次 `start`,或把当前 State 交给 `update`。两个函数都必须返回引擎随后采用的 State;推荐返回新的 immutable value,但函数原地修改后返回同一对象时,采用语义也相同。
222
+ `match(event)` 是身份提取器,不是 fold:它只能收到当前 `SessionEventLike`,并返回 Definition 内部 id 与生命周期角色。命中后,Assembler 通过 `(kind, id)` 定位 Context;标准 event 可触发一次 `start`,标准或 packed event 可把当前 State 交给 `update`。两个函数都必须返回引擎随后采用的 State;推荐返回新的 immutable value,但函数原地修改后返回同一对象时,采用语义也相同。
199
223
 
200
224
  `buildLocationData(context, scope)` 可以把 Definition 拥有的数据发布到引擎拥有的 Turn 或 Step 上。通过 declaration merging 为每个 key 指定精确 value 类型。同一 Location 内的另一个 Node 可以使用受限 slot hook(例如 `useTurnData(key)`)读取该值,无须取得 Session,也无须扫描 `snapshot.chat.nodes`。
201
225
 
202
226
  `target` 与 `buildViewNode(context)` 必须同时声明一项由 target 拥有的渲染贡献。把 `context.key` 保留为 React 侧身份,根据持久排序证据选择 `anchorSeq`,并且只返回 renderer 可以直接使用的数据。某个 target Node 一旦发布,就要继续返回同一个 key;需要暂时离开可见流时使用 `visibility: 'hidden'`,不要改为返回 `null` 撤回它。
203
227
 
204
- ## 3. 只在 start 时查询更早的业务 Context
228
+ ## Predecessor read
205
229
 
206
230
  有些 Definition 需要另一个业务 kind 在当前位置之前的最新 State。`start` 会收到 `ConversationContextReader`;应在这里调用 `reader.previous<State>(kind)`,不要接收 Context 集合或扫描事件。Reader 返回当前 start `seq` 之前最近一个已启动 Context 的只读数据。
207
231
 
208
232
  Assembler 会记录这项依赖。如果后续 older prepend 带来了更近的前序 Context、补齐了原先未知的窗口缺口,或者前序 State 被修订,引擎会从 `start` 重新运行依赖方 Context,并按 `seq` 升序回放其 update。被查询的 Definition 仍负责把有用信息写入自身 State;Reader 不提供业务专用查询方法,也不授予修改其他 Context 的权限。
209
233
 
210
- ## 4. 理解三条摄入路径
234
+ ## Window 更新路径
211
235
 
212
- 历史可能从尾部开始一页一页向前请求,但每个已接收分页都会先按 `seq` 升序归一化,再进入 State 回放。
236
+ 历史可能从尾部开始一页一页向前请求。Session journal 先校验互不重叠的逻辑 seq range,Assembler 再按每个已接受 input 的首 `seq` 排序并进入 State 回放。
213
237
 
214
238
  | 路径 | 引擎工作 | Definition 可观察到的行为 |
215
239
  |---|---|---|
216
- | open、resync 或 gap repair 时 replace | 重建已加载窗口,每条事件对每个 Definition 匹配一次,再回放每个已有 start 的 Context | 先执行 `start`,再按 `seq` 升序执行其 update;只有 update 的 pending Context 仍没有 State |
217
- | prepend 一页更早历史 | 只匹配新增的更早事件,按 `(kind, id)` 合并进 Context,保留现有 keyed node,并只重放受影响的 Context 与依赖 | 新发现的 start 会激活已收集 update;Location 或前序依赖变化也可能重跑 Context |
218
- | append 一条实时事件 | 每个 Definition 各调用一次 `match`,按 key 查找命中的 Context,只更新该 Context | 对 start 之后的匹配事件执行一次 `update` 并请求一次发布;不扫描已有 Context |
240
+ | open、resync 或 gap repair 时 replace | 重建已加载窗口,每条标准 event 或 packed run 对每个 Definition 匹配一次,再回放每个已有 start 的 Context | 先执行 `start`,再按逻辑 `seq` 升序执行其 update;只有 update 的 pending Context 仍没有 State |
241
+ | prepend 一页更早历史 | 只匹配新增的更早 input,按 `(kind, id)` 合并进 Context,保留现有 keyed node,并只重放受影响的 Context 与依赖 | 新发现的 scalar start 会激活已收集的 scalar 与 packed update;Location 或前序依赖变化也可能重跑 Context |
242
+ | append 一条实时事件 | 每个 Definition 各调用一次 `match`,按 key 查找命中的 Context,只更新该 Context | 对 start 之后的匹配事件执行一次 scalar `update` 并请求一次发布;不扫描已有 Context |
219
243
 
220
- 注册 `D` 个 Definition 时,一条新事件会进行 `D` 次仅当前事件匹配;命中后的 Context key 查询是常数时间。Definition 代码必须维持这个性质:正常 append 热路径不得遍历完整事件窗口、所有 Context、`context.matches` 或已渲染 Node 集合。累计事实放进 State,同 Turn/Step 共享信息放进 Location data,有索引的前序依赖使用 `reader.previous()`。
244
+ 注册 `D` 个 Definition 时,一条新 scalar event 或 packed run 会进行 `D` 次仅当前 input 匹配;命中后的 Context key 查询是常数时间。Definition 代码必须维持这个性质:正常 append 热路径不得遍历完整事件窗口、所有 Context、`context.matches` 或已渲染 Node 集合。累计事实放进 State,同 Turn/Step 共享信息放进 Location data,有索引的前序依赖使用 `reader.previous()`。
221
245
 
222
- `publication` 控制发生 State 变更后何时物化。结构或 terminal 变化使用 `immediate`,高频可见 delta 使用 `animation-frame`,只为后续发布积累 State 时使用 `none`。引擎仍会按日志顺序应用每条 update;该选项只合并视图发布频率。
246
+ `publication` 控制发生 State 变更后何时物化。结构或 terminal 变化使用 `immediate`,高频可见 delta 使用 `animation-frame`,只为后续发布积累 State 时使用 `none`。引擎按日志顺序应用每条 scalar update,并用一次 batch update 应用一个 packed run;该选项只合并视图发布频率。
223
247
 
224
- ## 5. 验证回放、分页与渲染
248
+ ## 验证要求
225
249
 
226
250
  添加聚焦测试,证明以下结果:
227
251
 
@@ -231,5 +255,7 @@ Assembler 会记录这项依赖。如果后续 older prepend 带来了更近的
231
255
  4. prepend 更早分页只增加更早的行;数据未变化的既有 keyed Node value 不被替换。
232
256
  5. 重复的可见 delta 保持 `context.key`,并在请求 `animation-frame` 时每帧最多发布一次。
233
257
  6. keyed renderer 只消费 `node.data` 与受限 Location hook,不扫描 Session 事件窗口、Context 或 Chat Node。
258
+ 7. scalar 与 packed Assistant 历史产生相同的最终 State、timing boundary 和 target snapshot;一个 packed run 在 replace、prepend、Location replay 与 registry rebuild 中始终只保留一个 Match。
259
+ 8. 创建 target source 不执行 builder 工作;显式选择或首次订阅执行一次完整 replace,后续更新送达所有 active target,重复激活不会再次 replace。
234
260
 
235
- 流式与中断处理可参考 [`packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/src/client/conversation-nodes/assistant.ts),前序查询可参考 [`inbox.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/src/client/conversation-nodes/inbox.ts) 与 [`message.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-conversation/src/client/conversation-nodes/message.ts),只发布 Turn data 而不创建自有 Node 的例子见 [`packages/client/ui-deliverables`](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-deliverables)。
261
+ 流式与中断处理可参考 [`packages/client/ui-chat/src/client/conversation-nodes/assistant.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/assistant.ts),前序查询可参考 [`inbox.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/inbox.ts) 与 [`message.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-chat/src/client/conversation-nodes/message.ts),只发布 Turn data 而不创建自有 Node 的例子见 [`packages/client/ui-deliverables`](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/client/ui-deliverables)。