dsh-any-agent 0.3.9 โ 0.5.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/CHANGELOG.md +153 -1
- package/README.md +75 -634
- package/README.zh.md +64 -649
- package/bin/dsh-any-agent.mjs +59 -0
- package/demo/mcp-client.mjs +130 -0
- package/demo/run.mjs +326 -0
- package/demo/transcript.json +47 -0
- package/docs/brand/README.md +58 -0
- package/docs/brand/header.jpg +0 -0
- package/docs/en/architecture.md +43 -0
- package/docs/en/compatibility.md +51 -0
- package/docs/en/demo.md +90 -0
- package/docs/en/install.md +257 -0
- package/docs/en/limits.md +27 -0
- package/docs/en/locking.md +24 -0
- package/docs/en/reference.md +40 -0
- package/docs/en/release.md +33 -0
- package/docs/en/security.md +59 -0
- package/docs/en/team.md +74 -0
- package/docs/en/tests.md +96 -0
- package/docs/en/usage.md +71 -0
- package/docs/zh/architecture.md +43 -0
- package/docs/zh/compatibility.md +49 -0
- package/docs/zh/demo.md +90 -0
- package/docs/zh/install.md +274 -0
- package/docs/zh/limits.md +30 -0
- package/docs/zh/locking.md +24 -0
- package/docs/zh/reference.md +40 -0
- package/docs/zh/release.md +38 -0
- package/docs/zh/security.md +59 -0
- package/docs/zh/team.md +71 -0
- package/docs/zh/tests.md +95 -0
- package/docs/zh/usage.md +69 -0
- package/package.json +5 -1
- package/scripts/check-release.mjs +73 -0
- package/src/acp.mjs +31 -3
- package/src/audit.mjs +32 -0
- package/src/bootstrap.mjs +26 -1
- package/src/config.mjs +178 -9
- package/src/host.mjs +148 -0
- package/src/hub.mjs +494 -73
- package/src/procstat.mjs +72 -0
- package/src/tools.mjs +561 -27
package/README.md
CHANGED
|
@@ -15,38 +15,12 @@
|
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
๐งช
|
|
18
|
+
๐งช 0.4.0 offline suite green (memory controls, team mode and the dispatch surface are all covered) ยท ๐ MCP over stdio ยท ๐ฆ Zero dependencies ยท ๐ MIT
|
|
19
19
|
|
|
20
20
|
An MCP server that drives DeepSeek Harness as a **long-lived coding agent**. Hand it a goal, let it write code and run scripts, steer it mid-flight, and collect the result when you are ready.
|
|
21
21
|
|
|
22
22
|
**Claude Code ยท Claude Desktop ยท Codex ยท Cursor ยท Windsurf ยท Zed ยท Gemini CLI ยท opencode ยท VS Code** โ or any client that speaks MCP over stdio.
|
|
23
23
|
|
|
24
|
-
## Quick start
|
|
25
|
-
|
|
26
|
-
Requires **Node.js โฅ 22.15** and **DSH installed with a working model route and credentials**. See [Requirements](#-requirements) for details.
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install -g dsh-any-agent
|
|
30
|
-
dsh-any-agent-setup
|
|
31
|
-
dsh-any-agent-setup --yes
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
The second command previews the changes; `--yes` applies them. Reconnect your client's MCP server, then ask your agent to start a DSH conversation in your project, dispatch a task, and collect the result. The [dispatch workflow](#-workflow-dispatch-then-collect-later) shows the tool sequence.
|
|
35
|
-
|
|
36
|
-
> **Before your first task:** read the [Usage Notes](USAGE-NOTES.en.md), especially the model-config and external-MCP permission boundaries. External MCP tools bypass DSH's sandbox. For a fresh machine, follow the [migration guide](USAGE-MIGRATION.en.md).
|
|
37
|
-
|
|
38
|
-
<details>
|
|
39
|
-
<summary>Full contents</summary>
|
|
40
|
-
|
|
41
|
-
- **Quick start** โ [Install and connect](#quick-start) ยท [Changelog](CHANGELOG.md)
|
|
42
|
-
- **Start here** โ [The problem](#-the-problem-this-solves) ยท [Why it's different](#-why-its-different) ยท [Why not just wrap the DSH CLI](#-why-not-just-wrap-the-dsh-cli) ยท [Requirements](#-requirements) ยท [Setup](#-setup)
|
|
43
|
-
- **Using it** โ [Tools](#-tools) ยท [Dispatch then collect](#-workflow-dispatch-then-collect-later) ยท [Completion notifications](#-workflow-get-notified-on-completion-no-polling) ยท [Many conversations](#-workflow-many-conversations-at-once)
|
|
44
|
-
- **Behavior** โ [Permissions](#-permissions) ยท [Reasoning](#-reasoning-hidden-by-default) ยท [Logging and privacy](#-logging-and-privacy) ยท [Workspace & GUI](#-workspace-visibility-in-the-dsh-gui)
|
|
45
|
-
- **Hard parts** โ [The write lock](#-the-write-lock-one-writer-per-conversation-at-a-time)
|
|
46
|
-
- **Reference** โ [Environment](#-environment-variables) ยท [Tests](#-tests) ยท [Known limitations](#-known-limitations) ยท [Why the old profile name?](#-why-is-the-profile-still-called-dsh-mcp)
|
|
47
|
-
|
|
48
|
-
</details>
|
|
49
|
-
|
|
50
24
|
## ๐ฏ The problem this solves
|
|
51
25
|
|
|
52
26
|
You already have a coding agent in your editor. What you do **not** have is one that can:
|
|
@@ -75,302 +49,68 @@ DSH (DeepSeek Harness) can do all of that โ it has file/shell tools, skills, s
|
|
|
75
49
|
| ๐งฉ **Capabilities compose** | DSH can mount its own MCP servers (IDA Pro, browsers, your internal tooling), so this is a bridge to a whole toolbox. |
|
|
76
50
|
| ๐ก๏ธ **Read-only that really is read-only** | `read-only` sandbox plus auto-denied approvals, for working on untrusted samples. |
|
|
77
51
|
|
|
78
|
-
##
|
|
79
|
-
|
|
80
|
-
DSH ships two programmable entry points. The `sdk` profile looks simpler, but its JSON-RPC surface has only **3 requests and 4 notifications** โ and it lacks three things that make an agent unusable in practice. The `acp` profile (Agent Client Protocol) has all of them. Everything below was verified against DSH `0.2.1-rc.5`.
|
|
81
|
-
|
|
82
|
-
| Capability | `sdk` profile | `acp` profile (**this project**) |
|
|
83
|
-
|---|---|---|
|
|
84
|
-
| **Resume a conversation** | โ no resume method; reusing an id fails with `already exists` | โ
`session/resume` โ verified **across processes** |
|
|
85
|
-
| **Interrupt a running turn** | โ only by killing the process | โ
`session/cancel` โ the same path as the user's own stop button |
|
|
86
|
-
| **Answer approval prompts** | โ no channel; operations silently fail closed | โ
`session/request_permission` round-trip |
|
|
87
|
-
| List / close sessions | โ none | โ
`session/list`, `session/close` |
|
|
88
|
-
| Gray vs. black streaming | one message, split by a `type` field | โ
two separate channels: `agent_thought_chunk` / `agent_message_chunk` |
|
|
89
|
-
| Per-session MCP servers | โ process-wide | โ
`mcpServers` accepted by `session/new` and `session/resume` |
|
|
90
|
-
|
|
91
|
-
**Core idea: the process is a cache, not the identity.** A conversation's identity is DSH's `sessionId`, persisted under `~/.dsh/sessions`. If the process is reaped, crashes, or the whole MCP server restarts, the next call transparently `session/resume`s it โ history intact.
|
|
92
|
-
|
|
93
|
-
<details>
|
|
94
|
-
<summary>How the clients, server and DSH connect</summary>
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
any MCP client โ Claude Code, Codex, Cursor, Zed, Cline, your ownโฆ
|
|
98
|
-
(Claude/Codex/Cursor/โฆ) โ
|
|
99
|
-
โ MCP over stdio โ newline-delimited JSON-RPC (ACP)
|
|
100
|
-
โผ โผ
|
|
101
|
-
โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
102
|
-
โdsh-any-agentโโโโโโโโโโโโโถโ dsh --profile dsh-mcp โ one long-lived process
|
|
103
|
-
โโโโโโโโโโโโโโโ โ โ per conversation
|
|
104
|
-
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
|
|
105
|
-
โ
|
|
106
|
-
โโโโโโโโโโโโโโดโโโโโโโโโโโโโโ
|
|
107
|
-
โ DSH's own tools โ files, shell, search,
|
|
108
|
-
โ โ skills, subagents, workflows
|
|
109
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโค
|
|
110
|
-
โ dsh-mcp-client โ โ your other MCP servers
|
|
111
|
-
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
**MCP compatibility.** The server uses only `initialize`, `ping`, `tools/list` and `tools/call`. It has also been verified with a bare-bones client that negotiates an older protocol version and never sends `notifications/initialized`.
|
|
115
|
-
|
|
116
|
-
</details>
|
|
117
|
-
|
|
118
|
-
## ๐ฆ Requirements
|
|
119
|
-
|
|
120
|
-
- **Node.js โฅ 22.15** โ the server uses `zstdDecompressSync` from `node:zlib` to read DSH session headers, and that API does not exist before 22.15
|
|
121
|
-
- **DSH installed** (`@deepseek-ai/dsh`), with working credentials under `~/.dsh`
|
|
122
|
-
- A working provider/model route in your local DSH configuration. The server prepares its `dsh-mcp` ACP profile automatically from that route; no provider config or credentials are bundled.
|
|
123
|
-
|
|
124
|
-
## ๐ Setup
|
|
125
|
-
|
|
126
|
-
### 1. Prepare the DSH profile
|
|
127
|
-
|
|
128
|
-
On first launch, the server prepares a `dsh-mcp` profile from the shipped ACP template and copies the model route from your existing DSH configuration. Credentials stay with DSH. Use `DSH_MCP_ROUTE_SOURCE` to choose a source profile, or `DSH_MCP_BOOTSTRAP=0` to manage the profile yourself. See [Environment variables](#-environment-variables).
|
|
129
|
-
|
|
130
|
-
<details>
|
|
131
|
-
<summary>Manual profile setup and ACP configuration details</summary>
|
|
132
|
-
|
|
133
|
-
```bash
|
|
134
|
-
dsh --profile dsh-mcp --from-default-profile acp
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Then edit `~/.dsh/profiles/dsh-mcp/cordis.patch.yml`, remove its `# dsh-mcp:managed` marker if present, and declare your provider and model. Start from [`profile-example/cordis.patch.yml`](profile-example/cordis.patch.yml).
|
|
138
|
-
|
|
139
|
-
> โ ๏ธ **Two traps here** (both learned the hard way):
|
|
140
|
-
>
|
|
141
|
-
> 1. **ACP's model does NOT come from `agent-default-model`.** It comes from the `dsh-acp` plugin's own `config.provider` / `config.model`, which the `acp` bundle hard-codes to `deepseek-official`. If you don't override the `acp` row, your sessions silently run on the wrong provider and fail with `no API key for provider route "deepseek-official"`.
|
|
142
|
-
> 2. **ACP's `reasoning_effort` defaults to empty** (= "Provider default"), not to the maximum. This server explicitly sets it to `max` on every `session/new` and after every `session/resume` โ because a fresh process does not remember the previous choice.
|
|
143
|
-
|
|
144
|
-
</details>
|
|
145
|
-
|
|
146
|
-
### 2. Register the server
|
|
52
|
+
## ๐งญ Positioning: a dispatcher, not a capability aggregator
|
|
147
53
|
|
|
148
|
-
|
|
54
|
+
**This server dispatches; it does not re-implement what DSH already does.** The caller
|
|
55
|
+
gives a goal, and DSH decides which tools, skills, subagents or team to use. Nothing
|
|
56
|
+
from DSH's tool surface is proxied, duplicated or preloaded.
|
|
149
57
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
- **dry-run by default**; `--yes` is required to write anything, and a preview starts **no subprocess at all** โ it only reads the files it would touch
|
|
153
|
-
- every file is backed up to `<file>.bak-dsh-<timestamp>` first, holding the exact **pre-write bytes**
|
|
154
|
-
- the config is parsed before and after writing, so a broken config never lands; malformed JSON and a read-only file are refused rather than forced, while a missing directory is created safely
|
|
155
|
-
- **text-level writes**: only the bytes of setup's own entry are replaced, so indentation (2-space, 4-space, tabs), key order, CRLF endings, a UTF-8 BOM and a missing trailing newline all survive untouched
|
|
156
|
-
- **idempotent** โ re-running only changes what differs, and a second `--yes` writes nothing and adds no `.bak`
|
|
157
|
-
|
|
158
|
-
**Measured impact scope (2026-11-02, plan mode on this machine):** 5 of the 9 clients were detected (Claude Code, Codex, Cursor, Zed, VS Code) and 4 were skipped; `--json` reported `mode=plan, changed=5, skipped=4, failed=0`. The SHA-256, size and mtime of every existing client config were unchanged. A real `--yes` is additive: only the single `dsh-any-agent` entry is written, every other byte is preserved, and a `.bak-dsh-<timestamp>` copy is taken first; `--remove` deletes only that entry.
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
dsh-any-agent-setup --yes --only cursor,zed # just these
|
|
162
|
-
dsh-any-agent-setup --yes --permission read-only
|
|
163
|
-
dsh-any-agent-setup --remove --yes # take it back out; only its own entry is removed
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
<details>
|
|
167
|
-
<summary>Advanced flags and client configuration paths</summary>
|
|
168
|
-
|
|
169
|
-
**Three new flags**
|
|
170
|
-
|
|
171
|
-
| Flag | What it does |
|
|
172
|
-
|---|---|
|
|
173
|
-
| `--json` | **stdout carries one parseable JSON object and nothing else** โ every human-readable line goes to stderr. Shape: `{ ok, mode, clients: [{ id, label, path, status, summary, error? }], changed, skipped, failed }` with `mode` being `plan`, `apply` or `remove`. This is a contract, not a convenience: a script can pipe stdout straight into a JSON parser |
|
|
174
|
-
| `--home <dir>` | use this directory instead of the real `HOME` / `USERPROFILE` / `APPDATA`, so a register-then-remove cycle can be rehearsed without touching a live config |
|
|
175
|
-
| `--force` | only two things: (a) create a directory or file that does not exist yet (it does that by default too โ the flag makes it reliable for automation, including when the client itself was not detected); (b) overwrite a same-named entry that exists but is not the shape this program expects. It never deletes somebody else's entry and never skips validation |
|
|
176
|
-
|
|
177
|
-
**Which file each client actually gets**
|
|
178
|
-
|
|
179
|
-
A `.bak-dsh-<timestamp>` backup lands beside every file before it is written. Paths are generic examples; "its own entry" means the single entry/section named `dsh-any-agent`.
|
|
180
|
-
|
|
181
|
-
| Client (`--only` id) | Config file | Container key | Install / update | `--remove` | Undo |
|
|
182
|
-
|---|---|---|---|---|---|
|
|
183
|
-
| Claude Code (`claude-code`) | `~/.claude.json` | `mcpServers` | its own entry only, with `type: "stdio"` | deletes its own entry only | copy the backup back |
|
|
184
|
-
| Claude Desktop (`claude-desktop`) | Windows `%APPDATA%\Claude\claude_desktop_config.json`; macOS `~/Library/Application Support/Claude/claude_desktop_config.json`; Linux `~/.config/Claude/claude_desktop_config.json` | `mcpServers` | adds or rewrites its own entry only | deletes its own entry only | copy the backup back |
|
|
185
|
-
| Codex (`codex`) | `~/.codex/config.toml` | `[mcp_servers.dsh-any-agent]` | appends its two TOML sections line-wise | deletes those two sections and nothing else | copy the backup back |
|
|
186
|
-
| Cursor (`cursor`) | `~/.cursor/mcp.json` | `mcpServers` | its own entry only | its own entry only | copy the backup back |
|
|
187
|
-
| Windsurf (`windsurf`) | `~/.codeium/windsurf/mcp_config.json` | `mcpServers` | its own entry only | its own entry only | copy the backup back |
|
|
188
|
-
| Zed (`zed`) | Windows `%APPDATA%\Zed\settings.json`; elsewhere `~/.config/zed/settings.json` | `context_servers` | its own entry only | its own entry only | copy the backup back |
|
|
189
|
-
| Gemini CLI (`gemini`) | `~/.gemini/settings.json` | `mcpServers` | its own entry only | its own entry only | copy the backup back |
|
|
190
|
-
| opencode (`opencode`) | `~/.config/opencode/opencode.json` | `mcp` | its own entry only, written in opencode's shape: `{ type: "local", command: [exe, ...args], enabled: true }` โ one array, not separate `command`/`args` | its own entry only | copy the backup back |
|
|
191
|
-
| VS Code (`vscode`) | Windows `%APPDATA%\Code\User\mcp.json`; elsewhere the same `mcp.json` under your VS Code user directory | `servers` โ not `mcpServers` | its own entry only | its own entry only | copy the backup back |
|
|
192
|
-
|
|
193
|
-
For the exact path on your machine, read the `path` field for each client in `--json` output โ that is the file this run would write, on your OS, which beats any table in a README.
|
|
194
|
-
|
|
195
|
-
Three shapes worth spelling out, because getting them wrong is silent. **VS Code** stores MCP servers under `servers`, not `mcpServers`; writing the usual key there produces a config the editor ignores. **opencode** takes a single `command` array (`{ type: "local", command: [exe, ...args], enabled: true }`) rather than separate `command`/`args` fields. **Claude Code** is the reason this release does not use a JSON library at all: `~/.claude.json` really does hold a `projects` map with keys that differ only by letter case (`.../Rust` next to `.../rust`), so a naive `JSON.parse` fails on it and any whole-file rewrite would destroy the user's project list. A byte-range scanner that never builds a value object handles it, and an earlier design that shelled out to `claude mcp add` was dropped for the same reason โ that command rewrites the file in its own formatting with no backup you could restore.
|
|
196
|
-
|
|
197
|
-
</details>
|
|
198
|
-
|
|
199
|
-
<details>
|
|
200
|
-
<summary>Registration checks and round-trip verification</summary>
|
|
201
|
-
|
|
202
|
-
**What `setup` will and will not touch**
|
|
203
|
-
|
|
204
|
-
It is additive, and it only ever touches its own entry. Verified against a real machine holding existing MCP servers (Cursor with four, Codex with two, Claude Code with five, Zed with 33 settings keys): every pre-existing entry survived **byte for byte**, key order included, and the only diff was one new entry per client.
|
|
205
|
-
|
|
206
|
-
```
|
|
207
|
-
Cursor 4 -> 5 entries existing entries byte-identical
|
|
208
|
-
Codex 2 -> 3 sections node_repl and rea bodies unchanged
|
|
209
|
-
Claude Code 5 -> 6 servers all four originals still listed
|
|
210
|
-
Zed 33 -> 33 keys only context_servers gained a child
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
What changed since 0.1.x: JSON configs used to be **re-serialised whole** (values preserved, layout lost), which is why the docs said whitespace was not guaranteed. Writes are text-level now โ apart from its own entry, the file comes back byte-identical, whether it was indented with 4 spaces, 2 spaces or tabs, laid out as one long line, missing its trailing newline, using CRLF, or carrying a UTF-8 BOM.
|
|
214
|
-
|
|
215
|
-
Codex's `config.toml` gets the same treatment line-wise: your other `[tables]` and their comments, the top-level keys (`model`, `approval_policy`, `sandbox_mode`, โฆ) and CRLF endings come back exactly as they were โ `--remove` deletes only the two `[mcp_servers.dsh-any-agent]` sections and leaves the rest intact.
|
|
216
|
-
|
|
217
|
-
The coverage for each of those claims is in [`test/`](test/) (`test/config-safety.mjs` asserts them one by one, 407 checks).
|
|
218
|
-
|
|
219
|
-
**Checked against a real home, read-only.** Running `setup --json` (plan mode) against an actual developer profile reported all nine clients and left every existing config untouched โ identical size, mtime and SHA-256, no `.bak-dsh-*` created, no new file created:
|
|
220
|
-
|
|
221
|
-
```
|
|
222
|
-
~/.claude.json 182693 B unchanged
|
|
223
|
-
~/.cursor/mcp.json 1396 B unchanged
|
|
224
|
-
~/.codex/config.toml 6647 B unchanged
|
|
225
|
-
~/.gemini/settings.json 465 B unchanged
|
|
226
|
-
~/.config/opencode/opencode.json 441 B unchanged
|
|
227
|
-
%APPDATA%/Zed/settings.json 5647 B unchanged
|
|
228
|
-
%APPDATA%/Claude/claude_desktop_config.json 247 B unchanged
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
The same run with `--home <tmp>` put **every** reported path inside that directory, `APPDATA` included โ which is what makes the register/remove cycle safe to rehearse.
|
|
232
|
-
|
|
233
|
-
**A full register -> remove round trip on the real home, not just a preview.** All seven configs were backed up, `--yes` was run for real, then `--remove --yes`, and everything was verified byte by byte:
|
|
234
|
-
|
|
235
|
-
| Client | Before | After register | After remove | Other MCP servers |
|
|
236
|
-
|---|---|---|---|---|
|
|
237
|
-
| ~/.claude.json | 182693 B | 182693 B (already current) | byte-identical to baseline | untouched; 67 top-level keys, 60 projects entries and both case-only duplicate keys still there |
|
|
238
|
-
| ~/.cursor/mcp.json | 1396 B | 1389 B | 1009 B | stitch / obsidian-mcp-server / codegraph / rea identical field by field |
|
|
239
|
-
| ~/.codex/config.toml | 6647 B | 6647 B (already current) | 6239 B | every other table, comment and top-level key kept |
|
|
240
|
-
| %APPDATA%/Zed/settings.json | 5647 B | 5647 B (already current) | 5240 B | all 32 top-level settings keys kept |
|
|
241
|
-
| ~/.gemini/settings.json | 465 B | 845 B | 465 B | restored byte for byte |
|
|
242
|
-
| ~/.config/opencode/opencode.json | 441 B | 869 B | 441 B | `$schema` and the pencil entry kept |
|
|
243
|
-
| %APPDATA%/Claude/claude_desktop_config.json | 247 B | 627 B | 247 B | restored byte for byte |
|
|
244
|
-
|
|
245
|
-
Removal is not accepted because it *looks* right: the check is that **taking our one entry out of the backup with the same editing engine reproduces the file now on disk, byte for byte**. All seven files pass. That proves removal is the strict inverse of registration and cannot quietly take anything else with it.
|
|
246
|
-
|
|
247
|
-
Afterwards every JSON config still parses and `codex mcp list` still reads the file; no client needs hand repair.
|
|
248
|
-
|
|
249
|
-
</details>
|
|
250
|
-
|
|
251
|
-
**`cwd` is optional: it defaults to the project the client has open**
|
|
252
|
-
|
|
253
|
-
The client already knows which folder the user is working in (MCP roots). `dsh_start` without `cwd` now sends one `roots/list` request and converts the returned `file://` URI into a path, so "DSH works where the client is looking" is the default - which also removes the usual way to end up on the home directory by building the path by hand. A client that does not advertise roots, or does not answer in time, gets an actionable error instead of a guessed directory.
|
|
254
|
-
|
|
255
|
-
**Do not use the home directory as `cwd`**
|
|
256
|
-
|
|
257
|
-
DSH's own ACL sandbox requires its temp directory to be **outside** the workspace. On Windows `%TEMP%` is `C:/Users/<you>/AppData/Local/Temp`, inside the home directory, so `cwd` set to home makes the first shell command fail with `Windows ACL: temp root must be outside the workspace`. `dsh_start` now rejects that combination and names both escapes: a narrower project directory, or `permission=read-only` / `danger-full-access`.
|
|
258
|
-
|
|
259
|
-
**Backups and rollback**
|
|
260
|
-
|
|
261
|
-
Both registering and removing snapshot the file first: `<config>.bak-dsh-<timestamp>`, holding the exact pre-operation bytes. Both directions, because the state you want back differs โ the pre-register backup undoes a bad install, the pre-remove backup reinstalls after a bad removal.
|
|
262
|
-
|
|
263
|
-
After every real write, each client entry in `--json` carries a `backup` field, and the human-readable output prints `backup: <path>` under each written entry.
|
|
264
|
-
|
|
265
|
-
The copies live next to each client's config, so picking the right one by hand is error-prone. There is a companion command:
|
|
266
|
-
|
|
267
|
-
| What | Command |
|
|
268
|
-
|---|---|
|
|
269
|
-
| List backups, newest first | `dsh-any-agent-restore --list` |
|
|
270
|
-
| Restore the newest backup per file | `dsh-any-agent-restore --latest` |
|
|
271
|
-
| Restore one batch | `dsh-any-agent-restore --stamp 2026-10-30T01-35` |
|
|
272
|
-
| Restore one specific copy | `dsh-any-agent-restore --file <backup path>` |
|
|
273
|
-
| See what would happen | add `--dry-run` |
|
|
274
|
-
|
|
275
|
-
A restore is itself destructive, so it copies the **current** file aside as `.bak-dsh-pre-restore-<timestamp>` before overwriting โ a restore can be undone.
|
|
276
|
-
|
|
277
|
-
**Install / update / remove**
|
|
278
|
-
|
|
279
|
-
| Task | Command |
|
|
58
|
+
| Owned by DSH (this server stays out) | Owned by this server (dispatch semantics) |
|
|
280
59
|
|---|---|
|
|
281
|
-
|
|
|
282
|
-
|
|
|
283
|
-
|
|
|
284
|
-
|
|
|
285
|
-
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
Nothing outside those entries is read, rewritten or deleted. Your credentials, other MCP servers and unrelated settings are not touched. The package itself is *not* uninstalled by `--remove` โ that is `npm uninstall -g dsh-any-agent`, and the two are independent: you can keep the package installed with no clients registered, or unregister without removing it.
|
|
309
|
-
|
|
310
|
-
To verify by hand afterwards: `claude mcp list`, `codex mcp list`, and inspect `~/.cursor/mcp.json` / the `context_servers` key of Zed's settings.
|
|
311
|
-
|
|
312
|
-
</details>
|
|
313
|
-
|
|
314
|
-
**The recorded path is absolute**
|
|
315
|
-
|
|
316
|
-
`setup` writes the absolute path of the installation it ran from. **If you move or delete that directory, every client will fail with `CONNECTION_CLOSED`** until you either re-run `setup` from the new location or `--remove` the stale entries. This bites when you install into a temporary folder to try things out, then clean it up: the configs still point into the void.
|
|
317
|
-
|
|
318
|
-
<details>
|
|
319
|
-
<summary>Manual client configuration: Claude Code, Codex and generic MCP</summary>
|
|
320
|
-
|
|
321
|
-
**From a local checkout**
|
|
60
|
+
| File I/O, shell, search, editing | Session identity and cross-crash resume |
|
|
61
|
+
| Skills, subagents, Agent Teams, task board | Async dispatch, receipts, completion sentinels |
|
|
62
|
+
| Model routing, sandbox, approval policy | Incremental read, interject, interrupt |
|
|
63
|
+
| Memory/context/compaction | Approval bridge, write-lock governance, queue, inbox |
|
|
64
|
+
| worktrees (the agent creates them with git) | Setup, status, doctor, audit |
|
|
65
|
+
|
|
66
|
+
**Dispatch on demand**: `dsh_send` passes a goal, not a tool manifest, and the caller
|
|
67
|
+
does not need to know DSH's tool list. The DSH agent picks what it needs โ including
|
|
68
|
+
capabilities added after this server was built (for example the experimental Agent
|
|
69
|
+
Teams). This server does not grow one MCP tool per DSH feature.
|
|
70
|
+
|
|
71
|
+
**Team mode (opt-in since 0.5.0)**: off by default. Run `dsh-any-agent team on`
|
|
72
|
+
and reconnect; the profile switches to `dsh-team` (the experimental Agent Teams
|
|
73
|
+
packages), and `dsh_send` then adds a short "form a team if this needs parallel or
|
|
74
|
+
multi-role work" line; simple tasks are not forced into a team. Turn it off with
|
|
75
|
+
`dsh-any-agent team off`, or for one call with `dsh_send(team=false)`. The team
|
|
76
|
+
profile disables the legacy `subagent` tools and replaces them with nine team
|
|
77
|
+
tools; teammates share one workspace.
|
|
78
|
+
|
|
79
|
+
**Only dispatch semantics become tools**: start, send, read, get, interject,
|
|
80
|
+
interrupt, approve, queue, inbox, status, release, takeover, doctor. No file I/O, no
|
|
81
|
+
shell, no editor, no task board.
|
|
82
|
+
|
|
83
|
+
**When to add something outside DSH**: only when you need a *guarantee* rather than a
|
|
84
|
+
*behavior* โ for example, several top-level sessions editing the same repository need
|
|
85
|
+
outer worktree isolation. For collaboration, prefer letting DSH run the team itself
|
|
86
|
+
(mount the experimental team packages into the profile; teammates share one workspace).
|
|
322
87
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
---
|
|
88
|
+
## Quick start
|
|
326
89
|
|
|
327
|
-
**
|
|
90
|
+
Requires **Node.js โฅ 22.15** and **DSH installed with a working model route and credentials**. See [Requirements](#-requirements) for details.
|
|
328
91
|
|
|
329
92
|
```bash
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
# local checkout
|
|
334
|
-
claude mcp add dsh-any-agent -- node /absolute/path/to/server/bin/dsh-any-agent.mjs
|
|
93
|
+
npm install -g dsh-any-agent
|
|
94
|
+
dsh-any-agent-setup
|
|
95
|
+
dsh-any-agent-setup --yes
|
|
335
96
|
```
|
|
336
97
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
```toml
|
|
340
|
-
[mcp_servers.dsh-any-agent]
|
|
341
|
-
command = "dsh-any-agent" # or "node" + path to bin/dsh-any-agent.mjs
|
|
342
|
-
args = []
|
|
343
|
-
startup_timeout_sec = 20.0
|
|
344
|
-
tool_timeout_sec = 120.0
|
|
345
|
-
```
|
|
98
|
+
The second command previews the changes; `--yes` applies them. Reconnect your client's MCP server, then ask your agent to start a DSH conversation in your project, dispatch a task, and collect the result. The [dispatch workflow](#-workflow-dispatch-then-collect-later) shows the tool sequence.
|
|
346
99
|
|
|
347
|
-
**
|
|
348
|
-
|
|
349
|
-
```json
|
|
350
|
-
{
|
|
351
|
-
"mcpServers": {
|
|
352
|
-
"dsh-any-agent": {
|
|
353
|
-
"command": "dsh-any-agent",
|
|
354
|
-
"args": []
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
```
|
|
100
|
+
> **Before your first task:** read the [Usage Notes](USAGE-NOTES.en.md), especially the model-config and external-MCP permission boundaries. External MCP tools bypass DSH's sandbox. For a fresh machine, follow the [migration guide](USAGE-MIGRATION.en.md).
|
|
359
101
|
|
|
360
|
-
|
|
102
|
+
<details>
|
|
103
|
+
<summary>Full contents</summary>
|
|
361
104
|
|
|
362
|
-
|
|
105
|
+
- **Quick start** โ [Install and connect](#quick-start) ยท [Changelog](CHANGELOG.md)
|
|
106
|
+
- **Start here** โ [The problem](#-the-problem-this-solves) ยท [Why it's different](#-why-its-different) ยท [Why not just wrap the DSH CLI](#-why-not-just-wrap-the-dsh-cli) ยท [Requirements](#-requirements) ยท [Setup](#-setup)
|
|
107
|
+
- **Using it** โ [Tools](#-tools) ยท [Dispatch then collect](#-workflow-dispatch-then-collect-later) ยท [Completion notifications](#-workflow-get-notified-on-completion-no-polling) ยท [Many conversations](#-workflow-many-conversations-at-once)
|
|
108
|
+
- **Behavior** โ [Permissions](#-permissions) ยท [Reasoning](#-reasoning-hidden-by-default) ยท [Logging and privacy](#-logging-and-privacy) ยท [Workspace & GUI](#-workspace-visibility-in-the-dsh-gui)
|
|
109
|
+
- **Hard parts** โ [The write lock](#-the-write-lock-one-writer-per-conversation-at-a-time)
|
|
110
|
+
- **Reference** โ [Environment](#-environment-variables) ยท [Tests](#-tests) ยท [Known limitations](#-known-limitations) ยท [Why the old profile name?](#-why-is-the-profile-still-called-dsh-mcp)
|
|
363
111
|
|
|
364
112
|
</details>
|
|
365
113
|
|
|
366
|
-
### 3. Try it
|
|
367
|
-
|
|
368
|
-
```bash
|
|
369
|
-
dsh-any-agent --version # prints the version, starts nothing
|
|
370
|
-
dsh-any-agent --help # flags, key env vars, docs link
|
|
371
|
-
node test/smoke.mjs # no LLM calls, verifies the whole plumbing
|
|
372
|
-
```
|
|
373
|
-
|
|
374
114
|
## ๐งฐ Tools
|
|
375
115
|
|
|
376
116
|
| Tool | What it does |
|
|
@@ -385,335 +125,36 @@ node test/smoke.mjs # no LLM calls, verifies the whole plumbing
|
|
|
385
125
|
| `dsh_interrupt` | Stop the current turn (the conversation stays healthy) |
|
|
386
126
|
| `dsh_release` | Hand the conversation back (frees the write lock so you can open it in your own DSH GUI). Still resumable; `forget=true` writes a tombstone โ by-id lookups are refused until you delete `$DSH_HOME/.dsh-any-agent-forgotten/<id>.json` |
|
|
387
127
|
| `dsh_approval_decide` | Adjudicate a pending approval (only when `on_approval=ask`) |
|
|
388
|
-
| `dsh_status` | Lightweight status check |
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
MCP tool calls **block**, so `dsh_send` **does not block by default** (`wait=false`): you get a receipt immediately, the turn runs in the background, and the sentinel file wakes you when it finishes. The whole flow:
|
|
393
|
-
|
|
394
|
-
```text
|
|
395
|
-
1. dsh_send(conversation_id, prompt, wait=false)
|
|
396
|
-
โ { run_id: "run-abc123", background: true } # returns without waiting for the turn
|
|
397
|
-
|
|
398
|
-
2. โฆkeep working on something else (verified: another conversation ran a full turn meanwhile)โฆ
|
|
399
|
-
|
|
400
|
-
3. dsh_status(conversation_id)
|
|
401
|
-
โ state=running | elapsed=3.5s | current_tool=pwsh | out=37 chars
|
|
402
|
-
|
|
403
|
-
4. dsh_read(conversation_id, cursor) # peek mid-flight
|
|
404
|
-
โ [me] โฆ | [turn] turn 1 started | [tool] pwsh [in_progress]
|
|
405
|
-
|
|
406
|
-
5. dsh_get(conversation_id, run_id) # collect the result
|
|
407
|
-
โ status=done, elapsed=20.3s, full answer
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
`run_id` is the receipt. Run records are **memory-only, last 20 kept**; under many concurrent runs old ones get evicted and `dsh_get` will fail โ which is exactly why the completion sentinel below carries the result.
|
|
411
|
-
|
|
412
|
-
## ๐ Workflow: get notified on completion (no polling)
|
|
413
|
-
|
|
414
|
-
Besides `run_id`, `wait=false` returns a **`sentinel_file`** path plus **`sentinel_file_posix`** (the Bash/MSYS form โ use it directly; hand-converting Windows paths is the easiest way to make your waiter hang until timeout). When the turn ends โ **whether it succeeds, fails, or is cancelled** โ the service **atomically writes** a small file there containing the final `status` and `result`. So you neither poll nor block: **wait for that file with a background task in your own host (Claude/Codex); the moment it appears, you're woken up.**
|
|
415
|
-
|
|
416
|
-
```bash
|
|
417
|
-
# sent="$sentinel_file_posix" โ copy it straight from the tool result
|
|
418
|
-
sent='/c/Users/you/.dsh/.dsh-any-agent/runs/<conversation_id>/<run_id>.json'
|
|
419
|
-
dl=$(( $(date +%s) + 2100 )) # 35min safety net so a dead service can't hang you forever
|
|
420
|
-
until [ -f "$sent" ]; do
|
|
421
|
-
[ "$(date +%s)" -ge "$dl" ] && { echo "TIMEOUT"; exit 1; }
|
|
422
|
-
sleep 2
|
|
423
|
-
done
|
|
424
|
-
echo "DONE" # task exits โ host wakes you โ read the file to collect
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
Once the file exists: **read it** for `status` and `result` (preferred โ immune to the in-memory window and survives restarts), or use `dsh_get(conversation_id, run_id)`.
|
|
428
|
-
|
|
429
|
-
Design notes (especially under **concurrent multi-session** use):
|
|
430
|
-
|
|
431
|
-
- **No cross-talk**: the path is `<state dir>/runs/<conversation_id>/<run_id>.json`, namespaced by conversation. `run_id` sequence numbers are counted per-conversation, so two conversations *can* mint the same `run_id` in the same millisecond โ the per-conversation directory keeps them apart. Arm one background task per run; each completes and wakes you independently.
|
|
432
|
-
- **Latch semantics**: the file is written once and kept. Even if the run finishes *before* you arm the waiter, `[ -f ]` is immediately true โ you never miss it.
|
|
433
|
-
- **Atomic write**: `tmp` + `rename`; if the file exists its contents are complete. Each run uses its own tmp, so concurrent writes never clobber.
|
|
434
|
-
- **No server push**: the service only answers requests over MCP (it sends no notifications). "Completion notification" is entirely the sentinel file plus your background waiter.
|
|
435
|
-
- **โ
No reasoning in the sentinel**: the payload carries the answer, thinking *statistics*, usage and (credential-redacted) tool inputs, but never reasoning text โ `result.thinking` is stripped and replaced by a boolean `thinking_omitted` (`true` when reasoning was removed, `false` when there was none), because this server's rule is that reasoning never reaches its own disk artifacts. `dsh_get(run_id)` still returns it from memory while the record lives. `DSH_MCP_SENTINEL_INCLUDE_REASONING=1` breaks that guarantee deliberately. Note that **DSH's own session log does contain reasoning** โ see [Logging and privacy](#-logging-and-privacy).
|
|
436
|
-
|
|
437
|
-
> Cleanup: sentinels contain full answers, so the service **prunes sentinels older than 7 days at startup** (tune with `DSH_MCP_SENTINEL_TTL_MS`; `0` disables). Your waiter should still `rm` the file after consuming it.
|
|
438
|
-
|
|
439
|
-
## ๐งต Workflow: many conversations at once
|
|
440
|
-
|
|
441
|
-
```text
|
|
442
|
-
1. dsh_list(only_running=true)
|
|
443
|
-
- โฆ | id=448b239bโฆ | ๐ต running (9s, tool=pwsh, 40 chars this turn) | cwd=โฆ
|
|
444
|
-
- โฆ | id=33427096โฆ | ๐ต running (8s, 34 chars this turn) | cwd=โฆ
|
|
445
|
-
2. dsh_read(id, cursor=0) # read the first, note the cursor
|
|
446
|
-
3. dsh_read(id2, cursor=0) # switch to the second
|
|
447
|
-
4. dsh_read(id, cursor=<prev>) # switch back; entries that grew since <prev> reappear (match by seq)
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
## ๐คซ Reasoning: hidden by default
|
|
451
|
-
|
|
452
|
-
DSH separates reasoning from answer text at the model layer, and ACP exposes them as **two independent streams**. This server hides reasoning by default and returns only **statistics** (characters, duration, chunk count) โ so the caller knows thinking happened without paying for it in context.
|
|
453
|
-
|
|
454
|
-
- `reasoning=hide` (default) โ content dropped, stats returned
|
|
455
|
-
- `reasoning=marker` / `summary` / `full` โ progressively more content (mind your context budget)
|
|
456
|
-
- `dsh_read(include_reasoning=true)` โ live reasoning of the *currently running* turn only; it is never buffered and is discarded when the turn ends
|
|
457
|
-
|
|
458
|
-
> โ ๏ธ This server never writes reasoning to its own logs, state file or sentinels โ but **DSH's own session log (`~/.dsh/sessions/**/session.v4.jsonl.zstd`) does contain the full reasoning text**, even when `reasoning=hide`. That is DSH's storage (it is what makes resume possible) and is outside this server's control; account for it when analyzing sensitive data.
|
|
459
|
-
|
|
460
|
-
## ๐ Permissions
|
|
461
|
-
|
|
462
|
-
| Tier | Approval default | Use for |
|
|
463
|
-
|---|---|---|
|
|
464
|
-
| `danger-full-access` (raw server default) | `auto-allow` | Zero friction; `setup` writes `workspace-write` by default |
|
|
465
|
-
| `workspace-write` | `auto-allow` | Writes are limited to the workspace by DSH's sandbox; out-of-workspace requests are auto-allowed unless you pass `on_approval=ask` |
|
|
466
|
-
| `read-only` | `auto-deny` | Analyzing untrusted samples (malware, unknown dumps) |
|
|
467
|
-
|
|
468
|
-
`on_approval` can be `auto-allow`, `auto-deny`, or `ask` (which exposes a pending approval for `dsh_approval_decide` to adjudicate).
|
|
469
|
-
|
|
470
|
-
**`read-only` is enforced, not merely a default**: it always runs with `auto-deny`; passing `auto-allow` or `ask` for a read-only conversation is rejected at `dsh_start`, and the approval layer refuses any permission request from a read-only conversation. After create/resume the server reads back the session's recorded tier; for read-only it **fails closed** if the projection cache has no permission record within the DSH flush window, rather than relying on the approval layer alone. Without that, a profile `defaultPreset` plus a permissive approval policy could let a write through (found by the adversarial audit).
|
|
471
|
-
|
|
472
|
-
## ๐ช Workspace visibility in the DSH GUI
|
|
473
|
-
|
|
474
|
-
Two different things โ don't confuse them:
|
|
475
|
-
|
|
476
|
-
| | Depends on | Status |
|
|
477
|
-
|---|---|---|
|
|
478
|
-
| **Workspace effectiveness** | the conversation's `cwd` | โ
Always correct. Process working directory, file placement and sandbox write root all follow it |
|
|
479
|
-
| **GUI grouping** | `sessionIds` in `~/.dsh/storages/workspace.json` | โ ๏ธ This server registers it, but **the running DSH server caches the registry in memory** โ external file edits only take effect after a DSH restart |
|
|
480
|
-
|
|
481
|
-
Verified experimentally ([`test/workspace-effect.mjs`](test/workspace-effect.mjs)): two conversations in two desktop folders, given **no path at all**, each wrote its file into its own workspace, self-reported the correct working directory, and leaked nothing into the server's cwd / home / temp / the other workspace (24/24 checks).
|
|
482
|
-
|
|
483
|
-
Maintenance CLI:
|
|
484
|
-
|
|
485
|
-
```bash
|
|
486
|
-
node bin/dsh-any-agent-workspaces.mjs --list # inspect the registry
|
|
487
|
-
node bin/dsh-any-agent-workspaces.mjs --backfill # register every session found on disk
|
|
488
|
-
node bin/dsh-any-agent-workspaces.mjs --prune-temp # drop temp-directory entries
|
|
489
|
-
node bin/dsh-any-agent-workspaces.mjs --prune-empty # drop empty workspaces whose path is gone
|
|
490
|
-
node bin/dsh-any-agent-workspaces.mjs --purge-test-sessions # delete test sessions (strict rules)
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
> โ ๏ธ Before restarting DSH, **don't touch workspaces in the GUI** โ the server will write its in-memory state back over the file.
|
|
494
|
-
|
|
495
|
-
## ๐ถ๏ธ Logging and privacy
|
|
128
|
+
| `dsh_status` | Lightweight status check (single conversation) |
|
|
129
|
+
| `dsh_inbox` | **Cross-conversation approval inbox**: every pending approval with reason, input and age; decide with `dsh_approval_decide` |
|
|
130
|
+
| `dsh_queue` | Inspect/modify the session queue: `list` / `remove` / `update` / `promote` (created by `dsh_send(queue=true)`, memory only) |
|
|
131
|
+
| `dsh_doctor` | Read-only health check: DSH version, profile, state/dir writability, orphan locks, sessions and RSS, effective config |
|
|
496
132
|
|
|
497
|
-
|
|
133
|
+
## ๐ Documentation map
|
|
498
134
|
|
|
499
|
-
|
|
500
|
-
2. This server is launched by Claude/Codex, so its stderr lands in *their* logs.
|
|
501
|
-
3. DSH's own stderr may carry model output **including reasoning**, so it is never forwarded by default.
|
|
502
|
-
|
|
503
|
-
| Level | Behavior |
|
|
135
|
+
| Document | Contents |
|
|
504
136
|
|---|---|
|
|
505
|
-
|
|
|
506
|
-
|
|
|
507
|
-
|
|
|
508
|
-
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
| `DSH_MCP_ROUTE_SOURCE` | unset | Pin where model routes are copied from during bootstrap: a profile name (`desktop`, `web`, โฆ) or `__home__` for `$DSH_HOME/cordis.patch.yml`. Unset = try home โ desktop โ web โ headless โ acp |
|
|
527
|
-
| `DSH_MCP_STATE` | `$DSH_HOME/.dsh-any-agent/conversations.json` | Conversation registry, survives restarts. The old `<repo>/.state` location is migrated once on startup |
|
|
528
|
-
| `DSH_MCP_APPROVAL` | `auto-allow` (non-read-only) | Default approval policy. `ask` makes out-of-workspace requests surface as pending approvals instead of being auto-allowed |
|
|
529
|
-
| `DSH_MCP_APPROVAL_TIMEOUT_MS` | `300000` | How long a pending `ask` approval waits before being cancelled |
|
|
530
|
-
| `DSH_MCP_FORGOTTEN_DIR` | `$DSH_HOME/.dsh-any-agent-forgotten` | Tombstones written by `dsh_release(forget=true)` |
|
|
531
|
-
| `DSH_MCP_RUNS_DIR` | `<state file dir>/runs` | Root for async completion sentinels (`<this>/<conversation_id>/<run_id>.json`) |
|
|
532
|
-
| `DSH_MCP_LOCKS_DIR` | `<state file dir>/locks` | Holder registrations for the write lock. โ ๏ธ **All instances must agree on this value**, or they cannot see each other's registrations and will misread each other as an unidentifiable holder (a GUI) and refuse to preempt |
|
|
533
|
-
| `DSH_MCP_LOCK_STALE_MS` | `90000` (3ร `REAP_INTERVAL_MS`, floor 30 s) | How long without a heartbeat before a holder counts as lost (and becomes auto-preemptible by `dsh_takeover`); changing the reap interval changes the derived default |
|
|
534
|
-
| `DSH_MCP_IDLE_TTL_MS` | `0` (never reap) | How long a conversation may sit idle before its DSH process is reaped. โ
โ
**`0` means never reap (the default)**: the server keeps holding the write lock, so **the GUI cannot take it** โ opening the conversation there is read-only and still works. Cost: one resident DSH process per conversation (**measured 300 MB and 476 MB in two cold starts**). Set a millisecond value (e.g. `300000`) to save memory and accept that the lock can be taken |
|
|
535
|
-
| `DSH_MCP_MAX_LIVE` | `8` (โ2.5โ4 GB worst case; see note 8) | โ
**How many conversation processes to keep alive at once** (`0` = unlimited). Going over the cap reaps the **least recently used** one (a running turn is never reaped) โ "**bounded memory, locks kept where it matters**". Use `4` when memory is tight, `0` on a big machine. To free idle conversations at once: `dsh_release(all=true)` (busy turns are skipped unless `force=true`) |
|
|
536
|
-
| `DSH_MCP_LIST_PROBE_TTL_MS` | `10000` | Cache lifetime for `dsh_list`'s on-disk probe (the probe spawns a DSH process and takes ~1 s) |
|
|
537
|
-
| `DSH_MCP_SENTINEL_TTL_MS` | `604800000` (7 days) | Startup pruning age for sentinels; `0` disables pruning |
|
|
538
|
-
| `DSH_MCP_SENTINEL_INCLUDE_REASONING` | unset | `1` writes `result.thinking` into the sentinel (**breaks the never-on-disk guarantee**); answer/error/title/tool inputs are still credential-redacted |
|
|
539
|
-
| `DSH_MCP_PERMISSION` | `danger-full-access` | Default tier for `dsh_start` |
|
|
540
|
-
| `DSH_MCP_REASONING_EFFORT` | `max` | Default reasoning effort |
|
|
541
|
-
| `DSH_MCP_PROMPT_TIMEOUT_MS` | `0` (no timeout) | Wait bound for **`wait=true` only**; on expiry the turn merely moves to the background |
|
|
542
|
-
| `DSH_MCP_REGISTER_WORKSPACE` | `project` | `project` / `all` / `0` |
|
|
543
|
-
| `DSH_MCP_GIT_STATUS` | on | `0` disables the per-turn `git status` summary. Useful for untrusted repos (git can run repo-configured fsmonitor/filters outside DSH's sandbox) |
|
|
544
|
-
| `DSH_MCP_BACKUP_KEEP` | `20` | How many `.bak-dsh-*` copies to keep per client config (rotation is best-effort) |
|
|
545
|
-
| `DSH_MCP_REQUEST_TIMEOUT_MS` | `60000` | Per-request timeout on the ACP channel (initialize, session/new, โฆ) |
|
|
546
|
-
| `DSH_MCP_REAP_INTERVAL_MS` | `30000` | How often the idle reaper checks for conversations to reclaim. Values below the safe floor fall back to the default |
|
|
547
|
-
| `DSH_MCP_LOG` | `silent` | `silent` / `info` / `debug` |
|
|
548
|
-
| `DSH_MCP_LOG_STDERR` | unset | `1` forwards DSH's raw stderr |
|
|
549
|
-
|
|
550
|
-
## ๐งช Tests
|
|
551
|
-
|
|
552
|
-
```bash
|
|
553
|
-
npm test # offline suites; smoke skips DSH sections if no DSH
|
|
554
|
-
npm run test:all # offline + model-backed suites (needs DSH/provider)
|
|
555
|
-
node test/cleanup.mjs # suite teardown on its own (test-created dirs only; --dry-run to preview)
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
Model-backed suites are not part of the hosted CI matrix (they need a real DSH
|
|
559
|
-
install and provider credentials). Run them locally, or dispatch the
|
|
560
|
-
`model-suites` workflow with a self-hosted runner that has DSH configured.
|
|
561
|
-
|
|
562
|
-
The suite **cleans up after itself**: `run.mjs` always ends with `cleanup.mjs`, which removes sessions and workspace registrations whose cwd is **a test-created directory** (`tmpdir()/dsh-any-agent-*` or `Desktop/dsh-any-agent-test-*`). Real project sessions โ including projects under `%TEMP%` โ and your live state directory are never touched. To remove `Desktop/dsh-any-agent-test-*` artifacts explicitly, run `node bin/dsh-any-agent-workspaces.mjs --purge-test-sessions`.
|
|
563
|
-
|
|
564
|
-
| Suite | Checks | Covers |
|
|
565
|
-
|---|---|---|
|
|
566
|
-
| `smoke` | 29 | handshake, tool table, conversation creation, config, registry purity |
|
|
567
|
-
| `boundary` | 46 | protocol edges (double initialize, malformed lines, unknown method), argument validation (including read-only + permissive approval rejection, unknown provider/model, and no stale write-lock marker on a failed create), unknown ids, cursor edges, lifecycle idempotence, unicode |
|
|
568
|
-
| `integration` | 30 | resume-with-memory across processes, interrupt, both interject modes |
|
|
569
|
-
| `async` | 16 | fire-and-forget + later collection |
|
|
570
|
-
| `sentinel` | 36 | completion sentinel: atomicity, latch semantics, per-conversation namespacing under concurrency, cancelled runs still land it, and **reasoning never reaching the file** |
|
|
571
|
-
| `prune` | 11 | **sentinel retention**: prunes over-age and crash-leftover files, keeps fresh ones, removes empty shells, `TTL=0` disables pruning, and never touches the conversation registry beside it (no tokens, runs every time) |
|
|
572
|
-
| `guards` | 31 | **environment-variable guards**: odd numbers always land on the safe side โ `IDLE_TTL_MS` values like `-1` or garbage mean "never reap" (never give the write lock away), a too-small reap interval falls back to the default (no busy loop), and the **lost-holder threshold is floored at 30 s** (so another instance cannot "legitimately" preempt and kill our turn) (no tokens, runs every time) |
|
|
573
|
-
| `sandbox-guard` | 21 | **the test sandbox itself**: a "throwaway HOME" that resolves to the real user profile, to the filesystem root, or to anywhere outside the OS temp dir is refused with an error instead of being written to and deleted. Every other suite builds its HOME through `test/sandbox-home.mjs` and can only destroy what it created there (no tokens, runs every time) |
|
|
574
|
-
| `approval-details` | 27 | **approval review context**: the pending-approval snapshot carries the command, reason, cwd and original approval ID โ not just a bare ID โ so a caller can actually adjudicate; missing `rawInput` is correlated from the earlier `tool_call` update by ID; credential-shaped fields and common inline literals are redacted; closing a conversation cancels pending approvals. Verified end-to-end over real stdio MCP |
|
|
575
|
-
| `line-decoder` | 4 | **UTF-8 line framing**: multi-byte characters split across MCP/ACP stream chunks are preserved (no U+FFFD), including CRLF and EOF-without-newline (no tokens) |
|
|
576
|
-
| `read-cursor` | 10 | **`dsh_read` cursor semantics**: text appended to an already-read entry is delivered again (revision cursor, match by `seq`), and `limit` is clamped to 1..500 so `0`/negative cannot spin or drop the tail (no tokens) |
|
|
577
|
-
| `sentinel-redaction` | 19 | **sentinel disk hygiene**: `result.thinking` is stripped, `thinking_omitted` is always a boolean, and `answer`/`error`/`title`/`tools_used[].input` all pass the same credential redaction as approvals (no tokens) |
|
|
578
|
-
| `forget-tombstone` | 10 | **`forget` is terminal**: `release(forget=true)` writes a tombstone, by-id lookups fail with a recovery path, lists exclude it, and deleting the tombstone restores the normal unknown-session path (no tokens) |
|
|
579
|
-
| `bootstrap-profile` | 24 | **first-start profile works in the published zero-dependency form**: copies routing with the built-in YAML subset parser, strips `apiKey`/`headers`/literal secrets, never writes `defaultPreset`, is idempotent, and leaves a user-maintained profile byte-identical (no tokens) |
|
|
580
|
-
| `workspace-safety` | 23 | **data-loss boundaries**: `isTempPath` is segment-aware (`tmpdir()+'Suffix'` and `/tmpfoo` are *not* temp), a corrupt `workspace.json` is never overwritten, an unreadable session root never wipes `sessionIds`, and automatic cleanup deletes only test-created sessions (no tokens) |
|
|
581
|
-
| `permission-adopt` | 17 | **no silent privilege escalation**: adopting an external session with no permission record fails closed to `read-only`; a recorded `read-only` cannot be promoted by the sidecar; the runtime read-back rejects a recorded/requested mismatch (no tokens) |
|
|
582
|
-
| `state-safety` | 26 | **state/sentinel/lock storage**: defaults live under `$DSH_HOME/.dsh-any-agent`, an unwritable directory fails startup loudly, sentinel write failures return `null` + `sentinel_error`, and bad numeric env values land on the safe side (no tokens) |
|
|
583
|
-
| `setup` | 45 | **the registration CLI**: preview writes nothing, `--remove` takes out only its own entry, an absent client is skipped rather than failed, the Codex TOML block round-trips, and the command recorded in a config is actually spawned and must answer `initialize` (no tokens). It never invokes `claude mcp get` โ that CLI rewrites `~/.claude.json` |
|
|
584
|
-
| `removal-scope` | 24 | **unregistering touches only its own entry**: configs holding unrelated MCP servers, top-level Codex settings and sibling Zed settings are registered and then removed, asserting every other value is byte-identical; removing something never written is a no-op with no write (no tokens) |
|
|
585
|
-
| `config-safety` | 407 | **the property the 0.3 line is about**, per client: preview leaves files byte-identical with no `.bak`; register-then-remove restores every other entry byte-for-byte, keeps key order and drops no top-level key; 4-space / 2-space indentation, single-line JSON, missing trailing newline, CRLF and a UTF-8 BOM all survive a write; malformed JSON and read-only files are refused without damage; the second `--yes` is a no-op with no new backup; every `.bak-dsh-<stamp>` appears once and equals the pre-write bytes; a `claude` on PATH is never invoked (no tokens) |
|
|
586
|
-
| `real-configs` | 84 (66 with 7/9, 48 with 5/9 fixtures) | **round-trip on the real thing**: copies this machine's actual client configs (an 182693-byte `~/.claude.json` holding hundreds of `projects` entries and case-colliding keys, a 238-line Codex TOML, Zed's full `settings.json`) into a temp dir, registers, then removes, and requires every file back **byte for byte**; entry-presence is checked by key (not a bare substring), and missing fixtures are reported as skipped rather than green (no tokens) |
|
|
587
|
-
| `commit-script` | 26 | commit messages go through stdin, never a temp file that `git add -A` could capture (no tokens) |
|
|
588
|
-
| `roots` | 12 | `dsh_start` without `cwd` asks the client for MCP roots; a client without roots gets an actionable error (no tokens) |
|
|
589
|
-
| `workspace-titles` | 15 | home-as-workspace is rejected before DSH starts; `title_hint` is returned separately from DSH's automatic title (no tokens) |
|
|
590
|
-
| `package-check` | 14 | `files` whitelist, zero runtime dependencies, Node floor, and bin shebangs (no tokens) |
|
|
591
|
-
| `timeout` | 38 | **timeout semantics**: `wait=false` is unaffected by `timeout_ms`; a `wait=true` expiry merely downgrades to background (turn not cancelled, result not lost, `busy` never lies); `timeout_ms<=0` waits forever; a failed resume invalidates the process instead of wedging the conversation; an empty prompt yields a clear error |
|
|
592
|
-
| `lock` | 79 | **write lock and preemption**: four holder classifications, `writeMarker` never overwriting a live holder, malformed/missing registration edge cases; **end-to-end** with two real MCP instances fighting over one conversation โ clear error โ refusal โ `force` takeover; **a crash releases the lock automatically**, **a wedged holder is preempted without `force`**; plus the "one turn, one sentinel" invariant (inline turns and idle interjects included) |
|
|
593
|
-
| `cycle` | 36 | **the async dispatch lifecycle**: dispatch โ collect the sentinel โ idle reap โ dispatch again, three rounds with no lock error; re-dispatch right on the reap boundary (widening the race); an immediate re-dispatch after `dsh_release`; and an assertion that reaping leaves no unattributable lock (this suite caught the reaper collecting a freshly spawned process as if it were idle) |
|
|
594
|
-
| `concurrency` | 31 | three simultaneous conversations + live incremental reads |
|
|
595
|
-
| `capability` | 22 | writing code, running scripts, **spawning its own subagents** (verified on disk via child session headers) |
|
|
596
|
-
| `workspace-effect` | 24 | workspace actually effective when no path is given |
|
|
597
|
-
| `acceptance` | 36 | two folders ร two conversations doing a read-only IDA Pro analysis |
|
|
598
|
-
| `multi` | 19 | **several instances and non-ASCII paths**: reproduces "two instances share the registry and the later save drops the earlier instance's conversation", then proves **that conversation is recoverable by id alone from the session store and actually usable** (registry loss, not an explicit `forget` โ forget is a terminal tombstone); **recovery never silently escalates privileges** (a read-only conversation stays read-only); no `.tmp` residue; and **CJK / emoji workspace paths** create sessions, do real work, and place files in the right directory |
|
|
599
|
-
| `permission` | 11 | โ
**whether the permission tiers actually take effect** (a safety property): it ignores our own return values (the very thing that used to lie) and reads **the session's own record** (`permissions.preset` / `sandboxMode` in the projection cache), checking all three tiers and that their recorded values differ. **This suite caught a silent privilege escalation**: a `defaultPreset` in the profile overrides `DSH_PERMISSION_MODE` at session creation |
|
|
600
|
-
| `list` | 13 | **the cost of `dsh_list`** (measured): a default call with the on-disk probe takes ~1 s (it spawns a DSH process) while **repeated calls hit a cache and drop to single-digit milliseconds**; `only_running=true` and `include_closed=false` **skip the probe** with equivalent semantics (unopened on-disk sessions are simply excluded) |
|
|
601
|
-
| `reasoning-check` | 8 | **reasoning visibility**: `reasoning=hide` returns no thinking body but non-zero stats; `reasoning=full` returns the text and the rendered section |
|
|
602
|
-
|
|
603
|
-
**`node test/run.mjs` runs 843 checks on a machine with five client fixtures (879 with all nine)** โ smoke 29 + 814 offline checks (850 with nine). Every suite exited 0 on 0.3.9; none needs a provider (smoke skips its session-creation sections when DSH is absent).
|
|
604
|
-
|
|
605
|
-
That is **524 assertions in the client-registration suites alone** (`setup` 45, `removal-scope` 24, `config-safety` 407, `real-configs` 48; 560 with all nine fixtures), which is where the 0.3 work actually is.
|
|
606
|
-
|
|
607
|
-
The model-backed suites (`smoke` 29, `boundary` 46, `integration` 30, `async` 16, `sentinel` 36, `timeout` 38, `lock` 79, `cycle` 36, `concurrency` 31, `capability` 22, `workspace-effect` 24, `acceptance` 36, `multi` 19, `permission` 11, `list` 13, `reasoning-check` 8) total 474 and were last measured in full on 0.3.9 โ they need a working provider, so re-run them yourself before trusting a release: `node test/run.mjs --all`. `acceptance` now **skips explicitly** when the DSH profile has no IDA MCP (`mcp__ida__*`).
|
|
608
|
-
|
|
609
|
-
> Three different totals are intentional: `node test/run.mjs` (843/879), the CI offline job (nominal 814/850, no smoke; hosted runners without client fixtures skip real-configs' 48 checks), and the model-backed set (474 nominal). Counts are the numbers each suite prints itself; don't use one number for a different runner.
|
|
610
|
-
|
|
611
|
-
### ๐งฏ Why `sandbox-guard` exists
|
|
612
|
-
|
|
613
|
-
On 2026-10-29 a throwaway verification script stored its sandbox path in a **PowerShell** variable named `$home`. `$home` is a read-only automatic variable, so the assignment failed silently (the script ran with `$ErrorActionPreference = 'Continue'`), `$env:USERPROFILE` was pointed at the sandbox name anyway, and the cleanup line โ `Remove-Item $home -Recurse -Force` โ resolved to the **real user profile**. The run timed out at 120 s mid-delete, taking part of `~/.codex`, `~/.cursor` and `~/.claude` with it.
|
|
614
|
-
|
|
615
|
-
Nothing in the test suites was ever green-but-wrong about the *writer*; what was missing was any assertion about the *sandbox*. `sandbox-guard` is that assertion: it refuses the real home, its subdirectories, case variants, the filesystem root, and any path outside `os.tmpdir()`. `test/sandbox-home.mjs` is now the only place a sandbox is created or destroyed, and `setup`/`removal-scope` no longer call `mkdtempSync` or `rmSync` themselves โ the suite fails if they go back to doing so.
|
|
616
|
-
|
|
617
|
-
### ๐ Numbers in this README must be reproducible
|
|
618
|
-
|
|
619
|
-
Every figure in this document is either **printed by a test** or **measured with a command you can re-run**. If you change a number, change it to a measurement โ not to an estimate, and never to a plausible-looking value.
|
|
620
|
-
|
|
621
|
-
This section exists because that rule was violated. An early draft claimed `dsh_send(wait=false)` returned "in ~1 ms" (measured: 81 ms), that interruption converged in "~18 ms" (no test ever produced that number โ `integration.mjs` only asserts `waited_ms` is between 0 and 25 s; four manual samples gave 91/102/105/119 ms), and that a conversation costs "~120 MB" (measured: 295โ476 MB) โ the last one contradicting itself in a single sentence, quoting both "120 MB each" and "three = 428 MB".
|
|
622
|
-
|
|
623
|
-
**Three tiers, and how to state each:**
|
|
624
|
-
|
|
625
|
-
| Tier | What it means | How to write it |
|
|
626
|
-
|---|---|---|
|
|
627
|
-
| **Tested** | A suite asserts it | Give the number; the suite is the authority |
|
|
628
|
-
| **Measured** | You ran a command | Give the number **and the method**, so a reader can reproduce it |
|
|
629
|
-
| **Estimated** | Derived or bounded | Say so ("roughly", "worst case", "โ") and show the arithmetic |
|
|
630
|
-
|
|
631
|
-
**Recording a measurement inline.** The memory figures model the pattern โ they name the tool, the before/after, and both samples:
|
|
632
|
-
|
|
633
|
-
```text
|
|
634
|
-
measured 300-480 MB per conversation
|
|
635
|
-
(Get-Process node: WorkingSet64 summed before vs. after one active
|
|
636
|
-
conversation reached end_turn; two cold starts gave 476 MB and 295 MB)
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
**When yours disagrees, say so.** Figures depend on machine, DSH version and load. A measurement outside a documented range is worth an issue โ the range is a claim, not decoration.
|
|
640
|
-
|
|
641
|
-
## ๐ The write lock: one writer per conversation at a time
|
|
642
|
-
|
|
643
|
-
DSH conversations carry a **cross-process write lock**: **it never expires while the holder lives, and no API can take it from a live holder** โ so **whoever gets there first decides everything**. This server turns that into "**you can look, we can write**":
|
|
644
|
-
|
|
645
|
-
| Situation | Outcome |
|
|
646
|
-
|---|---|
|
|
647
|
-
| **This server holds the lock** (the default) | โ
Opening it in the GUI is **read-only**: you still **see the content**, but you **cannot take the lock**. The default `DSH_MCP_IDLE_TTL_MS=0` (never reap) keeps the lock from ever going free |
|
|
648
|
-
| **You open it in the GUI while the lock is free** | โ **The GUI keeps it permanently** (navigating away, waiting and archiving all fail to release it) and this server **can never reconnect** โ only restarting `dsh web` helps |
|
|
649
|
-
| **Recommended** | Watch progress with **`dsh_read`** (cursor-based incremental reads) or the **sentinel file**; if you must use the GUI, `dsh_release` first (that interrupts a running turn, so it fits the gaps between tasks) |
|
|
650
|
-
|
|
651
|
-
**Who holds it** is reported by `dsh_status.lock_holder`; `dsh_takeover` handles the four cases:
|
|
652
|
-
|
|
653
|
-
| `lock_holder` | Meaning | Action |
|
|
654
|
-
|---|---|---|
|
|
655
|
-
| `self` | this process holds it | nothing to preempt |
|
|
656
|
-
| `stale-mcp` | the holder is **alive but silent** (no heartbeat for 90 s โ typically that MCP is wedged) | โ
`dsh_takeover` **kills and takes over** |
|
|
657
|
-
| `live-mcp` | another **live** dsh-any-agent instance is using it | โ ๏ธ refused by default; `force=true` takes it |
|
|
658
|
-
| `none` | no registration โ most likely **your own DSH GUI** | โ **never killed** (GUI conversations live inside the single `dsh web` process, so killing it takes down your whole UI and every GUI conversation with it), reported only |
|
|
659
|
-
|
|
660
|
-
> **Two measured footnotes.** โ **A crash leaves no orphan lock** โ `SIGKILL` the MCP and its children exit with it (their stdio pipe closes), releasing the lock automatically โ; so `stale-mcp` really means "**alive but wedged**". โก **Several instances**: the registry is last-writer-wins (a later save drops conversations it never saw), but the **session store is authoritative** โ any session on disk is recoverable **by id alone**; if you give instances different `DSH_MCP_STATE` files, **point `DSH_MCP_LOCKS_DIR` at one shared directory**, or they cannot see each other's holder registrations.
|
|
661
|
-
>
|
|
662
|
-
> The full measurements (the A/B probe method, the evidence that the GUI opens read-only, why even archiving does not release the lock, and crash-vs-wedge) are in [Usage Notes ยง3.5โ3.12](USAGE-NOTES.en.md).
|
|
663
|
-
|
|
664
|
-
## โ ๏ธ Known limitations
|
|
665
|
-
|
|
666
|
-
1. **No token-level streaming into model context** โ an MCP limitation, not DSH's. Callers get per-step results; humans can follow progress via stderr logs.
|
|
667
|
-
2. **True mid-turn steering is impossible** โ ACP rejects concurrent prompts (`a prompt is already in flight for this session`). `dsh_interject` is the practical equivalent: cancel, then immediately start a new turn, history preserved.
|
|
668
|
-
3. **Image prompts are unsupported** โ ACP advertises `promptCapabilities: {image: false}`.
|
|
669
|
-
4. **Restarting the MCP server takes its children with it** โ when this service is restarted or killed, the DSH children it spawned exit too (their stdio is a pipe to us): **in-flight turns are interrupted and no sentinel lands** (judge by inspecting the workspace, don't just wait for the file). The good news: the **write lock is released automatically**, so a restart always gets it back. The service also pushes no MCP notifications; "completion notification" is the sentinel file plus the caller's background waiter.
|
|
670
|
-
5. **A conversation that dies before its first successful turn may never have materialized on disk** โ resume then fails with a clear error. Safe after the first message.
|
|
671
|
-
6. **No renaming** โ DSH's title subsystem has no external rename API (`SessionTitleService.rename` requires a live in-process session). Titles are auto-generated from the first message.
|
|
672
|
-
7. **`session/list` returns only `{sessionId, cwd}` and excludes already-open sessions** โ titles are filled in by this server from DSH's projection cache.
|
|
673
|
-
8. **"Never reap" costs memory (measured 300โ480 MB per conversation)** โ within the `MAX_LIVE` window that is what buys "the GUI cannot take the write lock". Two fresh-start measurements of one active conversation gave 476 MB and 295 MB of added resident memory (the DSH child plus this server); expect the higher figures on a cold process. Two ways to spend less: โ **`DSH_MCP_MAX_LIVE` (default 8** โ over the cap, the least recently used process is reaped โ bounded memory, locks still held where it matters); โก set `DSH_MCP_IDLE_TTL_MS` to a millisecond value (at the cost of the lock being takeable). To drop to zero right after a batch of work: **`dsh_release(all=true)`** โ (releases idle conversations; busy turns are skipped and keep running โ pass `force=true` to interrupt them too. Everything resumes on demand).
|
|
674
|
-
|
|
675
|
-
## โ Why is the profile still called `dsh-mcp`?
|
|
676
|
-
|
|
677
|
-
Because renaming it would break things that already exist on disk.
|
|
678
|
-
|
|
679
|
-
When you first start this server it creates a DSH profile under `~/.dsh/profiles/dsh-mcp/`. That directory name is referenced by three other persisted things:
|
|
680
|
-
|
|
681
|
-
| Persisted identifier | Location | What breaks if renamed |
|
|
682
|
-
|---|---|---|
|
|
683
|
-
| profile directory | `~/.dsh/profiles/dsh-mcp/` | existing sessions can no longer resolve their provider (`no adapter registered for provider "โฆ"`) |
|
|
684
|
-
| `DSH_MCP_*` environment variables | your client configs | every deployment must change at once |
|
|
685
|
-
| `# dsh-mcp:managed` marker line | first line of the generated `cordis.patch.yml` | bootstrap no longer recognizes the file as its own and stops syncing it |
|
|
686
|
-
|
|
687
|
-
So the **product name** is `dsh-any-agent`, while the **on-disk identifiers** deliberately keep the old name. This is a compatibility decision, not an oversight. For a fresh deployment that wants a different profile name, set `DSH_MCP_PROFILE` โ the server will use the name you give it.
|
|
688
|
-
|
|
689
|
-
## ๐ฆ Release process
|
|
690
|
-
|
|
691
|
-
The order matters; two of these steps exist because a bad release shipped without them.
|
|
692
|
-
|
|
693
|
-
```bash
|
|
694
|
-
# 1. Bump the version. SERVER_VERSION reads package.json, nothing else.
|
|
695
|
-
npm version patch --no-git-tag-version
|
|
696
|
-
# Then edit CHANGELOG.md: turn [Unreleased] into the new version.
|
|
697
|
-
|
|
698
|
-
# 2. Run the offline suites locally (same set as CI, no tokens, no DSH needed for most).
|
|
699
|
-
node test/run.mjs
|
|
700
|
-
node test/package-check.mjs
|
|
701
|
-
|
|
702
|
-
# 3. Verify the tarball itself: builds, installs, starts.
|
|
703
|
-
npm pack --dry-run
|
|
704
|
-
# Then in a temp dir: npm install <tgz> && node_modules/.bin/dsh-any-agent --version
|
|
705
|
-
|
|
706
|
-
# 4. Tag and push (wait for CI before publishing).
|
|
707
|
-
git add -A && git commit -m "chore: release x.y.z"
|
|
708
|
-
git tag vx.y.z && git push origin main --tags
|
|
709
|
-
|
|
710
|
-
# 5. Publish to npm and verify.
|
|
711
|
-
npm publish --access public
|
|
712
|
-
npm view dsh-any-agent version
|
|
713
|
-
# A 404 right after publish is usually propagation, not failure: npm returns
|
|
714
|
-
# 202 and the registry can lag briefly. Confirm the local npm log shows the
|
|
715
|
-
# version before retrying.
|
|
716
|
-
```
|
|
137
|
+
| [Setup](docs/en/install.md) | Requirements, profile bootstrap, 9 MCP clients |
|
|
138
|
+
| [Usage](docs/en/usage.md) | Dispatch/collect, sentinels, many conversations, reasoning |
|
|
139
|
+
| [Team mode](docs/en/team.md) | Default behavior, how to disable (per client), verify and revert |
|
|
140
|
+
| [Permissions & privacy](docs/en/security.md) | Tiers, approvals, GUI visibility, logging |
|
|
141
|
+
| [Write lock](docs/en/locking.md) | Four holder classes, takeover, GUI contention |
|
|
142
|
+
| [Environment variables](docs/en/reference.md) | Every switch and its default |
|
|
143
|
+
| [Tests](docs/en/tests.md) | Offline/integration suites and coverage |
|
|
144
|
+
| [Known limitations](docs/en/limits.md) | Limits, profile naming |
|
|
145
|
+
| [Compatibility](docs/en/compatibility.md) | DSH version matrix, team-package requirements, upgrade checks |
|
|
146
|
+
| [Architecture](docs/en/architecture.md) | Why ACP, sdk/plugin comparison |
|
|
147
|
+
| [Release process](docs/en/release.md) | Release checklist |
|
|
148
|
+
|
|
149
|
+
## ๐งโ๐คโ๐ง Team mode (opt-in)
|
|
150
|
+
|
|
151
|
+
Off by default. Run `dsh-any-agent team on` and reconnect; the profile switches
|
|
152
|
+
to `dsh-team` (the experimental Agent Teams packages) and `dsh_send` adds a
|
|
153
|
+
short "form a team when this needs parallel or multi-role work" line. Simple
|
|
154
|
+
tasks are not forced into a team. Turn it off with `dsh-any-agent team off`, or
|
|
155
|
+
for one call with `dsh_send(team=false)`.
|
|
156
|
+
|
|
157
|
+
> Full steps (commands, verification, rollback) are in [Team mode](docs/en/team.md).
|
|
717
158
|
|
|
718
159
|
## Star History
|
|
719
160
|
|