dsh-plugin-dev-kb 1.0.8 → 1.0.9

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 (169) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.en.md +6 -6
  3. package/README.md +6 -6
  4. package/kb/INDEX.md +19 -5
  5. package/kb/README.md +11 -10
  6. package/kb/extra/AGENTS.md +4 -4
  7. package/kb/extra/cookbook/adding-a-vendored-package.md +2 -2
  8. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +2 -2
  9. package/kb/extra/deepseek-llm-api-wire-extensions.md +159 -0
  10. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +159 -0
  11. package/kb/extra/development.md +8 -14
  12. package/kb/extra/development.zh.md +8 -14
  13. package/kb/extra/event-producer-consumer.md +47 -41
  14. package/kb/extra/event-producer-consumer.zh.md +47 -41
  15. package/kb/extra/glossary.md +1 -1
  16. package/kb/extra/glossary.zh.md +1 -1
  17. package/kb/extra/graph-atlas.md +0 -2
  18. package/kb/extra/graph-atlas.zh.md +0 -2
  19. package/kb/extra/i18n/README.md +4 -4
  20. package/kb/extra/i18n/README.zh.md +4 -4
  21. package/kb/extra/module-graph.md +680 -413
  22. package/kb/extra/module-graph.zh.md +681 -414
  23. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +2 -2
  24. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +2 -2
  25. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +2 -2
  26. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +2 -2
  27. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +2 -2
  28. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +2 -2
  29. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +1 -1
  30. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +1 -1
  31. package/kb/extra/rescope.md +2 -2
  32. package/kb/extra/rescope.zh.md +2 -2
  33. package/kb/extra/subsystems/agent-team.md +24 -1
  34. package/kb/extra/subsystems/agent-team.zh.md +24 -1
  35. package/kb/extra/subsystems/attachment.md +12 -4
  36. package/kb/extra/subsystems/attachment.zh.md +12 -4
  37. package/kb/extra/subsystems/extensions.md +18 -0
  38. package/kb/extra/subsystems/extensions.zh.md +18 -0
  39. package/kb/extra/subsystems/feedback.md +2 -2
  40. package/kb/extra/subsystems/feedback.zh.md +2 -2
  41. package/kb/extra/subsystems/todo.md +32 -0
  42. package/kb/extra/subsystems/todo.zh.md +32 -0
  43. package/kb/extra/subsystems/webhook.md +70 -0
  44. package/kb/extra/subsystems/webhook.zh.md +70 -0
  45. package/kb/extra/testing.md +11 -10
  46. package/kb/extra/testing.zh.md +8 -7
  47. package/kb/meta/search-index.json +269 -161
  48. package/kb/meta/site-pages.txt +182 -168
  49. package/kb/meta/source.json +5 -5
  50. package/kb/meta/topics.md +14 -6
  51. package/kb/site/develop/basic/publish.md +2 -2
  52. package/kb/site/develop/basic/tool.md +1 -1
  53. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +4 -4
  54. package/kb/site/develop/framework/events.md +1 -1
  55. package/kb/site/develop/practice/dynamic-cordis.md +17 -0
  56. package/kb/site/develop/practice/llm-adapter.md +3 -3
  57. package/kb/site/en/develop/basic/publish.md +2 -2
  58. package/kb/site/en/develop/basic/tool.md +1 -1
  59. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +4 -4
  60. package/kb/site/en/develop/framework/events.md +1 -1
  61. package/kb/site/en/develop/practice/dynamic-cordis.md +17 -0
  62. package/kb/site/en/develop/practice/llm-adapter.md +3 -3
  63. package/kb/site/en/guide/github-review.md +104 -0
  64. package/kb/site/en/guide/mcp-memory.md +103 -0
  65. package/kb/site/en/guide/python-sdk.md +80 -34
  66. package/kb/site/en/guide/schedule.md +21 -0
  67. package/kb/site/en/reference/agent-lifecycle.md +1 -1
  68. package/kb/{extra → site/en/reference}/api-gateway.md +11 -9
  69. package/kb/site/en/reference/capability-seams.md +115 -67
  70. package/kb/site/en/reference/config-catalog.md +358 -164
  71. package/kb/site/en/reference/cookbook/adding-a-package.md +2 -2
  72. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +2 -2
  73. package/kb/site/en/reference/cookbook/adding-a-tool.md +11 -4
  74. package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +1 -1
  75. package/kb/site/en/reference/cookbook/extension-cookbook.md +6 -6
  76. package/kb/site/en/reference/cordis-api/inherited.md +1 -1
  77. package/kb/site/en/reference/cordis-primer.md +2 -1
  78. package/kb/site/en/reference/index.md +19 -7
  79. package/kb/site/en/reference/persistence-catalog.md +91 -44
  80. package/kb/site/en/reference/subsystems/approval.md +10 -10
  81. package/kb/site/en/reference/subsystems/client-modules.md +58 -16
  82. package/kb/site/en/reference/subsystems/code-runtime.md +3 -3
  83. package/kb/site/en/reference/subsystems/compaction.md +2 -2
  84. package/kb/site/en/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +43 -24
  85. package/kb/site/en/reference/subsystems/core.md +70 -12
  86. package/kb/site/en/reference/subsystems/credentials.md +43 -3
  87. package/kb/site/en/reference/subsystems/filesystem.md +12 -2
  88. package/kb/site/en/reference/subsystems/index.md +6 -1
  89. package/kb/site/en/reference/subsystems/jobs.md +1 -1
  90. package/kb/site/en/reference/subsystems/llm-streaming.md +132 -11
  91. package/kb/site/en/reference/subsystems/permission-presets.md +1 -1
  92. package/kb/site/en/reference/subsystems/persistence.md +22 -3
  93. package/kb/site/en/reference/subsystems/plan.md +1 -1
  94. package/kb/site/en/reference/subsystems/session-projection.md +74 -33
  95. package/kb/site/en/reference/subsystems/session-query.md +9 -1
  96. package/kb/site/en/reference/subsystems/session-reference.md +28 -7
  97. package/kb/site/en/reference/subsystems/session-telemetry.md +2 -3
  98. package/kb/site/en/reference/subsystems/session.md +260 -41
  99. package/kb/site/en/reference/subsystems/settings.md +78 -1
  100. package/kb/site/en/reference/subsystems/skills.md +23 -0
  101. package/kb/site/en/reference/subsystems/slots.md +177 -0
  102. package/kb/site/en/reference/subsystems/spill.md +2 -2
  103. package/kb/site/en/reference/subsystems/storage.md +9 -1
  104. package/kb/site/en/reference/subsystems/subagent.md +90 -23
  105. package/kb/site/en/reference/subsystems/system-prompt.md +4 -4
  106. package/kb/site/en/reference/subsystems/token-meter.md +25 -10
  107. package/kb/site/en/reference/subsystems/tools.md +39 -39
  108. package/kb/site/en/reference/subsystems/typert.md +44 -37
  109. package/kb/site/en/reference/subsystems/user-questions.md +33 -33
  110. package/kb/site/en/reference/subsystems/web-client.md +98 -0
  111. package/kb/site/en/reference/subsystems/web-server.md +11 -5
  112. package/kb/site/en/reference/subsystems/web.md +7 -1
  113. package/kb/site/en/reference/subsystems/workspace.md +95 -2
  114. package/kb/site/en/reference/tool-catalog.md +76 -18
  115. package/kb/site/en/reference/tool-execution-pipeline.md +1 -1
  116. package/kb/site/guide/github-review.md +104 -0
  117. package/kb/site/guide/mcp-memory.md +103 -0
  118. package/kb/site/guide/python-sdk.md +87 -41
  119. package/kb/site/guide/schedule.md +21 -0
  120. package/kb/site/reference/agent-lifecycle.md +1 -1
  121. package/kb/{extra/api-gateway.zh.md → site/reference/api-gateway.md} +11 -9
  122. package/kb/site/reference/capability-seams.md +115 -67
  123. package/kb/site/reference/config-catalog.md +357 -163
  124. package/kb/site/reference/cookbook/adding-a-package.md +2 -2
  125. package/kb/site/reference/cookbook/adding-a-settings-card.md +2 -2
  126. package/kb/site/reference/cookbook/adding-a-tool.md +11 -4
  127. package/kb/site/reference/cookbook/adding-an-llm-adapter.md +1 -1
  128. package/kb/site/reference/cookbook/extension-cookbook.md +6 -6
  129. package/kb/site/reference/cordis-api/inherited.md +1 -1
  130. package/kb/site/reference/cordis-primer.md +2 -1
  131. package/kb/site/reference/index.md +19 -7
  132. package/kb/site/reference/persistence-catalog.md +87 -40
  133. package/kb/site/reference/subsystems/approval.md +10 -10
  134. package/kb/site/reference/subsystems/client-modules.md +58 -16
  135. package/kb/site/reference/subsystems/code-runtime.md +3 -3
  136. package/kb/site/reference/subsystems/compaction.md +2 -2
  137. package/kb/site/reference/{cookbook/adding-a-conversation-node.md → subsystems/conversation.md} +43 -24
  138. package/kb/site/reference/subsystems/core.md +70 -12
  139. package/kb/site/reference/subsystems/credentials.md +43 -3
  140. package/kb/site/reference/subsystems/filesystem.md +12 -2
  141. package/kb/site/reference/subsystems/index.md +6 -1
  142. package/kb/site/reference/subsystems/jobs.md +1 -1
  143. package/kb/site/reference/subsystems/llm-streaming.md +132 -11
  144. package/kb/site/reference/subsystems/persistence.md +22 -3
  145. package/kb/site/reference/subsystems/plan.md +1 -1
  146. package/kb/site/reference/subsystems/session-projection.md +74 -33
  147. package/kb/site/reference/subsystems/session-query.md +9 -1
  148. package/kb/site/reference/subsystems/session-reference.md +28 -7
  149. package/kb/site/reference/subsystems/session-telemetry.md +2 -3
  150. package/kb/site/reference/subsystems/session.md +260 -41
  151. package/kb/site/reference/subsystems/settings.md +78 -1
  152. package/kb/site/reference/subsystems/skills.md +23 -0
  153. package/kb/site/reference/subsystems/slots.md +177 -0
  154. package/kb/site/reference/subsystems/spill.md +2 -2
  155. package/kb/site/reference/subsystems/storage.md +9 -1
  156. package/kb/site/reference/subsystems/subagent.md +90 -23
  157. package/kb/site/reference/subsystems/system-prompt.md +4 -4
  158. package/kb/site/reference/subsystems/token-meter.md +25 -10
  159. package/kb/site/reference/subsystems/tools.md +39 -39
  160. package/kb/site/reference/subsystems/typert.md +44 -37
  161. package/kb/site/reference/subsystems/user-questions.md +33 -33
  162. package/kb/site/reference/subsystems/web-client.md +98 -0
  163. package/kb/site/reference/subsystems/web-server.md +11 -5
  164. package/kb/site/reference/subsystems/web.md +7 -1
  165. package/kb/site/reference/subsystems/workspace.md +95 -2
  166. package/kb/site/reference/tool-catalog.md +76 -18
  167. package/kb/site/reference/tool-execution-pipeline.md +1 -1
  168. package/package.json +2 -2
  169. 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
 
@@ -64,7 +64,7 @@ interface CodeRunResult {
64
64
 
65
65
  ## 绑定:宿主函数作为程序全局变量
66
66
 
67
- 每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(Code Mode Consumer 传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道 Consumer 的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞):
67
+ 每个 `CodeBindingNamespace` 在程序内成为一个由异步可调用函数组成的全局对象(PTC mode Consumer 传入一个:`tools`)。参数与返回值必须是无损 JSON,且跨越边界时不受 seam 层字节上限约束;运行时可以通过结构化克隆桥接它们。命名空间可以声明程序可见的错误类,而无需让运行时知道 Consumer 的名称:运行时会注入真实构造函数,并将被拒绝的调用转为该类的实例。运行时也将绑定名视为不可信输入(`__proto__` 是普通自有属性,绝不会发生原型碰撞):
68
68
 
69
69
  ```ts type-equiv
70
70
  /**
@@ -72,7 +72,7 @@ interface CodeRunResult {
72
72
  * injects a real error constructor under `name`; rejected member calls become
73
73
  * its instances and expose the exact member name through
74
74
  * `memberNameProperty`. Both strings are runtime data rather than knowledge
75
- * of a particular consumer such as Code Mode.
75
+ * of a particular consumer such as PTC mode.
76
76
  */
77
77
  interface CodeBindingErrorClass {
78
78
  /** Constructor global and resulting `Error.name`; same portable identifier rule as {@link CodeBindingNamespace.global}. */
@@ -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
 
@@ -102,7 +102,7 @@ interface PrunedEntry {
102
102
  /** Newly appended pruned tool-result event. */
103
103
  readonly replacementSeq: number
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. */
@@ -1,14 +1,29 @@
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 }` 或 `{ type: 'chunks', event: ChunkRowEvent }`;两种内部 event 都公开 `type`、`seq`、`time` 与 `data`。`ui-conversation` 把这些 entry 直接交给 assembler,不另开 history stream、不转换 record,也不展开 packed member。每个 Session 对应一个 `ConversationNodeAssembler`,它应用所有已注册 Definition,并为每个已注册 view target 发布独立 source。
15
+
16
+ | 概念 | Owner 与用途 |
17
+ |---|---|
18
+ | Event Definition | 业务包一次匹配一条标准 event 或一个 packed Assistant run,以稳定 `(kind, id)` 关联输入、折叠确定性 State,并可选择 materialize 一个 target node。 |
19
+ | Context | Engine 为一个 `(kind, id)` 拥有的有序 Match 与当前 State。一个 packed run 只占一个 update Match;只有 update 的证据可以保持 pending,直到分页补齐其唯一 scalar 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
+ ## 可回放 event family
12
27
 
13
28
  编写 Definition 前先选定稳定的业务 id。构成同一个 Node 的每条事件都必须携带该 id,或只凭自身 payload 独立推导出该 id;Client 绝不能把 update 猜测为属于“最近一个未完成”的 Context。
14
29
 
@@ -24,18 +39,21 @@ editSource: "docs/cookbook/adding-a-conversation-node.zh.md"
24
39
 
25
40
  系统支持增量事件。如果生产方能以较低成本发出 whole-value checkpoint,应优先采用,因为 start 位于已加载窗口之外时它仍可直接使用。每条 delta 都必须携带稳定 id,并且按照日志 `seq` 升序回放时能够确定性地产生 State;它不能依赖只存在于实时内存中的状态。如果当前历史窗口只有 update,Assembler 会保留一个 pending Context,并在更早分页补齐 start 前不构造 State。如果产品必须在 start 尚未加载时渲染,terminal 或 checkpoint 事件就必须携带足够的完整 fallback 状态,让 Definition 能直接构造结果;不要通过扫描无关事件恢复它。
26
41
 
27
- ## 2. 实现 Definition 与类型化 Chat payload
42
+ 连续且属于同一 block 的历史 `assistant/chunk` delta 会以 `chunkrow/text-chunks`、`chunkrow/reasoning-chunks` 或 `chunkrow/tool-call-chunks` 到达。顶层 `seq` 与 `time` 表示首个逻辑成员,`data` 保留每个 fragment 与 timestamp gap。这些 Client-only event 只能充当 update;`start()` 只接收标准 `SessionEvent`。消费 Assistant delta 的 Definition 在同一组 `match()` 与 `update()` 方法里处理相关 packed tag,其他 Definition 直接返回 `null`,无需展开该 run。
43
+
44
+ ## Definition 与类型化 Chat payload
28
45
 
29
46
  为了完整展示关联关系,下面把生产方声明和 Client 贡献写在同一个代码块里。实际的包族中,branded id 与 `SessionEventMap` 声明留在事件生产方,Definition、Chat data 合并与 renderer 留在 Client 插件。
30
47
 
31
48
  ```ts ignore-check
32
49
  import { createElement } from 'react'
50
+ import type { Context as ClientContext } from '@deepseek-ai/cordis'
33
51
  import type { Branded } from '@deepseek-ai/dsh-brand'
34
52
  import type {
35
- ClientContext, ConversationLocation, ConversationNodeContext,
53
+ ConversationLocation, ConversationNodeContext,
36
54
  ConversationNodeDefinition,
37
- } from '@deepseek-ai/dsh-client-runtime/client'
38
- import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
55
+ } from '@deepseek-ai/dsh-client-ui-conversation/client'
56
+ import type { ChatNodeViewProps } from '@deepseek-ai/dsh-client-ui-chat/client'
39
57
 
40
58
  type ReviewId = Branded<'ReviewId'>
41
59
 
@@ -90,13 +108,13 @@ interface ReviewChatData {
90
108
  readonly summary?: string
91
109
  }
92
110
 
93
- declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
111
+ declare module '@deepseek-ai/dsh-client-ui-chat/client' {
94
112
  interface ChatNodeDataMap {
95
113
  'review-job': ReviewChatData
96
114
  }
97
115
  }
98
116
 
99
- declare module '@deepseek-ai/dsh-client-runtime/client' {
117
+ declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
100
118
  interface ConversationStepDataMap {
101
119
  'review-job': ReviewChatData
102
120
  }
@@ -184,10 +202,10 @@ function ReviewNodeView({ node }: ChatNodeViewProps<'review-job'>) {
184
202
  return createElement('p', null, text)
185
203
  }
186
204
 
187
- export const inject = ['conversationEvents', 'slots']
205
+ export const inject = ['uiConversation', 'slots']
188
206
 
189
207
  export function apply(ctx: ClientContext): void {
190
- ctx.conversationEvents.register(reviewDefinition)
208
+ ctx.uiConversation.events.register(reviewDefinition)
191
209
  ctx.slots.inject('conversation.chat.node', () => ctx.slots.register({
192
210
  name: 'conversation.chat.node',
193
211
  key: 'review-job',
@@ -195,33 +213,33 @@ export function apply(ctx: ClientContext): void {
195
213
  }
196
214
  ```
197
215
 
198
- `match(event)` 是身份提取器,不是 fold:它只能收到当前事件,并返回 Definition 内部 id 与生命周期角色。命中后,Assembler 通过 `(kind, id)` 定位 Context,再调用一次 `start`,或把当前 State 交给 `update`。两个函数都必须返回引擎随后采用的 State;推荐返回新的 immutable value,但函数原地修改后返回同一对象时,采用语义也相同。
216
+ `match(event)` 是身份提取器,不是 fold:它只能收到当前 `SessionEventLike`,并返回 Definition 内部 id 与生命周期角色。命中后,Assembler 通过 `(kind, id)` 定位 Context;标准 event 可触发一次 `start`,标准或 packed event 可把当前 State 交给 `update`。两个函数都必须返回引擎随后采用的 State;推荐返回新的 immutable value,但函数原地修改后返回同一对象时,采用语义也相同。
199
217
 
200
218
  `buildLocationData(context, scope)` 可以把 Definition 拥有的数据发布到引擎拥有的 Turn 或 Step 上。通过 declaration merging 为每个 key 指定精确 value 类型。同一 Location 内的另一个 Node 可以使用受限 slot hook(例如 `useTurnData(key)`)读取该值,无须取得 Session,也无须扫描 `snapshot.chat.nodes`。
201
219
 
202
220
  `target` 与 `buildViewNode(context)` 必须同时声明一项由 target 拥有的渲染贡献。把 `context.key` 保留为 React 侧身份,根据持久排序证据选择 `anchorSeq`,并且只返回 renderer 可以直接使用的数据。某个 target Node 一旦发布,就要继续返回同一个 key;需要暂时离开可见流时使用 `visibility: 'hidden'`,不要改为返回 `null` 撤回它。
203
221
 
204
- ## 3. 只在 start 时查询更早的业务 Context
222
+ ## Predecessor read
205
223
 
206
224
  有些 Definition 需要另一个业务 kind 在当前位置之前的最新 State。`start` 会收到 `ConversationContextReader`;应在这里调用 `reader.previous<State>(kind)`,不要接收 Context 集合或扫描事件。Reader 返回当前 start `seq` 之前最近一个已启动 Context 的只读数据。
207
225
 
208
226
  Assembler 会记录这项依赖。如果后续 older prepend 带来了更近的前序 Context、补齐了原先未知的窗口缺口,或者前序 State 被修订,引擎会从 `start` 重新运行依赖方 Context,并按 `seq` 升序回放其 update。被查询的 Definition 仍负责把有用信息写入自身 State;Reader 不提供业务专用查询方法,也不授予修改其他 Context 的权限。
209
227
 
210
- ## 4. 理解三条摄入路径
228
+ ## Window 更新路径
211
229
 
212
- 历史可能从尾部开始一页一页向前请求,但每个已接收分页都会先按 `seq` 升序归一化,再进入 State 回放。
230
+ 历史可能从尾部开始一页一页向前请求。Session journal 先校验互不重叠的逻辑 seq range,Assembler 再按每个已接受 input 的首 `seq` 排序并进入 State 回放。
213
231
 
214
232
  | 路径 | 引擎工作 | Definition 可观察到的行为 |
215
233
  |---|---|---|
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 |
234
+ | open、resync 或 gap repair 时 replace | 重建已加载窗口,每条标准 event 或 packed run 对每个 Definition 匹配一次,再回放每个已有 start 的 Context | 先执行 `start`,再按逻辑 `seq` 升序执行其 update;只有 update 的 pending Context 仍没有 State |
235
+ | prepend 一页更早历史 | 只匹配新增的更早 input,按 `(kind, id)` 合并进 Context,保留现有 keyed node,并只重放受影响的 Context 与依赖 | 新发现的 scalar start 会激活已收集的 scalar 与 packed update;Location 或前序依赖变化也可能重跑 Context |
236
+ | append 一条实时事件 | 每个 Definition 各调用一次 `match`,按 key 查找命中的 Context,只更新该 Context | 对 start 之后的匹配事件执行一次 scalar `update` 并请求一次发布;不扫描已有 Context |
219
237
 
220
- 注册 `D` 个 Definition 时,一条新事件会进行 `D` 次仅当前事件匹配;命中后的 Context key 查询是常数时间。Definition 代码必须维持这个性质:正常 append 热路径不得遍历完整事件窗口、所有 Context、`context.matches` 或已渲染 Node 集合。累计事实放进 State,同 Turn/Step 共享信息放进 Location data,有索引的前序依赖使用 `reader.previous()`。
238
+ 注册 `D` 个 Definition 时,一条新 scalar event 或 packed run 会进行 `D` 次仅当前 input 匹配;命中后的 Context key 查询是常数时间。Definition 代码必须维持这个性质:正常 append 热路径不得遍历完整事件窗口、所有 Context、`context.matches` 或已渲染 Node 集合。累计事实放进 State,同 Turn/Step 共享信息放进 Location data,有索引的前序依赖使用 `reader.previous()`。
221
239
 
222
- `publication` 控制发生 State 变更后何时物化。结构或 terminal 变化使用 `immediate`,高频可见 delta 使用 `animation-frame`,只为后续发布积累 State 时使用 `none`。引擎仍会按日志顺序应用每条 update;该选项只合并视图发布频率。
240
+ `publication` 控制发生 State 变更后何时物化。结构或 terminal 变化使用 `immediate`,高频可见 delta 使用 `animation-frame`,只为后续发布积累 State 时使用 `none`。引擎按日志顺序应用每条 scalar update,并用一次 batch update 应用一个 packed run;该选项只合并视图发布频率。
223
241
 
224
- ## 5. 验证回放、分页与渲染
242
+ ## 验证要求
225
243
 
226
244
  添加聚焦测试,证明以下结果:
227
245
 
@@ -231,5 +249,6 @@ Assembler 会记录这项依赖。如果后续 older prepend 带来了更近的
231
249
  4. prepend 更早分页只增加更早的行;数据未变化的既有 keyed Node value 不被替换。
232
250
  5. 重复的可见 delta 保持 `context.key`,并在请求 `animation-frame` 时每帧最多发布一次。
233
251
  6. keyed renderer 只消费 `node.data` 与受限 Location hook,不扫描 Session 事件窗口、Context 或 Chat Node。
252
+ 7. scalar 与 packed Assistant 历史产生相同的最终 State、timing boundary 和 target snapshot;一个 packed run 在 replace、prepend、Location replay 与 registry rebuild 中始终只保留一个 Match。
234
253
 
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)。
254
+ 流式与中断处理可参考 [`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)。
@@ -64,9 +64,9 @@ interface AgentHandle {
64
64
  源码:[`packages/core/agent/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/core/agent/src/types.ts)
65
65
 
66
66
  ```ts type-equiv
67
- /** Public live-agent handle. */
67
+ /** Public live-agent handle; the runtime face augments its live capabilities. */
68
68
  interface Agent {
69
- /** The single identity shared with {@link session}. */
69
+ /** Session-backed Agent identity. */
70
70
  readonly id: SessionId
71
71
  /** The provider route and model this agent's requests use. */
72
72
  readonly options: AgentOptions
@@ -168,12 +168,14 @@ interface AgentOptions {
168
168
  provider?: string
169
169
  /** Model id interpreted by the selected provider adapter. */
170
170
  model?: string
171
+ /** Adapter-owned reasoning effort for the selected provider/model route. */
172
+ reasoningEffort?: ReasoningEffortId
171
173
  /** Maximum output tokens for each conversation-model request. */
172
174
  maxTokens?: number
173
175
  }
174
176
  ```
175
177
 
176
- 在 `agent/request` 之后,分发要求 `provider` 与 `model` 都存在。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。agent 作用域的 `deployment:persona` 提示词段落可以遮蔽全局默认 persona。
178
+ 在 `agent/request` 之后,分发要求 `provider` 与 `model` 都存在。显式 `reasoningEffort` 会为该路由的首次请求提供初始值;确切模型解析会校验该值,省略时则允许填入适配器默认值。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值,否则提供方行为保持不变。agent 作用域的 `deployment:persona` 提示词段落可以遮蔽全局默认 persona。
177
179
 
178
180
  inbox 即投递词汇——agent 以持久投影形式拥有的两条有序待处理消息列表:
179
181
 
@@ -207,7 +209,7 @@ type AgentCancelCause =
207
209
  | { readonly kind: 'disposed' }
208
210
  ```
209
211
 
210
- cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有者会将它复制到仅运行时的 `AbortSignal.reason`;signal 不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录谁请求了取消,应使用单独的持久事件,而不是让终态结果承担额外含义。
212
+ cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有者会将它复制到仅运行时的 `AbortSignal.reason`;signal 不授予协作监听器任何分类权限。持久 `turn/end` `{ kind: 'aborted', reason: TurnEndCancelCause }` 记录结果,取消原因随终态结果一起持久化。
211
213
 
212
214
  [事件分类](../index.md#events)负责 `agent/*` 生命周期、检查点与 waterfall(瀑布式事件)约定。轮次和步骤边界是持久会话事件,而不是 agent emit。
213
215
 
@@ -233,7 +235,12 @@ pre-step 决策使用与持久 user-role 输入相同、带标识的 `UserMessag
233
235
  /** Whether and with which messages the loop enters a proposed step. */
234
236
  type PreStepDecision =
235
237
  | { kind: 'reject' }
236
- | { kind: 'enter'; messages: UserMessage[] }
238
+ | {
239
+ kind: 'enter'
240
+ messages: UserMessage[]
241
+ /** Start a distinct model-message series before this step's admitted messages. */
242
+ startsRequestSeries?: true
243
+ }
237
244
  ```
238
245
 
239
246
  `agent/request-error` 在失败的模型步骤关闭之后、其轮次关闭之前运行。listener 可以在失败轮次的 signal 仍然存活时修复持久状态或 await 策略工作。处理该错误的 listener 返回 `{ kind: 'retry' }` 且不调用 `next()`;默认的 `undefined` 会让失败保持终态。
@@ -243,7 +250,7 @@ type PreStepDecision =
243
250
  type RequestErrorAction = { kind: 'retry' } | undefined
244
251
  ```
245
252
 
246
- `agent/pre-step` 是请求推导前唯一的串行监听器链。`agent/turn-stopping` 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。
253
+ `agent/pre-step` 是请求推导前唯一的 waterfall(瀑布式)监听器链。`agent/turn-stopping` 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。
247
254
 
248
255
  `agent/session-start` 携带 `SessionStartSource`(会话生命周期为何开始;桥接层据此匹配其 SessionStart):
249
256
 
@@ -256,7 +263,7 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
256
263
 
257
264
  `Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM 消息历史从日志*派生*(`deriveMessages()`),而非单独存储。每个条目携带单调的 `seq`、`time` 与按 `type` 判别的 `data` payload;surface 变体还可以在 `sourceEventSeqs` 中列出被引用的较早事件,并携带 `surfaceOp`。
258
265
 
259
- `SessionEvent` 信封的确切条件字段、十二种事件变体(`turn/start`、`turn/end`、`step/start`、`step/end`、`user/message`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`steering/message`、`todo/write`、`request/header`)、`deriveMessages()` 投影规则、`TurnTrigger`/`TurnEndReason` 原因以及执行封闭和独立事件规则都在 **[session.md](./session.md)** 中。日志如何持久化——`SessionPersistence` 接口、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](./persistence.md)** 中。
266
+ `SessionEvent` 信封的确切条件字段、十二种核心事件变体(`turn/start`、`turn/end`、`step/start`、`step/end`、`user/message`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`request/header`、`request/context`、`session/end-seed`)、`deriveMessages()` 投影规则、`TurnEndReason` 原因以及执行封闭和独立事件规则都在 **[session.md](./session.md)** 中。日志如何持久化——`SessionPersistence` 接口、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](./persistence.md)** 中。
260
267
 
261
268
  ## `ToolDefinition`
262
269
 
@@ -291,14 +298,13 @@ declare module '@deepseek-ai/dsh-llm' {
291
298
  }
292
299
  ```
293
300
 
294
- 六个规范 map 使用此模式;插件作者扩展它们:
301
+ 五个规范 map 使用此模式;插件作者扩展它们:
295
302
 
296
303
  | Map | 包 | 派生 | 目录 |
297
304
  |---|---|---|---|
298
305
  | `ContentBlockMap` | dsh-llm | `ContentBlock` | [llm-streaming.md](./llm-streaming.md#content-blocks-and-messages) |
299
306
  | `MessageSourceMap` | dsh-llm | `MessageSource` | [llm-streaming.md](./llm-streaming.md#content-blocks-and-messages) |
300
307
  | `FinishReasonMap` | dsh-llm | `FinishReason` | [llm-streaming.md](./llm-streaming.md#the-model-request-and-result) |
301
- | `TurnTriggerMap` | dsh-session | `TurnTrigger` | [session.md](./session.md) |
302
308
  | `TurnEndReasonMap` | dsh-session | `TurnEndReason` | [session.md](./session.md) |
303
309
  | `SessionEventMap` | dsh-session | `SessionEvent` | [session.md](./session.md) |
304
310
 
@@ -308,7 +314,7 @@ declare module '@deepseek-ai/dsh-llm' {
308
314
 
309
315
  ### 品牌化 ID
310
316
 
311
- 在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `CallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。
317
+ 在包之间传递的 ID 都经过**品牌化**——结构上是字符串,但在类型层面不可互换(不能把 `SessionId` 传给需要 `ToolCallId` 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。
312
318
 
313
319
  `Branded<B>` 原语位于独立的纯类型包 [dsh-brand](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/util/brand) 中(没有运行时代码,也不依赖 harness 包),因此任何包都能品牌化其拥有的 id,而无需依赖无关的能力包。
314
320
 
@@ -319,7 +325,7 @@ declare module '@deepseek-ai/dsh-llm' {
319
325
  type Branded<B extends string> = string & { readonly [BRAND]: B }
320
326
  ```
321
327
 
322
- 两个核心 ID 是 `CallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [jobs.md](./jobs.md) 中的 `JobId`。
328
+ 两个核心 ID 是 `ToolCallId`(关联工具调用及其结果;dsh-llm)和 `SessionId`(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 [jobs.md](./jobs.md) 中的 `JobId`。
323
329
 
324
330
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
325
331
 
@@ -407,6 +413,16 @@ Discovery is unmemoized: `list()` and `resolve()` re-read the roots on every cal
407
413
  */
408
414
  async list(): Promise<AgentPreset[]>
409
415
 
416
+ /**
417
+ * The roster off the Host: {@link list} projected to path-free rows, with
418
+ * the default marked and this deployment's authoring capability beside it.
419
+ *
420
+ * Whether a client can open a preset's directory is the Host's own opener
421
+ * capability, not a roster property — a caller needing both joins them.
422
+ * @returns the rows and the authoring capability.
423
+ */
424
+ @Remote('list') async remoteExportList(): Promise<AgentPresetRoster>
425
+
410
426
  /**
411
427
  * Resolve one preset by id.
412
428
  *
@@ -481,6 +497,15 @@ composedPreset(agentCtx: Context): string | undefined
481
497
  */
482
498
  async read(id: string): Promise<string>
483
499
 
500
+ /**
501
+ * One preset's composition text with the roster row it belongs to.
502
+ * @param agentPreset - the preset id.
503
+ * @returns the composition beside its trust and published metadata.
504
+ * @throws {TypertRemoteFailure} `bad-request` for an empty id, or
505
+ * `agent-preset-not-found` when no configured root supplies it.
506
+ */
507
+ @Remote('read') async readDocument(agentPreset: string): Promise<AgentPresetDocument>
508
+
484
509
  /**
485
510
  * Create a locally authored preset by copying an existing one whole.
486
511
  *
@@ -498,13 +523,34 @@ async read(id: string): Promise<string>
498
523
  */
499
524
  async copy(from: string, id: string, name?: string): Promise<void>
500
525
 
526
+ /**
527
+ * Copy one preset through the Remote API.
528
+ * @param from - the source preset id.
529
+ * @param id - the new preset id.
530
+ * @param name - the copy's optional display name.
531
+ * @returns once the copy is stored.
532
+ * @throws {TypertRemoteFailure} with the corresponding stable preset code
533
+ * and details when the copy is refused.
534
+ */
535
+ @Remote('copy') async remoteExportCopy(from: string, id: string, name?: string): Promise<void>
536
+
501
537
  /**
502
538
  * Delete a locally authored preset.
539
+ *
503
540
  * @param id - the preset id.
504
541
  * @throws when the preset is unknown or ships with the deployment.
505
542
  */
506
543
  async remove(id: string): Promise<void>
507
544
 
545
+ /**
546
+ * Delete one preset through the Remote API.
547
+ * @param id - the preset id.
548
+ * @returns once the preset is deleted.
549
+ * @throws {TypertRemoteFailure} with the corresponding stable preset code
550
+ * and details when deletion is refused.
551
+ */
552
+ @Remote('deletePreset') async remoteExportDelete(id: string): Promise<void>
553
+
508
554
  /**
509
555
  * One agent's instance of a service its preset mounted.
510
556
  *
@@ -537,7 +583,9 @@ serviceFor<K extends string & keyof Context>(agent: { ctx: Context }, name: K):
537
583
  * state to restore. The re-link runs through the binding this roster kept
538
584
  * from the agent's mount — dsh-scope's only re-link authority. An agent
539
585
  * that never composed one has nothing to re-link: the switch is then the
540
- * agent's first bind, exactly a mount.
586
+ * agent's first bind, exactly a mount. A committed re-link emits
587
+ * `tools/change` because changing the parent scope changes the Agent's
588
+ * resolved tool set without adding or removing registry entries.
541
589
  * @param agentCtx - the agent's scope context.
542
590
  * @param id - the preset to compose the agent from instead.
543
591
  * @returns the preset now installed.
@@ -545,6 +593,16 @@ serviceFor<K extends string & keyof Context>(agent: { ctx: Context }, name: K):
545
593
  */
546
594
  async recompose(agentCtx: Context, id: string): Promise<AgentPreset>
547
595
 
596
+ /**
597
+ * Compose a blank session's agent from a different preset and record it.
598
+ * @param agent - the session's live agent, resolved from the wire identity.
599
+ * @param agentPreset - the preset to compose the agent from instead.
600
+ * @returns the preset id that was recorded.
601
+ * @throws {TypertRemoteFailure} with `bad-request`, `agent-preset-locked`,
602
+ * `agent-preset-not-found`, or `agent-preset-invalid` when refused.
603
+ */
604
+ @Remote('select') async select(agent: Agent, agentPreset: string): Promise<string>
605
+
548
606
  /**
549
607
  * The standing scope key of one preset, for a host reader with no agent.
550
608
  *