dsh-plugin-dev-kb 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +56 -0
  3. package/cordis.patch.yml +12 -0
  4. package/kb/INDEX.md +210 -0
  5. package/kb/README.md +69 -0
  6. package/kb/extra/AGENTS.md +75 -0
  7. package/kb/extra/api-gateway.md +164 -0
  8. package/kb/extra/api-gateway.zh.md +164 -0
  9. package/kb/extra/cookbook/adding-a-vendored-package.md +59 -0
  10. package/kb/extra/cookbook/adding-a-vendored-package.zh.md +59 -0
  11. package/kb/extra/cookbook/maintaining-dsh-code-review.md +64 -0
  12. package/kb/extra/cookbook/maintaining-dsh-code-review.zh.md +64 -0
  13. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
  14. package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
  15. package/kb/extra/defensive-patterns.md +33 -0
  16. package/kb/extra/defensive-patterns.zh.md +33 -0
  17. package/kb/extra/development.md +171 -0
  18. package/kb/extra/development.zh.md +171 -0
  19. package/kb/extra/event-producer-consumer.md +76 -0
  20. package/kb/extra/event-producer-consumer.zh.md +78 -0
  21. package/kb/extra/glossary.md +45 -0
  22. package/kb/extra/glossary.zh.md +45 -0
  23. package/kb/extra/graph-atlas.md +24 -0
  24. package/kb/extra/graph-atlas.zh.md +26 -0
  25. package/kb/extra/i18n/README.md +60 -0
  26. package/kb/extra/i18n/README.zh.md +60 -0
  27. package/kb/extra/i18n/style-samples.md +87 -0
  28. package/kb/extra/i18n/terminology.md +214 -0
  29. package/kb/extra/i18n/translation-prompt.md +263 -0
  30. package/kb/extra/i18n/translation-rules.md +69 -0
  31. package/kb/extra/i18n/translation-rules.zh.md +69 -0
  32. package/kb/extra/module-graph.md +1641 -0
  33. package/kb/extra/module-graph.zh.md +1643 -0
  34. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +113 -0
  35. package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
  36. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
  37. package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
  38. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
  39. package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
  40. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
  41. package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
  42. package/kb/extra/postmortem/README.md +18 -0
  43. package/kb/extra/postmortem/README.zh.md +18 -0
  44. package/kb/extra/rescope.md +53 -0
  45. package/kb/extra/rescope.zh.md +53 -0
  46. package/kb/extra/subsystems/attachment.md +125 -0
  47. package/kb/extra/subsystems/attachment.zh.md +125 -0
  48. package/kb/extra/subsystems/extensions.md +364 -0
  49. package/kb/extra/subsystems/extensions.zh.md +364 -0
  50. package/kb/extra/subsystems/feedback.md +266 -0
  51. package/kb/extra/subsystems/feedback.zh.md +266 -0
  52. package/kb/extra/testing.md +49 -0
  53. package/kb/extra/testing.zh.md +49 -0
  54. package/kb/extra/web-styling.md +25 -0
  55. package/kb/extra/web-styling.zh.md +25 -0
  56. package/kb/meta/search-index.json +1328 -0
  57. package/kb/meta/site-pages.txt +168 -0
  58. package/kb/meta/source.json +13 -0
  59. package/kb/meta/topics.md +75 -0
  60. package/kb/site/develop/basic/config.md +108 -0
  61. package/kb/site/develop/basic/index.md +146 -0
  62. package/kb/site/develop/basic/publish.md +185 -0
  63. package/kb/site/develop/basic/tool.md +54 -0
  64. package/kb/site/develop/cordis-tutorial/01-first-plugin.md +95 -0
  65. package/kb/site/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  66. package/kb/site/develop/cordis-tutorial/03-services.md +98 -0
  67. package/kb/site/develop/cordis-tutorial/04-events.md +144 -0
  68. package/kb/site/develop/cordis-tutorial/05-config.md +84 -0
  69. package/kb/site/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
  70. package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +107 -0
  71. package/kb/site/develop/cordis-tutorial/index.md +62 -0
  72. package/kb/site/develop/framework/events.md +145 -0
  73. package/kb/site/develop/framework/index.md +139 -0
  74. package/kb/site/develop/framework/service.md +152 -0
  75. package/kb/site/develop/practice/index.md +157 -0
  76. package/kb/site/develop/practice/llm-adapter.md +190 -0
  77. package/kb/site/en/develop/basic/config.md +108 -0
  78. package/kb/site/en/develop/basic/index.md +146 -0
  79. package/kb/site/en/develop/basic/publish.md +185 -0
  80. package/kb/site/en/develop/basic/tool.md +54 -0
  81. package/kb/site/en/develop/cordis-tutorial/01-first-plugin.md +95 -0
  82. package/kb/site/en/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
  83. package/kb/site/en/develop/cordis-tutorial/03-services.md +98 -0
  84. package/kb/site/en/develop/cordis-tutorial/04-events.md +144 -0
  85. package/kb/site/en/develop/cordis-tutorial/05-config.md +84 -0
  86. package/kb/site/en/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
  87. package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +107 -0
  88. package/kb/site/en/develop/cordis-tutorial/index.md +60 -0
  89. package/kb/site/en/develop/framework/events.md +145 -0
  90. package/kb/site/en/develop/framework/index.md +139 -0
  91. package/kb/site/en/develop/framework/service.md +150 -0
  92. package/kb/site/en/develop/practice/index.md +157 -0
  93. package/kb/site/en/develop/practice/llm-adapter.md +190 -0
  94. package/kb/site/en/guide/providers-custom-form.png +0 -0
  95. package/kb/site/en/guide/providers-models-page.png +0 -0
  96. package/kb/site/en/guide/providers.md +100 -0
  97. package/kb/site/en/guide/python-sdk.md +106 -0
  98. package/kb/site/en/guide/quickstart.md +32 -0
  99. package/kb/site/en/index.md +8 -0
  100. package/kb/site/en/reference/agent-lifecycle.md +86 -0
  101. package/kb/site/en/reference/capability-seams.md +475 -0
  102. package/kb/site/en/reference/config-catalog.md +3155 -0
  103. package/kb/site/en/reference/cookbook/adding-a-conversation-node.md +235 -0
  104. package/kb/site/en/reference/cookbook/adding-a-package.md +120 -0
  105. package/kb/site/en/reference/cookbook/adding-a-settings-card.md +102 -0
  106. package/kb/site/en/reference/cookbook/adding-a-tool.md +96 -0
  107. package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +45 -0
  108. package/kb/site/en/reference/cookbook/extension-cookbook.md +131 -0
  109. package/kb/site/en/reference/cordis-api/context.md +368 -0
  110. package/kb/site/en/reference/cordis-api/events.md +211 -0
  111. package/kb/site/en/reference/cordis-api/fiber.md +379 -0
  112. package/kb/site/en/reference/cordis-api/inherited.md +43 -0
  113. package/kb/site/en/reference/cordis-api/registry.md +156 -0
  114. package/kb/site/en/reference/cordis-api/service.md +106 -0
  115. package/kb/site/en/reference/cordis-primer.md +46 -0
  116. package/kb/site/en/reference/index.md +131 -0
  117. package/kb/site/en/reference/persistence-catalog.md +949 -0
  118. package/kb/site/en/reference/subsystems/approval.md +173 -0
  119. package/kb/site/en/reference/subsystems/client-modules.md +121 -0
  120. package/kb/site/en/reference/subsystems/code-runtime.md +194 -0
  121. package/kb/site/en/reference/subsystems/commands.md +190 -0
  122. package/kb/site/en/reference/subsystems/compaction.md +241 -0
  123. package/kb/site/en/reference/subsystems/core.md +1073 -0
  124. package/kb/site/en/reference/subsystems/credentials.md +136 -0
  125. package/kb/site/en/reference/subsystems/filesystem.md +498 -0
  126. package/kb/site/en/reference/subsystems/goal.md +280 -0
  127. package/kb/site/en/reference/subsystems/index.md +58 -0
  128. package/kb/site/en/reference/subsystems/invariants.md +91 -0
  129. package/kb/site/en/reference/subsystems/jobs.md +293 -0
  130. package/kb/site/en/reference/subsystems/llm-streaming.md +920 -0
  131. package/kb/site/en/reference/subsystems/lsp.md +205 -0
  132. package/kb/site/en/reference/subsystems/permission-presets.md +134 -0
  133. package/kb/site/en/reference/subsystems/persistence.md +388 -0
  134. package/kb/site/en/reference/subsystems/plan.md +90 -0
  135. package/kb/site/en/reference/subsystems/sandbox.md +221 -0
  136. package/kb/site/en/reference/subsystems/schedule.md +189 -0
  137. package/kb/site/en/reference/subsystems/scope.md +62 -0
  138. package/kb/site/en/reference/subsystems/session-projection.md +265 -0
  139. package/kb/site/en/reference/subsystems/session-query.md +498 -0
  140. package/kb/site/en/reference/subsystems/session-reference.md +111 -0
  141. package/kb/site/en/reference/subsystems/session-telemetry.md +197 -0
  142. package/kb/site/en/reference/subsystems/session-title.md +207 -0
  143. package/kb/site/en/reference/subsystems/session.md +852 -0
  144. package/kb/site/en/reference/subsystems/settings.md +313 -0
  145. package/kb/site/en/reference/subsystems/shell.md +306 -0
  146. package/kb/site/en/reference/subsystems/skills.md +334 -0
  147. package/kb/site/en/reference/subsystems/spill.md +120 -0
  148. package/kb/site/en/reference/subsystems/storage.md +232 -0
  149. package/kb/site/en/reference/subsystems/subagent.md +737 -0
  150. package/kb/site/en/reference/subsystems/subprocess.md +327 -0
  151. package/kb/site/en/reference/subsystems/system-prompt.md +210 -0
  152. package/kb/site/en/reference/subsystems/terminal.md +187 -0
  153. package/kb/site/en/reference/subsystems/token-meter.md +93 -0
  154. package/kb/site/en/reference/subsystems/tools.md +723 -0
  155. package/kb/site/en/reference/subsystems/typert.md +339 -0
  156. package/kb/site/en/reference/subsystems/user-questions.md +181 -0
  157. package/kb/site/en/reference/subsystems/web-server.md +111 -0
  158. package/kb/site/en/reference/subsystems/web.md +202 -0
  159. package/kb/site/en/reference/subsystems/workflow.md +281 -0
  160. package/kb/site/en/reference/subsystems/workspace.md +231 -0
  161. package/kb/site/en/reference/tool-catalog.md +1877 -0
  162. package/kb/site/en/reference/tool-execution-pipeline.md +66 -0
  163. package/kb/site/guide/providers-custom-form.zh.png +0 -0
  164. package/kb/site/guide/providers-models-page.zh.png +0 -0
  165. package/kb/site/guide/providers.md +100 -0
  166. package/kb/site/guide/python-sdk.md +106 -0
  167. package/kb/site/guide/quickstart.md +32 -0
  168. package/kb/site/index.md +8 -0
  169. package/kb/site/reference/agent-lifecycle.md +86 -0
  170. package/kb/site/reference/capability-seams.md +475 -0
  171. package/kb/site/reference/config-catalog.md +3154 -0
  172. package/kb/site/reference/cookbook/adding-a-conversation-node.md +235 -0
  173. package/kb/site/reference/cookbook/adding-a-package.md +120 -0
  174. package/kb/site/reference/cookbook/adding-a-settings-card.md +102 -0
  175. package/kb/site/reference/cookbook/adding-a-tool.md +98 -0
  176. package/kb/site/reference/cookbook/adding-an-llm-adapter.md +45 -0
  177. package/kb/site/reference/cookbook/extension-cookbook.md +133 -0
  178. package/kb/site/reference/cordis-api/context.md +368 -0
  179. package/kb/site/reference/cordis-api/events.md +211 -0
  180. package/kb/site/reference/cordis-api/fiber.md +379 -0
  181. package/kb/site/reference/cordis-api/inherited.md +43 -0
  182. package/kb/site/reference/cordis-api/registry.md +156 -0
  183. package/kb/site/reference/cordis-api/service.md +106 -0
  184. package/kb/site/reference/cordis-primer.md +52 -0
  185. package/kb/site/reference/index.md +135 -0
  186. package/kb/site/reference/persistence-catalog.md +949 -0
  187. package/kb/site/reference/subsystems/approval.md +173 -0
  188. package/kb/site/reference/subsystems/client-modules.md +121 -0
  189. package/kb/site/reference/subsystems/code-runtime.md +194 -0
  190. package/kb/site/reference/subsystems/commands.md +190 -0
  191. package/kb/site/reference/subsystems/compaction.md +241 -0
  192. package/kb/site/reference/subsystems/core.md +1081 -0
  193. package/kb/site/reference/subsystems/credentials.md +136 -0
  194. package/kb/site/reference/subsystems/filesystem.md +498 -0
  195. package/kb/site/reference/subsystems/goal.md +280 -0
  196. package/kb/site/reference/subsystems/index.md +58 -0
  197. package/kb/site/reference/subsystems/invariants.md +91 -0
  198. package/kb/site/reference/subsystems/jobs.md +293 -0
  199. package/kb/site/reference/subsystems/llm-streaming.md +926 -0
  200. package/kb/site/reference/subsystems/lsp.md +205 -0
  201. package/kb/site/reference/subsystems/permission-presets.md +134 -0
  202. package/kb/site/reference/subsystems/persistence.md +388 -0
  203. package/kb/site/reference/subsystems/plan.md +90 -0
  204. package/kb/site/reference/subsystems/sandbox.md +221 -0
  205. package/kb/site/reference/subsystems/schedule.md +189 -0
  206. package/kb/site/reference/subsystems/scope.md +62 -0
  207. package/kb/site/reference/subsystems/session-projection.md +265 -0
  208. package/kb/site/reference/subsystems/session-query.md +498 -0
  209. package/kb/site/reference/subsystems/session-reference.md +111 -0
  210. package/kb/site/reference/subsystems/session-telemetry.md +197 -0
  211. package/kb/site/reference/subsystems/session-title.md +207 -0
  212. package/kb/site/reference/subsystems/session.md +854 -0
  213. package/kb/site/reference/subsystems/settings.md +313 -0
  214. package/kb/site/reference/subsystems/shell.md +306 -0
  215. package/kb/site/reference/subsystems/skills.md +334 -0
  216. package/kb/site/reference/subsystems/spill.md +120 -0
  217. package/kb/site/reference/subsystems/storage.md +232 -0
  218. package/kb/site/reference/subsystems/subagent.md +739 -0
  219. package/kb/site/reference/subsystems/subprocess.md +327 -0
  220. package/kb/site/reference/subsystems/system-prompt.md +210 -0
  221. package/kb/site/reference/subsystems/terminal.md +187 -0
  222. package/kb/site/reference/subsystems/token-meter.md +93 -0
  223. package/kb/site/reference/subsystems/tools.md +723 -0
  224. package/kb/site/reference/subsystems/typert.md +339 -0
  225. package/kb/site/reference/subsystems/user-questions.md +181 -0
  226. package/kb/site/reference/subsystems/web-server.md +111 -0
  227. package/kb/site/reference/subsystems/web.md +202 -0
  228. package/kb/site/reference/subsystems/workflow.md +281 -0
  229. package/kb/site/reference/subsystems/workspace.md +231 -0
  230. package/kb/site/reference/tool-catalog.md +1880 -0
  231. package/kb/site/reference/tool-execution-pipeline.md +66 -0
  232. package/package.json +40 -0
  233. package/scripts/rebuild-index.mjs +88 -0
  234. package/skills/dsh-plugin-dev-kb.md +66 -0
@@ -0,0 +1,313 @@
1
+ ---
2
+ editSource: "docs/subsystems/settings.zh.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # 用户设置
7
+
8
+ [dsh-settings](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/settings/settings) 的用户设置 seam 持有一份按 namespace 分节的用户文档,并把每个已注册 namespace 解析为:schema 默认值,然后注册方的组合 `base`,最后用户分节。[dsh-settings-file](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/settings/settings-file) 这类提供方存储原始文档并推送外部编辑;消费方插件注册 schema 后读取或观察解析值。组合配置仍留在 `cordis.yml`——namespace 只承载用户可编辑子集。
9
+
10
+ 来源:[`packages/settings/settings/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/settings/settings/src/index.ts)
11
+
12
+ ## 标识
13
+
14
+ namespace 命名用户文档中一个归插件所有的分节。brand 防止调用方将设置 namespace 与在包或进程之间传递的其他 id 混用;构造时校验小写 kebab-case 语法。
15
+
16
+ ```ts type-equiv
17
+ /** Nominal id of one registered settings namespace. */
18
+ type SettingsNamespace = Branded<'SettingsNamespace'>
19
+ ```
20
+
21
+ ## 注册
22
+
23
+ 注册把 schemastery schema 绑定到调用方插件 fiber 上的 namespace——dispose(资源释放)该 fiber 即移除 namespace 及其观察者。options 携带组合层、owner 的生效时机,以及一个可选的、用于校验 schema 表达不了的约束的钩子。
24
+
25
+ ```ts type-equiv
26
+ /** Registration options beyond the namespace schema. */
27
+ interface SettingsRegisterOptions<T> {
28
+ /** Composition-layer values resolved below the user layer (entry-config subset). */
29
+ base?: Partial<T>
30
+ /** Owner's effect timing, surfaced to configuration UIs; defaults to `live`. */
31
+ applies?: SettingsApplies
32
+ /**
33
+ * Reject a resolved section the owner could not act on, for constraints its
34
+ * schema cannot express — a cross-field requirement, or one field's validity
35
+ * depending on another's. Throwing here refuses the *write* that produced the
36
+ * value, so a caller learns at `update`/`replace`/`mutate` instead of storing
37
+ * something that would silently disable the owner.
38
+ *
39
+ * Kept separate from the schema because the schema is also what a
40
+ * configuration surface renders and what an absent section resolves through;
41
+ * folding a cross-field check into it would change both.
42
+ *
43
+ * Once the owner is registered, a stored section that fails this keeps the
44
+ * namespace's last good value and warns, exactly as a schema failure does,
45
+ * so an externally edited document cannot strand a running owner. At
46
+ * registration there is no last good value yet, so a stored section that
47
+ * already fails rejects the registration itself — again exactly as a schema
48
+ * failure does.
49
+ * @param value - the resolved section, schema-valid by construction.
50
+ */
51
+ validate?: (value: T) => void
52
+ }
53
+ ```
54
+
55
+ `validate` 在 schema 接纳该值之后运行,因此它看到的默认值和组合 base 与 owner 实际看到的完全一致。`dsh-llm-pi-ai` 用它在写入处拒绝自己无法服务的提供方 profile,而不是先存下来、再让该 namespace 下每条路由失效。
56
+
57
+ `applies` 是 UI 提示而非机制:`restart` 的 owner 只是从不 watch,其值在构造期读取一次,配置界面可为待生效变更加标。
58
+
59
+ ```ts type-equiv
60
+ /** When a namespace's changes take effect for its owner. */
61
+ type SettingsApplies = 'live' | 'restart'
62
+ ```
63
+
64
+ ## Owner scope
65
+
66
+ scope 是面向 owner 的句柄。`update` 把稀疏 patch 只合并进用户分节(绝不进 `base`);`replace` 整体替换分节,是删除/重置路径——替换中缺席的键重新继承 `base` 与 schema 默认值。同一 namespace 的写入按调用顺序串行,解析值是深冻结快照。
67
+
68
+ ```ts type-equiv
69
+ /** Owner-facing handle for one registered namespace. */
70
+ interface SettingsScope<T> {
71
+ /** Current resolved value: schema defaults, then `base`, then the user layer. */
72
+ get(): T
73
+ /**
74
+ * Observe committed changes to this namespace's resolved value. Invocations
75
+ * of one callback run asynchronously, one at a time, in commit order; a
76
+ * rejection is contained and logged like a sync throw. After the disposer
77
+ * returns, no further invocation starts — one already queued is skipped;
78
+ * one already started still settles, and service disposal waits for it.
79
+ * @param callback - invoked after each commit with the next and previous values.
80
+ * @returns the disposer removing this observer.
81
+ */
82
+ watch(callback: (next: T, prev: T) => void | Promise<void>): () => void
83
+ /**
84
+ * Merge a partial patch into this namespace's user layer and persist it.
85
+ * @param patch - plain-object patch over the user section; JSON-compatible data
86
+ * only (non-JSON values reject with their path before anything persists).
87
+ */
88
+ update(patch: object): Promise<void>
89
+ /**
90
+ * Replace this namespace's user section wholesale; absent keys re-inherit
91
+ * the composition `base` and schema defaults (`replace({})` resets all).
92
+ * @param section - the complete next user section; JSON-compatible data only,
93
+ * as for {@link update}.
94
+ */
95
+ replace(section: object): Promise<void>
96
+ }
97
+ ```
98
+
99
+ ## 描述符
100
+
101
+ `describe()` 为配置界面序列化每个已注册 namespace:schemastery 的 `toJSON()` 封装结构驱动 schema 渲染的表单,解析值填充表单,分离出的 `base`/`user` 层让表单按字段是否出现在 user 层标注「用户已覆盖」。`describe({ redactSecrets: true })`——每个对外传输接口都必须传入——从三层剥离 `role('secret')` 字段并枚举其 `{path, set}` slot,页面因此能渲染只写输入框而永远收不到机密值。
102
+
103
+ ```ts type-equiv
104
+ /** One registered namespace as surfaced to configuration UIs. */
105
+ interface SettingsDescriptor {
106
+ /** The registered namespace. */
107
+ ns: SettingsNamespace
108
+ /** Serialized schemastery schema (`schema.toJSON()`). */
109
+ schema: unknown
110
+ /** Current resolved value. */
111
+ value: unknown
112
+ /**
113
+ * Monotonic revision of the raw user section this descriptor was read at.
114
+ * Send it back as `expectedRevision` on a write to refuse a stale one.
115
+ */
116
+ revision: number
117
+ /** Registrant's composition `base` layer (detached), when one was declared. */
118
+ base?: unknown
119
+ /**
120
+ * Raw user section from the stored document (detached), when one exists and
121
+ * is well-formed; a field's presence here is what marks it user-overridden.
122
+ */
123
+ user?: unknown
124
+ /** Owner's declared effect timing. */
125
+ applies: SettingsApplies
126
+ /** Schema-declared secret positions; present only under `redactSecrets`. */
127
+ secrets?: RedactedSecret[]
128
+ }
129
+ ```
130
+
131
+ 只持有脱敏 descriptor 的调用方无法安全地重建分节,因此删除改以路径 op 传递。每个 descriptor 还携带针对原始分节的 `revision`;写入可以把它作为 `expectedRevision` 送回,不再匹配的写入会被拒绝,而不会覆盖先落地的写入。
132
+ ```ts type-equiv
133
+ /**
134
+ * One path-addressed edit to a namespace's user section. Path mutation exists
135
+ * for a caller holding an INCOMPLETE view of the section — a configuration UI
136
+ * reads the redacted descriptor, which by construction never received the
137
+ * `role('secret')` fields. Such a caller can name the field it means without
138
+ * restating the section: a wholesale `replace` rebuilt from a redacted
139
+ * document silently deletes every secret the wire never returned.
140
+ */
141
+ type SettingsPathOp =
142
+ | { op: 'set'; path: readonly string[]; value: unknown }
143
+ | { op: 'unset'; path: readonly string[] }
144
+ ```
145
+
146
+ ```ts type-equiv
147
+ /** Options for {@link SettingsProvider.describe}. */
148
+ interface SettingsDescribeOptions {
149
+ /**
150
+ * Strip `role('secret')` fields from `value`/`base`/`user` and enumerate
151
+ * them in each descriptor's `secrets`. Every wire surface MUST pass this;
152
+ * the verbatim default exists for same-process configuration UIs only.
153
+ */
154
+ redactSecrets?: boolean
155
+ }
156
+ ```
157
+
158
+ ## 变更提交
159
+
160
+ 每次提交的变更——进程内写入或提供方观察到的外部编辑——在新值成为权威值之后发出 `settings/updated (ns, next, prev, source)`,解析值深相等时绝不发出。source 标记区分两条入口路径。
161
+
162
+ ```ts type-equiv
163
+ /** Origin of one committed settings change. */
164
+ type SettingsUpdateSource = 'update' | 'provider'
165
+ ```
166
+
167
+ <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
168
+
169
+ <a id="cordis-surface"></a>
170
+
171
+ ## Cordis API
172
+
173
+ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
174
+
175
+ <a id="ctxsettings--settingsprovider-abstract-seam"></a>
176
+
177
+ ### `ctx.settings` — `SettingsProvider` (abstract seam)
178
+
179
+ Abstract settings service. Providers implement raw-document storage (`load`/`persist`) and push external changes through Settings.publish; the base class owns namespace registration, resolution, validation, change detection, and the `settings/updated` commit event.
180
+
181
+ ```ts cordis-catalog
182
+ /**
183
+ * Prepare the provider's user-editable document for a native editor. File
184
+ * providers may materialize an absent document before returning its path;
185
+ * non-file providers return undefined.
186
+ * @returns the absolute local document path, or undefined for non-file storage.
187
+ */
188
+ prepareDocument(): Promise<string | undefined>
189
+
190
+ /**
191
+ * Register a namespace schema and receive its owner scope. The registration
192
+ * is an effect on the calling plugin's fiber: disposing that fiber removes
193
+ * the namespace and its observers. An invalid stored section fails the
194
+ * registration itself — the earliest point where the schema can judge it.
195
+ * @param ns - unique namespace; duplicate registration fails loud.
196
+ * @param schema - schemastery schema resolving this namespace's value.
197
+ * @param options - composition `base` layer and effect timing.
198
+ * @returns the owner scope for reads, observation, and updates.
199
+ */
200
+ register<T>(ns: SettingsNamespace, schema: z<T>, options?: SettingsRegisterOptions<T>): SettingsScope<T>
201
+
202
+ /**
203
+ * Describe every registered namespace for configuration surfaces, including
204
+ * the composition `base` and raw user layers so a form can mark which fields
205
+ * the user overrode (presence in `user`) and what a reset returns to.
206
+ * @param options - redaction switch; wire surfaces must redact.
207
+ * @returns one descriptor per registered namespace, in registration order.
208
+ */
209
+ describe(options?: SettingsDescribeOptions): SettingsDescriptor[]
210
+
211
+ /**
212
+ * Read one registered namespace's resolved value.
213
+ * @param ns - the namespace to read.
214
+ * @returns the resolved value, or `undefined` while unregistered.
215
+ */
216
+ get(ns: SettingsNamespace): unknown
217
+
218
+ /**
219
+ * Merge a patch into one registered namespace's user layer, validate the
220
+ * resolved candidate, persist through the provider, then commit and emit.
221
+ * A validation failure rejects before anything is persisted. Writes to one
222
+ * namespace are serialized: concurrent updates apply in call order, each
223
+ * merging over the previous write's committed section.
224
+ * @param ns - the registered namespace to update.
225
+ * @param patch - plain-object patch over the user section.
226
+ * @param expectedRevision - the descriptor `revision` the caller read; a
227
+ * namespace that moved past it rejects with {@link SettingsConflictError}.
228
+ */
229
+ async update(ns: SettingsNamespace, patch: object, expectedRevision?: number): Promise<void>
230
+
231
+ /**
232
+ * Replace one registered namespace's user section wholesale, validate,
233
+ * persist, then commit and emit. Keys absent from `section` fall back to the
234
+ * composition `base` and schema defaults — this is the removal/reset path a
235
+ * merge-only patch cannot express (`replace({})` re-inherits everything).
236
+ * @param ns - the registered namespace to replace.
237
+ * @param section - the complete next user section.
238
+ * @param expectedRevision - the descriptor `revision` the caller read; a
239
+ * namespace that moved past it rejects with {@link SettingsConflictError}.
240
+ */
241
+ async replace(ns: SettingsNamespace, section: object, expectedRevision?: number): Promise<void>
242
+
243
+ /**
244
+ * Apply path-addressed edits to one registered namespace's user section,
245
+ * validate, persist, then commit and emit. The ops are applied to the
246
+ * section as it stands when the write reaches the front of the queue, so a
247
+ * caller never has to restate fields it did not touch — and, crucially,
248
+ * cannot delete fields it never saw. This is the write path for any caller
249
+ * holding a redacted view; `replace` remains the wholesale reset.
250
+ * @param ns - the registered namespace to edit.
251
+ * @param ops - ordered path edits; later ops observe earlier ones.
252
+ * @param expectedRevision - the descriptor `revision` the caller read; a
253
+ * namespace that moved past it rejects with {@link SettingsConflictError}.
254
+ */
255
+ async mutate(ns: SettingsNamespace, ops: readonly SettingsPathOp[], expectedRevision?: number): Promise<void>
256
+ ```
257
+
258
+ Source: [`packages/settings/settings/src/index.ts:350`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/settings/settings/src/index.ts)
259
+
260
+ <a id="settings-events"></a>
261
+
262
+ ### `settings/*` events
263
+
264
+ <a id="settingsdocument-updated--emit"></a>
265
+
266
+ #### `settings/document-updated` — emit
267
+
268
+ One registered namespace's RAW user section changed, whether or not the resolved value did. `settings/updated` is the consumer-facing event and stays deep-equal-gated; this one exists for configuration surfaces, which must learn that a field went from inherited to overridden (same resolved value, different meaning) and that their held revision is stale. Listener containment matches `settings/updated`.
269
+
270
+ ```ts cordis-catalog
271
+ /**
272
+ * One registered namespace's RAW user section changed, whether or not the
273
+ * resolved value did. `settings/updated` is the consumer-facing event and
274
+ * stays deep-equal-gated; this one exists for configuration surfaces,
275
+ * which must learn that a field went from inherited to overridden (same
276
+ * resolved value, different meaning) and that their held revision is
277
+ * stale. Listener containment matches `settings/updated`.
278
+ * @param ns - the namespace whose stored section changed.
279
+ * @param revision - the namespace's new revision.
280
+ * @mode emit
281
+ */
282
+ 'settings/document-updated'(ns: SettingsNamespace, revision: number): void
283
+ ```
284
+
285
+ Source: [`packages/settings/settings/src/types.ts:48`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/settings/settings/src/types.ts)
286
+
287
+ <a id="settingsupdated--emit"></a>
288
+
289
+ #### `settings/updated` — emit
290
+
291
+ Committed change to one registered namespace's resolved value. Emitted after the provider persisted (for `update`) or published (`provider`) the change; never emitted when the resolved value is deep-equal. Listener failures are contained and logged — a sync throw and an async rejection alike — except `INVARIANT`-coded failures, which rethrow after every listener ran; that rethrow reaches the emitter only from synchronous listeners, so invariant checks on this event must not be async functions.
292
+
293
+ ```ts cordis-catalog
294
+ /**
295
+ * Committed change to one registered namespace's resolved value. Emitted
296
+ * after the provider persisted (for `update`) or published (`provider`)
297
+ * the change; never emitted when the resolved value is deep-equal.
298
+ * Listener failures are contained and logged — a sync throw and an async
299
+ * rejection alike — except `INVARIANT`-coded failures, which rethrow
300
+ * after every listener ran; that rethrow reaches the emitter only from
301
+ * synchronous listeners, so invariant checks on this event must not be
302
+ * async functions.
303
+ * @param ns - the namespace whose resolved value changed.
304
+ * @param next - the new resolved value.
305
+ * @param prev - the previous resolved value.
306
+ * @param source - whether the change entered through `update()` or the provider.
307
+ * @mode emit
308
+ */
309
+ 'settings/updated'(ns: SettingsNamespace, next: unknown, prev: unknown, source: SettingsUpdateSource): void
310
+ ```
311
+
312
+ Source: [`packages/settings/settings/src/types.ts:35`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/settings/settings/src/types.ts)
313
+ <!-- END GENERATED cordis-surface -->
@@ -0,0 +1,306 @@
1
+ ---
2
+ editSource: "docs/subsystems/shell.zh.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # Bash 执行器
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)之后。
9
+
10
+ 源码:[`packages/shell/shell/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/shell/shell/src/types.ts)
11
+
12
+ ## 受管 shell 环境命名空间
13
+
14
+ `DSH_*` 变量是归 Harness 所有的子进程事实。面向模型的 bash 工具通过 `ctx.shellEnv` 收集它们,再经由 `ShellExecRequest.dshEnv` 传递;子进程服务在合并当前快照之前会移除继承而来的 `DSH_*` 名称。`DshEnvironmentKey`/`DshEnvironment` 词汇归[子进程 seam](./subprocess.md)所有,由 `dsh-shell` 重导出。
15
+
16
+ ## 请求与规格:`resolve()` 拆分
17
+
18
+ 该 seam 将**面向模型/插件的请求**(`workdir`/`timeoutMs`/`stdoutMaxBytes` 可选,由配置或请求策略补全)与执行器实际使用的**完全解析后的 spec**(这些字段均为必填)分开。工具层在二者之间调用 `ctx.shell.resolve(request)`(仓库的「包边界处显式优于隐式」规则);`ShellExecSpec` 携带的是已解析的值。
19
+
20
+ ```ts type-equiv
21
+ /**
22
+ * A caller's execution REQUEST: `workdir` and `timeoutMs` are optional and
23
+ * filled by {@link ShellExecutor.resolve} from the implementation's config.
24
+ * This is the model-/plugin-facing shape; pass it to `resolve()` to obtain a
25
+ * fully-resolved {@link ShellExecSpec}.
26
+ */
27
+ interface ShellExecRequest {
28
+ command: string
29
+ /** Working directory override (default: implementation-configured). */
30
+ workdir?: string | undefined
31
+ /** Timeout override in milliseconds (implementations cap it). */
32
+ timeoutMs?: number | undefined
33
+ /**
34
+ * Foreground stdout capture budget in bytes. Absent uses the executor's
35
+ * default output cap. Trusted in-process consumers use this when they must
36
+ * parse complete stdout up to their own bounded limit; the model-facing bash
37
+ * tool does not expose it as a parameter.
38
+ */
39
+ stdoutMaxBytes?: number | undefined
40
+ /** Abort signal — implementations kill the command when it fires. */
41
+ signal?: AbortSignal | undefined
42
+ /**
43
+ * Bytes to write to the command's stdin, then close it. Absent leaves stdin
44
+ * closed/empty (the default for model-driven tool calls). Set by in-process
45
+ * plugins (e.g. the hooks bridges, which write a hook command's JSON payload
46
+ * to its stdin); the model-facing bash tool does not expose it as a parameter
47
+ * (a model that needs stdin uses shell syntax like a heredoc or a pipe).
48
+ */
49
+ stdin?: string | undefined
50
+ /**
51
+ * Ordinary environment entries for the command, merged after the credential
52
+ * scrub. Managed facts belong in {@link dshEnv}, which merges after this
53
+ * map, so an entry here can never displace one. Set by in-process plugins
54
+ * (the hooks bridges set `CLAUDE_PROJECT_DIR`, `CLAUDE_PLUGIN_ROOT`, …); the
55
+ * model-facing bash tool does not expose it as a parameter.
56
+ */
57
+ env?: Record<string, string> | undefined
58
+ /**
59
+ * Harness-owned `DSH_*` variables for this execution (typed to managed
60
+ * keys). Executors discard ambient `DSH_*` entries before merging this
61
+ * snapshot last, so an unavailable current fact cannot inherit a stale
62
+ * value from the harness process and a caller {@link env} entry cannot
63
+ * displace a managed one.
64
+ */
65
+ dshEnv?: DshEnvironment | undefined
66
+ /** Fully resolved per-call sandbox policy; sandboxing executors default it. */
67
+ sandboxPolicy?: SandboxExecutionPolicy | undefined
68
+ }
69
+ ```
70
+
71
+ ```ts type-equiv
72
+ /**
73
+ * A resolved execution spec. {@link ShellExecutor.resolve} fills and caps the
74
+ * required fields; {@link ShellExecutor.start} ignores `timeoutMs` because
75
+ * background processes have no executor timeout.
76
+ */
77
+ interface ShellExecSpec {
78
+ command: string
79
+ workdir: string
80
+ timeoutMs: number
81
+ /**
82
+ * Resolved foreground stdout capture budget in bytes. `run()` uses it for
83
+ * stdout; background jobs and stderr keep the executor's own output cap.
84
+ */
85
+ stdoutMaxBytes: number
86
+ /** Abort signal — implementations kill the command when it fires. */
87
+ signal?: AbortSignal | undefined
88
+ /** Bytes to write to stdin before closing it; absent means no stdin. */
89
+ stdin?: string | undefined
90
+ /**
91
+ * Ordinary environment entries carried through from
92
+ * {@link ShellExecRequest.env}; {@link dshEnv} still merges after them.
93
+ * OPTIONAL on the spec for the same reason as `stdin`: absent means no
94
+ * ordinary extra environment.
95
+ */
96
+ env?: Record<string, string> | undefined
97
+ /** Managed `DSH_*` snapshot (typed to managed keys); merges after {@link env}. */
98
+ dshEnv?: DshEnvironment | undefined
99
+ /** Resolved sandbox policy; ignored by executors that do not confine. */
100
+ sandboxPolicy: SandboxExecutionPolicy | undefined
101
+ }
102
+ ```
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.md)。
105
+
106
+ `stdoutMaxBytes` 同样仅供受信任插件使用。它让前台消费方能在有界解析预算内请求完整 stdout,而不会改变 stderr、后台任务或面向模型的 bash 工具的常规输出上限。
107
+
108
+ ## 前台运行:`ShellRunResult`
109
+
110
+ 一次已完成(或被终止)的前台运行的结果。正交的结果**独立报告**:一个进程可以同时超时并以退出码 0 退出(因为它捕获了信号),因此 `timedOut`、`aborted`、`signal` 和 `exitCode` 各自独立为一个字段;调用方永远不会把一次被提前中断的运行误读为正常成功。
111
+
112
+ ```ts type-equiv
113
+ /** The outcome of one completed (or killed) foreground run. */
114
+ interface ShellRunResult {
115
+ /** Exit code; null when the process died from a signal. */
116
+ exitCode: number | null
117
+ /** Terminating signal (e.g. 'SIGTERM'); null on normal exit. */
118
+ signal: NodeJS.Signals | null
119
+ /**
120
+ * True when the executor's own timeout was the FIRST cause to cut the command
121
+ * short. Mutually exclusive with {@link aborted}: one fused deadline drives
122
+ * both the timeout and the caller's cancellation, so a timeout and an abort
123
+ * racing before process close report the single first-abort cause, not both
124
+ * (see the [timeout-library Agent Note](../../../../.agents/notes/implemented/architecture/2026-07-06-timeout-deadline-library.md)).
125
+ */
126
+ timedOut: boolean
127
+ /**
128
+ * True when the caller's `AbortSignal` was the FIRST cause to kill the command
129
+ * (and it was not the executor's own timeout). Mutually exclusive with
130
+ * {@link timedOut} — see there for the first-cause classification.
131
+ */
132
+ aborted: boolean
133
+ /** The effective timeout applied to this run (after defaulting/capping). */
134
+ timeoutMs: number
135
+ stdout: CollectedOutput
136
+ stderr: CollectedOutput
137
+ /** Sandbox execution facts, absent for an unsandboxed executor. */
138
+ sandbox?: ShellSandboxInfo
139
+ }
140
+ ```
141
+
142
+ 每个流是一个 `CollectedOutput`:(可能被截断的)文本加恢复信息;截断时,`text` 是**尾部**,完整流溢出到一个私有文件。这些字段归[子进程 seam](./subprocess.md)所有,由 `dsh-shell` 重导出。
143
+
144
+ ## 文件沙箱:`ShellSandboxInfo`
145
+
146
+ 使用沙箱的执行器通过 `ShellExecutor.sandboxMode` 暴露其已配置的模式回退值。工具层请求 [`@deepseek-ai/dsh-sandbox-policy`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/sandbox/sandbox-policy/README.md),把每个调用会话的持久 `sandbox/mode` 覆盖值与不可变 cwd 解析为 `ShellExecRequest.sandboxPolicy`;经用户批准、严格更宽松的调用只替换模式。模式/root/enforcement 词汇归 [`@deepseek-ai/dsh-sandbox` 沙箱 seam](./sandbox.md) 所有;模式仅管辖文件效果。
147
+
148
+ 沙箱化运行会报告其模式、保守的拒绝分类与强制执行完整度。`runnerFailed` 标记命令运行前沙箱 runner 已失败;前台执行会抛出 `SANDBOX_UNAVAILABLE`,而已结束的后台进程只能通过其事实通道报告。
149
+
150
+ ```ts type-equiv
151
+ /**
152
+ * Sandbox facts for one run, present iff a sandboxing executor handled it.
153
+ * Facts are reported independently of process exit status so callers can
154
+ * distinguish command failures from policy denials and runner failures.
155
+ */
156
+ interface ShellSandboxInfo {
157
+ /** The mode the command actually ran under. */
158
+ mode: SandboxMode
159
+ /** Whether the sandbox denied a file operation. */
160
+ denied: boolean
161
+ /** How completely the selected runner enforced the requested mode. */
162
+ enforcement?: SandboxEnforcement
163
+ /** Whether the sandbox runner failed before the command could run. */
164
+ runnerFailed?: boolean
165
+ }
166
+ ```
167
+
168
+ 当受限模式没有可用后端时,`ctx.sandbox` 提供方会抛出、执行器会传播由[沙箱 seam](./sandbox.md)所有的 `SANDBOX_UNAVAILABLE` 错误码。选定的 runner 拒绝其 profile 时会触达同一个故障关闭的前台错误;已结束的后台任务则记录 `runnerFailed`。模型会在结果中收到拒绝/runner 事实,仅当拒绝标记指出生效模式时才得知该模式,并可通过 `sandbox_permissions` 加 `justification` 请求一次性、严格更宽松的重试;执行任何操作前,`ctx.approval` 必须批准该次确切调用。完整的策略与切换设计见[沙箱 Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-07-06-sandbox.md)。
169
+
170
+ ## 后台进程:`ShellProcess`
171
+
172
+ `start()` 返回不含 id 或所有者的句柄。`dsh-tool-bash` 将它适配为 `ctx.jobs.start()` 钩子;随后由通用运行时拥有任务标识与生命周期。`done` 在进程关闭时完成且绝不被拒绝;进程结束后仍可读取,并且沙箱事实会在 `done` 完成前写入。
173
+
174
+ ```ts type-equiv
175
+ /**
176
+ * A background process handle returned by {@link ShellExecutor.start}. It is the
177
+ * only access path; buffered output remains readable after exit. Composition
178
+ * teardown (the subprocess service's disposal) kills running processes and
179
+ * awaits {@link done}; an executor-only reload leaves them running.
180
+ */
181
+ interface ShellProcess {
182
+ /** Process lifecycle state (settled exactly once). */
183
+ status: ShellProcessStatus
184
+ /** Exit code once finished (null = killed by signal / still running). */
185
+ exitCode: number | null
186
+ /** Terminating signal name, when signal-killed. */
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). */
189
+ readonly done: Promise<void>
190
+ /** Sandbox facts, stamped once a confined process settles. */
191
+ sandbox?: ShellSandboxInfo
192
+ /**
193
+ * Read output produced since the previous read (consuming — consecutive
194
+ * reads never re-deliver). Reads that lost data flag `lossy` and point at
195
+ * full-stream spill files when available.
196
+ */
197
+ readOutput(): ShellProcessRead
198
+ /**
199
+ * Kill the process group. Returns false when it had already finished
200
+ * (no-op); idempotent.
201
+ */
202
+ kill(): boolean
203
+ }
204
+ ```
205
+
206
+ `readOutput()` 返回增量内容与 spill 恢复信息:
207
+
208
+ ```ts type-equiv
209
+ /** One incremental {@link ShellProcess.readOutput} read. */
210
+ interface ShellProcessRead {
211
+ /** Output produced since the previous read (stderr in a marked section). */
212
+ delta: string
213
+ /** True when truncation dropped unread bytes the delta cannot include. */
214
+ lossy: boolean
215
+ /** Full stdout spill file, when stdout truncation occurred and a safe path is available. */
216
+ stdoutSpillPath?: string
217
+ /** Full stderr spill file, when stderr truncation occurred and a safe path is available. */
218
+ stderrSpillPath?: string
219
+ }
220
+ ```
221
+
222
+ ## 服务
223
+
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。
225
+
226
+ <!-- BEGIN GENERATED cordis-surface (gen-cordis-catalog.ts) — do not edit between markers -->
227
+
228
+ <a id="cordis-surface"></a>
229
+
230
+ ## Cordis API
231
+
232
+ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnpm run verify-cordis-catalog` in doc-sync; regenerate with `pnpm run gen-cordis-catalog`) — this section is byte-identical in both language sides of the page. Signature blocks use a `ts cordis-catalog` fence and keep the original source JSDoc; dispatch modes are defined in the [primer](../cordis-primer.md#dispatch-modes), and the framework-inherited `ctx` API lives in [cordis-api/inherited.md](../cordis-api/inherited.md).
233
+
234
+ <a id="ctxshell--shellexecutor-abstract-seam"></a>
235
+
236
+ ### `ctx.shell` — `ShellExecutor` (abstract seam)
237
+
238
+ Abstract bash execution service. Subclass, implement the abstract methods, and load the subclass as a plugin — it registers as `ctx.shell` (one implementation per context; loading a second throws, which is cordis' standard duplicate-service behavior).
239
+
240
+ Implementations must honor these semantics:
241
+
242
+ - run rejects only for infrastructure failures. Nonzero exits, timeout kills, and abort kills resolve with a ShellRunResult.
243
+ - start returns immediately; no timeout applies to background processes. `done` settles at process close and never rejects; spawn failures settle as `killed` with the error on stderr.
244
+ - ShellProcess.readOutput is incremental: consecutive reads never repeat output. Lossy reads report truncation and available spill files.
245
+ - A still-running background process is stopped and awaited when its owning composition tears down. With the subprocess seam that boundary is `ctx.subprocess` disposal, so a background process survives an executor-only reload.
246
+
247
+ ```ts cordis-catalog
248
+ /**
249
+ * Apply implementation-owned defaults and caps to a request before execution.
250
+ * @param request - the caller's request; omitted fields get this
251
+ * implementation's defaults, capped fields are clamped.
252
+ * @returns the fully-specified spec to hand to {@link run}/{@link start}.
253
+ */
254
+ abstract resolve(request: ShellExecRequest): ShellExecSpec
255
+
256
+ /**
257
+ * Run a command in the foreground; resolves when it finishes.
258
+ * @param spec - a resolved spec from {@link resolve}, never a raw request.
259
+ * @returns the outcome; nonzero exits, timeout kills, and abort kills
260
+ * resolve with a descriptive result rather than reject.
261
+ */
262
+ abstract run(spec: ShellExecSpec): Promise<ShellRunResult>
263
+
264
+ /**
265
+ * Start a background process and return its handle immediately.
266
+ * @param spec - a resolved spec from {@link resolve}, never a raw request.
267
+ * @returns the live process handle (reads, kill, quiescence promise).
268
+ */
269
+ abstract start(spec: ShellExecSpec): ShellProcess
270
+ ```
271
+
272
+ Source: [`packages/shell/shell/src/index.ts:65`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/shell/shell/src/index.ts)
273
+
274
+ <a id="ctxshellenv--shellenvregistry"></a>
275
+
276
+ ### `ctx.shellEnv` — `ShellEnvRegistry`
277
+
278
+ Registry (`ctx.shellEnv`) for trusted, per-execution `DSH_*` variables. The namespace is rebuilt for every model shell call: ambient `DSH_*` values are discarded by the executor, then the registry's current snapshot is injected. Built-in shell facts remain owned by the registry itself while plugins can register additional, enumerable facts with effect-scoped disposal.
279
+
280
+ ```ts cordis-catalog
281
+ /**
282
+ * Register one environment contributor. Names and keys are unique; built-in
283
+ * keys are reserved. Registration is disposed with the calling plugin fiber.
284
+ * @param contributor - declared key ownership and per-execution resolver.
285
+ * @returns the disposer that unregisters the contribution.
286
+ */
287
+ register(contributor: BashEnvContributor): () => void
288
+
289
+ /**
290
+ * Build the trusted `DSH_*` snapshot for one shell tool execution.
291
+ * @param execution - the current tool execution.
292
+ * @returns an immutable environment overlay containing built-ins and current contributions.
293
+ */
294
+ collect(execution: ToolExecution): DshEnvironment
295
+
296
+ /**
297
+ * Enumerate plugin-contributed variables without executing their resolvers.
298
+ * @returns declarations sorted by environment variable name.
299
+ */
300
+ list(): BashEnvVariableInfo[]
301
+ ```
302
+
303
+ Types: [DshEnvironment](./subprocess.md) · [ToolExecution](./tools.md)
304
+
305
+ Source: [`packages/shell/shell-env/src/index.ts:89`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/shell/shell-env/src/index.ts)
306
+ <!-- END GENERATED cordis-surface -->