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 CHANGED
@@ -7,7 +7,159 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- Post-0.3.9 hardening.
10
+ ## [0.5.0] — 2026-11-03
11
+
12
+ ### Process model
13
+
14
+ - **The shared ACP process is now the default.** Sessions with the same `(cwd, permission)`
15
+ land on one DSH process: a second session costs ≈300 ms instead of a ≈4 s cold start, and the
16
+ 300–480 MB is paid once per group instead of once per conversation.
17
+ `DSH_MCP_SHARED_PROCESS=0` restores one process per conversation (worth it when you need hard
18
+ isolation). Different workspaces or permission tiers still get separate processes —
19
+ `dsh-base` keeps the sandbox root and the approval policy process-wide.
20
+ - **`dsh_takeover` no longer kills a shared process blind.** When the PID it is about to kill
21
+ still carries other sessions, it refuses unless `force=true` and reports them as
22
+ `shared_sessions`; with `force` it says how many went down with it.
23
+ - **`dsh_doctor` de-duplicates RSS by PID** and reports the shared host count, so "live processes"
24
+ and "RSS total" stop multiplying by the number of sessions riding one process.
25
+ - Stale comments corrected: `DSH_MCP_IDLE_TTL_MS=0` is no longer the default (10 minutes since 0.4.0).
26
+
27
+ ### Team mode and defaults
28
+
29
+ - **Agent Teams is now opt-in.** `DSH_MCP_TEAM` defaults to off. Enable with
30
+ `dsh-any-agent team on` (writes `<DSH_HOME>/.dsh-any-agent/settings.json`),
31
+ disable with `team off`, inspect with `team status`; no client config editing.
32
+ When on, the profile switches to `dsh-team`, the legacy subagent tools are
33
+ disabled, and `dsh_send` adds the "form a team when needed" line.
34
+ - **The default session permission is now `workspace-write`** (was
35
+ `danger-full-access`). `permission=danger-full-access` additionally requires
36
+ `DSH_MCP_ALLOW_FULL_ACCESS=1` on the machine, so a bare `dsh_start` can no
37
+ longer hand the caller whole-machine write access.
38
+ - `dsh_doctor` has a dedicated `team` check: on / off / requested-but-unavailable.
39
+
40
+ ### Demo
41
+
42
+ - **`npm run demo`** — a five-minute, end-to-end case: create a conversation, dispatch a task with
43
+ `wait=false`, interject twice while it runs, collect the sentinel, then verify the artifacts on
44
+ disk (including `node --test`) instead of trusting the model's own summary. It runs against an
45
+ isolated registry / sentinel / lock directory, so it never touches your daily
46
+ `~/.dsh/.dsh-any-agent`. `--replay` walks a recorded transcript with no DSH and no tokens;
47
+ `--keep` keeps the workspace. Ships as `demo/` in the npm package.
48
+
49
+ ## [0.4.0] — 2026-11-03
50
+
51
+ ### Memory
52
+
53
+ - **Idle reaping is on by default.** `DSH_MCP_IDLE_TTL_MS` now defaults to
54
+ 10 minutes (was `0` = never). Idle sessions release their DSH process and
55
+ resume automatically on the next call; the write lock is released with it.
56
+ Set `0` to restore the old "hold the lock forever" behavior.
57
+ - **`DSH_MCP_MAX_LIVE` defaults to 4** (was 8), keeping the worst case around
58
+ 1.2–1.9 GB instead of 2.5–4 GB at the documented 300–480 MB/session.
59
+ - **`keep_alive` on `dsh_start`.** A session created with `keep_alive: true`
60
+ is exempt from idle TTL and `MAX_LIVE`; it keeps its DSH process (and write
61
+ lock) until `dsh_release`. Use it for "must stay attached" sessions.
62
+ - **Global RSS budget** via `DSH_MCP_MAX_RSS_MB` (default `0` = off). When the
63
+ sum of live child RSS exceeds the budget, the least-recently-used idle
64
+ sessions are reaped until it fits. `keep_alive` sessions are reaped last.
65
+ - **`dsh_list` / `dsh_status` report `rss_mb`** (and `keep_alive`), so the cost
66
+ is visible instead of guessed.
67
+ - **Optional child heap cap** via `DSH_MCP_CHILD_MAX_OLD_SPACE_MB` (default
68
+ `0` = off). It appends `--max-old-space-size=<n>` to the child's
69
+ `NODE_OPTIONS` without clobbering existing options.
70
+ - **Experimental shared ACP process** via `DSH_MCP_SHARED_PROCESS=1`. Sessions
71
+ with the same `(cwd, permission)` share one DSH process; ACP supports
72
+ concurrent sessions over one connection and DSH gives each session its own
73
+ Agent. Measured on DSH 0.1.7: two sessions → one PID, second `session/new`
74
+ took ~300 ms instead of ~4 s. Same `(cwd, permission)` is required because
75
+ `sandbox-policy.workspaceRoot`/`mode` and `approval.policy` are process-wide
76
+ in `dsh-base`. Trade-off: a process crash takes down the whole group; the
77
+ host writes error sentinels for every in-flight run and each session resumes
78
+ on the next call.
79
+
80
+ ### Dispatch surface
81
+
82
+ - **`dsh_inbox`** — a cross-conversation approval inbox. `dsh_status` remains
83
+ single-conversation; this tool lists every pending approval with
84
+ conversation id/title, approval id, tool, reason, input, options and age.
85
+ - **`client_run_id` idempotency** on `dsh_send` / `dsh_interject`: resubmitting
86
+ the same key returns the first run (`replayed=true`) instead of creating a
87
+ second one. The key is written into the sentinel and evicted together with
88
+ the run record.
89
+ - **Session queue** via `dsh_send(queue=true)` (explicit opt-in; the default
90
+ busy behavior stays "error") plus **`dsh_queue`** for list/remove/update/
91
+ promote. Sentinels are written only at terminal state; removing a queued
92
+ item writes a `cancelled` sentinel.
93
+ - **`dsh_get` text budget**: `max_chars` truncates only the rendered text
94
+ (structured content keeps the full result) and reports
95
+ `answer_truncated` / `next_offset`.
96
+ - **`dsh_doctor`** — read-only health check: DSH binary/version, profile
97
+ existence/drift, state file, `runs`/`locks` writability, orphan lock holders,
98
+ session/RSS totals, effective config.
99
+ - **Optional metadata audit** via `DSH_MCP_AUDIT_FILE`: JSONL records for
100
+ start/send/queue/interject/interrupt/approve/release/takeover. It records
101
+ IDs, decisions and counts only — never prompts, answers, thinking or tool
102
+ input. Rotates at 10 MB.
103
+ - **`resolveDshBin` prefers the newest cached DSH** instead of the first
104
+ directory it finds. This machine had both 0.1.5 and 0.1.7 cached; picking
105
+ 0.1.5 silently hid the experimental Agent Teams packages.
106
+ - **README positioning section**: the server is a dispatcher, not a capability
107
+ aggregator; DSH owns its tools/skills/teams, and dispatch is on demand.
108
+
109
+ ### Team mode
110
+
111
+ - **Agent Teams is on by default** (`DSH_MCP_TEAM=1`). The default profile is
112
+ now `dsh-team`, auto-created from the same routing source with
113
+ `@deepseek-ai/dsh-experimental-agent-team` +
114
+ `@deepseek-ai/dsh-experimental-tool-agent-team`; the legacy `subagent` tools
115
+ are disabled, matching the official team profile layer. Requires DSH
116
+ `>= 0.1.7-0` (the packages ship with 0.1.7-rc.1).
117
+ - `dsh_send` injects a short "form a team if this needs parallel or multi-role
118
+ work" line before the caller's prompt; simple tasks are not forced into a
119
+ team. `dsh_send(team=false)` disables it for one call; `DSH_MCP_TEAM=0`
120
+ returns to `dsh-mcp` and disables injection.
121
+ - `TEAM_ACTIVE` is false when the selected profile does not actually contain
122
+ the team package, so an explicit `DSH_MCP_PROFILE` pointing at a non-team
123
+ profile does not inject a tool the agent cannot use.
124
+ - **Switching**: `DSH_MCP_TEAM=0` returns to `dsh-mcp` and disables injection;
125
+ `dsh_send(team=false)` disables it for one call; `DSH_MCP_PROFILE` still
126
+ wins over the default profile. `dsh_start` / `dsh_doctor` report the
127
+ effective team mode and profile.
128
+
129
+ ### Correctness
130
+
131
+ - **A failed `ensureAlive` now has a terminal state.** It wakes every
132
+ `dsh_interject` waiter, writes an `error` sentinel, and attaches
133
+ `runId`/`status`/`sentinelFile` to the thrown error. Previously an interject
134
+ that arrived during a lock-conflict/cold-start window could wait forever
135
+ (`timeout_ms=0` has no timer).
136
+ - **`dsh_approval_decide` reports the effective outcome.** `decideApproval`
137
+ returns `effective: 'selected' | 'cancelled'` (plus `reason: 'no_option'`);
138
+ the tool text no longer claims "decided as allow" when ACP actually received
139
+ `cancelled`.
140
+ - **`dsh_get` can read a run back from its sentinel.** On a memory miss it
141
+ reads `RUNS_DIR/<conversation>/<run_id>.json`, returns
142
+ `run.source='sentinel'`, and always includes `sentinel_file` /
143
+ `sentinel_file_posix`.
144
+ - **Reap/respawn race fixed.** `stop()` is registered as `recycling` and
145
+ `proc` is cleared only after the child is confirmed gone, so a concurrent
146
+ `dsh_send` waits instead of spawning a new process against an unreleased
147
+ write lock. `lastChildPid` is updated only after a successful resume, so a
148
+ failed resume no longer loses the previous generation's takeover clue.
149
+ - **`timeout_ms` is validated.** Non-finite or negative values now raise
150
+ instead of silently becoming `NaN`/immediate background receipts.
151
+
152
+ ### Tests
153
+
154
+ - New `failure-terminal.mjs` (interject wake + error sentinel + approval
155
+ effective + sentinel read-back).
156
+ - New `shared-host.mjs` (session routing, exit fan-out, refcount teardown,
157
+ RSS helper).
158
+ - New `shared-process.mjs` (real-DSH shared process; skips without DSH).
159
+ - New `dispatch-tools.mjs` (`dsh_inbox`, `client_run_id` idempotency, session
160
+ queue operations, `dsh_get` text budget).
161
+ - `guards.mjs` and `smoke.mjs` updated for the new defaults and the
162
+ `keepAlive` metadata field.
11
163
 
12
164
  ## [0.3.9] — 2026-11-02
13
165