@deepseek-ai/dsh 0.0.1-rc.1 → 0.0.1-rc.2

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/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write apps/cli/README.md
5
- README.md: 4fae5338a89ce12c2620e123530acf883ae9efff
6
- README.zh.md: a2d086b8ff12fb07f2446fc4162de09739bcdeab
5
+ README.md: 98a856261bc632c97f350db8fc7bb0b10c22235d
6
+ README.zh.md: 283e54138e24202ed6b88d1d309538c58cd66b3e
package/README.md CHANGED
@@ -31,8 +31,8 @@ dsh --help # the launcher's own help
31
31
 
32
32
  A profile directory holds a `package.json` (out-of-tree plugin dependencies plus the profile manifest `dsh.profile` with its ordered `bundles` list) and a `cordis.patch.yml` (the user's own patch layer, hot-reloaded on long-lived surfaces). The tree composes over an empty root: each bundle's patch in `dsh.profile.bundles` order, then the profile's `cordis.patch.yml`, then the home-level `$DSH_HOME/cordis.patch.yml`, then `--patch` overlays. Bundles named in `dsh.profile.bundles` resolve from the dsh installation first (`@deepseek-ai/dsh-base`, `@deepseek-ai/dsh-web-app`, `@deepseek-ai/dsh-headless`), then from the profile's own `node_modules`, where pnpm installs out-of-tree plugins. Use `--dump-default-config` and `--dump-config` to inspect the composed tree without booting it.
33
33
 
34
- The [CLI behavior reference](reference/README.md) owns exact layer precedence, flags, shutdown behavior, deployment defaults, and the source launcher.
34
+ The [CLI behavior reference](reference/README.md) owns exact layer precedence, flags, shutdown behavior, deployment defaults, and source execution.
35
35
 
36
36
  ## Development
37
37
 
38
- Production runs require built package and frontend artifacts. From a checkout, `pnpm run dsh` runs the TypeScript entry and forwards arguments; the [source-launcher reference](reference/README.md#source-launcher) describes the PATH symlink and module-resolution contract.
38
+ Production runs require built package and frontend artifacts. From the repository root, `pnpm dsh <args...>` builds those artifacts, runs the TypeScript entry, and forwards every argument; the [source-execution reference](reference/README.md#source-execution) owns the module-resolution contract.
package/README.zh.md CHANGED
@@ -31,8 +31,8 @@ dsh --help # the launcher's own help
31
31
 
32
32
  profile 目录包含一个 `package.json`(树外插件依赖,加上 profile manifest(元数据清单)`dsh.profile` 及其有序的 `bundles` 列表)和一个 `cordis.patch.yml`(用户自己的 patch 层,在长期运行的 surface 上热重载)。配置树在空根之上组合:先按 `dsh.profile.bundles` 顺序应用各组合包的 patch,然后是 profile 的 `cordis.patch.yml`,然后是 home 级的 `$DSH_HOME/cordis.patch.yml`,然后是 `--patch` overlay。`dsh.profile.bundles` 中列出的组合包先从 dsh 安装目录解析(`@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`),再从 profile 自己的 `node_modules` 解析;pnpm 把树外插件安装在后者。使用 `--dump-default-config` 和 `--dump-config` 可在不启动的情况下检查组合后的配置树。
33
33
 
34
- [CLI(命令行界面)行为参考](reference/README.md)负责确切的层优先级、flag、关闭行为、部署默认值和源码启动器。
34
+ [CLI(命令行界面)行为参考](reference/README.md)负责确切的层优先级、flag、关闭行为、部署默认值和源码执行。
35
35
 
36
36
  ## 开发
37
37
 
38
- 生产运行需要已构建的包与前端产物。在 checkout 中,`pnpm run dsh` 会运行 TypeScript 入口并转发参数;[源码启动器参考](reference/README.md#source-launcher)说明 PATH 符号链接和模块解析契约。
38
+ 生产运行需要已构建的包与前端产物。从仓库根目录运行 `pnpm dsh <args...>` 会先构建这些产物,再运行 TypeScript 入口并转发所有参数;模块解析约定由[源码执行参考](reference/README.md#source-execution)负责。
@@ -129,17 +129,20 @@
129
129
 
130
130
  # `compact-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
131
131
  # share this realm rather than sit outside it.
132
+ #
133
+ # `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
134
+ # plane, and the rows here resolve that one instance. It takes no configuration,
135
+ # keys every fold by Session, and owns the context-meter projection units the
136
+ # browser reads for every session — behind a realm those units would come and go
137
+ # with whichever presets happen to be mounted. What a preset chooses is whether
138
+ # its agent compacts at all, which is `compact-basic` below.
132
139
  - id: compaction
133
140
  name: cordis:group
134
141
  group: true
135
142
  isolate:
136
- tokenMeter: true
137
143
  compact: true
138
144
  toolResultPrune: true
139
145
  config:
140
- - id: token-meter
141
- name: '@deepseek-ai/dsh-token-meter'
142
-
143
146
  - id: compact-basic
144
147
  name: '@deepseek-ai/dsh-compact-basic'
145
148
 
@@ -24,7 +24,7 @@
24
24
 
25
25
  Two planes decide where an edit belongs. The HOST composition holds the registries and anything shared across sessions — persistence, the sandbox and approval stack, the model route, the subagent registry and its backends. An AGENT PRESET holds what one session contributes to those registries: its tools, its persona, its prompt sections. A row that publishes a service belongs in the host composition, or inside an `isolate` realm if the preset genuinely owns that service and nothing outside one agent reads it.
26
26
 
27
- Presets you author live under `${DSH_HOME:-$HOME/.dsh}/.agent-presets/<id>/`, one directory per preset. NEVER edit or delete the shipped preset install (the `agent-presets` directory beside the deployment's own config): it belongs to the deployment, an upgrade overwrites it, and corrupting the `cordis` preset would disable this very mode. To change what a shipped preset does, copy its composition into a new preset directory and edit the copy.
27
+ Presets you author live one directory per preset under `${DSH_HOME:-$HOME/.dsh}/.agent-presets/<id>/`; the roster reports each preset's real path, so take the one you edit from there. NEVER edit or delete the shipped preset install (the `agent-presets` directory beside the deployment's own config): it belongs to the deployment, an upgrade overwrites it, and corrupting the `cordis` preset would disable this very mode. To change what a shipped preset does, copy its composition into a new preset directory and edit the copy.
28
28
 
29
29
  Load the `editing-cordis-compositions` skill before writing or changing a composition.
30
30
 
@@ -110,17 +110,20 @@
110
110
 
111
111
  # `compact-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
112
112
  # share this realm rather than sit outside it.
113
+ #
114
+ # `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
115
+ # plane, and the rows here resolve that one instance. It takes no configuration,
116
+ # keys every fold by Session, and owns the context-meter projection units the
117
+ # browser reads for every session — behind a realm those units would come and go
118
+ # with whichever presets happen to be mounted. What a preset chooses is whether
119
+ # its agent compacts at all, which is `compact-basic` below.
113
120
  - id: compaction
114
121
  name: cordis:group
115
122
  group: true
116
123
  isolate:
117
- tokenMeter: true
118
124
  compact: true
119
125
  toolResultPrune: true
120
126
  config:
121
- - id: token-meter
122
- name: '@deepseek-ai/dsh-token-meter'
123
-
124
127
  - id: compact-basic
125
128
  name: '@deepseek-ai/dsh-compact-basic'
126
129
 
@@ -1,12 +1,18 @@
1
1
  ---
2
2
  name: editing-cordis-compositions
3
- description: Use when creating or changing a Cordis composition for this harness — writing or editing an agent preset, adding or removing a plugin row, deciding whether something belongs to the host composition or to one session, or diagnosing a row that mounted but contributed nothing.
3
+ description: Use when creating, changing, or validating a Cordis composition for this harness — writing or editing an agent preset, adding or removing a plugin row, deciding whether something belongs to the host composition or to one session, checking whether a preset you authored actually mounts, or diagnosing a row that mounted but contributed nothing.
4
4
  ---
5
5
 
6
6
  # Editing Cordis compositions
7
7
 
8
8
  Every capability in this harness is a plugin row in a `cordis.yml`. There is no separate configuration language: changing what an agent can do means changing which rows are composed for it.
9
9
 
10
+ ## Off-limits
11
+
12
+ **Never edit, delete, or overwrite a preset that ships with the deployment** — the `agent-presets` directory beside the deployment's own config, which supplies `standard`, `code`, `minimal`, and `cordis`. Never escalate the sandbox to reach it, even when a change there looks quicker. An upgrade overwrites that install, and corrupting `cordis` disables preset authoring itself. Reading a shipped composition is the intended way to start; writing to one is not, and neither is editing the host composition to work around a preset limitation.
13
+
14
+ To change what a shipped preset does, copy it and edit the copy. Locally authored presets under the user root are yours to create, edit, and delete.
15
+
10
16
  ## Decide the plane first
11
17
 
12
18
  Two planes, and the choice is not about how "agent-related" something feels — it is about whether the thing must be shared.
@@ -17,16 +23,105 @@ Two planes, and the choice is not about how "agent-related" something feels —
17
23
 
18
24
  **A service with a consumer outside the agent plane cannot move into a preset.** `subagents` is the worked example: the registry answers cross-session queries for the host api-proxy, so a per-session copy both starves that host row — it waits forever for a service nothing provides — and collides on the second session, since a provider name registers once. The preset contributes the delegation *tools*; the registry and its backends stay host-side.
19
25
 
20
- A preset is a directory holding one `agent.cordis.yml`, optionally beside a `preset.yml` carrying display metadata — `name` and `description` (and, for shipped presets, a roster `order`). Write the metadata too: a preset without it shows up in every picker as its bare directory name. The shipped presets live beside the deployment's composition; locally authored ones live under `${DSH_HOME:-$HOME/.dsh}/.agent-presets/<name>/`.
26
+ A preset is a directory holding one `agent.cordis.yml`, optionally beside a `preset.yml` carrying display metadata — `name` and `description` (and, for shipped presets, a roster `order`). Write the metadata too: a preset without it shows up in every picker as its bare directory name.
27
+
28
+ Locally authored presets live one directory per preset under `${DSH_HOME:-$HOME/.dsh}/.agent-presets/`, and the shipped set sits beside the deployment's own config. Use those when the user asks where to look. Both roots are configuration rather than fixed locations, though, and no call reports them — `authorable` says only whether a writable one exists — so take the path you actually read or edit from `list()` or `resolve()`, which is also where `copy()` reports what it just created.
29
+
30
+ ## The roster service
31
+
32
+ `ctx.agentPresets` owns discovery, authoring, and mounting. You reach it by mounting a temporary plugin that injects it and registers a tool for yourself — `cordis_mount` returns only the mount acknowledgement, so a registered tool is how a service answer gets back to you, and it becomes callable on your next step.
33
+
34
+ Read `cordis_inspect what:"api" name:"agentPresets"` for the current signatures before writing the code. The four calls this skill relies on:
35
+
36
+ - `list()` — every preset with its `id`, `trust` (`system` for the shipped set, `user` for authored ones), and the absolute `path` of its composition file. This is how you locate any composition without knowing the install layout; the directory is that path's parent.
37
+ - `read(id)` — one preset's composition text, without a file tool or a path.
38
+ - `copy(from, id, name?)` — the only authoring write (see below).
39
+ - `standingKeyFor(id)` — mount-validate one preset (see below).
40
+
41
+ ```js
42
+ return {
43
+ name: 'preset-tools',
44
+ inject: ['agentPresets', 'tools'],
45
+ apply(ctx) {
46
+ harness.registerTool(ctx, harness.defineTool({
47
+ name: 'preset_check',
48
+ description: 'Mount-validate one preset by id.',
49
+ parameters: { id: { type: 'string', required: true } },
50
+ output: { schema: { type: 'string' }, render(_a, v) { return [{ type: 'text', text: v }] } },
51
+ async execute(args) {
52
+ try {
53
+ await ctx.agentPresets.standingKeyFor(args.id)
54
+ return 'mounted OK'
55
+ } catch (error) {
56
+ return error.message
57
+ }
58
+ },
59
+ }))
60
+ },
61
+ }
62
+ ```
63
+
64
+ Unmount the plugin with `cordis_unmount` when you are done; it is a probe, not a capability to leave behind.
21
65
 
22
66
  ## Authoring a preset
23
67
 
24
- 1. **Start from a copy.** Read a shipped composition close to what you want (the `standard` preset is the full coding agent) and copy its whole directory into `${DSH_HOME:-$HOME/.dsh}/.agent-presets/<id>/` the id must be lowercase letters, digits, and hyphens, because it becomes the directory name. A composition written from scratch usually forgets a group realm or a consumer row; a copy starts loadable.
25
- 2. **Expect the file sandbox.** The preset root lies outside the session workspace, so under the default `workspace-write` policy the first write is denied. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands.
26
- 3. **Rewrite `preset.yml`**: give the copy its own `name` and `description`, and drop any `order` the source declared that field sorts the shipped roster.
27
- 4. **Edit `agent.cordis.yml`** row by row, keeping the plane rule and realm rule above.
68
+ 1. **Start from a copy.** `copy(from, id, name)` copies a whole preset directory into the user root — composition, metadata, skill directories, assets. It validates the id against `[a-z0-9][a-z0-9-]*` (it becomes the directory name, so no leading hyphen), refuses an id any root already supplies, rolls a failed copy back, and rewrites the copy's `preset.yml` to keep the source's description while dropping its name and roster `order`. Prefer it over a shell copy: it needs no sandbox escalation, it lands the copy in whichever root this deployment made writable, and the copy is exactly as loadable as its source. `resolve(id)` then names the file it created that path, not a guessed one, is what the following edits target. `standard` is the full coding agent and the usual source.
69
+ 2. **Expect the file sandbox on every edit after the copy.** The user preset root lies outside the session workspace, so under the default `workspace-write` policy the first write there is denied. Only writes are: reading any composition by absolute path needs no escalation. Retry that exact command once with `sandbox_permissions` escalation and a short justification — the user sees and approves it. Batch your writes (one heredoc per file) rather than escalating many small commands. `copy()` itself runs host-side and needs none of this; the edits do.
70
+ 3. **Write the copy's `description`** in `preset.yml`, and its `name` if you passed none to `copy()`.
71
+ 4. **Edit `agent.cordis.yml`** row by row, keeping the plane rule and the realm rule.
72
+ 5. **Mount-validate the result**, then hand off to the user for a real session — both under *Verifying a change*.
73
+
74
+ A composition written from scratch usually forgets a group realm or a consumer row; a copy starts loadable.
75
+
76
+ ## The rule that catches people
77
+
78
+ **A row that publishes a service may not sit loose in a preset.** Registering a service without an isolate realm puts it in the process-global realm, so the second session mounting that preset collides with the first. The mount rejects it rather than letting the collision surface later.
79
+
80
+ Whether a row publishes a service is not visible from its name, and package READMEs are absent from an installed deployment. Read it off the live runtime instead: `cordis_inspect what:"services"` lists every service with the fiber that owns it, so a service attributed to a fiber other than the row you are adding is one that row consumes rather than provides. For a row not in your current composition, mount-validate and read the rejection — it names the offending service.
81
+
82
+ When a preset genuinely owns a service, wrap the provider **and every consumer that reaches it** in one group carrying an `isolate` realm. The shipped `standard` composition does this for `workflows`, which nothing outside an agent reads — its `delegation` group, with the delegation tools omitted here:
83
+
84
+ ```yaml
85
+ - id: delegation
86
+ name: cordis:group
87
+ group: true
88
+ isolate:
89
+ workflows: true
90
+ config:
91
+ - id: workflow-workerthread
92
+ name: '@deepseek-ai/dsh-workflow-workerthread'
93
+ config:
94
+ provider: spawn
95
+ - id: tool-workflow
96
+ name: '@deepseek-ai/dsh-tool-workflow'
97
+ ```
98
+
99
+ `true` means a realm private to each mounting session. A string label instead joins subtrees into one shared realm; `provide()` still throws on the second registration under that symbol, so a label does not pool instances and is not what a preset needs.
100
+
101
+ A consumer left outside the group resolves the host's registry, which the preset did not populate, and then contributes nothing. Mount-validation catches that as a row that never activated.
102
+
103
+ Realms are for services a preset owns, not for every group. A host capability the preset only consumes must stay outside a realm, or the row cannot resolve it: `tool-bash`, `tool-tasks`, and `tool-goal` publish nothing and sit loose in `standard`, which explains in comments which host instance each one resolves and why a realm would break it. Wrapping a consumer row in a realm of its own is the same error as leaving one outside its provider's realm.
104
+
105
+ ## Verifying a change
106
+
107
+ **`standingKeyFor(id)` is the check.** It composes the preset's plugin subtree for real — the same mount a session start performs, minus the agent — and rejects the four ways a composition fails:
108
+
109
+ - a row whose package does not resolve (`Cannot find package …`);
110
+ - a row whose config is invalid (`invalid config: $.<field> missing required value`);
111
+ - a row that never activated (`N row(s) did not activate: <id>: waiting for <service>`);
112
+ - a service published into the root realm, which arrives as one of two messages. A name the host does not supply lands in the root realm and the mount audit rejects it: `row(s) published process-global service(s) [<name>]; a preset service must sit behind an isolate realm or move to the host composition` — this is the shape a preset's own forgotten realm takes. A name the host already supplies collides before the audit: `service "<name>" has been registered at <Owner>`. Both name the offending service.
113
+
114
+ It returns normally when the composition mounts. Run it as the final check on a finished edit rather than after every line: a successful mount installs a standing generation that lives until the process exits, while a failed one disposes its subtree and leaves nothing behind.
115
+
116
+ **Do not treat the roster's `broken` field as validation.** `list()` reports `broken` from a shape check — the file parses in the loader's YAML dialect and holds named rows — which every failure above passes. It catches a damaged file, not an unusable composition.
28
117
 
29
- ### Native product subagents
118
+ `cordis_inspect` reports THIS session's composition, so it confirms what a row does in the runtime you are already in, never what your new preset will do.
119
+
120
+ After a clean mount-validation, ask the user to start a session on the new preset and confirm the tool list; the preset decides tool schemas and prompt sections, and only a real session shows the agent that composition produces.
121
+
122
+ `cordis_mount` evaluates JavaScript against the live runtime and disappears on restart. It is for probing, not for shipping a capability: a capability belongs in a composition file.
123
+
124
+ ## Native product subagents
30
125
 
31
126
  Codex and Claude Code providers already live in the host composition. A preset chooses either product by contributing the same ordinary delegation-tool row used for spawn and fork; never move a product provider into the preset and never add a product-specific settings field.
32
127
 
@@ -54,43 +149,6 @@ Copy these disabled templates from a shipped full preset and remove `disabled` o
54
149
 
55
150
  The two rows are independent. Leaving both disabled preserves the copied preset, enabling one exposes only that product tool, and enabling both exposes both. The host must provide `codex` or `claude` on `PATH`; the preset does not install, authenticate, select a model for, or probe either product.
56
151
 
57
- The shipped preset directories are off-limits: never edit or delete them, and never escalate the sandbox to reach them, even when a change there looks quicker — an upgrade overwrites the install, and corrupting the `cordis` preset disables preset authoring itself. Locally authored presets under the user root are yours to create, edit, and delete.
58
-
59
- ## The rule that catches people
60
-
61
- **A row that publishes a service may not sit loose in a preset.** Registering a service without an isolate realm puts it in the process-global realm, so the second session mounting that preset collides with the first. The mount rejects it rather than letting the collision surface later.
62
-
63
- Whether a row publishes a service is not visible from its name. `tool-bash` reads like a tool but provides `bashEnv`. Check the package's README, or mount the preset and read the rejection — it names the offending service.
64
-
65
- When a preset genuinely owns a service, wrap the provider **and every consumer that reaches it** in one group carrying an `isolate` realm:
66
-
67
- ```yaml
68
- - id: tasks
69
- name: cordis:group
70
- group: true
71
- isolate:
72
- tasks: true
73
- config:
74
- - id: tasks-local
75
- name: '@deepseek-ai/dsh-tasks-local'
76
- - id: tool-tasks
77
- name: '@deepseek-ai/dsh-tool-tasks'
78
- ```
79
-
80
- `true` means a realm private to each mounting session. A string label instead pools one instance across every subtree naming that label — use it only for something genuinely expensive to duplicate.
81
-
82
- A consumer left outside the group resolves the host's registry, which the preset did not populate, and then contributes nothing. That is the quietest failure here: the mount succeeds and a tool is simply missing.
83
-
84
- Host capabilities exposed through registries need no realm: the host `tools` and `skills` registries are layered per scope, so rows like `skill-local` and `tool-skill` sit loose in the preset and their registrations file into this preset's layer automatically — the agent's catalog merges them with whatever the deployment registered globally.
85
-
86
- ## Verifying a change
87
-
88
- Read the live runtime with `cordis_inspect` — it reports the services, the plugin fibers, and the registered tools as they actually are, which is the only reliable check that a row did what its name suggests. Note it shows THIS session's composition: a preset you just wrote is not mounted anywhere until a session starts on it.
89
-
90
- To check a preset you authored, re-read the files and validate these fields: the top level is a YAML list, every row is a map with a `name`, every group carries its own list, and service-publishing rows sit behind an `isolate` realm. The settings page's preset roster validates the same fields and marks an unloadable preset broken in red — point the user there, and ask them to start a session on the new preset to confirm the tool list; you cannot start one yourself.
91
-
92
- `cordis_mount` evaluates JavaScript against the live runtime and disappears on restart. It is for probing, not for shipping a capability: a capability belongs in a composition file.
93
-
94
152
  ## What not to move into a preset
95
153
 
96
154
  `agent-loop` registers the one agent factory and throws on a second. The registries own the per-session layering and cannot themselves be per-session. Session persistence must stay host-side or the session list fragments. The sandbox, approval, and permission rows are a deliberate boundary: a preset is exactly as privileged as the plugins it names, so letting one relax its own confinement would defeat the confinement.
@@ -1,8 +1,9 @@
1
- # The `minimal` agent preset: a fixed-prompt, two-tool coding surface.
1
+ # The `minimal` agent preset: a fixed-prompt, two-tool coding-agent composition.
2
2
  #
3
- # The persona is the complete system prompt, so global identity, Web surface,
3
+ # The persona is the complete system prompt, so global identity, Web orientation,
4
4
  # tool guidance, and later assembly listeners cannot add prompt text. The model
5
- # composes only the persistent `bash` and `str_replace_editor` tools.
5
+ # composes only the persistent `bash` and `str_replace_editor` tools. Context
6
+ # compaction is deliberately absent.
6
7
 
7
8
  - id: persona
8
9
  name: '@deepseek-ai/dsh-persona'
@@ -41,30 +42,20 @@
41
42
  * Please avoid commands that may produce a very large amount of output.
42
43
  * Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background.
43
44
 
44
- # The editor requires absolute paths unconditionally.
45
- - id: str-replace-editor
46
- name: '@deepseek-ai/dsh-tool-str-replace-editor'
47
- config:
48
- maxOutputChars: 16000
49
-
50
- # Model capacity comes from routed model metadata; this block states the
51
- # compaction policy explicitly.
52
- - id: compaction
45
+ # The bare local filesystem shadows the host's sandboxed provider only for this
46
+ # preset. The editor shares that realm and requires absolute paths.
47
+ - id: filesystem
53
48
  name: cordis:group
54
49
  group: true
55
50
  isolate:
56
- tokenMeter: true
57
- compact: true
51
+ fs: true
58
52
  config:
59
- - id: token-meter
60
- name: '@deepseek-ai/dsh-token-meter'
53
+ - id: fs-local
54
+ name: '@deepseek-ai/dsh-fs-local'
55
+ config:
56
+ cwd: !!js process.env.DSH_CWD ?? process.cwd()
61
57
 
62
- - id: compact-basic
63
- name: '@deepseek-ai/dsh-compact-basic'
58
+ - id: str-replace-editor
59
+ name: '@deepseek-ai/dsh-tool-str-replace-editor'
64
60
  config:
65
- thresholdRatio: 0.8
66
- retainTokens: 20480
67
- summarizationProvider: ''
68
- summarizationModel: ''
69
- maxTokens: 8192
70
- compactionRetries: 1
61
+ maxOutputChars: 16000
@@ -122,17 +122,20 @@
122
122
 
123
123
  # `compact-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
124
124
  # share this realm rather than sit outside it.
125
+ #
126
+ # `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
127
+ # plane, and the rows here resolve that one instance. It takes no configuration,
128
+ # keys every fold by Session, and owns the context-meter projection units the
129
+ # browser reads for every session — behind a realm those units would come and go
130
+ # with whichever presets happen to be mounted. What a preset chooses is whether
131
+ # its agent compacts at all, which is `compact-basic` below.
125
132
  - id: compaction
126
133
  name: cordis:group
127
134
  group: true
128
135
  isolate:
129
- tokenMeter: true
130
136
  compact: true
131
137
  toolResultPrune: true
132
138
  config:
133
- - id: token-meter
134
- name: '@deepseek-ai/dsh-token-meter'
135
-
136
139
  - id: compact-basic
137
140
  name: '@deepseek-ai/dsh-compact-basic'
138
141
 
package/lib/bin.js CHANGED
@@ -129,7 +129,7 @@ function readVersion() {
129
129
  const invocation = parseDshArgs(process.argv.slice(2), readVersion());
130
130
  switch (invocation.mode) {
131
131
  case "profile": {
132
- const { runProfile } = await import("./profile-boot-T5aOCb6v.js");
132
+ const { runProfile } = await import("./profile-boot-CuKpzL79.js");
133
133
  await runProfile({
134
134
  environment: loadLayeredEnv("dsh"),
135
135
  profile: invocation.profile,
@@ -139,12 +139,12 @@ switch (invocation.mode) {
139
139
  break;
140
140
  }
141
141
  case "plugin": {
142
- const { runPlugin } = await import("./plugin-Xxr7y7sa.js");
142
+ const { runPlugin } = await import("./plugin-D9IP9d5I.js");
143
143
  process.exit(runPlugin(invocation.profile, invocation.args));
144
144
  break;
145
145
  }
146
146
  case "dump-config": {
147
- const { runDumpConfig } = await import("./dump-config-DlxoPVYx.js");
147
+ const { runDumpConfig } = await import("./dump-config-CBIM2Y1u.js");
148
148
  runDumpConfig(invocation.profile, invocation.defaultOnly, invocation.patches);
149
149
  break;
150
150
  }
@@ -1,4 +1,4 @@
1
- import { i as prepareProfile, n as PROFILE_ROOT_FILENAME, r as homePatchPath, s as resolveWindowsShellLayer } from "./profile-boot-DQYCwKII.js";
1
+ import { i as prepareProfile, n as PROFILE_ROOT_FILENAME, r as homePatchPath, s as resolveWindowsShellLayer } from "./profile-boot-D95nr1z0.js";
2
2
  import { existsSync } from "node:fs";
3
3
  import { loadOptionalPatches, loadOverlayPatches, renderConfigDump } from "@deepseek-ai/dsh-app-boot";
4
4
  import { join, resolve } from "node:path";
@@ -1,4 +1,4 @@
1
- import { t as INSTALL_ANCHOR } from "./profile-boot-DQYCwKII.js";
1
+ import { t as INSTALL_ANCHOR } from "./profile-boot-D95nr1z0.js";
2
2
  import { existsSync } from "node:fs";
3
3
  import { DEFAULT_PROFILE_BUNDLES, PROFILE_TEMPLATES, initProfile, readProfileManifest, resolveBundleDir, resolveProfileDir, writeProfileManifest } from "@deepseek-ai/dsh-app-boot";
4
4
  import { join, resolve } from "node:path";
@@ -0,0 +1,2 @@
1
+ import { o as runProfile } from "./profile-boot-D95nr1z0.js";
2
+ export { runProfile };
@@ -138,8 +138,6 @@ function homePatchPath() {
138
138
  const INSTALL_ANCHOR = fileURLToPath(new URL("../package.json", import.meta.url));
139
139
  /** The session-telemetry row id the DSH_TELEMETRY_DISABLED switch targets. */
140
140
  const TELEMETRY_ROW_ID = "telemetry-otel";
141
- /** The one-shot runner row: its presence means this composition exits by itself. */
142
- const HEADLESS_ROW_ID = "headless-runner";
143
141
  /** The empty root entry list every profile tree patches over. */
144
142
  const PROFILE_ROOT_CONFIG = `# dsh profile root — an empty entry list. The tree is composed as patches:
145
143
  # each bundle in package.json's dsh.profile.bundles, then cordis.patch.yml, then any
@@ -245,9 +243,20 @@ function composeProfile(name, patchFiles) {
245
243
  rows
246
244
  };
247
245
  }
248
- /** Re-throw setup failures unless this invocation's signal already owns shutdown. */
249
- function suppressSignalShutdownError(signal, error) {
250
- if (!signal.aborted) throw error;
246
+ /**
247
+ * Re-throw a watcher-setup failure unless a shutdown already owns the tree:
248
+ * a signal aborted this invocation, or an app requested exit (`ctx.appExit`
249
+ * from a fast one-shot) and the root's disposal rejected the in-flight setup
250
+ * await. Either way the failure describes a tree that is exiting as asked,
251
+ * not a broken watch.
252
+ * @param ctx - the booted root context.
253
+ * @param signal - this invocation's signal-shutdown fact.
254
+ * @param error - the setup failure.
255
+ */
256
+ function suppressShutdownError(ctx, signal, error) {
257
+ if (signal.aborted) return;
258
+ if (ctx.fiber.state !== 2 || ctx.get("loader") === void 0) return;
259
+ throw error;
251
260
  }
252
261
  /**
253
262
  * Boot one profile invocation end to end and leave process lifetime to the
@@ -257,8 +266,6 @@ function suppressSignalShutdownError(signal, error) {
257
266
  */
258
267
  async function runProfile(options) {
259
268
  const composed = composeProfile(options.profile, options.patchFiles);
260
- const headlessRow = composed.rows.get(HEADLESS_ROW_ID);
261
- const oneShot = headlessRow !== void 0 && headlessRow.disabled !== true;
262
269
  const app = {};
263
270
  const shutdown = createProcessShutdown(async () => {
264
271
  await app.current?.fiber.dispose();
@@ -269,7 +276,7 @@ async function runProfile(options) {
269
276
  shutdown.interrupt(code);
270
277
  };
271
278
  process.on("SIGTERM", () => {
272
- interrupt(oneShot ? 143 : 0);
279
+ interrupt(0);
273
280
  });
274
281
  process.on("SIGINT", () => {
275
282
  interrupt(130);
@@ -285,7 +292,6 @@ async function runProfile(options) {
285
292
  ...loadOptionalPatches(NAME, homePatchPath()) ?? [],
286
293
  ...composed.overlays
287
294
  ]);
288
- const watchProfilePatch = !oneShot;
289
295
  const ctx = await boot(NAME, rootConfig, structuredClone(allPatches(composed)), (hostCtx) => {
290
296
  app.current = hostCtx;
291
297
  hostCtx.provide(DSH_ENVIRONMENT_KEY, options.environment);
@@ -293,19 +299,9 @@ async function runProfile(options) {
293
299
  args: options.args,
294
300
  exit: (code) => void shutdown.shutdown(code)
295
301
  });
296
- if (oneShot) {
297
- const io = {
298
- stdout: process.stdout,
299
- stderr: process.stderr,
300
- exit: (code) => {
301
- shutdown.shutdown(code);
302
- }
303
- };
304
- hostCtx.provide("headlessIo", io);
305
- }
306
302
  });
307
303
  app.current = ctx;
308
- if (watchProfilePatch && !signalShutdown.signal.aborted && ctx.fiber.state === 2 && ctx.get("loader") !== void 0) try {
304
+ if (!signalShutdown.signal.aborted && ctx.fiber.state === 2 && ctx.get("loader") !== void 0) try {
309
305
  if (ctx.get("hmr") === void 0) {
310
306
  if (ctx.get("timer") === void 0) await ctx.loader.create({ name: "@deepseek-ai/cordis-plugin-timer" });
311
307
  await ctx.loader.create({
@@ -324,7 +320,7 @@ async function runProfile(options) {
324
320
  compose: composeLive
325
321
  });
326
322
  } catch (error) {
327
- suppressSignalShutdownError(signalShutdown.signal, error);
323
+ suppressShutdownError(ctx, signalShutdown.signal, error);
328
324
  }
329
325
  return {
330
326
  ctx,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh",
3
3
  "description": "dsh CLI: profile boot, plugin management, and the browser UI alias",
4
- "version": "0.0.1-rc.1",
4
+ "version": "0.0.1-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "restricted"
7
7
  },
@@ -25,71 +25,74 @@
25
25
  "node-addon-require-builtin": "^0.1.4",
26
26
  "@deepseek-ai/cordis-plugin-hmr": "^1.0.16-rc.1",
27
27
  "@deepseek-ai/cordis-plugin-include": "^1.0.5-rc.1",
28
- "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
29
- "@deepseek-ai/dsh-agent-tool-mode": "^0.0.1-rc.1",
30
- "@deepseek-ai/dsh-app-boot": "^0.0.1-rc.1",
28
+ "@deepseek-ai/dsh-app-boot": "^0.0.1-rc.2",
29
+ "@deepseek-ai/dsh-agent-tool-mode": "^0.0.1-rc.2",
30
+ "@deepseek-ai/dsh-base": "^0.0.1-rc.2",
31
31
  "@deepseek-ai/cordis-plugin-timer": "^1.1.3-rc.1",
32
- "@deepseek-ai/dsh-base": "^0.0.1-rc.1",
33
- "@deepseek-ai/dsh-client-ui-agent-preset": "^0.0.1-rc.1",
34
- "@deepseek-ai/dsh-command-compact": "^0.0.1-rc.1",
35
- "@deepseek-ai/dsh-command-goal": "^0.0.1-rc.1",
36
- "@deepseek-ai/dsh-compact-basic": "^0.0.1-rc.1",
37
- "@deepseek-ai/dsh-compact-tool-result-prune": "^0.0.1-rc.1",
38
- "@deepseek-ai/dsh-goal": "^0.0.1-rc.1",
39
- "@deepseek-ai/dsh-goal-session": "^0.0.1-rc.1",
40
- "@deepseek-ai/dsh-cmdline": "^0.0.1-rc.1",
41
- "@deepseek-ai/dsh-environment": "^0.0.1-rc.1",
42
- "@deepseek-ai/dsh-headless": "^0.0.1-rc.1",
43
- "@deepseek-ai/dsh-mcp-client": "^0.0.1-rc.1",
44
- "@deepseek-ai/dsh-paths": "^0.0.1-rc.1",
45
- "@deepseek-ai/dsh-persona": "^0.0.1-rc.1",
46
- "@deepseek-ai/dsh-plan-mode": "^0.0.1-rc.1",
47
- "@deepseek-ai/dsh-pty": "^0.0.1-rc.1",
48
- "@deepseek-ai/dsh-pty-local": "^0.0.1-rc.1",
49
- "@deepseek-ai/dsh-pwsh-local": "^0.0.1-rc.1",
50
- "@deepseek-ai/dsh-skill": "^0.0.1-rc.1",
51
- "@deepseek-ai/dsh-skill-local": "^0.0.1-rc.1",
52
- "@deepseek-ai/dsh-tasks-local": "^0.0.1-rc.1",
53
- "@deepseek-ai/dsh-tmux-context": "^0.0.1-rc.1",
54
- "@deepseek-ai/dsh-token-meter": "^0.0.1-rc.1",
55
- "@deepseek-ai/dsh-tool-ask-user": "^0.0.1-rc.1",
56
- "@deepseek-ai/dsh-tool-bash": "^0.0.1-rc.1",
57
- "@deepseek-ai/dsh-tool-bash-persistent": "^0.0.1-rc.1",
58
- "@deepseek-ai/dsh-tool-cordis": "^0.0.1-rc.1",
59
- "@deepseek-ai/dsh-tool-fs": "^0.0.1-rc.1",
60
- "@deepseek-ai/dsh-tool-fs-search": "^0.0.1-rc.1",
61
- "@deepseek-ai/dsh-tool-goal": "^0.0.1-rc.1",
62
- "@deepseek-ai/dsh-tool-pwsh": "^0.0.1-rc.1",
63
- "@deepseek-ai/dsh-tool-ralph": "^0.0.1-rc.1",
64
- "@deepseek-ai/dsh-tool-skill": "^0.0.1-rc.1",
65
- "@deepseek-ai/dsh-session-reference": "^0.0.1-rc.1",
66
- "@deepseek-ai/dsh-tool-str-replace-editor": "^0.0.1-rc.1",
67
- "@deepseek-ai/dsh-pwsh-sandbox": "^0.0.1-rc.1",
68
- "@deepseek-ai/dsh-tool-subagent": "^0.0.1-rc.1",
69
- "@deepseek-ai/dsh-tool-subagent-control": "^0.0.1-rc.1",
70
- "@deepseek-ai/dsh-tool-tasks": "^0.0.1-rc.1",
71
- "@deepseek-ai/dsh-tool-todo": "^0.0.1-rc.1",
72
- "@deepseek-ai/dsh-tool-web": "^0.0.1-rc.1",
73
- "@deepseek-ai/dsh-tool-workflow": "^0.0.1-rc.1",
74
- "@deepseek-ai/dsh-web-app": "^0.0.1-rc.1",
75
- "@deepseek-ai/dsh-workflow-workerthread": "^0.0.1-rc.1",
76
- "@deepseek-ai/dsh-workspace-context": "^0.0.1-rc.1",
77
- "@deepseek-ai/cordis": "^4.0.1-rc.1"
32
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.1-rc.1",
33
+ "@deepseek-ai/dsh-command-compact": "^0.0.1-rc.2",
34
+ "@deepseek-ai/dsh-command-goal": "^0.0.1-rc.2",
35
+ "@deepseek-ai/dsh-compact-basic": "^0.0.1-rc.2",
36
+ "@deepseek-ai/dsh-goal-session": "^0.0.1-rc.2",
37
+ "@deepseek-ai/dsh-cmdline": "^0.0.1-rc.2",
38
+ "@deepseek-ai/dsh-compact-tool-result-prune": "^0.0.1-rc.2",
39
+ "@deepseek-ai/dsh-goal": "^0.0.1-rc.2",
40
+ "@deepseek-ai/dsh-environment": "^0.0.1-rc.2",
41
+ "@deepseek-ai/dsh-fs-local": "^0.0.1-rc.2",
42
+ "@deepseek-ai/dsh-headless": "^0.0.1-rc.2",
43
+ "@deepseek-ai/dsh-mcp-client": "^0.0.1-rc.2",
44
+ "@deepseek-ai/dsh-persona": "^0.0.1-rc.2",
45
+ "@deepseek-ai/dsh-pty": "^0.0.1-rc.2",
46
+ "@deepseek-ai/dsh-plan-mode": "^0.0.1-rc.2",
47
+ "@deepseek-ai/dsh-pty-local": "^0.0.1-rc.2",
48
+ "@deepseek-ai/dsh-pwsh-local": "^0.0.1-rc.2",
49
+ "@deepseek-ai/dsh-pwsh-sandbox": "^0.0.1-rc.2",
50
+ "@deepseek-ai/dsh-session-projection": "^0.0.1-rc.2",
51
+ "@deepseek-ai/dsh-session-reference": "^0.0.1-rc.2",
52
+ "@deepseek-ai/dsh-time-context": "^0.0.1-rc.2",
53
+ "@deepseek-ai/dsh-skill": "^0.0.1-rc.2",
54
+ "@deepseek-ai/dsh-skill-local": "^0.0.1-rc.2",
55
+ "@deepseek-ai/dsh-tasks-local": "^0.0.1-rc.2",
56
+ "@deepseek-ai/dsh-tmux-context": "^0.0.1-rc.2",
57
+ "@deepseek-ai/dsh-token-meter": "^0.0.1-rc.2",
58
+ "@deepseek-ai/dsh-tool-ask-user": "^0.0.1-rc.2",
59
+ "@deepseek-ai/dsh-tool-bash": "^0.0.1-rc.2",
60
+ "@deepseek-ai/dsh-tool-bash-persistent": "^0.0.1-rc.2",
61
+ "@deepseek-ai/dsh-tool-cordis": "^0.0.1-rc.2",
62
+ "@deepseek-ai/dsh-tool-fs-search": "^0.0.1-rc.2",
63
+ "@deepseek-ai/dsh-tool-pwsh": "^0.0.1-rc.2",
64
+ "@deepseek-ai/dsh-tool-goal": "^0.0.1-rc.2",
65
+ "@deepseek-ai/dsh-tool-schedule": "^0.0.1-rc.2",
66
+ "@deepseek-ai/dsh-tool-ralph": "^0.0.1-rc.2",
67
+ "@deepseek-ai/dsh-tool-str-replace-editor": "^0.0.1-rc.2",
68
+ "@deepseek-ai/dsh-tool-skill": "^0.0.1-rc.2",
69
+ "@deepseek-ai/dsh-paths": "^0.0.1-rc.2",
70
+ "@deepseek-ai/dsh-tool-fs": "^0.0.1-rc.2",
71
+ "@deepseek-ai/dsh-tool-subagent": "^0.0.1-rc.2",
72
+ "@deepseek-ai/dsh-tool-tasks": "^0.0.1-rc.2",
73
+ "@deepseek-ai/dsh-tool-web": "^0.0.1-rc.2",
74
+ "@deepseek-ai/dsh-tool-workflow": "^0.0.1-rc.2",
75
+ "@deepseek-ai/dsh-web-app": "^0.0.1-rc.2",
76
+ "@deepseek-ai/dsh-workspace-context": "^0.0.1-rc.2",
77
+ "@deepseek-ai/cordis": "^4.0.1-rc.1",
78
+ "@deepseek-ai/dsh-tool-todo": "^0.0.1-rc.2",
79
+ "@deepseek-ai/dsh-workflow-workerthread": "^0.0.1-rc.2",
80
+ "@deepseek-ai/dsh-tool-subagent-control": "^0.0.1-rc.2"
78
81
  },
79
82
  "devDependencies": {
80
83
  "@types/js-yaml": "^4.0.9",
81
84
  "execa": "^10.0.0",
82
- "@deepseek-ai/dsh-frontend-static": "^0.0.1-rc.1",
83
- "@deepseek-ai/dsh-agent": "^0.0.1-rc.1",
84
- "@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.1",
85
- "@deepseek-ai/dsh-llm": "^0.0.1-rc.1",
86
- "@deepseek-ai/dsh-llm-mock-server": "^0.0.1-rc.1",
87
- "@deepseek-ai/dsh-loader-smoke": "^0.0.1-rc.1",
88
- "@deepseek-ai/dsh-session": "^0.0.1-rc.1",
89
- "@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.1",
90
- "@deepseek-ai/dsh-settings": "^0.0.1-rc.1",
91
- "@deepseek-ai/dsh-subagent": "^0.0.1-rc.1",
92
- "@deepseek-ai/dsh-system-prompt": "^0.0.1-rc.1",
93
- "@deepseek-ai/dsh-tools": "^0.0.1-rc.1"
85
+ "@deepseek-ai/dsh-frontend-static": "^0.0.1-rc.2",
86
+ "@deepseek-ai/dsh-host-apiproxy": "^0.0.1-rc.2",
87
+ "@deepseek-ai/dsh-host-webserver": "^0.0.1-rc.2",
88
+ "@deepseek-ai/dsh-llm": "^0.0.1-rc.2",
89
+ "@deepseek-ai/dsh-llm-mock-server": "^0.0.1-rc.2",
90
+ "@deepseek-ai/dsh-loader-smoke": "^0.0.1-rc.2",
91
+ "@deepseek-ai/dsh-agent": "^0.0.1-rc.2",
92
+ "@deepseek-ai/dsh-settings": "^0.0.1-rc.2",
93
+ "@deepseek-ai/dsh-session": "^0.0.1-rc.2",
94
+ "@deepseek-ai/dsh-subagent": "^0.0.1-rc.2",
95
+ "@deepseek-ai/dsh-tools": "^0.0.1-rc.2",
96
+ "@deepseek-ai/dsh-system-prompt": "^0.0.1-rc.2"
94
97
  }
95
98
  }
@@ -1,2 +0,0 @@
1
- import { o as runProfile } from "./profile-boot-DQYCwKII.js";
2
- export { runProfile };