dsh-plugin-dev-kb 1.0.9 → 1.3.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 (169) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.en.md +14 -8
  3. package/README.md +14 -8
  4. package/docs/banner.svg +25 -0
  5. package/kb/INDEX.md +8 -2
  6. package/kb/README.md +4 -4
  7. package/kb/extra/AGENTS.md +3 -3
  8. package/kb/extra/cookbook/adding-a-remote-api.md +197 -0
  9. package/kb/extra/cookbook/adding-a-remote-api.zh.md +197 -0
  10. package/kb/extra/cookbook/adding-a-session-format-version.md +109 -0
  11. package/kb/extra/cookbook/adding-a-session-format-version.zh.md +109 -0
  12. package/kb/extra/cookbook/adding-a-vendored-package.md +1 -1
  13. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +1 -1
  14. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +1 -1
  15. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +1 -1
  16. package/kb/extra/deepseek-llm-api-wire-extensions.md +19 -15
  17. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +19 -15
  18. package/kb/extra/development.md +13 -3
  19. package/kb/extra/development.zh.md +13 -3
  20. package/kb/extra/event-producer-consumer.md +49 -46
  21. package/kb/extra/event-producer-consumer.zh.md +52 -49
  22. package/kb/extra/i18n/README.md +2 -1
  23. package/kb/extra/i18n/README.zh.md +3 -2
  24. package/kb/extra/i18n/style-samples.md +2 -2
  25. package/kb/extra/i18n/translation-prompt.md +1 -1
  26. package/kb/extra/module-graph.md +493 -1002
  27. package/kb/extra/module-graph.zh.md +495 -1004
  28. package/kb/extra/rescope.md +1 -1
  29. package/kb/extra/rescope.zh.md +1 -1
  30. package/kb/extra/session-format-status.md +47 -0
  31. package/kb/extra/session-format-status.zh.md +47 -0
  32. package/kb/extra/subsystems/agent-team.md +3 -2
  33. package/kb/extra/subsystems/agent-team.zh.md +3 -2
  34. package/kb/extra/subsystems/attachment.md +157 -4
  35. package/kb/extra/subsystems/attachment.zh.md +157 -4
  36. package/kb/extra/subsystems/feedback.md +164 -30
  37. package/kb/extra/subsystems/feedback.zh.md +164 -30
  38. package/kb/extra/testing.md +9 -4
  39. package/kb/extra/testing.zh.md +9 -4
  40. package/kb/extra/web-styling.md +5 -0
  41. package/kb/extra/web-styling.zh.md +5 -0
  42. package/kb/meta/search-index.json +223 -151
  43. package/kb/meta/site-pages.txt +187 -181
  44. package/kb/meta/source.json +5 -5
  45. package/kb/meta/topics.md +5 -5
  46. package/kb/site/develop/basic/publish.md +1 -1
  47. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +3 -2
  48. package/kb/site/develop/practice/llm-adapter.md +4 -3
  49. package/kb/site/en/develop/basic/publish.md +1 -1
  50. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +3 -2
  51. package/kb/site/en/develop/practice/llm-adapter.md +4 -3
  52. package/kb/site/en/guide/network-proxy.md +87 -0
  53. package/kb/site/en/guide/providers.md +70 -17
  54. package/kb/site/en/guide/python-sdk.md +19 -3
  55. package/kb/site/en/guide/schedule.md +2 -0
  56. package/kb/site/en/reference/agent-lifecycle.md +17 -8
  57. package/kb/site/en/reference/api-gateway.md +2 -2
  58. package/kb/site/en/reference/capability-seams.md +24 -10
  59. package/kb/site/en/reference/config-catalog.md +322 -264
  60. package/kb/site/en/reference/cookbook/adding-a-package.md +1 -2
  61. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +10 -8
  62. package/kb/site/en/reference/cookbook/adding-a-tool.md +1 -1
  63. package/kb/site/en/reference/cookbook/extension-cookbook.md +16 -13
  64. package/kb/site/en/reference/index.md +26 -9
  65. package/kb/site/en/reference/persistence-catalog.md +189 -95
  66. package/kb/site/en/reference/subsystems/approval.md +1 -1
  67. package/kb/site/en/reference/subsystems/client-modules.md +9 -0
  68. package/kb/site/en/reference/subsystems/client-resources.md +94 -0
  69. package/kb/site/en/reference/subsystems/code-runtime.md +7 -3
  70. package/kb/site/en/reference/subsystems/commands.md +25 -16
  71. package/kb/site/en/reference/subsystems/compaction.md +10 -10
  72. package/kb/site/en/reference/subsystems/conversation.md +11 -4
  73. package/kb/site/en/reference/subsystems/core.md +173 -29
  74. package/kb/site/en/reference/subsystems/credentials.md +5 -4
  75. package/kb/site/en/reference/subsystems/filesystem.md +15 -0
  76. package/kb/site/en/reference/subsystems/goal.md +38 -2
  77. package/kb/site/en/reference/subsystems/index.md +3 -1
  78. package/kb/site/en/reference/subsystems/invariants.md +1 -1
  79. package/kb/site/en/reference/subsystems/llm-streaming.md +43 -12
  80. package/kb/site/en/reference/subsystems/lsp.md +1 -1
  81. package/kb/site/en/reference/subsystems/permission-presets.md +5 -5
  82. package/kb/site/en/reference/subsystems/persistence.md +211 -199
  83. package/kb/site/en/reference/subsystems/plan.md +3 -3
  84. package/kb/site/en/reference/subsystems/sandbox.md +2 -0
  85. package/kb/site/en/reference/subsystems/schedule.md +9 -3
  86. package/kb/site/en/reference/subsystems/scope.md +1 -1
  87. package/kb/site/en/reference/subsystems/session-projection.md +50 -24
  88. package/kb/site/en/reference/subsystems/session-query.md +19 -13
  89. package/kb/site/en/reference/subsystems/session-reference.md +29 -1
  90. package/kb/site/en/reference/subsystems/session-telemetry.md +27 -10
  91. package/kb/site/en/reference/subsystems/session-title.md +6 -6
  92. package/kb/site/en/reference/subsystems/session.md +208 -103
  93. package/kb/site/en/reference/subsystems/settings.md +30 -12
  94. package/kb/site/en/reference/subsystems/shell.md +9 -6
  95. package/kb/site/en/reference/subsystems/sidebar-right.md +151 -0
  96. package/kb/site/en/reference/subsystems/skills.md +1 -1
  97. package/kb/site/en/reference/subsystems/slots.md +41 -32
  98. package/kb/site/en/reference/subsystems/spill.md +13 -6
  99. package/kb/site/en/reference/subsystems/storage.md +28 -5
  100. package/kb/site/en/reference/subsystems/subagent.md +58 -105
  101. package/kb/site/en/reference/subsystems/subprocess.md +25 -26
  102. package/kb/site/en/reference/subsystems/system-prompt.md +18 -3
  103. package/kb/site/en/reference/subsystems/token-meter.md +4 -3
  104. package/kb/site/en/reference/subsystems/tools.md +5 -5
  105. package/kb/site/en/reference/subsystems/typert.md +18 -18
  106. package/kb/site/en/reference/subsystems/web-client.md +1 -1
  107. package/kb/site/en/reference/subsystems/workspace.md +88 -9
  108. package/kb/site/en/reference/tool-catalog.md +56 -66
  109. package/kb/site/en/reference/tool-execution-pipeline.md +2 -2
  110. package/kb/site/guide/network-proxy.md +87 -0
  111. package/kb/site/guide/providers.md +70 -17
  112. package/kb/site/guide/python-sdk.md +19 -3
  113. package/kb/site/guide/schedule.md +2 -0
  114. package/kb/site/reference/agent-lifecycle.md +17 -8
  115. package/kb/site/reference/api-gateway.md +2 -2
  116. package/kb/site/reference/capability-seams.md +24 -10
  117. package/kb/site/reference/config-catalog.md +321 -263
  118. package/kb/site/reference/cookbook/adding-a-package.md +1 -2
  119. package/kb/site/reference/cookbook/adding-a-settings-card.md +10 -8
  120. package/kb/site/reference/cookbook/adding-a-tool.md +1 -1
  121. package/kb/site/reference/cookbook/extension-cookbook.md +16 -13
  122. package/kb/site/reference/index.md +26 -9
  123. package/kb/site/reference/persistence-catalog.md +190 -96
  124. package/kb/site/reference/subsystems/approval.md +1 -1
  125. package/kb/site/reference/subsystems/client-modules.md +9 -0
  126. package/kb/site/reference/subsystems/client-resources.md +94 -0
  127. package/kb/site/reference/subsystems/code-runtime.md +7 -3
  128. package/kb/site/reference/subsystems/commands.md +25 -16
  129. package/kb/site/reference/subsystems/compaction.md +10 -10
  130. package/kb/site/reference/subsystems/conversation.md +11 -4
  131. package/kb/site/reference/subsystems/core.md +173 -29
  132. package/kb/site/reference/subsystems/credentials.md +5 -4
  133. package/kb/site/reference/subsystems/filesystem.md +15 -0
  134. package/kb/site/reference/subsystems/goal.md +38 -2
  135. package/kb/site/reference/subsystems/index.md +14 -12
  136. package/kb/site/reference/subsystems/invariants.md +1 -1
  137. package/kb/site/reference/subsystems/llm-streaming.md +43 -12
  138. package/kb/site/reference/subsystems/lsp.md +1 -1
  139. package/kb/site/reference/subsystems/permission-presets.md +5 -5
  140. package/kb/site/reference/subsystems/persistence.md +210 -198
  141. package/kb/site/reference/subsystems/plan.md +3 -3
  142. package/kb/site/reference/subsystems/schedule.md +9 -3
  143. package/kb/site/reference/subsystems/scope.md +1 -1
  144. package/kb/site/reference/subsystems/session-projection.md +50 -24
  145. package/kb/site/reference/subsystems/session-query.md +19 -13
  146. package/kb/site/reference/subsystems/session-reference.md +29 -1
  147. package/kb/site/reference/subsystems/session-telemetry.md +27 -10
  148. package/kb/site/reference/subsystems/session-title.md +6 -6
  149. package/kb/site/reference/subsystems/session.md +208 -103
  150. package/kb/site/reference/subsystems/settings.md +30 -12
  151. package/kb/site/reference/subsystems/shell.md +9 -6
  152. package/kb/site/reference/subsystems/sidebar-right.md +151 -0
  153. package/kb/site/reference/subsystems/skills.md +1 -1
  154. package/kb/site/reference/subsystems/slots.md +41 -32
  155. package/kb/site/reference/subsystems/spill.md +13 -6
  156. package/kb/site/reference/subsystems/storage.md +28 -5
  157. package/kb/site/reference/subsystems/subagent.md +58 -105
  158. package/kb/site/reference/subsystems/subprocess.md +25 -26
  159. package/kb/site/reference/subsystems/system-prompt.md +18 -3
  160. package/kb/site/reference/subsystems/token-meter.md +4 -3
  161. package/kb/site/reference/subsystems/tools.md +5 -5
  162. package/kb/site/reference/subsystems/typert.md +18 -18
  163. package/kb/site/reference/subsystems/web-client.md +1 -1
  164. package/kb/site/reference/subsystems/workspace.md +88 -9
  165. package/kb/site/reference/tool-catalog.md +55 -65
  166. package/kb/site/reference/tool-execution-pipeline.md +2 -2
  167. package/lib/index.js +25 -44
  168. package/package.json +3 -2
  169. package/skills/dsh-plugin-dev-kb.md +4 -4
@@ -200,8 +200,22 @@ prepareDocument(): Promise<string | undefined>
200
200
  * @param schema - schemastery schema resolving this namespace's value.
201
201
  * @param options - composition `base` layer and effect timing.
202
202
  * @returns the owner scope for reads, observation, and updates.
203
+ * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
203
204
  */
204
- register<T>(ns: SettingsNamespace, schema: z<T>, options?: SettingsRegisterOptions<T>): SettingsScope<T>
205
+ register<const Namespace extends string, T>( ns: Namespace & SettingsNamespaceInput<Namespace>, schema: z<T>, options?: SettingsRegisterOptions<T>, ): SettingsScope<T>
206
+
207
+ /**
208
+ * Attach one optional-settings consumer to this provider. The consumer
209
+ * registers its composition entry as the base layer while this provider is
210
+ * present, then falls back to that entry if the provider detaches.
211
+ * @param owner - consumer context whose unload suppresses fallback work.
212
+ * @param ns - consumer-owned settings namespace.
213
+ * @param schema - schema resolving the namespace.
214
+ * @param entry - composition entry used as the base and fallback value.
215
+ * @param hooks - source sink, change notification, and optional validation.
216
+ * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
217
+ */
218
+ installSection<const Namespace extends string, T>( owner: Context, ns: Namespace & SettingsNamespaceInput<Namespace>, schema: z<T>, entry: T, hooks: SettingsSectionHooks<T>, ): void
205
219
 
206
220
  /**
207
221
  * Describe every registered namespace for configuration surfaces, including
@@ -216,8 +230,9 @@ describe(options?: SettingsDescribeOptions): SettingsDescriptor[]
216
230
  * Read one registered namespace's resolved value.
217
231
  * @param ns - the namespace to read.
218
232
  * @returns the resolved value, or `undefined` while unregistered.
233
+ * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
219
234
  */
220
- get(ns: SettingsNamespace): unknown
235
+ get<const Namespace extends string>(ns: Namespace & SettingsNamespaceInput<Namespace>): unknown
221
236
 
222
237
  /**
223
238
  * Merge a patch into one registered namespace's user layer, validate the
@@ -229,8 +244,9 @@ get(ns: SettingsNamespace): unknown
229
244
  * @param patch - plain-object patch over the user section.
230
245
  * @param expectedRevision - the descriptor `revision` the caller read; a
231
246
  * namespace that moved past it rejects with {@link SettingsConflictError}.
247
+ * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
232
248
  */
233
- async update(ns: SettingsNamespace, patch: object, expectedRevision?: number): Promise<void>
249
+ async update<const Namespace extends string>( ns: Namespace & SettingsNamespaceInput<Namespace>, patch: object, expectedRevision?: number, ): Promise<void>
234
250
 
235
251
  /**
236
252
  * Replace one registered namespace's user section wholesale, validate,
@@ -241,8 +257,9 @@ async update(ns: SettingsNamespace, patch: object, expectedRevision?: number): P
241
257
  * @param section - the complete next user section.
242
258
  * @param expectedRevision - the descriptor `revision` the caller read; a
243
259
  * namespace that moved past it rejects with {@link SettingsConflictError}.
260
+ * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
244
261
  */
245
- async replace(ns: SettingsNamespace, section: object, expectedRevision?: number): Promise<void>
262
+ async replace<const Namespace extends string>( ns: Namespace & SettingsNamespaceInput<Namespace>, section: object, expectedRevision?: number, ): Promise<void>
246
263
 
247
264
  /**
248
265
  * Apply path-addressed edits to one registered namespace's user section,
@@ -255,8 +272,9 @@ async replace(ns: SettingsNamespace, section: object, expectedRevision?: number)
255
272
  * @param ops - ordered path edits; later ops observe earlier ones.
256
273
  * @param expectedRevision - the descriptor `revision` the caller read; a
257
274
  * namespace that moved past it rejects with {@link SettingsConflictError}.
275
+ * @throws {TypeError} when `ns` is not a lowercase hyphenated identifier.
258
276
  */
259
- async mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[], expectedRevision?: number): Promise<void>
277
+ async mutate<const Namespace extends string>( ns: Namespace & SettingsNamespaceInput<Namespace>, ops: readonly SettingsPathOp[], expectedRevision?: number, ): Promise<void>
260
278
  ```
261
279
 
262
280
  Source: [`packages/settings/settings/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/settings/settings/src/index.ts)
@@ -265,14 +283,14 @@ Source: [`packages/settings/settings/src/index.ts`](https://github.com/deepseek-
265
283
 
266
284
  ### `ctx.settingsController` — `SettingsController`
267
285
 
268
- Host service backing the generated `ctx.remote.settings` namespace. Every remote read uses `redactSecrets: true`, so a `role('secret')` field cannot ride a response. Writes expose the settings service's merge, replacement, and path-addressed operations, and classify every provider refusal as `settings-conflict` or `settings-rejected` with the service's message.
286
+ Host service backing the generated `ctx.remote.settings` namespace. Every remote read uses `redactSecrets: true`, so a `role('secret')` field cannot ride a response. Writes expose the settings service's merge, replacement, and path-addressed operations, and classify every provider refusal as `settings/conflict` or `settings/rejected` with the service's message.
269
287
 
270
288
  ```ts cordis-catalog
271
289
  /**
272
290
  * Describe every registered namespace for a configuration page: redacted
273
291
  * layered values plus the serialized schema the page renders its form from.
274
292
  * @returns provider writability, local-document presence, and one view per namespace.
275
- * @throws TypertRemoteFailure when no settings provider is mounted.
293
+ * @throws RemoteError when no settings provider is mounted.
276
294
  */
277
295
  @Remote describe(): SettingsDescribeValue
278
296
 
@@ -288,7 +306,7 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
288
306
  * @param patch - fields to merge into the user section.
289
307
  * @param expectedRevision - revision the caller read; `undefined` writes unconditionally.
290
308
  * @returns the namespace's redacted view after the write.
291
- * @throws TypertRemoteFailure when the request is invalid, no provider is mounted, or the provider refuses the write.
309
+ * @throws RemoteError when the request is invalid, no provider is mounted, or the provider refuses the write.
292
310
  */
293
311
  @Remote update( ns: string, patch: Record<string, JsonValue>, expectedRevision: number | undefined, ): Promise<SettingsNamespaceView>
294
312
 
@@ -298,7 +316,7 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
298
316
  * @param section - complete replacement user section.
299
317
  * @param expectedRevision - revision the caller read; `undefined` writes unconditionally.
300
318
  * @returns the namespace's redacted view after the write.
301
- * @throws TypertRemoteFailure when the request is invalid, no provider is mounted, or the provider refuses the write.
319
+ * @throws RemoteError when the request is invalid, no provider is mounted, or the provider refuses the write.
302
320
  */
303
321
  @Remote replace( ns: string, section: Record<string, JsonValue>, expectedRevision: number | undefined, ): Promise<SettingsNamespaceView>
304
322
 
@@ -310,7 +328,7 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
310
328
  * @param ops - the edits to apply, in order.
311
329
  * @param expectedRevision - revision the caller read; `undefined` writes unconditionally.
312
330
  * @returns the namespace's redacted view after the write.
313
- * @throws TypertRemoteFailure when the request is invalid, no provider is mounted, or the provider refuses the write.
331
+ * @throws RemoteError when the request is invalid, no provider is mounted, or the provider refuses the write.
314
332
  */
315
333
  @Remote async mutate( ns: string, ops: SettingsPathOpView[], expectedRevision: number | undefined, ): Promise<SettingsNamespaceView>
316
334
 
@@ -318,7 +336,7 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
318
336
  * Materialize the provider-owned settings document and open it in a native text editor.
319
337
  * @param signal - caller lifetime; abort terminates preparation or the native command.
320
338
  * @returns confirmation after the native opener accepts the document.
321
- * @throws TypertRemoteFailure when no document exists, preparation fails, or opening fails.
339
+ * @throws RemoteError when no document exists, preparation fails, or opening fails.
322
340
  */
323
341
  @Remote async openSettingsDocument(signal: AbortSignal): Promise<SettingsDocumentOpenValue>
324
342
 
@@ -327,7 +345,7 @@ Host service backing the generated `ctx.remote.settings` namespace. Every remote
327
345
  * @param agentPreset - preset id resolved against Host-owned roots.
328
346
  * @param signal - caller lifetime; abort terminates the native command.
329
347
  * @returns an opened confirmation or the resolved directory for text display.
330
- * @throws TypertRemoteFailure when the preset is missing, read-only, invalid, or cannot be opened.
348
+ * @throws RemoteError when the preset is missing, read-only, invalid, or cannot be opened.
331
349
  */
332
350
  @Remote async openAgentPresetDirectory( agentPreset: string, signal: AbortSignal, ): Promise<AgentPresetDirectoryOpenValue>
333
351
  ```
@@ -5,7 +5,7 @@ outline: [2,3]
5
5
 
6
6
  # Bash 执行器
7
7
 
8
- bash 执行 seam 分为 Service Definition([dsh-shell](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/shell),`ctx.shell`)、Service Provider([dsh-bash-local](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/bash-local) 与 [dsh-bash-sandbox](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/bash-sandbox))和 Consumer([dsh-tool-bash](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/tool-bash),即 `bash` schema)。通用后台任务的 job id、所有权与控制位于 [jobs.md](./jobs.md);本 seam 返回一个不含任务概念的进程句柄。原始进程组机制封装在[子进程 seam](./subprocess.md)之后。
8
+ bash 执行 seam 分为 Service Definition([dsh-shell](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/shell),`ctx.shell`)、Service Provider([dsh-bash-local](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/bash-local) 与 [dsh-bash-sandbox](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/bash-sandbox))和 Consumer([dsh-tool-bash](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/tool-bash),即 `bash` schema)。通用后台任务的 job id、所有权与控制位于 [jobs.md](./jobs.md);本 seam 返回一个不含任务概念的进程句柄。managed-range 机制封装在[子进程 seam](./subprocess.md)之后。
9
9
 
10
10
  源码:[`packages/shell/shell/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/shell/shell/src/types.ts)
11
11
 
@@ -101,7 +101,7 @@ interface ShellExecSpec {
101
101
  }
102
102
  ```
103
103
 
104
- `stdin` 和 `env` 是受信任的进程内插件输入,不由 `dsh-tool-bash` 暴露。本地执行器会先清除环境中的凭据,再合并调用方显式提供的 env。见 [bash-stdin-env Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-api.zh.md)
104
+ `stdin` 和 `env` 是受信任的进程内插件输入,不由 `dsh-tool-bash` 暴露。本地执行器会先清除环境中的凭据,再合并调用方显式提供的 env。
105
105
 
106
106
  `stdoutMaxBytes` 同样仅供受信任插件使用。它让前台消费方能在有界解析预算内请求完整 stdout,而不会改变 stderr、后台任务或面向模型的 bash 工具的常规输出上限。
107
107
 
@@ -169,7 +169,7 @@ interface ShellSandboxInfo {
169
169
 
170
170
  ## 后台进程:`ShellProcess`
171
171
 
172
- `start()` 返回不含 id 或所有者的句柄。`dsh-tool-bash` 将它适配为 `ctx.jobs.start()` 钩子;随后由通用运行时拥有任务标识与生命周期。`done` 在进程关闭时完成且绝不被拒绝;进程结束后仍可读取,并且沙箱事实会在 `done` 完成前写入。
172
+ `start()` 返回不含 id 或所有者的句柄。`dsh-tool-bash` 将它适配为 `ctx.jobs.start()` 钩子;随后由通用运行时拥有任务标识与生命周期。`done` 会在底层进程结算时完成且绝不 reject;subprocess 提供方的 rejection 会生成状态为 `killed` 的进程,并把不声明阶段的错误写入 stderr。进程结算后仍可读取,并且沙箱事实会在 `done` 完成前写入。
173
173
 
174
174
  ```ts type-equiv
175
175
  /**
@@ -185,7 +185,10 @@ interface ShellProcess {
185
185
  exitCode: number | null
186
186
  /** Terminating signal name, when signal-killed. */
187
187
  signal: NodeJS.Signals | null
188
- /** Resolves when the underlying process closes (never rejects — a spawn failure settles as `killed` with the error on stderr). */
188
+ /**
189
+ * Resolves when the underlying process settles (never rejects — provider
190
+ * rejection settles as `killed` with a stage-neutral error on stderr).
191
+ */
189
192
  readonly done: Promise<void>
190
193
  /** Sandbox facts, stamped once a confined process settles. */
191
194
  sandbox?: ShellSandboxInfo
@@ -196,7 +199,7 @@ interface ShellProcess {
196
199
  */
197
200
  readOutput(): ShellProcessRead
198
201
  /**
199
- * Kill the process group. Returns false when it had already finished
202
+ * Terminate the provider-managed range. Returns false when it had already finished
200
203
  * (no-op); idempotent.
201
204
  */
202
205
  kill(): boolean
@@ -221,7 +224,7 @@ interface ShellProcessRead {
221
224
 
222
225
  ## 服务
223
226
 
224
- `ShellExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;进程组、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[子进程服务](./subprocess.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](./jobs.md)。`dsh-shell` 拥有 shell 工具共享的退出状态约定:导出的 `parseExitStatus`/`ParsedExitStatus` 是 `dsh-tool-bash` 的 `renderResult` 与 `dsh-tool-pwsh` 的 `renderPwshResult` 所追加的 `[exit code: N]` / `[killed by signal: X]` 标记的逆解析,两个工具的 `presentResult` 都用它把渲染文本拆分为 terminal 卡的输出正文与退出状态 pill。
227
+ `ShellExecutor` 拥有 `resolve`、前台 `run`、后台进程 `start` 以及 `sandboxMode` 能力事实。`dsh-bash-local` 拥有命令默认值补全、超时/中止分类、终端环境以及后台读取合并;managed-range 终止、有界收集器、spill 文件、凭据清除与 dispose(资源释放)后完全停稳归[子进程服务](./subprocess.md)所有。`dsh-tool-bash` 拥有面向模型的渲染,并将后台句柄适配到[通用任务运行时](./jobs.md)。`dsh-shell` 拥有 shell 工具共享的退出状态约定:导出的 `parseExitStatus`/`ParsedExitStatus` 是 `dsh-tool-bash` 的 `renderResult` 与 `dsh-tool-pwsh` 的 `renderPwshResult` 所追加的 `[exit code: N]` / `[killed by signal: X]` 标记的逆解析,两个工具的 `presentResult` 都用它把渲染文本拆分为 terminal 卡的输出正文与退出状态 pill。
225
228
 
226
229
  <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
227
230
 
@@ -0,0 +1,151 @@
1
+ ---
2
+ editSource: "docs/subsystems/sidebar-right.zh.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # 右侧 Sidebar
7
+
8
+ 右侧 Sidebar 是 Web Client 里每个会话一份的停靠面:会话区旁的一列 pane 与 tab,按地址寻址的内容——工作区文件、目录树、产品自带页面——在这里打开、分栏、浮出、关闭。[`dsh-client-ui-sidebar-right`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md) 拥有这个面、tab 类型注册表与导航服务;[`dsh-client-ui-dockkit`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-dockkit/README.zh.md) 是它内部的布局引擎;[`dsh-client-resources`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md) 把地址变成任何组件都能读的活数据;[`dsh-api-workspace-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md) 同时提供 Host 工作区文件服务与 Client `file` 资源提供者。
9
+
10
+ 本页是该子系统契约的参考:地址、tab 类型注册、导航服务、扩展 slot 与其 owner props、资源模型、Workspace Files 服务、内置类型,以及明确不做的事。布局引擎、frame 与停靠面如何拼在一起见 [Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md);slot 机制见 [Slots 参考](./slots.md)。
11
+
12
+ ## 定位与归属
13
+
14
+ 每个会话恰有一个停靠面,保存在会话作用域的 slot store 里、由 `rightbar.session` 绘制。root 作用域的 `rightbar` 控制器仅在选中 Conversation 时挂载该席位;刷新页面后每个会话回到折叠的默认态,切换会话时各自的面保持原状([状态](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#state))。面的每一次变化都是 kit 纯规划器算出的一条历史记录;停靠的 pane 从不空着,根 pane 为空时会加入根据已注册引导入口选出的默认页。
15
+
16
+ 一个 tab 类型是共用定义 `id` 的两次注册:在 `ctx.sidebarRightTabs` 里的静态定义说明其 `kind` 打开哪些地址,一次 keyed slot 注册提供它的正文。框架注入 `useTabInfo()` 以读取 Sidebar、窗格和标签的实时信息;各类型把自身状态放在 slot store 里。各包之间只以类型形式引用彼此的声明。
17
+
18
+ | 包 | 职责 |
19
+ |---|---|
20
+ | [`client/ui-sidebar-right`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md) | 面板与栏席位、布局 store、`ctx.sidebarRightTabs`、`ctx.sidebarRight`、Tab 域、引导类型 |
21
+ | [`client/ui-dockkit`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-dockkit/README.zh.md) | 纯布局引擎与 React 面;`ui-sidebar-right` 的内部依赖,不是稳定接口 |
22
+ | [`client/resources`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md) | `ctx.resources`、`useResource`、协议 → 值类型的花名册 `ResourceProtocolMap` |
23
+ | [`api/workspace-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md) | Host `ctx.workspaceFiles`、`workspaceFiles` Remote 命名空间与 Client `file` 资源提供者 |
24
+ | [`util/workspace-path`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/util/workspace-path/README.zh.md) | 文件地址语法:`fileAddressFor`、`parseFileAddress` |
25
+ | [`client/ui-sidebar-documentpreview`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/README.zh.md)、[`client/ui-sidebar-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-files/README.zh.md) | 内置的 `text` 与 `files` 类型 |
26
+
27
+ ## 地址
28
+
29
+ 每个 tab 都由一个地址字串打开,地址就是 tab 的内容身份。地址分两族。
30
+
31
+ **资源地址**是 `dsh-resource://<type>/…` 形式的 URL。host 命名资源协议——即 `ResourceProtocolMap` 的键——其后是该协议自己的路径;所有协议共用一个 scheme,新增协议只新增 host、不新增 scheme。`file` 协议的路径以其作用域开头:`session/<sessionId>` 后接相对该会话工作区根的路径(`dsh-resource://file/session/abc/src/notes.txt`),或 `absolute` 后接去掉前导 `/` 的绝对路径(`dsh-resource://file/absolute/home/ys/notes.txt`,Windows 上为 `dsh-resource://file/absolute/C:/x/y.txt`)。id 与每一段路径都做组件编码,盘符的 `:` 保留原样。`fileAddressFor(sessionId, cwd, path)` 构造地址——相对路径或工作区内的绝对路径成为 `session` 相对地址,其他绝对路径成为 `absolute` 地址——`parseFileAddress(address)` 读回各部分或返回 `undefined`([语法](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/util/workspace-path/README.zh.md))。
32
+
33
+ **页面地址**是 Sidebar 为按 kind(而非按资源)打开的 tab 记下的地址:`sidebar://<kind>`,由 Sidebar 自己在 `openTab(kind)` 运行时写入。调用方从不拼它——引导页与文件树以 `openTab('guide')`、`openTab('files')` 打开——此外不存在任何导航地址([不做](#not-built))。
34
+
35
+ tab 身份是 `(kind, address)` 二元组:注册表的认领把地址原文用作记录的 `contentId`,因此同一地址经同一类型再次打开会找到已有 tab,同一地址经两个类型打开则是两个 tab。
36
+
37
+ ## Tab 类型注册
38
+
39
+ `ctx.sidebarRightTabs.register(definition)` 在调用方的生命周期内注册一个类型的一份实现并返回注销器;调用方把它放在自己的 `ctx.effect` 里,因此实现与贡献它的插件同寿,同一 `id` 的第二次注册抛错([扩展席位](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#extension-seats))。定义是静态的:没有运行时 hook,没有按 tab 或按会话的东西。
40
+
41
+ | 字段 | 含义 |
42
+ |---|---|
43
+ | `id` | 该实现的身份,在所有注册中唯一;包名是自然取值(`@deepseek-ai/dsh-client-ui-sidebar-files`)。正文与标题坑位按它注册。 |
44
+ | `kind` | 类型的判别名:它的 tab 是什么,也是 `openTab` 点名的对象。不唯一——extension 可以接管 builtin 的 kind。内置 kind 为 `guide`、`text`、`files`。 |
45
+ | `patterns` | 可选的资源地址 glob;按 kind 打开的页面类型省略。含 `:` 的模式匹配整个地址(`dsh-resource://file/**`);不含的匹配 URL 的路径部分且任意深度都中(`*.md`),不是 URL 的地址不会命中此类模式。匹配不分大小写、不隐藏 dotfile;语法为 picomatch 的 POSIX 方言。 |
46
+ | `priority` | 三档字面量之一:`extension`(缺省且最高:产品之外的类型压过所有内置查看器)、`builtin`(随产品发布的类型)、`fallback`(任何更具体的类型都应压过的纯内容查看器)。 |
47
+ | `canOpen(address)` | 可选的同步否决,对 glob 命中生效;每次路由决策都会调用。 |
48
+ | `title(address)` | chip 文本,在 tab 打开时捕获进布局记录,之后不再改写。 |
49
+ | `guide` | 可选的引导页入口框:`{ order, title(), description?(), icon? }`。点一框即把贡献它的类型作为页面打开;省略即不上引导页。 |
50
+
51
+ 路由是一次排序认领。`candidates(address)` 对模式命中且未被 `canOpen` 否决的类型排序:先按档,再按最长命中模式的长度,最后按注册顺序。`claim(address, kind?)` 取第一个候选,或直接用点名的 `kind`——跳过它的 glob,但 `canOpen` 仍生效——返回 `{ kind, contentId: address, title }`。没有任何类型认领的地址会抛错:这是接线错误,不是用户错误。
52
+
53
+ 同一个 `kind` 可同时携带一个 `builtin` 与一个 `extension` 注册。extension 在认领、`get(kind)`、`openTab(kind)` 与引导页上生效,席位按生效定义的 `id` 找 tab 的正文与标题,不涉及任何 slot 优先级;extension 注销后 builtin 恢复。kind 上的其它任何撞名以及任何重复的 `id` 都抛错。
54
+
55
+ ```ts ignore-check
56
+ import type { Context } from '@deepseek-ai/cordis'
57
+ import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
58
+
59
+ export const inject = ['sidebarRightTabs', 'slots']
60
+
61
+ export function apply(ctx: Context): void {
62
+ ctx.effect(() => ctx.sidebarRightTabs.register({
63
+ id: '@acme/dsh-client-ui-image',
64
+ kind: 'image',
65
+ patterns: ['*.png', '*.jpg', '*.gif', '*.svg'],
66
+ canOpen: address => address.startsWith('dsh-resource://file/'),
67
+ title: address => address.slice(address.lastIndexOf('/') + 1),
68
+ }), 'image type')
69
+ ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register(
70
+ { name: 'sidebar.right.pane.tab', key: '@acme/dsh-client-ui-image' },
71
+ ImageBody,
72
+ )), 'image body')
73
+ }
74
+ ```
75
+
76
+ ## 导航:`ctx.sidebarRight`
77
+
78
+ 两种打开构成导航控制器,进入这一列的每条路都调用其一:`openResource(address, options?)` 打开 `dsh-resource://` 地址——会话区的文件链接、工具行的行号引用、文件树的行;`openTab(kind, options?)` 打开页面——tab 条的新增控件、引导页入口框。两者都以一条历史记录走完四步——认领(注册表为资源排候选,或点名 `kind` 的生效实现应答);聚焦已显示同一 `(kind, address)` 的 tab;否则落一个新 tab;展开这一列——然后把导航记入 Tab 域([服务](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#ctxsidebarright))。用户看不见的内容不算打开,所以折叠的列会在同一步展开。`openResource` 对 `dsh-resource://` 之外的地址或无人认领的地址抛错;`openTab` 对无人注册的 kind 抛错:二者都是接线错误,不是用户错误。
79
+
80
+ | 选项 | 含义 |
81
+ |---|---|
82
+ | `paneId` | 新 tab 落到这个 pane;缺省为活动的停靠 pane(活动的是浮窗时取第一个停靠 pane)。 |
83
+ | `replaceTab` | 占用这个 tab 的 pane 与条上位置,并在同一步关闭它;浮窗里的 tab 让不出位置,新 tab 按未指定位置落位。 |
84
+ | `revealIfOpened` | 缺省 `true`:已显示同一 `(kind, address)` 的 tab 被聚焦并收到 `params`。`false` 则无论如何再开一个。 |
85
+ | `kind`(仅 `openResource`) | 点名打开类型而不排候选;该 kind 的生效实现打开地址,它的 `canOpen` 仍生效。 |
86
+ | `params` | 给正文的导航参数,作为 `navigation.params` 送达。`openResource` 按资源类型经声明合并表 `SidebarRightResourceParamsMap` 定型(文本预览声明 `{ line?: number }`);`openTab<K>` 按 kind 经 `SidebarRightTabParamsMap` 定型,未声明的 kind 为 `undefined`;正文读到的是二者联合 `SidebarRightNavigationParams`。值按约定为 JSON 形状,运行时不校验。 |
87
+
88
+ 落位是调用方的选项,从不是类型的属性。会话区调 `openResource(fileAddressFor(sessionId, cwd, path))`,`read` 工具行另加 `{ params: { line } }`(来自调用的 1 起 `offset`);引导页入口框调 `tab.actions.openTab(entry.kind, { replaceTab: true })`;文件树的行调 `tab.actions.openResource(address)`;tab 条的新增控件调 `openTab('guide', { paneId, revealIfOpened: false })`。
89
+
90
+ `close(tabId)` 关闭一个 tab;`active()` 返回活动 pane 的活动 tab;`isExpanded()` 与 `toggleExpanded()` 读取与翻转这一列,翻转记入序列。无会话时读操作返回 `undefined` 或 `false`;写操作需要已挂载的会话面,没有时抛错而不是写进没人绘制的面。
91
+
92
+ `focus(tabId)` 让一个 tab 成为其 pane 的活动 tab;`split(paneId?)` 分割活动的停靠 pane 或点名的 pane,返回新 pane 的 id——pane 数预算或列宽不允许时返回 `undefined` 且不记账;`float(tabId, rect?)` 把一个 tab 浮出为浮窗 pane;`dock(paneId)` 把浮窗 pane 收回停靠区。四者都走 store 既有动作、各记一条历史;目标不存在或已处于目标状态时是空操作,与 `open` 一样在没有已挂载会话面时抛错。`TabId`、`PaneId`、`TabRecord`、`FloatRect` 自本包 `/client` 入口再导出,调用方无需引 dockkit。
93
+
94
+ ## Slot 与 owner props
95
+
96
+ Sidebar 声明四个扩展 slot;其文档 tab 另行声明下表中的 keyed 文档正文 slot([层级](./slots.md))。
97
+
98
+ | Slot | Cardinality | 用途 |
99
+ |---|---|---|
100
+ | `sidebar.right.pane.tab` | 按定义的 `id` keyed,会话作用域 | 一个 tab 的正文。席位把 tab 分发到其 kind 生效实现的 `id`,因此注册者收到该 kind 的每个 tab,停靠或浮窗。实现没有注册正文的 kind 渲染 owner 的「无法查看此内容」提示。 |
101
+ | `sidebar.right.pane.tab.title` | 按定义的 `id` keyed,会话作用域 | chip 的标题,owner share 与正文相同。可选:没有条目时 chip 显示打开时捕获的 `title(address)` 文本;有活标题的类型在此读自己的 store。 |
102
+ | `sidebar.right.tab.guide` | chain,会话作用域 | 替换引导 tab 的内容而不替换 tab;第一个不拒绝的条目接管正文,否则渲染自带引导。 |
103
+ | `sidebar.right.tab.menu.item` | list,会话作用域 | 追加在 kit 自身布局动作之后的内容级动作。执行了动作的条目必须调用 owner 的 `dismiss()`。 |
104
+ | `sidebar.right.tab.document` | 按文档实现的 `id` keyed,会话作用域 | 文档 tab 内选中的文件渲染器;父组件拥有共享加载与工具栏控件。 |
105
+
106
+ 正文、标题与引导页替换项接收框架注入的 `useTabInfo()`。它返回 `{ sidebar, panel, tab }`:`sidebar` 包含 `expanded` 与 `fullscreen`,`panel.id` 标识所属窗格,`tab` 包含记录字段以及 `visible`、`navigation`、`signal` 和 `actions`。停靠正文仅在展开且活跃时可见;停靠标题只要求展开;浮窗保持可见。`signal` 在记录消失或插件卸载时中止,不因隐藏或切换 Session 而中止。`tab.actions` 提供绑定到标签所属 Session 的 `openResource`、`openTab` 与 `close`。打开位置缺省为当前所属窗格;`revealIfOpened` 缺省为 `true`,`replaceTab: true` 在同一历史项中替换本记录。菜单项保留普通的 `tab` 与 `dismiss` owner 参数。
107
+
108
+ `navigation.revision` 在每次导航到该 tab 时递增,`params` 不变也递增,正文可仅凭「又被导航了」行动;按地址打开的 tab 为 `1`,没有人按地址打开的记录——种入的引导、撤销恢复的 tab——为 `0`。Tab 域为每条打开的记录保有一个 occurrence:记录出现即在资源模型里钉住,因此切换 tab 卸载正文也不丢内容;记录消失即中止并丢弃;撤销恢复的记录是新的 occurrence([Tab 域](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#the-tab-domain))。
109
+
110
+ ## 文档渲染器
111
+
112
+ `text` tab 是共享的 Document Preview 所有者。其[根注册](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/src/client/index.ts)声明 `sidebar.right.tab.document` 并提供 `ctx.documentPreviews`。渲染器在自己的 effect 中注册 `DocumentPreviewDefinition` 元数据,再通过 `ctx.slots.inject('sidebar.right.tab.document', ...)` 等待 slot,以 `key: definition.id` 和自己的 locale 命名空间注册组件。切换渲染器不改变 tab 或资源地址;[扩展决议](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.zh.md)将预览策略与资源归属分开。
113
+
114
+ [注册表](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/src/client/document/registry.ts)记录唯一的 `id`、`extensions`、本地化 `title()`、`loading`,以及可选的 `priority` 和 `wrap`。后缀匹配不区分大小写,先排 `extension`(缺省值)、再排 `builtin`,随后比较后缀长度(长者优先)与注册顺序。与 tab kind 替换不同,注册表保留所有实现;工具栏列出匹配的候选,按 tab 记住选择。未知扩展名使用纯文本。`loading` 为 `text-pages` 或 `bytes-complete`;`wrap` 声明是否支持共享的源码换行控件。
115
+
116
+ [`DocumentPreviewProps`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/src/client/document/contract.ts) 派生自 `PropsRuntime<'sidebar.right.tab.document'>`。owner 提供原始 `resourceAddress`、`content` 与当前 `wrap`:文本内容为 `{ kind: 'text', text, pages: [{ offset, text, lines }], eof }`,其中 `text` 为累积文本;完整字节为 `{ kind: 'bytes', data }`,其中 `data` 为 `Uint8Array<ArrayBuffer>`。这些瞬时缓冲区按只读方式借用,不得进入持久布局或 Session JSON。PDF 在转移到 Worker 前复制字节,以保留 owner 的缓冲区。子组件收到同一个框架绑定的 `useTabInfo`,以及全局共享、仅提供元数据的 `useResource`。父组件通过普通 inject 回调调用 `remote.workspaceFiles.read`/`readAll`,拥有追加分页、逐 tab 刷新与加载状态。HTML 自己的 inject 回调使用 `readRelated`;路径由 Host 代码解析。Markdown 和代码在追加期间保留同一个增量渲染器,到 EOF 完成最终解析;HTML 和 PDF 接收完整字节。
117
+
118
+ Preview 记录已载入版本和读取开始时的观察版本。刷新只重读当前 tab,不改变共享元数据或其他 tab 的内容。读取不具备事务性;版本是不透明的相等性令牌,不是可排序的时间戳([资源观察与 Preview RPC](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-09-08-document-preview-operations.zh.md))。
119
+
120
+ ## 资源模型
121
+
122
+ 模型本身见[客户端资源](./client-resources.md);本节只写 Sidebar 依赖的部分。一份资源是一个地址,资源地址是 `dsh-resource://<type>/…` 形式的 URL,小写 host 即协议键。协议所属的客户端包用 `ctx.resources.register(provider)` 在自身生命周期内注册唯一的提供方;同一协议的第二个提供方抛错([提供协议](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md#provide-a-protocol))。提供方是 `{ protocol, open(address, { signal }) }`:`open` 产出 `RemoteResult` 帧——首帧是当前状态,之后每次变化一帧——并在 `signal` 中止时停下;失败是 `{ ok: false, error }` 帧而不是抛错,流里抛出的东西是编程错误,模型不捕获。
123
+
124
+ `useResource<P>(address)` 是每个 slot 组件都有的全局标准 prop,不论作用域。它返回 `{ status, value, failure }`:地址协议没有提供方或地址不是资源地址(`sidebar://guide` 不指向资源)时为 `none`,首帧之前为 `loading`,`live` 携带最新 `ok` 值,`failed` 在最后一个值旁携带最新帧的失败。([读取资源](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md#read-a-resource))。
125
+
126
+ 资源有持有者就保持打开——订阅中的 `useResource` 或一次 `ctx.resources.pin(address, signal)`;第一个持有者打开提供方的流,之后的持有者共享它并立刻读到最新值,最后一个释放时中止流并丢弃值。流只推元数据不推内容:`file` 的值是 `{ absolutePath, version, bytes? }`,消费方自己经 Workspace Files 服务按页读文件文本([生命周期](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/resources/README.zh.md#lifecycle))。
127
+
128
+ ## Workspace Files
129
+
130
+ Host 的 `ctx.workspaceFiles` 服务与生成的 `workspaceFiles` Remote 命名空间读取 Session 文件系统后端允许的文件:`stat(path)` 返回 `{ absolutePath, version, bytes? }`;`read(path, { offset?, limit? })` 返回一页行(`offset` 1 起,`limit` 受配置页长限制),形如 `{ …stat, offset, text, eof }`;`readBytes(path, { offset?, length? })` 返回一个原始字节窗口(`offset` 0 起,`length` 受配置字节上限限制),形如 base64 的 `{ …stat, offset, data, eof }`、不做文本解码。`list(path)` 仍限定在工作区根内,返回目录的直接子项(`name`、`type: 'file' | 'directory' | 'other'`、`size?`),按配置上限截断并置 `truncated`。`changes()` 同样限定于工作区,订阅就绪后产出 `{ kind: 'ready' }`,随后产出 `{ kind: 'change', change }` 帧,其载荷为 `{ absolutePath, version }` 或 `{ absolutePath, absent: true }`([README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md#use-this-package))。文件操作拒绝末端符号链接并执行传输上限;`read` 还要求 UTF-8 文本。失败使用 `workspace-file/*` 错误码([失败](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md))。
131
+
132
+ [`dsh-api-workspace-files`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/api/workspace-files/README.zh.md) 注册 `file` 提供方,`ResourceProtocolMap.file` 直接是 `WorkspaceFileStat`。Session 地址携带授权 Session 与相对或绝对路径,Host 原样接收并解析。提供方在 stat 前等待 Host 的 `ready` 帧,并按 `stat.absolutePath` 过滤变更。裸 `absolute` 地址没有授权 Session,以 `workspace-file/unknown-workspace` 失败,不借用当前或 Tab Session。任何 UI(包括 Global)访问同一完整地址都共享观察。Preview 的普通 Remote 回调使用地址中的 Session;Host `readAll` 和 `readRelated` 保留,字节结果由 Preview 的 `rpc.ts` 解码。
133
+
134
+ ## 内置类型
135
+
136
+ - **`guide`**——`builtin`,以 `openTab('guide')` 打开。一枚弱化的罗盘位于各类型按 `order` 贡献的入口胶囊上方;入口较少时显示已注册的描述,未提供图标的入口统一使用内置占位符。点选胶囊即在引导 tab 的位置把贡献它的类型作为页面打开。每个 pane 最多一个引导 tab,tab 条的新增控件只在本 pane 没有引导时出现。新 pane 使用已注册的默认页:只有一个引导入口时直接使用该入口,否则使用引导页([引导](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-right/README.zh.md#the-guide))。
137
+ - **`text`**——`fallback`,`dsh-resource://file/**`,只认领 Session 地址。Document Preview 通过 `useResource<'file'>` 观察元数据,经 Remote 回调加载内容,并拥有渲染器选择、工具栏、逐 tab 刷新、滚动与源码定位;未知扩展名按纯文本渲染([README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-documentpreview/README.zh.md))。
138
+ - **`files`**——`builtin`,以 `openTab('files')` 打开。工作区目录树,经 `list` 懒加载,用 `tab.actions.openResource(fileAddressFor(sessionId, root, path))` 在自己所在 pane 打开文件([README](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/client/ui-sidebar-files/README.zh.md))。
139
+
140
+ <a id="not-built"></a>
141
+ ## 不做
142
+
143
+ - 持久化:布局状态只在内存里;刷新后每个会话从折叠开始,任何会话的 tab 都不会出现在另一个会话里。
144
+ - `ctx.sidebarRight` 上的只读布局快照或订阅:服务只暴露操作,dockkit 的 `LayoutState`/`LayoutOp` 是内部的。
145
+ - 服务上的能力探测数组(`features`)。
146
+ - tab 类型的 `option` 优先级档:没有「只列出、不许认领」的 tab 类型。
147
+ - 改写记录的标题:`title(address)` 只捕获一次;活的 chip 来自标题 slot,而不是记录。
148
+ - 打开时点名某个 tab 实现:`openResource` 最多点名一个 kind;文档渲染器由文件 tab 的工具栏选择。
149
+ - 服务上的地址查找(`find`):调用方用 `revealIfOpened` 打开,由停靠面去重。
150
+ - Sidebar 自身 `sidebar://<kind>` 记账之外的导航地址;其语法等导航控制器整体做时再定。
151
+ - 面向用户的撤销、内容导航栈与 tab 图标([暂缓](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-09-04-right-sidebar-docking-infrastructure.zh.md#deferred))。
@@ -261,7 +261,7 @@ Host service backing `ctx.remote.skills` without activating a cold Agent.
261
261
  * @param request - Session identity whose cwd and preset select the catalog view.
262
262
  * @param signal - caller lifetime carried by the Remote transport; admitted catalog reads retain their existing completion semantics.
263
263
  * @returns user-invocable skill metadata without loading skill bodies.
264
- * @throws TypertRemoteFailure when the Session cannot be inspected or no registry can serve it.
264
+ * @throws RemoteError when the Session cannot be inspected or no registry can serve it.
265
265
  */
266
266
  @Remote async list(request: SkillListRequest, signal: AbortSignal): Promise<SkillListValue>
267
267
  ```
@@ -85,6 +85,7 @@ Slot 声明固定两个相互独立的维度。
85
85
  |---|---|---|
86
86
  | 所有 scope | `useSessions`、`useSessionPendingInteraction` | `ui-session` |
87
87
  | 所有 scope | `useWorkspaces` | `ui-workspace` |
88
+ | 所有作用域 | `usePanelInfo` | `ui-layout` |
88
89
  | `session` | `sessionId`、`useSession`、`useProjection` | `ui-session` |
89
90
  | `session-maybe` | 结果可选的 `sessionId`、`useSession`、`useProjection` | `ui-session` |
90
91
  | `session` | `useConversation`、`useInput`、`inputActions` | `ui-conversation` |
@@ -115,6 +116,7 @@ root
115
116
  ├─ sidebar
116
117
  │ ├─ sidebar.brand.mark
117
118
  │ ├─ sidebar.brand.name
119
+ │ ├─ sidebar.panellist
118
120
  │ ├─ sidebar.footer.action
119
121
  │ ├─ sidebar.workspaces
120
122
  │ │ └─ sidebar.workspaces.directoryFlow
@@ -130,38 +132,45 @@ root
130
132
  │ ├─ settings.models.footer
131
133
  │ └─ settings.plugins.tab
132
134
  │ └─ settings.plugin.item
133
- ├─ conversation
134
- ├─ conversation.session
135
- │ └─ conversation.view
136
- │ ├─ conversation.chat.node
137
- ├─ conversation.chat.assistant-actions
138
- │ │ ├─ conversation.chat.commandview
139
- │ │ ├─ conversation.chat.turnTail
140
- │ │ └─ tool.call.toolview
141
- │ │ └─ tool.view.cordis
142
- │ ├─ conversation.message.images
143
- │ └─ conversation.trajectory.images
144
- ├─ conversation.session.header
145
- ├─ conversation.session.header.lineage
146
- ├─ conversation.session.header.actions
147
- └─ conversation.session.header.utilities
148
- │ ├─ conversation.composer
149
- └─ conversation.approval.detail
150
- ├─ conversation.composer.bar
151
- ├─ conversation.input.attachments
152
- ├─ conversation.input.plan
153
- │ └─ conversation.input.model
154
- │ ├─ conversation.input.overlay
155
- │ ├─ conversation.input.dock
156
- ├─ conversation.composer.dock
157
- ├─ conversation.input.left
158
- ├─ conversation.input.right
159
- ├─ conversation.hero.brand.mark
160
- ├─ conversation.hero.workspace
161
- │ └─ conversation.hero.workspace.directoryFlow
162
- └─ conversation.hero.agentPreset
163
- ├─ details
164
- │ └─ conversation.details.tool
135
+ ├─ main
136
+ └─ main.conversation
137
+ ├─ conversation.session
138
+ └─ conversation.view
139
+ │ ├─ conversation.chat.node
140
+ │ │ ├─ conversation.chat.assistant-actions
141
+ │ │ ├─ conversation.chat.commandview
142
+ │ │ ├─ conversation.chat.turnTail
143
+ │ │ └─ tool.call.toolview
144
+ ├─ tool.call.images
145
+ └─ tool.view.cordis
146
+ ├─ conversation.message.images
147
+ └─ conversation.trajectory.images
148
+ ├─ conversation.session.header
149
+ ├─ conversation.session.header.lineage
150
+ ├─ conversation.session.header.actions
151
+ ├─ conversation.session.header.utilities
152
+ └─ conversation.session.header.corner
153
+ ├─ conversation.composer
154
+ └─ conversation.approval.detail
155
+ ├─ conversation.composer.bar
156
+ ├─ conversation.input.attachments
157
+ ├─ conversation.input.plan
158
+ └─ conversation.input.model
159
+ ├─ conversation.input.overlay
160
+ ├─ conversation.input.dock
161
+ ├─ conversation.composer.dock
162
+ ├─ conversation.input.left
163
+ ├─ conversation.input.right
164
+ ├─ conversation.hero.brand.mark
165
+ ├─ conversation.hero.workspace
166
+ └─ conversation.hero.workspace.directoryFlow
167
+ │ └─ conversation.hero.agentPreset
168
+ ├─ rightbar
169
+ │ └─ rightbar.session
170
+ │ ├─ sidebar.right.pane.tab
171
+ │ │ └─ sidebar.right.tab.guide
172
+ │ ├─ sidebar.right.pane.tab.title
173
+ │ └─ sidebar.right.tab.menu.item
165
174
  └─ shell.overlay
166
175
  ```
167
176
 
@@ -5,13 +5,13 @@ outline: [2,3]
5
5
 
6
6
  # spill 存储
7
7
 
8
- spill 存储 seam 是一项[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md),它持久保存工具的超大文本,并返回面向模型的定位符与检索指引;该能力拆分到三个包:Service Definition[dsh-spill](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill),`ctx.spillStore`)、Service Provider[dsh-spill-local](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill-local),宿主文件系统中会话作用域的私有文件)和 Consumer([dsh-spill-policy](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill-policy),`tools/post-execute` 策略)。spill 是**一项可选能力**,不属于 agent loop(智能体循环)主干,因此其词汇记录在此处,而不在 [core.md](./core.md) 中。预览机制仍归 [dsh-output-retention](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/util/output-retention) 所有;该 seam 只保存策略交给它的最终文本。
8
+ spill 存储[能力 seam](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/architecture/2026-07-08-tool-output-spill-files.zh.md)持久保存调用方提供的文本,并返回面向模型的定位符与检索指引。其 Service Definition[dsh-spill](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill)(`ctx.spillStore`),本地 Service Provider[dsh-spill-local](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill-local)。消费方包括[工具结果策略](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/spill/spill-policy)[会话引用](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/context/session-reference/README.zh.md)。spill 是可选能力,不属于[智能体循环主干](./core.md);预览与 spill 决策由消费方负责,存储则原样保存所提供的文本。
9
9
 
10
10
  源码:[`packages/spill/spill/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/spill/spill/src/types.ts)
11
11
 
12
12
  ## 保存请求
13
13
 
14
- `saveText` 是唯一的服务操作:原样持久保存 `content`,并返回不透明的定位符、后端提供的检索提示和准确字节数。请求携带保存时的存储命名空间(`owner`)、生成内容的工具和调用(`source`,用于命名和检查,而非访问控制)以及后端可用作命名提示的 `suggestedName`(它不是路径)。
14
+ `saveText` 是唯一的服务操作:原样持久保存 `content`,并返回不透明的定位符、后端提供的检索提示和精确字节数。请求携带保存时的存储命名空间(`owner`)、描述性的生产者来源信息(`source`,绝非访问控制)以及后端可用作命名提示而非路径的 `suggestedName`。工具来源标识实际工具调用;会话引用来源标识被捕获的源会话,而其归属是接收上下文的目标会话。
15
15
 
16
16
  ```ts type-equiv
17
17
  /** One request to persist text to a spill artifact. */
@@ -45,17 +45,24 @@ interface SpillOwner {
45
45
 
46
46
  ```ts type-equiv
47
47
  /**
48
- * Tool and call that produced one spilled artifact recorded by the backend for a readable
49
- * filename and inspection. Not interpreted for access control; purely
50
- * descriptive.
48
+ * Producer of a spilled artifact. Tool results carry their model-issued call id;
49
+ * session references identify the captured source session instead. Descriptive
50
+ * provenance only, never access control.
51
51
  */
52
- interface SpillSource {
52
+ type SpillSource = {
53
+ kind: 'tool'
53
54
  /** The tool whose result was spilled (e.g. `web_fetch`). */
54
55
  toolName: string
55
56
  /** The model-issued call id the result belongs to. */
56
57
  callId: ToolCallId
57
58
  /** A short human label for the artifact (e.g. `result`). */
58
59
  label: string
60
+ } | {
61
+ kind: 'session-reference'
62
+ /** Session whose projected conversation was captured. */
63
+ sessionId: SessionId
64
+ /** Host-provided label for the referenced session. */
65
+ label: string
59
66
  }
60
67
  ```
61
68
 
@@ -47,7 +47,7 @@ interface StorageBackend {
47
47
  }
48
48
  ```
49
49
 
50
- 一个后端拥有一个介质(一棵文件树的根目录、一个数据库文件),并提供可选的操作组;`kv` 是唯一已交付的操作组。`KvFacet.open(descriptor)` 打开一个具名 unit——`KvUnitDescriptor` 携带名称、格式版本、表名清单,以及是否存在全局单例 slot——并返回提供 `loadAll`、`putRecord`、`deleteRecord`、`setGlobal` 和 `close` 的 `KvUnit`。unit 名与表名必须匹配 `UNIT_NAME_RE`(既可安全用作文件名,也可安全用作 SQL 标识符片段);记录键是任意字符串,绝不进入文件路径。unit 不对并发写入做串行化——顺序由调用方负责——但每次单独调用在介质上都是原子的,且 resolve 后即已持久。介质上记录的版本与之不同时拒绝 `version-mismatch`;无法按该 unit 解析的介质拒绝 `malformed-medium`(不做迁移:预发布立场)。[`backend.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/storage/storage/src/backend.ts) 是逐条款的规范性约定,[`tests/contract.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/storage/storage/tests/contract.ts) 中的共享一致性套件会针对每个后端检查每项条款。[json 后端](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/storage/storage-json/README.zh.md)以原子方式为每个 unit 整文件重新发布一份人类可读文件;[sqlite 后端](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/storage/storage-sqlite/README.zh.md)在单个数据库中每行存储一份文档,用于频繁更新的数据。
50
+ 一个后端拥有一个介质(一棵文件树的根目录、一个数据库文件),并提供可选的操作组;`kv` 是唯一已交付的操作组。`KvFacet.open(descriptor)` 打开一个具名 unit——`KvUnitDescriptor` 携带名称、当前格式版本、可选的兼容记录版本、表名清单,以及是否存在全局单例 slot——并返回提供 `loadAll`、`putRecord`、`deleteRecord`、`setGlobal` 和 `close` 的 `KvUnit`。unit 名与表名必须匹配 `UNIT_NAME_RE`(既可安全用作文件名,也可安全用作 SQL 标识符片段);记录键是任意字符串,绝不进入文件路径。unit 不对并发写入做串行化——顺序由调用方负责——但每次单独调用在介质上都是原子的,且 resolve 后即已持久。`single` 介质上记录的版本不同时拒绝 `version-mismatch`;`per-record` 文档的版本在接受集合之外时读作不存在。无法按该 unit 解析的介质拒绝 `malformed-medium`。[`backend.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/storage/storage/src/backend.ts) 是逐条款的规范性约定,[`tests/contract.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/storage/storage/tests/contract.ts) 中的共享一致性套件会针对每个后端检查每项条款。[json 后端](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/storage/storage-json/README.zh.md)以原子方式为每个 unit 整文件重新发布一份人类可读文件;[sqlite 后端](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/storage/storage-sqlite/README.zh.md)在单个数据库中每行存储一份文档,用于频繁更新的数据。
51
51
 
52
52
  ## 声明领域
53
53
 
@@ -58,16 +58,36 @@ interface StorageBackend {
58
58
  interface DomainSpec {
59
59
  /** Domain name; must match `UNIT_NAME_RE` (doubles as the backend unit name). */
60
60
  readonly name: string
61
- /** Domain format version; a medium stamped with a different version rejects at open. */
61
+ /** Current domain format version; reads enforce it according to the selected layout. */
62
62
  readonly version: number
63
63
  /**
64
64
  * Medium layout for the backend unit: `single` (the default) stores the
65
65
  * whole unit as one document; `per-record` stores each record as its own
66
66
  * document, for units whose records are large, sparse, or individually
67
- * disposable — the projection cache — and scopes version bumps per record
68
- * (a stale record document is discarded, never migrated).
67
+ * disposable — the projection cache — and scopes version checks per record
68
+ * (an unaccepted record document is discarded, never migrated).
69
69
  */
70
70
  readonly layout?: 'single' | 'per-record'
71
+ /**
72
+ * Older domain versions whose stored records the current record schemas
73
+ * also accept (the declaring owner vouches for that, typically by
74
+ * declaring the fields older records lack as optional). `per-record` backends
75
+ * read documents stamped with a listed version instead of discarding them,
76
+ * and accept a legacy whole-unit file so stamped for the one-time
77
+ * bootstrap; writes always stamp {@link version}.
78
+ */
79
+ readonly compatibleVersions?: readonly number[]
80
+ /**
81
+ * What `open` does with a stored table record that fails its zod schema.
82
+ * Absent (the default), the whole open rejects with `invalid-record` —
83
+ * right for authoritative data. `'backup-and-skip'` is for domains whose
84
+ * records are disposable derived data: the backend moves the record's
85
+ * document aside (`KvUnit.backupRecord`), the failure is logged with
86
+ * its cause, and the open continues with the record absent. A backend
87
+ * without `backupRecord` (no per-record document to move) falls back
88
+ * to the rejecting default. The global slot always rejects.
89
+ */
90
+ readonly invalidRecords?: 'backup-and-skip'
71
91
  /** Optional global singleton slot. */
72
92
  readonly global?: DomainGlobalSpec<unknown>
73
93
  /** Table declarations keyed by table name; each name must match `UNIT_NAME_RE`. */
@@ -183,7 +203,10 @@ The mounted domain facility. Opens declared domains over routed backends; one fa
183
203
  * (`facet-unsupported`); open the unit projected from the spec (backend
184
204
  * `version-mismatch`/`malformed-medium` pass through); load and validate
185
205
  * every stored record against the spec's zod schemas (`invalid-record`
186
- * with the offending table and key); construct the domain.
206
+ * with the offending table and key unless the spec declares
207
+ * `invalidRecords: 'backup-and-skip'` and the unit can move documents aside, in
208
+ * which case the failing record is backed up, logged, and skipped);
209
+ * construct the domain.
187
210
  *
188
211
  * Lifecycle: the CALLER owns the returned handle and closes it via
189
212
  * `Domain.close()` (typically as its own `ctx.effect` disposer) — the