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.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # User Settings
7
+
8
+ The user-settings seam of [dsh-settings](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/settings/settings) holds one user-owned document of per-namespace sections and resolves each registered namespace as schema defaults, then the registrant's composition `base`, then the user section. Providers such as [dsh-settings-file](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/settings/settings-file) store the raw document and push external edits; consumer plugins register a schema and read or observe the resolved value. Composition config stays in `cordis.yml` — a namespace carries only the user-editable subset.
9
+
10
+ Source: [`packages/settings/settings/src/index.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/settings/settings/src/index.ts)
11
+
12
+ ## Identity
13
+
14
+ A namespace names one plugin-owned section of the user document. The brand prevents callers from mixing settings namespaces with other ids passed between packages or processes; construction validates lowercase kebab-case syntax.
15
+
16
+ ```ts type-equiv
17
+ /** Nominal id of one registered settings namespace. */
18
+ type SettingsNamespace = Branded<'SettingsNamespace'>
19
+ ```
20
+
21
+ ## Registration
22
+
23
+ Registration binds a schemastery schema to a namespace on the calling plugin's fiber — disposing that fiber removes the namespace and its observers. The options carry the composition layer, the owner's effect timing, and an optional check for what the schema cannot express.
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` runs after the schema admits a value, so it sees defaults and the composition base exactly as the owner will. `dsh-llm-pi-ai` uses it to refuse a provider profile it could not serve at the write that produced it, rather than storing one that would disable every route in its namespace.
56
+
57
+ `applies` is a UI hint, not a mechanism: a `restart` owner simply never watches, so its value is read once at construction and configuration surfaces can badge the pending change.
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
+ The scope is the owner-facing handle. `update` merges a sparse patch over the user section only (never into `base`); `replace` sets the section wholesale, which is the removal/reset path — keys absent from the replacement re-inherit `base` and schema defaults. Writes to one namespace are serialized in call order, and resolved values are deep-frozen snapshots.
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
+ ## Descriptors
100
+
101
+ `describe()` serializes every registered namespace for configuration surfaces: the schemastery `toJSON()` envelope drives schema-rendered forms, the resolved value fills them, and the detached `base`/`user` layers let a form mark user-overridden fields by presence. `describe({ redactSecrets: true })` — mandatory on every wire surface — strips `role('secret')` fields from all three layers and enumerates their `{path, set}` slots so a page can render write-only inputs without ever receiving a secret.
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
+ A caller that holds only the redacted descriptor cannot safely rebuild a section, so removals travel as path ops instead. Each descriptor also carries a `revision` over the raw section; a write may send it back as `expectedRevision`, and one that no longer matches is refused rather than applied over the writer that landed first.
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
+ ## Change commits
159
+
160
+ Every committed change — an in-process write or an externally observed provider edit — emits `settings/updated (ns, next, prev, source)` after the new value is authoritative, and never when the resolved value is deep-equal. The source tag separates the two entry paths.
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.md"
3
+ outline: [2,3]
4
+ ---
5
+
6
+ # Bash Executor
7
+
8
+ The bash execution seam is split across a Service Definition ([dsh-shell](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/shell), `ctx.shell`), Service Providers ([dsh-bash-local](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/bash-local) and [dsh-bash-sandbox](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/bash-sandbox)), and Consumer ([dsh-tool-bash](https://github.com/deepseek-ai/deepseek-harness/tree/master/packages/shell/tool-bash), the `bash` schema). Generic background-job ids, ownership, and controls live in [jobs.md](./jobs.md); this seam returns a task-free process handle. Raw process-group mechanics live behind the [subprocess seam](./subprocess.md).
9
+
10
+ Source: [`packages/shell/shell/src/types.ts`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/shell/shell/src/types.ts)
11
+
12
+ ## Managed shell environment namespace
13
+
14
+ `DSH_*` variables are Harness-owned child-process facts. The model-facing bash tool collects them through `ctx.shellEnv` and passes them through `ShellExecRequest.dshEnv`; the subprocess service removes inherited `DSH_*` names before merging the current snapshot. The `DshEnvironmentKey`/`DshEnvironment` vocabulary is owned by the [subprocess seam](./subprocess.md) and re-exported by `dsh-shell`.
15
+
16
+ ## Request vs. spec: the `resolve()` split
17
+
18
+ The seam separates the **model-/plugin-facing request** (optional `workdir`/`timeoutMs`/`stdoutMaxBytes`, filled from config or request policy) from the **fully-resolved spec** the executor acts on (those fields required). The tool layer calls `ctx.shell.resolve(request)` between them (the repo's "explicit > implicit at package boundaries" rule); a `ShellExecSpec` carries resolved values.
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` and `env` are trusted in-process plugin inputs and are not exposed by `dsh-tool-bash`. The local executor scrubs ambient credentials before merging explicit caller-supplied env. See [the 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` is also trusted-plugin-only. It lets a foreground consumer request complete stdout up to a bounded parser budget without changing stderr, background jobs, or the model-facing bash tool's ordinary output cap.
107
+
108
+ ## Foreground runs: `ShellRunResult`
109
+
110
+ The outcome of one completed (or killed) foreground run. Orthogonal outcomes are reported **independently** — a process can both time out AND exit 0 because it trapped the signal — so `timedOut`, `aborted`, `signal`, and `exitCode` are each their own field; a caller never reads a cut-short run as a clean success.
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
+ Each stream is a `CollectedOutput` — the (possibly truncated) text plus recovery info; when truncated, `text` is the **tail** and the complete stream spills to a private file. The fields are owned by the [subprocess seam](./subprocess.md) and re-exported by `dsh-shell`.
143
+
144
+ ## File sandbox: `ShellSandboxInfo`
145
+
146
+ A sandbox-consuming executor exposes its configured mode fallback through `ShellExecutor.sandboxMode`. The tool layer asks [`@deepseek-ai/dsh-sandbox-policy`](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/sandbox/sandbox-policy/README.md) to resolve each calling session's durable `sandbox/mode` override and immutable cwd into `ShellExecRequest.sandboxPolicy`; a user-approved strictly wider call replaces only the mode. The mode/root/enforcement vocabulary is owned by the [`@deepseek-ai/dsh-sandbox` seam](./sandbox.md); modes govern file effects only.
147
+
148
+ A sandboxed run reports its mode, conservative denial classification, and enforcement completeness. `runnerFailed` marks a sandbox runner failure before the command ran; foreground execution throws `SANDBOX_UNAVAILABLE`, while a settled background process has only its facts channel.
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
+ The `SANDBOX_UNAVAILABLE` error code (owned by the [sandbox seam](./sandbox.md)) is what the `ctx.sandbox` provider throws — and the executor propagates — when a confined mode has no usable backend. A selected runner refusing its profile reaches the same fail-closed foreground error; a settled background job records `runnerFailed`. The model receives denial/runner facts in results, learns the effective mode only when a denial marker names it, and can request a one-shot strictly wider retry through `sandbox_permissions` plus `justification`; `ctx.approval` must grant that exact call before anything executes. The complete policy and switching design is the [sandbox Agent Note](https://github.com/deepseek-ai/deepseek-harness/blob/master/.agents/notes/implemented/feature/2026-07-06-sandbox.md).
169
+
170
+ ## Background processes: `ShellProcess`
171
+
172
+ `start()` returns a handle with no id or owner. `dsh-tool-bash` adapts it into `ctx.jobs.start()` hooks; the generic runtime then owns job identity and lifecycle. `done` resolves when the process closes and never rejects, reads remain valid after settlement, and sandbox facts are stamped before `done` resolves.
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()` returns the incremental delta and spill recovery facts:
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
+ ## The service
223
+
224
+ `ShellExecutor` owns `resolve`, foreground `run`, background-process `start`, and the `sandboxMode` capability fact. `dsh-bash-local` owns command defaulting, timeout/abort classification, the terminal environment, and the background read merge; process groups, bounded collectors, spill files, credential scrubbing, and disposal quiescence are the [subprocess service](./subprocess.md)'s. `dsh-tool-bash` owns model-facing rendering and adapts background handles into the [generic job runtime](./jobs.md). `dsh-shell` owns the shell tools' shared exit-status contract: the exported `parseExitStatus`/`ParsedExitStatus` inverts the `[exit code: N]` / `[killed by signal: X]` markers `dsh-tool-bash`'s `renderResult` and `dsh-tool-pwsh`'s `renderPwshResult` append, and both tools' `presentResult` use it to split the rendered text into the terminal card's output body and its exit-status 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 -->