dsh-any-agent 0.3.4 → 0.3.6
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 +665 -0
- package/DESIGN.md +2 -2
- package/README.md +66 -22
- package/README.zh.md +37 -25
- package/USAGE-MIGRATION.md +9 -9
- package/USAGE-NOTES.en.md +4 -4
- package/USAGE-NOTES.md +7 -7
- package/bin/dsh-any-agent-workspaces.mjs +22 -3
- package/bin/dsh-any-agent.mjs +3 -2
- package/bin/restore.mjs +7 -3
- package/package.json +2 -1
- package/src/acp.mjs +24 -3
- package/src/bootstrap.mjs +73 -64
- package/src/clients.mjs +35 -17
- package/src/config.mjs +59 -8
- package/src/hub.mjs +154 -19
- package/src/locks.mjs +39 -1
- package/src/mcp.mjs +6 -0
- package/src/tools.mjs +43 -14
- package/src/workspace.mjs +148 -43
- package/src/yaml-lite.mjs +424 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
Post-0.3.6 hardening.
|
|
11
|
+
|
|
12
|
+
## [0.3.6] — 2026-11-02
|
|
13
|
+
|
|
14
|
+
Post-0.3.5 hardening (remaining completeness-audit items).
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `dsh_status` now reports `lock_holder_reason` and no longer calls `none`
|
|
19
|
+
"your GUI" when the conversation is simply idle/released; the GUI hint is
|
|
20
|
+
only shown after an actual lock conflict.
|
|
21
|
+
- `dsh_get` reports `title_source` (`native` / `title_hint` / `null`) and no
|
|
22
|
+
longer labels a `title_hint` fallback as "DSH generated".
|
|
23
|
+
- `dsh_read` caps a single output entry at 200k characters (keeps the tail)
|
|
24
|
+
so one runaway tool output cannot grow the in-memory buffer without bound.
|
|
25
|
+
- `dsh_list` exposes `probe_ok` / `probe_error` / `probe_cached`; a failed
|
|
26
|
+
on-disk probe is no longer indistinguishable from "no sessions".
|
|
27
|
+
- MCP clients that send `notifications/roots/list_changed` now invalidate the
|
|
28
|
+
cached project directory and re-request `roots/list`.
|
|
29
|
+
- Client config backups rotate (default 20 per file, `DSH_MCP_BACKUP_KEEP`);
|
|
30
|
+
`dsh-any-agent-restore` writes atomically and preserves the target mode.
|
|
31
|
+
- The shared `workspace.json` writer keeps a `<file>.bak-dsh` last-known-good
|
|
32
|
+
copy before replacing it.
|
|
33
|
+
- `dsh_takeover` now compares the child's OS start time with the registration
|
|
34
|
+
before sending SIGKILL; a mismatched PID (reuse) is refused.
|
|
35
|
+
- Website: full English page at `/en/`, hreflang alternates, bilingual sitemap,
|
|
36
|
+
JSON-LD, Cloudflare Insights domains in CSP, and a pre-paint theme script
|
|
37
|
+
(CSP sha256) so light-system users no longer see a dark flash.
|
|
38
|
+
|
|
39
|
+
### Known residual items
|
|
40
|
+
|
|
41
|
+
- Cloudflare Managed robots.txt / Content Signals are dashboard-managed; the
|
|
42
|
+
repository documents the split instead of trying to override them.
|
|
43
|
+
- Session-log header decompression is still synchronous; it is bounded by the
|
|
44
|
+
fact that only the header is parsed, but a very large log can still block
|
|
45
|
+
the event loop briefly. A fully async reader needs a broader refactor of
|
|
46
|
+
`Hub.get`/`findSessionHeader`.
|
|
47
|
+
|
|
48
|
+
## [0.3.5] — 2026-11-02
|
|
49
|
+
|
|
50
|
+
Completeness-audit follow-up. Data-safety and privilege boundaries first,
|
|
51
|
+
then documentation/CI truthfulness.
|
|
52
|
+
|
|
53
|
+
### Security
|
|
54
|
+
|
|
55
|
+
- **Unknown permission on an adopted external session now fails closed to
|
|
56
|
+
`read-only`.** It previously fell back to `danger-full-access` when the
|
|
57
|
+
projection cache had no permission record.
|
|
58
|
+
- **Runtime permission read-back.** After `session/new` and every
|
|
59
|
+
`session/resume`, the session's own recorded `permissions.preset` is read
|
|
60
|
+
back; a mismatch (for example a profile `defaultPreset`) fails the call
|
|
61
|
+
instead of silently escalating. `dsh_get` exposes `permission_verified`.
|
|
62
|
+
- **Workspace-write semantics documented honestly.** Out-of-workspace
|
|
63
|
+
requests are auto-allowed by default; pass `on_approval=ask` to adjudicate.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
|
|
67
|
+
- **Temp-path prefix bug could delete real sessions.** `isTempPath` now
|
|
68
|
+
compares path segments, so `tmpdir()+'Suffix'` and `/tmpfoo` are no longer
|
|
69
|
+
treated as the OS temp directory. Automatic cleanup only deletes
|
|
70
|
+
test-created sessions (`tmpdir()/dsh-any-agent-*` and the Desktop test
|
|
71
|
+
folders); `tempOnly` remains an explicit manual flag.
|
|
72
|
+
- **`setup` no longer invokes `claude mcp get`.** That probe made Claude Code
|
|
73
|
+
rewrite `~/.claude.json` (dropping `projects[*].history` and adding keys).
|
|
74
|
+
- **First-start profile bootstrap works in the published package.** The
|
|
75
|
+
undeclared `js-yaml` dependency is replaced by a built-in YAML subset parser
|
|
76
|
+
(`src/yaml-lite.mjs`); generated profiles include the three permission
|
|
77
|
+
presets but never `defaultPreset`. A route without a default provider/model
|
|
78
|
+
fails with an actionable error instead of writing an empty `acp` config.
|
|
79
|
+
- **State/sentinel/lock storage moved to `$DSH_HOME/.dsh-any-agent`.** The
|
|
80
|
+
old in-package `.state` location is migrated once (including metadata
|
|
81
|
+
sidecars). Startup now fails loudly if the directories are unwritable, and
|
|
82
|
+
a sentinel write failure is surfaced as `sentinel_error`.
|
|
83
|
+
- **Corrupt `workspace.json` is no longer overwritten** with an empty
|
|
84
|
+
document; register/prune report the error and leave the file byte-identical.
|
|
85
|
+
The temp filename is unique per writer.
|
|
86
|
+
- **`defaultLaunch` uses `fileURLToPath`**, so install paths containing
|
|
87
|
+
spaces or non-ASCII characters no longer become percent-encoded.
|
|
88
|
+
- **`release(all=true)` now skips busy conversations by default** (pass
|
|
89
|
+
`force=true` to interrupt them); the interject timeout description matches
|
|
90
|
+
the busy-path behavior.
|
|
91
|
+
- Numeric env guards: negative sentinel TTL no longer deletes every sentinel;
|
|
92
|
+
empty permission/reasoning-effort values fall back to defaults.
|
|
93
|
+
- `ACP clientInfo.version` now comes from `package.json`.
|
|
94
|
+
|
|
95
|
+
### Tests
|
|
96
|
+
|
|
97
|
+
- New offline suites: `bootstrap-profile`, `workspace-safety`,
|
|
98
|
+
`permission-adopt`, `state-safety` (81 checks total). `package-check` is
|
|
99
|
+
part of `node test/run.mjs`; `acceptance` is part of `--all` (it self-skips
|
|
100
|
+
without IDA); `reasoning-check` now asserts rather than only printing.
|
|
101
|
+
- CI runs every offline suite and the website build/tests; stale counts in
|
|
102
|
+
the SCOPE header and READMEs were corrected (815 checks on five client
|
|
103
|
+
fixtures, 851 with nine; CI offline 786/822).
|
|
104
|
+
|
|
105
|
+
## [0.3.4] — 2026-11-02
|
|
106
|
+
|
|
107
|
+
Docs-only release. No runtime code changed; this publishes the updated
|
|
108
|
+
README (Star History, corrected setup wording) and the website source so the
|
|
109
|
+
npm package page matches the repository. The site is deployed separately at
|
|
110
|
+
<https://dsh-any-agent.xmtlz.dev/>.
|
|
111
|
+
|
|
112
|
+
### Changed
|
|
113
|
+
|
|
114
|
+
- README / README.zh: add the Star History chart and keep the measured
|
|
115
|
+
setup impact-scope note.
|
|
116
|
+
- Website: the release version is read at runtime from npm (fallback:
|
|
117
|
+
GitHub release/tag) instead of being baked at build time; the CSP allows
|
|
118
|
+
those endpoints; canonical and Open Graph URLs use the public domain.
|
|
119
|
+
- Website wording now states that copying the model route into the ACP
|
|
120
|
+
profile needs a YAML parser (js-yaml), that external MCP servers are
|
|
121
|
+
configured in the DSH profile, and that `setup --yes` writes absolute
|
|
122
|
+
node/entry paths plus env.
|
|
123
|
+
|
|
124
|
+
## [0.3.3] — 2026-11-02
|
|
125
|
+
|
|
126
|
+
An adversarial audit of the 0.3.2 MCP surface found one safety boundary that
|
|
127
|
+
could be bypassed and several places where the implementation and the docs
|
|
128
|
+
disagreed. This release closes them, with regressions for each.
|
|
129
|
+
|
|
130
|
+
### Security
|
|
131
|
+
|
|
132
|
+
- **`read-only` is now a hard boundary.** `dsh_start` rejects
|
|
133
|
+
`permission=read-only` combined with `on_approval=auto-allow`/`ask`, and the
|
|
134
|
+
approval layer refuses every permission request from a read-only
|
|
135
|
+
conversation. Previously DSH could ask for a `sandbox_permissions` escalation
|
|
136
|
+
and a permissive policy would let the write through while the session record
|
|
137
|
+
still said `read-only`.
|
|
138
|
+
|
|
139
|
+
### Fixed
|
|
140
|
+
|
|
141
|
+
- **`dsh_read` no longer loses streamed text.** The cursor is a revision
|
|
142
|
+
number: text appended to an already-read entry is delivered again, so a
|
|
143
|
+
polling caller sees the rest of the message. `limit` is clamped to 1..500.
|
|
144
|
+
- **`dsh_release(forget=true)` is terminal.** It writes a tombstone under
|
|
145
|
+
`$DSH_HOME/.dsh-any-agent-forgotten`; by-id lookups fail with a recovery
|
|
146
|
+
path, lists exclude the session, and stale tombstones are pruned at startup.
|
|
147
|
+
- **A failed `dsh_start` no longer leaves a write-lock marker.** It was
|
|
148
|
+
visible as an orphan holder after an invalid provider/model.
|
|
149
|
+
- **Real approval context.** The permission request's `toolCallId` is
|
|
150
|
+
correlated with the earlier `tool_call` update, so
|
|
151
|
+
`pending_approval_details` carries the command and reason instead of an
|
|
152
|
+
opaque id.
|
|
153
|
+
- **Sentinels redact tool inputs** with the approval credential rules, and
|
|
154
|
+
`thinking_omitted` is always a boolean.
|
|
155
|
+
- **Empty prompts fail synchronously** instead of producing an accepted
|
|
156
|
+
receipt for a turn that immediately errors.
|
|
157
|
+
- **`dsh_start` rejects an unknown provider/model** instead of silently
|
|
158
|
+
falling back to the profile default model.
|
|
159
|
+
- **Pending approvals are cancelled and cleared** when a turn ends or a
|
|
160
|
+
conversation closes.
|
|
161
|
+
- **`queue`-mode interject honors `timeout_ms`** (0 = wait forever).
|
|
162
|
+
- Tests: `read-cursor`, `sentinel-redaction` and `forget-tombstone`
|
|
163
|
+
regressions; `real-configs` checks entry keys rather than a bare substring;
|
|
164
|
+
test teardown removes only the conversation ids it created; acceptance
|
|
165
|
+
skips explicitly when no IDA MCP is configured.
|
|
166
|
+
|
|
167
|
+
### Changed
|
|
168
|
+
|
|
169
|
+
- `dsh_read` cursor semantics: entries reappear when their text grows; match
|
|
170
|
+
by `seq` and replace the text.
|
|
171
|
+
- Documentation now states that **DSH's own session log contains reasoning**
|
|
172
|
+
(even with `reasoning=hide`); the "never reaches disk" guarantee covers this
|
|
173
|
+
server's logs, sentinels and registry only.
|
|
174
|
+
- README links the official site at <https://dsh-any-agent.xmtlz.dev/> and
|
|
175
|
+
records the measured config impact scope for the nine supported clients.
|
|
176
|
+
|
|
177
|
+
## [0.3.2] — 2026-10-30
|
|
178
|
+
|
|
179
|
+
Two fixes and one feature, all from driving this server from Claude Code
|
|
180
|
+
rather than from reading the code.
|
|
181
|
+
|
|
182
|
+
### Added
|
|
183
|
+
|
|
184
|
+
- **`cwd` is optional and defaults to the project the client has open.** The
|
|
185
|
+
client already knows which folder the user is working in, but nothing asked
|
|
186
|
+
it. `initialize` now records the client's `roots` capability, and
|
|
187
|
+
`dsh_start` without `cwd` sends one `roots/list` request and converts the
|
|
188
|
+
returned `file://` URI to a path. "DSH works where the client is looking"
|
|
189
|
+
becomes the default, which also removes the usual way to land on the home
|
|
190
|
+
directory by building the path by hand. A client that does not advertise
|
|
191
|
+
roots, or does not answer in time, gets an actionable error naming an example
|
|
192
|
+
`cwd` — it never guesses a directory. `projectDir()` is exposed on the
|
|
193
|
+
server object for callers that want the raw value.
|
|
194
|
+
- **The home directory is rejected as a working directory, with the reason.**
|
|
195
|
+
DSH's own ACL sandbox requires its temp directory to be *outside* the
|
|
196
|
+
workspace. On Windows `%TEMP%` is `C:/Users/<name>/AppData/Local/Temp`,
|
|
197
|
+
inside the home directory, so `cwd: C:/Users/<name>` made the first shell
|
|
198
|
+
command fail with `Windows ACL: temp root must be outside the workspace` —
|
|
199
|
+
several layers away from the cause and easy to mistake for a broken install.
|
|
200
|
+
The combination is now refused up front, naming both escapes: a narrower
|
|
201
|
+
project directory, or `permission=read-only` / `danger-full-access`.
|
|
202
|
+
- `snapshot()` returns `title_hint` alongside `title`. DSH's own title is a
|
|
203
|
+
truncated prefix of the first message (13–15 characters, e.g.
|
|
204
|
+
`这是一次连通性冒烟测试,请`), which is what the GUI shows. `title` is
|
|
205
|
+
unchanged — it stays the field the GUI reads, so the two agree — and the
|
|
206
|
+
hint is returned separately rather than replacing it.
|
|
207
|
+
|
|
208
|
+
### Changed
|
|
209
|
+
|
|
210
|
+
- The `title_hint` tool description now states what happens: DSH's generated
|
|
211
|
+
title wins for display, and the hint is returned alongside it, instead of
|
|
212
|
+
the vaguer "fallback display".
|
|
213
|
+
|
|
214
|
+
### Tests
|
|
215
|
+
|
|
216
|
+
- `test/roots.mjs` — 12 checks over a real stdio handshake: that the server
|
|
217
|
+
sends `roots/list` when the client advertises it, that an answered root
|
|
218
|
+
becomes the working directory, that an unsupported client gets the
|
|
219
|
+
actionable message, and that a nonexistent explicit `cwd` still reports
|
|
220
|
+
cleanly.
|
|
221
|
+
- `test/workspace-titles.mjs` — 15 checks: the home-directory guard's
|
|
222
|
+
case-insensitive and trailing-separator handling (asserted only where the
|
|
223
|
+
filesystem actually has it), that `read-only` is still allowed, and that the
|
|
224
|
+
two title fields do not overwrite each other.
|
|
225
|
+
- 660 offline checks across eleven suites, green on Ubuntu and Windows for
|
|
226
|
+
Node 22 and 24.
|
|
227
|
+
|
|
228
|
+
## [0.3.1] — 2026-10-30
|
|
229
|
+
|
|
230
|
+
0.3.0 could overwrite a client config the user had deliberately made
|
|
231
|
+
read-only. Caught by CI on Linux, not on Windows, which is the interesting
|
|
232
|
+
part.
|
|
233
|
+
|
|
234
|
+
### Fixed
|
|
235
|
+
|
|
236
|
+
- **A read-only config was silently replaced on POSIX.** Writes go through
|
|
237
|
+
write-to-temp then `rename`, and `rename` only requires the *directory* to
|
|
238
|
+
be writable — not the target file. A `chmod 444` `~/.codex/config.toml`
|
|
239
|
+
was therefore replaced as if the bit were not there. Windows `rename`
|
|
240
|
+
returns EPERM for a read-only target, so the same code passed on
|
|
241
|
+
`windows-latest` and failed on `ubuntu-latest`; the bug had been in 0.3.0
|
|
242
|
+
the whole time. Writability is now checked with `access(W_OK)` before
|
|
243
|
+
writing, so both platforms refuse.
|
|
244
|
+
- **`--force` never actually overrode anything.** It was parsed, documented
|
|
245
|
+
and passed to neither the read-only check nor the writer. It now clears the
|
|
246
|
+
read-only bit, writes, and **restores the original mode** — verified, so it
|
|
247
|
+
does not leave a file whose permissions were quietly changed.
|
|
248
|
+
|
|
249
|
+
### Changed
|
|
250
|
+
|
|
251
|
+
- A file the current identity cannot write to is reported as
|
|
252
|
+
`config file is read-only, refusing to overwrite it (use --force to
|
|
253
|
+
override)` rather than a generic write failure, and no backup or temp file
|
|
254
|
+
is left behind.
|
|
255
|
+
|
|
256
|
+
### Tests
|
|
257
|
+
|
|
258
|
+
- 633 offline checks across nine suites, all green, on Ubuntu and Windows for
|
|
259
|
+
Node 22 and 24. The read-only section now asserts refusal, an untouched
|
|
260
|
+
file, no stray temp file, and that `--force` writes *and* restores the
|
|
261
|
+
mode. The root skip remains for hosts where permission bits genuinely do
|
|
262
|
+
not apply.
|
|
263
|
+
|
|
264
|
+
## [0.3.0] — 2026-10-30
|
|
265
|
+
|
|
266
|
+
**Configuration impact is now a bounded, auditable property.** Registering this
|
|
267
|
+
server with any of nine MCP clients changes exactly one entry per client and
|
|
268
|
+
nothing else — including in configs with comments, CRLF endings, a BOM or keys
|
|
269
|
+
that collide only by letter case. `package.json` reports `0.3.0`; both
|
|
270
|
+
`dsh-any-agent --version` and `dsh-any-agent-setup --version` print it.
|
|
271
|
+
|
|
272
|
+
### Added
|
|
273
|
+
|
|
274
|
+
- **Nine clients, up from five.** `dsh-any-agent-setup` now detects and
|
|
275
|
+
registers with Claude Code (`claude-code`), Claude Desktop
|
|
276
|
+
(`claude-desktop`), Codex (`codex`), Cursor (`cursor`), Windsurf
|
|
277
|
+
(`windsurf`), Zed (`zed`), Gemini CLI (`gemini`), opencode (`opencode`)
|
|
278
|
+
and VS Code (`vscode`) — these ids are what `--only` and the `--json`
|
|
279
|
+
output use. A client that is not installed is still skipped, never failed.
|
|
280
|
+
- **Per-client config shapes, written correctly rather than generically.**
|
|
281
|
+
VS Code keeps MCP servers under `servers` (not `mcpServers`); opencode
|
|
282
|
+
takes one `command` array (`{ type: "local", command: [exe, ...args],
|
|
283
|
+
enabled: true }`) instead of separate `command`/`args`; Zed uses
|
|
284
|
+
`context_servers`; Codex gets the two `[mcp_servers.dsh-any-agent]`
|
|
285
|
+
sections. Claude Code is now edited like every other JSON client — a
|
|
286
|
+
text-level `mcpServers` edit of `~/.claude.json` with a `type: "stdio"`
|
|
287
|
+
entry — rather than registered through `claude mcp add`.
|
|
288
|
+
- **A JSON scanner that does not build a value object.** `~/.claude.json`
|
|
289
|
+
really does hold a `projects` map with keys differing only in letter case
|
|
290
|
+
(`.../Rust` next to `.../rust`), which is enough to break a naive
|
|
291
|
+
`JSON.parse`; rewriting the file wholesale would destroy the user's project
|
|
292
|
+
list. The scanner walks the text into a tree of byte ranges (tolerating
|
|
293
|
+
comments and trailing commas, because Zed and VS Code configs allow them),
|
|
294
|
+
so the case-colliding keys are never materialised — only the range of our
|
|
295
|
+
own entry is touched.
|
|
296
|
+
- **JSON writes are text-level now: exactly one entry is rewritten, in place.**
|
|
297
|
+
Earlier versions re-serialised the whole file, so values survived but
|
|
298
|
+
formatting did not. Setup now locates its own entry in the existing text and
|
|
299
|
+
replaces only that span, so indentation (2-space, 4-space, tabs), key order,
|
|
300
|
+
CRLF line endings, a UTF-8 BOM and a missing trailing newline all come back
|
|
301
|
+
byte-identical apart from the entry itself.
|
|
302
|
+
- `--json` — **stdout is one parseable object and nothing else; every
|
|
303
|
+
human-readable line moves to stderr.** Shape: `{ ok, mode, clients: [{ id,
|
|
304
|
+
label, path, status, summary, error? }], changed, skipped, failed }` with
|
|
305
|
+
`mode` being `plan`, `apply` or `remove`. That split is the contract — a
|
|
306
|
+
caller can pipe stdout straight into a JSON parser. Exit code still reports
|
|
307
|
+
failure (`0` / `1`).
|
|
308
|
+
- `--home <dir>` — point setup at a sandbox (or any) home directory instead of
|
|
309
|
+
the real `HOME`/`USERPROFILE`/`APPDATA`, so registering, re-registering and
|
|
310
|
+
removing can be exercised without touching a live config.
|
|
311
|
+
- `--force` — exactly two effects: create a missing directory or file (the
|
|
312
|
+
default does that too; the flag makes it dependable under automation and
|
|
313
|
+
when the client itself is not detected), and overwrite a same-named entry
|
|
314
|
+
whose shape is not what this program expects. It never removes another
|
|
315
|
+
entry and never skips validation.
|
|
316
|
+
|
|
317
|
+
### Changed
|
|
318
|
+
|
|
319
|
+
- **The blast radius is now stated as a property, not a promise.** For every
|
|
320
|
+
client, setup writes one entry named `dsh-any-agent` and `--remove` deletes
|
|
321
|
+
exactly that entry. Nothing else in the file is re-serialised, re-indented or
|
|
322
|
+
reordered.
|
|
323
|
+
- **Codex (`config.toml`) is edited line-wise.** Other `[tables]`, their
|
|
324
|
+
comments, top-level keys (`model`, `approval_policy`, `sandbox_mode`) and
|
|
325
|
+
CRLF endings are carried over unchanged; `--remove` puts the file back the
|
|
326
|
+
way it was.
|
|
327
|
+
- **`dsh-any-agent-restore`** — a companion CLI for the backups. Setups and
|
|
328
|
+
removals both leave `<file>.bak-dsh-<stamp>` copies of the pre-operation
|
|
329
|
+
bytes, which is only useful if you can find the right one and put it back:
|
|
330
|
+
`--list` shows every copy newest-first, `--latest` / `--stamp` / `--file`
|
|
331
|
+
restore one, and `--dry-run` previews. A restore backs up the current file
|
|
332
|
+
as `.bak-dsh-pre-restore-<stamp>` first, so a mistaken restore can itself be
|
|
333
|
+
undone. Covered by 9 checks in `test/config-safety.mjs`, including
|
|
334
|
+
recovering a deliberately corrupted config.
|
|
335
|
+
- **Backups can no longer overwrite each other.** Two runs inside the same
|
|
336
|
+
millisecond produced the same filename, so the second copy silently replaced
|
|
337
|
+
the first — and the replaced one could be the only state worth going back to.
|
|
338
|
+
A colliding name now gets a `-2`, `-3` suffix instead.
|
|
339
|
+
- **The backup path is reported.** `--json` gained a `backup` field per client
|
|
340
|
+
and the human output prints `backup: <path>` under each written entry;
|
|
341
|
+
before this, a backup was created but you had to guess where it went.
|
|
342
|
+
- **A backup before every write**: `<file>.bak-dsh-<timestamp>`, one per run
|
|
343
|
+
per file, holding the exact pre-write bytes. No backup is left behind by a
|
|
344
|
+
preview.
|
|
345
|
+
- **Measured on seven real configs, not fixtures.** Registering added exactly
|
|
346
|
+
one `dsh-any-agent` entry per client and preserved every pre-existing line —
|
|
347
|
+
comments, CRLF endings, a 238-line Codex config, the 182693-byte
|
|
348
|
+
`~/.claude.json` — and a subsequent `--remove` restored all seven files
|
|
349
|
+
byte for byte.
|
|
350
|
+
- **The round trip was run on the real home, with backups.** `--yes` then
|
|
351
|
+
`--remove --yes` were executed against the developer machine's actual
|
|
352
|
+
configs after backing all seven up. Removal is verified as the strict inverse
|
|
353
|
+
of registration: taking our single entry out of the backup with the same
|
|
354
|
+
editing engine must reproduce the file then on disk, byte for byte. All seven
|
|
355
|
+
match. Other MCP servers were unchanged field by field (Cursor still has
|
|
356
|
+
stitch / obsidian-mcp-server / codegraph / rea), Zed still has all 32
|
|
357
|
+
top-level settings keys, and `~/.claude.json` still parses with its 67
|
|
358
|
+
top-level keys, 60 project entries and both case-only duplicate keys.
|
|
359
|
+
`codex mcp list` reads the file normally afterwards, so nothing needs hand
|
|
360
|
+
repair.
|
|
361
|
+
- **One deliberate change to an existing entry**: because the launch command is
|
|
362
|
+
derived from where setup runs, registering from this checkout replaced the
|
|
363
|
+
bundled `node/node.exe` in the pre-existing Cursor entry with the system
|
|
364
|
+
`node.exe`. That is the one entry we own; no other server was touched.
|
|
365
|
+
- **Preview mode is a hard guarantee, not a report.** Without `--yes` nothing
|
|
366
|
+
is written and no subprocess that writes is started. The Claude Code path
|
|
367
|
+
used to shell out to `claude mcp add`, which writes immediately — and, worse,
|
|
368
|
+
rewrites `~/.claude.json` in its own formatting with no backup we could
|
|
369
|
+
restore. It is now a text-level edit like every other client, and a preview
|
|
370
|
+
is a pure read: no CLI is invoked at all.
|
|
371
|
+
- **`claude mcp get` is used only as a read-only confirmation.** When the
|
|
372
|
+
`claude` CLI happens to be installed, a completed write is followed by one
|
|
373
|
+
`claude mcp get` probe and the summary says
|
|
374
|
+
`(confirmed by claude mcp get)`; if the client has not picked the entry up
|
|
375
|
+
yet the summary says so instead. The probe never writes.
|
|
376
|
+
- README (both languages): a per-client table of config file paths and exactly
|
|
377
|
+
what install / update / remove / undo touch, replacing the older "JSON is
|
|
378
|
+
re-serialised" caveat.
|
|
379
|
+
- `test/sandbox-home.mjs` + `test/sandbox-guard.mjs` (18 checks) — a test
|
|
380
|
+
sandbox can no longer point at real user data. The guard refuses the real
|
|
381
|
+
`home`, its subdirectories, case variants, the filesystem root, and any path
|
|
382
|
+
outside `os.tmpdir()`; `setup` and `removal-scope` now create and destroy
|
|
383
|
+
their HOME only through this module and fail if they go back to calling
|
|
384
|
+
`mkdtempSync`/`rmSync` directly.
|
|
385
|
+
- `test/removal-scope.mjs` — 24 checks proving unregistering touches only
|
|
386
|
+
its own entry. Given configs holding unrelated MCP servers, top-level Codex
|
|
387
|
+
settings (`model`, `approval_policy`, `sandbox_mode`) and Zed settings
|
|
388
|
+
(`theme`, `vim_mode`, a sibling `context_servers` child), it registers and
|
|
389
|
+
then removes, and asserts every other value is byte-identical afterwards.
|
|
390
|
+
Also covers removing something that was never there: no error, no write.
|
|
391
|
+
- README (both languages): a table for install / update / remove / undo, the
|
|
392
|
+
measured before-after of `--remove` per client, and an explicit note that
|
|
393
|
+
`--remove` does **not** uninstall the package.
|
|
394
|
+
- `dsh-any-agent-setup --help` now states that only one entry per client is
|
|
395
|
+
added and exactly that entry is removed, that other MCP servers are never
|
|
396
|
+
read or rewritten, and that the recorded path is absolute — moving or
|
|
397
|
+
deleting the installation leaves clients reporting `CONNECTION_CLOSED`
|
|
398
|
+
until setup is re-run from the new location.
|
|
399
|
+
|
|
400
|
+
### Fixed
|
|
401
|
+
|
|
402
|
+
- **Setup used to reformat a config it was only supposed to add one key to.**
|
|
403
|
+
The previous writer parsed each JSON client config and re-emitted it with
|
|
404
|
+
`JSON.stringify(..., null, 2)`, which silently normalised indentation, key
|
|
405
|
+
order, line endings and the trailing newline of every unrelated entry — the
|
|
406
|
+
values survived, the formatting did not. Writes now replace only the bytes
|
|
407
|
+
belonging to setup's own entry, and the invariant is asserted in
|
|
408
|
+
`test/config-safety.mjs` rather than described in prose.
|
|
409
|
+
- **A throwaway verification script deleted parts of the operator's real
|
|
410
|
+
home directory.** Not a bug in `setup` — a bug in the harness used to
|
|
411
|
+
verify it. The script kept its sandbox path in `$home`, which is a
|
|
412
|
+
read-only automatic variable in PowerShell; with
|
|
413
|
+
`$ErrorActionPreference = 'Continue'` the failed assignment was silent, so
|
|
414
|
+
`$env:USERPROFILE` still pointed at the intended sandbox name while the
|
|
415
|
+
cleanup line `Remove-Item $home -Recurse -Force` resolved to the real
|
|
416
|
+
profile. Three runs, each killed by a 120 s timeout mid-delete, removed part
|
|
417
|
+
of `~/.codex`, `~/.cursor` and `~/.claude` (the package's own config
|
|
418
|
+
entries survived; user data around them did not).
|
|
419
|
+
|
|
420
|
+
Evidence it was a sandbox-escape rather than a write: stray
|
|
421
|
+
`~/.cursor/mcp.json.bak-dsh-*` files contained the fixture the script had
|
|
422
|
+
written (`{"mcpServers":{"keep":...}}`), i.e. it was writing to the real
|
|
423
|
+
profile. `sandbox-guard` now makes that impossible to do silently.
|
|
424
|
+
|
|
425
|
+
### Tests
|
|
426
|
+
|
|
427
|
+
- **632 offline checks, all green on 0.3.0** — `guards` 31, `prune` 11,
|
|
428
|
+
`approval-details` 24, `sandbox-guard` 18, `setup` 41, `removal-scope`
|
|
429
|
+
24, `config-safety` 403, `real-configs` 66, `package-check` 14. Every
|
|
430
|
+
suite exited 0; none needs a model, a DSH install or a provider. **619 of
|
|
431
|
+
those assertions are in the client-registration suites** (`setup`,
|
|
432
|
+
`removal-scope`, `config-safety`, `real-configs`), which is where this
|
|
433
|
+
release's work actually is. The model-backed suites (462 checks) were last
|
|
434
|
+
measured in full for 0.1.2 and are re-run with `node test/run.mjs --all`.
|
|
435
|
+
- `test/real-configs.mjs` — **66 checks against real configs rather than
|
|
436
|
+
fixtures.** It copies this machine's actual client files into a temp
|
|
437
|
+
directory — the 182693-byte `~/.claude.json` with its hundreds of
|
|
438
|
+
`projects` entries and case-colliding keys, a 238-line Codex
|
|
439
|
+
`config.toml`, Zed's full `settings.json` — registers, then removes, and
|
|
440
|
+
requires every file back byte for byte. Where those files do not exist (CI),
|
|
441
|
+
the suite prints `skipped` explicitly instead of passing vacuously.
|
|
442
|
+
- `test/config-safety.mjs` — **403 checks**, the fixture matrix for the
|
|
443
|
+
property this release is about, per client and against a throwaway home: a
|
|
444
|
+
preview leaves every file byte-identical and creates no backup;
|
|
445
|
+
register-then-remove restores every other entry byte-for-byte and leaves no
|
|
446
|
+
top-level key behind; 4-space, 2-space and tab indentation, single-line
|
|
447
|
+
JSON, a missing trailing newline, CRLF endings and a UTF-8 BOM all survive a
|
|
448
|
+
write; malformed JSON and a read-only file are refused without damaging the
|
|
449
|
+
original, while a missing directory is created safely; the second `--yes`
|
|
450
|
+
is a no-op with no new backup; removing an entry that was never written
|
|
451
|
+
writes nothing and exits `0`; and every `.bak-dsh-<stamp>` appears once
|
|
452
|
+
and equals the pre-write bytes.
|
|
453
|
+
- `test/removal-scope.mjs` — 24 checks; `test/setup.mjs` — 41 checks,
|
|
454
|
+
including spawning the command written into a config and requiring a valid
|
|
455
|
+
`initialize` answer.
|
|
456
|
+
- **Verified against a real home, read-only.** Running `setup --json` (plan
|
|
457
|
+
mode) over an operator's actual profile left all seven existing client
|
|
458
|
+
configs — `~/.claude.json` (182693 B), `~/.cursor/mcp.json` (1396 B),
|
|
459
|
+
`~/.codex/config.toml` (6647 B), `~/.gemini/settings.json` (465 B),
|
|
460
|
+
`~/.config/opencode/opencode.json` (441 B), Zed's `settings.json`
|
|
461
|
+
(5647 B) and Claude Desktop's `claude_desktop_config.json` (247 B) — with
|
|
462
|
+
identical size, mtime and SHA-256, created no `.bak-dsh-*` file and created
|
|
463
|
+
no new config. All nine clients were reported. With `--home <tmp>` every
|
|
464
|
+
reported path moved inside that directory, `APPDATA` included.
|
|
465
|
+
|
|
466
|
+
## [0.1.2] — 2026-10-29
|
|
467
|
+
|
|
468
|
+
One-line fix, but it decides whether `dsh-any-agent-setup` can register
|
|
469
|
+
Claude Code on Windows at all.
|
|
470
|
+
|
|
471
|
+
### Fixed
|
|
472
|
+
|
|
473
|
+
- **Registration with Claude Code failed on any path containing a space.**
|
|
474
|
+
`process.execPath` is `C:\Program Files\nodejs\node.exe` on a default
|
|
475
|
+
Windows install, and setup passed it to the `claude` CLI through a shell
|
|
476
|
+
without quoting. cmd split the argument:
|
|
477
|
+
|
|
478
|
+
Command: C:\Program
|
|
479
|
+
Args: Files\nodejs\node.exe D:\...\bin\dsh-any-agent.mjs
|
|
480
|
+
|
|
481
|
+
Nothing reported an error. `claude mcp add` exited 0, and the JSON written
|
|
482
|
+
to the config was correct — the only symptom was `CONNECTION_CLOSED` when
|
|
483
|
+
Claude Code tried to start the server. Cursor, Zed and Codex were unaffected
|
|
484
|
+
because they receive a JSON/TOML value rather than a shell argument.
|
|
485
|
+
Arguments containing whitespace are now quoted.
|
|
486
|
+
|
|
487
|
+
### Added
|
|
488
|
+
|
|
489
|
+
- `test/setup.mjs` grew a ninth group that closes the gap which let the above
|
|
490
|
+
through: it takes the entry setup just wrote, spawns it exactly as a client
|
|
491
|
+
would, and requires a valid `initialize` response. Every earlier check
|
|
492
|
+
asserted file contents; none asked whether the command in that file runs.
|
|
493
|
+
35 → 41 checks.
|
|
494
|
+
|
|
495
|
+
### Documented
|
|
496
|
+
|
|
497
|
+
- **What setup does and does not touch**, with before/after counts measured on
|
|
498
|
+
a machine holding real MCP servers: Cursor 4→5 entries, Codex 2→3 sections,
|
|
499
|
+
Claude Code 5→6 servers, Zed 33→33 keys, every pre-existing entry unchanged.
|
|
500
|
+
Also states the one thing that does change: JSON configs are re-serialised,
|
|
501
|
+
so values are preserved but whitespace is not.
|
|
502
|
+
- **A release checklist**, including the two traps hit while shipping 0.1.0
|
|
503
|
+
and 0.1.1 — npm returning 202 while the registry still 404s for a minute,
|
|
504
|
+
and shields.io serving five-minute-stale badges (`license: package not
|
|
505
|
+
found` is its generic failure text, not a missing licence) — plus the
|
|
506
|
+
`npm deprecate` step for a broken published version and the fact that it
|
|
507
|
+
requires interactive OTP and therefore cannot run in CI.
|
|
508
|
+
|
|
509
|
+
## [0.1.1] — 2026-10-29
|
|
510
|
+
|
|
511
|
+
A correctness release. Two of the fixes matter if you are on Node 20 or were
|
|
512
|
+
relying on `dsh-any-agent-setup` to preview before writing.
|
|
513
|
+
|
|
514
|
+
### Fixed
|
|
515
|
+
|
|
516
|
+
- **`engines` was wrong: this needs Node ≥ 22.15, not ≥ 20.** `src/workspace.mjs`
|
|
517
|
+
imports `zstdDecompressSync` from `node:zlib` to read DSH session headers;
|
|
518
|
+
that API does not exist before 22.15, and a named import of a missing export
|
|
519
|
+
is a load-time error. On Node 20 the server exited with code 1 before it
|
|
520
|
+
could answer a single request, and `package.json` had been telling people
|
|
521
|
+
Node 20 was fine. Both `engines` and the README now say 22.15.
|
|
522
|
+
- **`dsh-any-agent-setup` wrote files during a dry run.** Every writer path
|
|
523
|
+
called the backup-and-write helper unconditionally and only *reported*
|
|
524
|
+
"would write" afterwards, so a preview run with no `--yes` modified the
|
|
525
|
+
client configs it found and left `.bak-dsh-*` files behind. The Claude Code
|
|
526
|
+
path shelled out to `claude mcp add` during the preview, which writes
|
|
527
|
+
immediately. All three paths now return a preview and stop before touching
|
|
528
|
+
disk, and a dry run performs no subprocess that can mutate state.
|
|
529
|
+
- **Claude Code registration broke on paths containing spaces.** On Windows
|
|
530
|
+
`process.execPath` is `C:\Program Files\nodejs\node.exe`, and the setup
|
|
531
|
+
command handed it to the `claude` CLI through a shell without quoting, so it
|
|
532
|
+
was split into `C:\Program` plus a stray `Files\nodejs\node.exe` argument.
|
|
533
|
+
The written config looked correct and Claude Code reported
|
|
534
|
+
`CONNECTION_CLOSED`. Every argument is now quoted when it contains a space.
|
|
535
|
+
|
|
536
|
+
### Added
|
|
537
|
+
|
|
538
|
+
- `dsh-any-agent --version` / `--help`, answered before the hub starts, so
|
|
539
|
+
asking for a version spawns no reaper and touches no registry. Same for
|
|
540
|
+
`dsh-any-agent-setup`.
|
|
541
|
+
- `test/setup.mjs` — 41 checks over the client-config writer, run against a
|
|
542
|
+
throwaway HOME: dry run is byte-identical, existing entries survive, writes
|
|
543
|
+
are idempotent, `--remove` takes out only its own entry, a corrupt config is
|
|
544
|
+
refused rather than rewritten, absent clients are skipped, the Codex TOML
|
|
545
|
+
block round-trips, and — added after the quoting bug — the command written
|
|
546
|
+
into the config is spawned for real and must answer `initialize`.
|
|
547
|
+
- `test/package-check.mjs` — catches what only a tarball reveals: a missing
|
|
548
|
+
`bin` field, a path without `./`, a missing shebang, `private:true`, test or
|
|
549
|
+
state paths in `files`, and an `engines` floor that contradicts what the
|
|
550
|
+
source actually imports.
|
|
551
|
+
- GitHub Actions: guards, prune, approval-details and setup across Node 22/24
|
|
552
|
+
on Linux and Windows, plus a packaging job that packs, installs into a
|
|
553
|
+
scratch project and asserts the tarball carries no state, tests or
|
|
554
|
+
credentials.
|
|
555
|
+
|
|
556
|
+
### Changed
|
|
557
|
+
|
|
558
|
+
- `SERVER_VERSION` now comes from `package.json` instead of a hard-coded copy
|
|
559
|
+
in `src/mcp.mjs`, so `npm version` cannot leave it stale.
|
|
560
|
+
|
|
561
|
+
## [0.1.0] — 2026-10-29
|
|
562
|
+
|
|
563
|
+
First public release, published to npm as [`dsh-any-agent`](https://www.npmjs.com/package/dsh-any-agent).
|
|
564
|
+
|
|
565
|
+
### Added
|
|
566
|
+
|
|
567
|
+
- **MCP server over stdio** that drives DeepSeek Harness (DSH) as a long-lived
|
|
568
|
+
background coding agent. Speaks only the four standard MCP methods
|
|
569
|
+
(`initialize`, `ping`, `tools/list`, `tools/call`), so any client that can
|
|
570
|
+
launch a stdio server works — no host-specific extensions.
|
|
571
|
+
- **11 `dsh_*` tools**: `dsh_start`, `dsh_send`, `dsh_list`, `dsh_read`,
|
|
572
|
+
`dsh_get`, `dsh_interject`, `dsh_interrupt`, `dsh_takeover`,
|
|
573
|
+
`dsh_release`, `dsh_approval_decide`, `dsh_status`.
|
|
574
|
+
- **Resume across processes.** Conversation identity is DSH's `sessionId` on
|
|
575
|
+
disk, not the process. A crash, an idle reap, or restarting this whole server
|
|
576
|
+
costs nothing: the next call transparently resumes the session, memory intact.
|
|
577
|
+
- **Asynchronous dispatch with sentinel files.** `dsh_send(wait=false)` returns
|
|
578
|
+
a `run_id` and a `sentinel_file` immediately; the turn writes that file
|
|
579
|
+
atomically when it finishes, so a background task in the host wakes the caller
|
|
580
|
+
instead of polling. Sentinels are namespaced per conversation, latched, and
|
|
581
|
+
carry the answer but never the reasoning.
|
|
582
|
+
- **Mid-turn steering.** `dsh_interject` redirects a running turn (cancel, then
|
|
583
|
+
immediately start a new one with history preserved); `dsh_interject(mode=queue)`
|
|
584
|
+
defers a remark until the current turn ends. `dsh_interrupt` stops a turn
|
|
585
|
+
without damaging the conversation.
|
|
586
|
+
- **Write-lock handling.** DSH conversations carry a cross-process write lock
|
|
587
|
+
that never expires while the holder lives and has no steal API. `dsh_status`
|
|
588
|
+
reports `lock_holder` (`self` / `stale-mcp` / `live-mcp` / `none`), and
|
|
589
|
+
`dsh_takeover` preempts only the cases that are provably safe — it never kills
|
|
590
|
+
a GUI, because GUI conversations live inside the single `dsh web` process.
|
|
591
|
+
- **Approval bridging** to the calling client, with credential-shaped fields
|
|
592
|
+
redacted from both the structured context and the displayed title.
|
|
593
|
+
- **First-start profile bootstrap.** On first use the server scaffolds
|
|
594
|
+
`~/.dsh/profiles/dsh-mcp/` from the shipped `acp` template and copies model
|
|
595
|
+
routes from an existing profile. Only route *declarations* are copied
|
|
596
|
+
(`apiKeyEnv`, `baseURL`, `api`, `models`, `reasoningEfforts`); `apiKey`
|
|
597
|
+
— a field the adapter never reads — and `headers` are stripped. No credential
|
|
598
|
+
is ever requested, stored or written by this project.
|
|
599
|
+
- **Reasoning hidden by default.** Only statistics (characters, duration, chunk
|
|
600
|
+
count) reach the caller; reasoning never reaches disk, with one deliberate
|
|
601
|
+
opt-out (`DSH_MCP_SENTINEL_INCLUDE_REASONING=1`).
|
|
602
|
+
- **Silent by default.** Not one byte on stderr, so nothing pollutes the host's
|
|
603
|
+
logs; stdout carries the protocol and nothing else.
|
|
604
|
+
- **Workspace maintenance CLI** (`dsh-any-agent-workspaces`): `--list`,
|
|
605
|
+
`--backfill`, `--prune-temp`, `--prune-empty`, `--purge-test-sessions`.
|
|
606
|
+
- Documentation: `README.md` / `README.zh.md`, `USAGE-NOTES.md` /
|
|
607
|
+
`USAGE-NOTES.en.md` (gotchas), `USAGE-MIGRATION.md` (new-environment setup),
|
|
608
|
+
`DESIGN.md` (protocol-level design notes).
|
|
609
|
+
|
|
610
|
+
### Verified
|
|
611
|
+
|
|
612
|
+
- **528 checks green** across 18 suites (492 in-suite + 36 acceptance), including
|
|
613
|
+
resume-with-memory across processes, sentinel atomicity under concurrency,
|
|
614
|
+
four lock-holder classifications, permission-tier enforcement read back from
|
|
615
|
+
the session's own record, and real model calls through a provider.
|
|
616
|
+
- **Zero runtime dependencies** — Node built-ins only
|
|
617
|
+
(`child_process`, `crypto`, `fs`, `module`, `os`, `path`, `url`, `zlib`).
|
|
618
|
+
- Installation from the published tarball: `npm install -g dsh-any-agent`
|
|
619
|
+
creates the `dsh-any-agent` / `.cmd` / `.ps1` shims, the server answers
|
|
620
|
+
`initialize` with `serverInfo: dsh-any-agent@0.1.0`, exposes all 11 tools, and
|
|
621
|
+
returns a real model reply with zero bytes on stderr.
|
|
622
|
+
- Documentation claims were re-measured before release rather than trusted.
|
|
623
|
+
Three figures did not survive: `dsh_send(wait=false)` was documented as
|
|
624
|
+
returning "in ~1 ms" (measured 81 ms), interruption as converging in
|
|
625
|
+
"~18 ms" (no test produced it; four samples of the `waited_ms` field gave
|
|
626
|
+
91/102/105/119 ms), and a conversation as costing "~120 MB"
|
|
627
|
+
(measured 295–476 MB, and self-contradictory in the original sentence).
|
|
628
|
+
The README's Tests section now states the rule: every number is either
|
|
629
|
+
printed by a suite or measured with a command the reader can re-run, and
|
|
630
|
+
measured values carry their method.
|
|
631
|
+
|
|
632
|
+
### Known limitations
|
|
633
|
+
|
|
634
|
+
- No token-level streaming into model context (an MCP limitation).
|
|
635
|
+
- True mid-turn steering is impossible — ACP rejects concurrent prompts, so
|
|
636
|
+
interjection is implemented as cancel-then-restart.
|
|
637
|
+
- Image prompts are unsupported (ACP advertises `promptCapabilities.image: false`).
|
|
638
|
+
- Restarting the MCP server takes its child processes with it: in-flight turns
|
|
639
|
+
are interrupted and no sentinel lands. The write lock *is* released, so a
|
|
640
|
+
restart always gets it back.
|
|
641
|
+
- A conversation that dies before its first successful turn may never have
|
|
642
|
+
materialized on disk; resume then fails with a clear error.
|
|
643
|
+
- No renaming — DSH's title subsystem has no external rename API.
|
|
644
|
+
- `session/list` returns only `{sessionId, cwd}` and excludes already-open
|
|
645
|
+
sessions; titles are filled in from DSH's projection cache.
|
|
646
|
+
|
|
647
|
+
### Notes
|
|
648
|
+
|
|
649
|
+
- The DSH profile is intentionally still named `dsh-mcp`, as are the
|
|
650
|
+
`DSH_MCP_*` environment variables and the `# dsh-mcp:managed` marker line.
|
|
651
|
+
These are persisted identifiers: renaming them would disconnect existing
|
|
652
|
+
profiles, sessions and client configs. The product name changed; the on-disk
|
|
653
|
+
contract did not. See the README FAQ.
|
|
654
|
+
|
|
655
|
+
[Unreleased]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.6...HEAD
|
|
656
|
+
[0.3.6]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.5...v0.3.6
|
|
657
|
+
[0.3.5]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.4...v0.3.5
|
|
658
|
+
[0.3.4]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.3...v0.3.4
|
|
659
|
+
[0.3.3]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.2...v0.3.3
|
|
660
|
+
[0.3.2]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.1...v0.3.2
|
|
661
|
+
[0.3.1]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.3.0...v0.3.1
|
|
662
|
+
[0.3.0]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.1.2...v0.3.0
|
|
663
|
+
[0.1.2]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.1.1...v0.1.2
|
|
664
|
+
[0.1.1]: https://github.com/xmtlzzz/dsh-any-agent/compare/v0.1.0...v0.1.1
|
|
665
|
+
[0.1.0]: https://github.com/xmtlzzz/dsh-any-agent/releases/tag/v0.1.0
|