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.
- package/LICENSE +21 -0
- package/README.md +56 -0
- package/cordis.patch.yml +12 -0
- package/kb/INDEX.md +210 -0
- package/kb/README.md +69 -0
- package/kb/extra/AGENTS.md +75 -0
- package/kb/extra/api-gateway.md +164 -0
- package/kb/extra/api-gateway.zh.md +164 -0
- package/kb/extra/cookbook/adding-a-vendored-package.md +59 -0
- package/kb/extra/cookbook/adding-a-vendored-package.zh.md +59 -0
- package/kb/extra/cookbook/maintaining-dsh-code-review.md +64 -0
- package/kb/extra/cookbook/maintaining-dsh-code-review.zh.md +64 -0
- package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.md +32 -0
- package/kb/extra/cookbook/responding-to-pr-review-on-a-stack.zh.md +32 -0
- package/kb/extra/defensive-patterns.md +33 -0
- package/kb/extra/defensive-patterns.zh.md +33 -0
- package/kb/extra/development.md +171 -0
- package/kb/extra/development.zh.md +171 -0
- package/kb/extra/event-producer-consumer.md +76 -0
- package/kb/extra/event-producer-consumer.zh.md +78 -0
- package/kb/extra/glossary.md +45 -0
- package/kb/extra/glossary.zh.md +45 -0
- package/kb/extra/graph-atlas.md +24 -0
- package/kb/extra/graph-atlas.zh.md +26 -0
- package/kb/extra/i18n/README.md +60 -0
- package/kb/extra/i18n/README.zh.md +60 -0
- package/kb/extra/i18n/style-samples.md +87 -0
- package/kb/extra/i18n/terminology.md +214 -0
- package/kb/extra/i18n/translation-prompt.md +263 -0
- package/kb/extra/i18n/translation-rules.md +69 -0
- package/kb/extra/i18n/translation-rules.zh.md +69 -0
- package/kb/extra/module-graph.md +1641 -0
- package/kb/extra/module-graph.zh.md +1643 -0
- package/kb/extra/postmortem/0001-acp-default-export-drops-inject.md +113 -0
- package/kb/extra/postmortem/0001-acp-default-export-drops-inject.zh.md +113 -0
- package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.md +47 -0
- package/kb/extra/postmortem/0002-js-expression-disabled-filesystem-tools.zh.md +47 -0
- package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.md +53 -0
- package/kb/extra/postmortem/0003-web-agent-gui-feedback-loop.zh.md +53 -0
- package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.md +55 -0
- package/kb/extra/postmortem/0004-landlock-partial-notice-misclassified-child-failures.zh.md +55 -0
- package/kb/extra/postmortem/README.md +18 -0
- package/kb/extra/postmortem/README.zh.md +18 -0
- package/kb/extra/rescope.md +53 -0
- package/kb/extra/rescope.zh.md +53 -0
- package/kb/extra/subsystems/attachment.md +125 -0
- package/kb/extra/subsystems/attachment.zh.md +125 -0
- package/kb/extra/subsystems/extensions.md +364 -0
- package/kb/extra/subsystems/extensions.zh.md +364 -0
- package/kb/extra/subsystems/feedback.md +266 -0
- package/kb/extra/subsystems/feedback.zh.md +266 -0
- package/kb/extra/testing.md +49 -0
- package/kb/extra/testing.zh.md +49 -0
- package/kb/extra/web-styling.md +25 -0
- package/kb/extra/web-styling.zh.md +25 -0
- package/kb/meta/search-index.json +1328 -0
- package/kb/meta/site-pages.txt +168 -0
- package/kb/meta/source.json +13 -0
- package/kb/meta/topics.md +75 -0
- package/kb/site/develop/basic/config.md +108 -0
- package/kb/site/develop/basic/index.md +146 -0
- package/kb/site/develop/basic/publish.md +185 -0
- package/kb/site/develop/basic/tool.md +54 -0
- package/kb/site/develop/cordis-tutorial/01-first-plugin.md +95 -0
- package/kb/site/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
- package/kb/site/develop/cordis-tutorial/03-services.md +98 -0
- package/kb/site/develop/cordis-tutorial/04-events.md +144 -0
- package/kb/site/develop/cordis-tutorial/05-config.md +84 -0
- package/kb/site/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
- package/kb/site/develop/cordis-tutorial/07-into-the-harness.md +107 -0
- package/kb/site/develop/cordis-tutorial/index.md +62 -0
- package/kb/site/develop/framework/events.md +145 -0
- package/kb/site/develop/framework/index.md +139 -0
- package/kb/site/develop/framework/service.md +152 -0
- package/kb/site/develop/practice/index.md +157 -0
- package/kb/site/develop/practice/llm-adapter.md +190 -0
- package/kb/site/en/develop/basic/config.md +108 -0
- package/kb/site/en/develop/basic/index.md +146 -0
- package/kb/site/en/develop/basic/publish.md +185 -0
- package/kb/site/en/develop/basic/tool.md +54 -0
- package/kb/site/en/develop/cordis-tutorial/01-first-plugin.md +95 -0
- package/kb/site/en/develop/cordis-tutorial/02-lifecycle-and-effects.md +98 -0
- package/kb/site/en/develop/cordis-tutorial/03-services.md +98 -0
- package/kb/site/en/develop/cordis-tutorial/04-events.md +144 -0
- package/kb/site/en/develop/cordis-tutorial/05-config.md +84 -0
- package/kb/site/en/develop/cordis-tutorial/06-composition-and-hmr.md +113 -0
- package/kb/site/en/develop/cordis-tutorial/07-into-the-harness.md +107 -0
- package/kb/site/en/develop/cordis-tutorial/index.md +60 -0
- package/kb/site/en/develop/framework/events.md +145 -0
- package/kb/site/en/develop/framework/index.md +139 -0
- package/kb/site/en/develop/framework/service.md +150 -0
- package/kb/site/en/develop/practice/index.md +157 -0
- package/kb/site/en/develop/practice/llm-adapter.md +190 -0
- package/kb/site/en/guide/providers-custom-form.png +0 -0
- package/kb/site/en/guide/providers-models-page.png +0 -0
- package/kb/site/en/guide/providers.md +100 -0
- package/kb/site/en/guide/python-sdk.md +106 -0
- package/kb/site/en/guide/quickstart.md +32 -0
- package/kb/site/en/index.md +8 -0
- package/kb/site/en/reference/agent-lifecycle.md +86 -0
- package/kb/site/en/reference/capability-seams.md +475 -0
- package/kb/site/en/reference/config-catalog.md +3155 -0
- package/kb/site/en/reference/cookbook/adding-a-conversation-node.md +235 -0
- package/kb/site/en/reference/cookbook/adding-a-package.md +120 -0
- package/kb/site/en/reference/cookbook/adding-a-settings-card.md +102 -0
- package/kb/site/en/reference/cookbook/adding-a-tool.md +96 -0
- package/kb/site/en/reference/cookbook/adding-an-llm-adapter.md +45 -0
- package/kb/site/en/reference/cookbook/extension-cookbook.md +131 -0
- package/kb/site/en/reference/cordis-api/context.md +368 -0
- package/kb/site/en/reference/cordis-api/events.md +211 -0
- package/kb/site/en/reference/cordis-api/fiber.md +379 -0
- package/kb/site/en/reference/cordis-api/inherited.md +43 -0
- package/kb/site/en/reference/cordis-api/registry.md +156 -0
- package/kb/site/en/reference/cordis-api/service.md +106 -0
- package/kb/site/en/reference/cordis-primer.md +46 -0
- package/kb/site/en/reference/index.md +131 -0
- package/kb/site/en/reference/persistence-catalog.md +949 -0
- package/kb/site/en/reference/subsystems/approval.md +173 -0
- package/kb/site/en/reference/subsystems/client-modules.md +121 -0
- package/kb/site/en/reference/subsystems/code-runtime.md +194 -0
- package/kb/site/en/reference/subsystems/commands.md +190 -0
- package/kb/site/en/reference/subsystems/compaction.md +241 -0
- package/kb/site/en/reference/subsystems/core.md +1073 -0
- package/kb/site/en/reference/subsystems/credentials.md +136 -0
- package/kb/site/en/reference/subsystems/filesystem.md +498 -0
- package/kb/site/en/reference/subsystems/goal.md +280 -0
- package/kb/site/en/reference/subsystems/index.md +58 -0
- package/kb/site/en/reference/subsystems/invariants.md +91 -0
- package/kb/site/en/reference/subsystems/jobs.md +293 -0
- package/kb/site/en/reference/subsystems/llm-streaming.md +920 -0
- package/kb/site/en/reference/subsystems/lsp.md +205 -0
- package/kb/site/en/reference/subsystems/permission-presets.md +134 -0
- package/kb/site/en/reference/subsystems/persistence.md +388 -0
- package/kb/site/en/reference/subsystems/plan.md +90 -0
- package/kb/site/en/reference/subsystems/sandbox.md +221 -0
- package/kb/site/en/reference/subsystems/schedule.md +189 -0
- package/kb/site/en/reference/subsystems/scope.md +62 -0
- package/kb/site/en/reference/subsystems/session-projection.md +265 -0
- package/kb/site/en/reference/subsystems/session-query.md +498 -0
- package/kb/site/en/reference/subsystems/session-reference.md +111 -0
- package/kb/site/en/reference/subsystems/session-telemetry.md +197 -0
- package/kb/site/en/reference/subsystems/session-title.md +207 -0
- package/kb/site/en/reference/subsystems/session.md +852 -0
- package/kb/site/en/reference/subsystems/settings.md +313 -0
- package/kb/site/en/reference/subsystems/shell.md +306 -0
- package/kb/site/en/reference/subsystems/skills.md +334 -0
- package/kb/site/en/reference/subsystems/spill.md +120 -0
- package/kb/site/en/reference/subsystems/storage.md +232 -0
- package/kb/site/en/reference/subsystems/subagent.md +737 -0
- package/kb/site/en/reference/subsystems/subprocess.md +327 -0
- package/kb/site/en/reference/subsystems/system-prompt.md +210 -0
- package/kb/site/en/reference/subsystems/terminal.md +187 -0
- package/kb/site/en/reference/subsystems/token-meter.md +93 -0
- package/kb/site/en/reference/subsystems/tools.md +723 -0
- package/kb/site/en/reference/subsystems/typert.md +339 -0
- package/kb/site/en/reference/subsystems/user-questions.md +181 -0
- package/kb/site/en/reference/subsystems/web-server.md +111 -0
- package/kb/site/en/reference/subsystems/web.md +202 -0
- package/kb/site/en/reference/subsystems/workflow.md +281 -0
- package/kb/site/en/reference/subsystems/workspace.md +231 -0
- package/kb/site/en/reference/tool-catalog.md +1877 -0
- package/kb/site/en/reference/tool-execution-pipeline.md +66 -0
- package/kb/site/guide/providers-custom-form.zh.png +0 -0
- package/kb/site/guide/providers-models-page.zh.png +0 -0
- package/kb/site/guide/providers.md +100 -0
- package/kb/site/guide/python-sdk.md +106 -0
- package/kb/site/guide/quickstart.md +32 -0
- package/kb/site/index.md +8 -0
- package/kb/site/reference/agent-lifecycle.md +86 -0
- package/kb/site/reference/capability-seams.md +475 -0
- package/kb/site/reference/config-catalog.md +3154 -0
- package/kb/site/reference/cookbook/adding-a-conversation-node.md +235 -0
- package/kb/site/reference/cookbook/adding-a-package.md +120 -0
- package/kb/site/reference/cookbook/adding-a-settings-card.md +102 -0
- package/kb/site/reference/cookbook/adding-a-tool.md +98 -0
- package/kb/site/reference/cookbook/adding-an-llm-adapter.md +45 -0
- package/kb/site/reference/cookbook/extension-cookbook.md +133 -0
- package/kb/site/reference/cordis-api/context.md +368 -0
- package/kb/site/reference/cordis-api/events.md +211 -0
- package/kb/site/reference/cordis-api/fiber.md +379 -0
- package/kb/site/reference/cordis-api/inherited.md +43 -0
- package/kb/site/reference/cordis-api/registry.md +156 -0
- package/kb/site/reference/cordis-api/service.md +106 -0
- package/kb/site/reference/cordis-primer.md +52 -0
- package/kb/site/reference/index.md +135 -0
- package/kb/site/reference/persistence-catalog.md +949 -0
- package/kb/site/reference/subsystems/approval.md +173 -0
- package/kb/site/reference/subsystems/client-modules.md +121 -0
- package/kb/site/reference/subsystems/code-runtime.md +194 -0
- package/kb/site/reference/subsystems/commands.md +190 -0
- package/kb/site/reference/subsystems/compaction.md +241 -0
- package/kb/site/reference/subsystems/core.md +1081 -0
- package/kb/site/reference/subsystems/credentials.md +136 -0
- package/kb/site/reference/subsystems/filesystem.md +498 -0
- package/kb/site/reference/subsystems/goal.md +280 -0
- package/kb/site/reference/subsystems/index.md +58 -0
- package/kb/site/reference/subsystems/invariants.md +91 -0
- package/kb/site/reference/subsystems/jobs.md +293 -0
- package/kb/site/reference/subsystems/llm-streaming.md +926 -0
- package/kb/site/reference/subsystems/lsp.md +205 -0
- package/kb/site/reference/subsystems/permission-presets.md +134 -0
- package/kb/site/reference/subsystems/persistence.md +388 -0
- package/kb/site/reference/subsystems/plan.md +90 -0
- package/kb/site/reference/subsystems/sandbox.md +221 -0
- package/kb/site/reference/subsystems/schedule.md +189 -0
- package/kb/site/reference/subsystems/scope.md +62 -0
- package/kb/site/reference/subsystems/session-projection.md +265 -0
- package/kb/site/reference/subsystems/session-query.md +498 -0
- package/kb/site/reference/subsystems/session-reference.md +111 -0
- package/kb/site/reference/subsystems/session-telemetry.md +197 -0
- package/kb/site/reference/subsystems/session-title.md +207 -0
- package/kb/site/reference/subsystems/session.md +854 -0
- package/kb/site/reference/subsystems/settings.md +313 -0
- package/kb/site/reference/subsystems/shell.md +306 -0
- package/kb/site/reference/subsystems/skills.md +334 -0
- package/kb/site/reference/subsystems/spill.md +120 -0
- package/kb/site/reference/subsystems/storage.md +232 -0
- package/kb/site/reference/subsystems/subagent.md +739 -0
- package/kb/site/reference/subsystems/subprocess.md +327 -0
- package/kb/site/reference/subsystems/system-prompt.md +210 -0
- package/kb/site/reference/subsystems/terminal.md +187 -0
- package/kb/site/reference/subsystems/token-meter.md +93 -0
- package/kb/site/reference/subsystems/tools.md +723 -0
- package/kb/site/reference/subsystems/typert.md +339 -0
- package/kb/site/reference/subsystems/user-questions.md +181 -0
- package/kb/site/reference/subsystems/web-server.md +111 -0
- package/kb/site/reference/subsystems/web.md +202 -0
- package/kb/site/reference/subsystems/workflow.md +281 -0
- package/kb/site/reference/subsystems/workspace.md +231 -0
- package/kb/site/reference/tool-catalog.md +1880 -0
- package/kb/site/reference/tool-execution-pipeline.md +66 -0
- package/package.json +40 -0
- package/scripts/rebuild-index.mjs +88 -0
- 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 -->
|