dsh-as-mcp 0.1.5 → 0.52.84
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 +1974 -0
- package/CHANGELOG.zh.md +949 -0
- package/README.md +493 -54
- package/README.zh.md +412 -49
- package/cordis.patch.yml +6 -0
- package/lib/client.js +2 -2
- package/lib/client.js.map +3 -3
- package/lib/index.d.ts +1472 -22
- package/lib/index.js +4344 -465
- package/lib/index.js.map +1 -1
- package/package.json +4 -2
- package/scripts/exercise.mjs +1026 -5
- package/scripts/smoke.mjs +58 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,1974 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `dsh-as-mcp`, newest first. Versions follow SemVer; the
|
|
4
|
+
plugin is pre-1.0, so a minor bump is a feature and a patch is a fix or docs.
|
|
5
|
+
|
|
6
|
+
A Chinese summary of every release lives in [CHANGELOG.zh.md](./CHANGELOG.zh.md);
|
|
7
|
+
this file stays the authoritative record.
|
|
8
|
+
|
|
9
|
+
## 0.52.84
|
|
10
|
+
|
|
11
|
+
- The GitHub-install instructions now lead with the approval that keeps working. pnpm matches a
|
|
12
|
+
git dependency's build by `<name>@<git url>` with no `#<ref>`, so one `allowBuilds` entry covers
|
|
13
|
+
every later commit; the previous text led with the commit-pinned key pnpm prints
|
|
14
|
+
(`…/tar.gz/<sha>: true`), which stops matching on the next push and forced a fresh manual
|
|
15
|
+
approval on every update. The section also states the correct floor for the two commands it
|
|
16
|
+
documents — `github:…` and a release `.tgz` are both fetched as tarballs, so the repository form
|
|
17
|
+
needs pnpm ≥11.19.0 (11.11.0 only covers cloned git dependencies), and pnpm 10 names the same
|
|
18
|
+
decision `onlyBuiltDependencies` — and points at the channels that need no build approval: `npm`
|
|
19
|
+
and the prebuilt Release asset, neither of which runs `prepare`.
|
|
20
|
+
- `tools/check-docs.mjs` now fails when either README's git-install guidance drifts from the
|
|
21
|
+
package's own identity. While `package.json` keeps a `prepare` script and `lib/` stays
|
|
22
|
+
gitignored, both languages must carry the manifest-derived key
|
|
23
|
+
(`dsh-as-mcp@git+https://github.com/xiseliuli/dsh-as-mcp.git`) and name
|
|
24
|
+
`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`. A package or repository rename, or making the build
|
|
25
|
+
unnecessary, therefore breaks the release check instead of silently leaving readers a key that
|
|
26
|
+
matches nothing. Issue #1 is why this exists: a Plugin Hub 1.6.5 install fell back to the git
|
|
27
|
+
channel after its own npm lookup missed this package, pnpm's build gate refused the `prepare`
|
|
28
|
+
run, and DSH's generic hint left the reporter to copy a key by hand.
|
|
29
|
+
- Install guidance is the only change: the built `lib/` and every tool behave as in 0.52.83, apart
|
|
30
|
+
from the version the MCP `initialize` result advertises.
|
|
31
|
+
|
|
32
|
+
## 0.52.83
|
|
33
|
+
|
|
34
|
+
- A first-time MCP client now learns the reporting contract where it actually looks. The `initialize`
|
|
35
|
+
instructions, the `session_prompt` description and its `wait` option all name the fire-and-forget
|
|
36
|
+
handoff (`wait: false` → follow with `session_status`, whose `waitMs` reports the first change) and
|
|
37
|
+
the failure channel (`turn.failure` = `{kind, message}`; a completed turn carries no failure block).
|
|
38
|
+
`session_prompt` advertises an output schema for the first time, including `turn.failure`, and
|
|
39
|
+
`session_status` now advertises its whole report (`idleMs`, `stalled`, `failure`, `lastTurn`,
|
|
40
|
+
`changed`, `waitedMs`, …) instead of only `sessionId`/`running` — a schema-aware client can see the
|
|
41
|
+
contract without guessing. Many MCP hosts do not forward `initialize.instructions` to their model,
|
|
42
|
+
so the tool surface is the only onboarding text that can be relied on; the wording is pinned by a
|
|
43
|
+
test rather than left to habit.
|
|
44
|
+
- `tools/live-checks.mjs` gained an onboarding section: a real endpoint must carry non-empty
|
|
45
|
+
instructions naming the follow-up, and both tools must keep those sentences and schema fields.
|
|
46
|
+
That makes a wording edit which drops the contract fail a check instead of silently making the
|
|
47
|
+
feature undiscoverable. Measured on 0.52.83, one boot with the search index open: live-checks
|
|
48
|
+
**195/195** (`--with-model --with-subagent`, the six onboarding checks among them) and acceptance
|
|
49
|
+
**166/166**.
|
|
50
|
+
|
|
51
|
+
## 0.52.82
|
|
52
|
+
|
|
53
|
+
- The changelog now has a Chinese summary for every release: `CHANGELOG.zh.md` mirrors the version
|
|
54
|
+
headings of this file one-to-one, with 1–3 Chinese bullets each. `tools/check-docs.mjs` fails when
|
|
55
|
+
the two files disagree on the version set or order, or when a version carries no Chinese bullet, so
|
|
56
|
+
a release that forgets its Chinese entry cannot ship silently. The Chinese file is a summary, not a
|
|
57
|
+
translation — implementation detail, measurements and design rationale stay here, in the
|
|
58
|
+
authoritative record. It is linked from both files and ships in the npm package (`files`).
|
|
59
|
+
- The failure-reporting evidence gap from 0.52.81 is closed by fault injection against a throwaway
|
|
60
|
+
profile. A forced credential failure ended a real turn with `turnEndReason`/`turn.failure`
|
|
61
|
+
`{kind: "error"}` (AUTH 401, empty reply, settled in 478 ms instead of the 120 s timeout); the same
|
|
62
|
+
was observed when the session log was renamed away mid-turn (ENOENT, `error`, 13.8 s); `kill -9`
|
|
63
|
+
of the harness mid-turn produced, on the next cold read, `session_status.lastTurn.reason`
|
|
64
|
+
`{kind: "interrupted"}` and `failure {kind: "interrupted"}`; and `stalled` flipped to true on a
|
|
65
|
+
real running turn after 62.9 s of log silence. Two wait-side detections remain unit-tested only:
|
|
66
|
+
`unreadable` (the harness closes a log-less turn with `error` before the wait can read it) and
|
|
67
|
+
`vanished` (a crash is synthesised as `interrupted` on the cold read). `docs/LIVE-VERIFICATION.md`
|
|
68
|
+
now carries the verified rows and a narrowed statement of what is still not observed live; the raw
|
|
69
|
+
run (setup, replies, the credential-precedence hazard) is recorded in
|
|
70
|
+
`docs/FAULT-INJECTION-2026-11-03.md`.
|
|
71
|
+
|
|
72
|
+
## 0.52.81
|
|
73
|
+
|
|
74
|
+
- New `session_status` reports one session's live state: whether a turn is open right now, its turn
|
|
75
|
+
and step, the newest durable event (`lastEventSeq`/`lastEventAt`), how long the log has been quiet
|
|
76
|
+
(`idleMs`), `stalled` once a running turn has been silent for a minute, how the last turn ended,
|
|
77
|
+
and a normalized `failure` when it ended abnormally. Work started with `session_prompt { wait:
|
|
78
|
+
false }` previously had no reporting channel at all — a caller could only poll `session_list` /
|
|
79
|
+
`session_messages` and could not tell a finished task from a dead one. `waitMs` turns the call
|
|
80
|
+
into a long-poll: it returns as soon as anything changes or the bound elapses, so a supervisor is
|
|
81
|
+
told once per change instead of on a guessed interval. Read-only, one durable-log read.
|
|
82
|
+
- `session_prompt` now says when a turn failed instead of returning it as an ordinary reply.
|
|
83
|
+
`turn.failure` carries `{kind, message}` for the harness's own failure endings (`error`,
|
|
84
|
+
`blocked`, `max-tokens`, `interrupted`) and for an ending this build does not recognize; it is
|
|
85
|
+
absent for `completed`, for a caller-requested `aborted`, and for a fork boundary. It also reports
|
|
86
|
+
two conditions the wait detects itself: `unreadable` — the session log stopped being readable
|
|
87
|
+
mid-wait, which used to surface as a bare ENOENT only after the caller's timeout — and `vanished`
|
|
88
|
+
— the turn stopped without a `turn/end` while its agent went idle, which used to sit out the whole
|
|
89
|
+
timeout and report `timedOut: true`, i.e. "still running". `tests/driver.test.ts` covers the
|
|
90
|
+
ending normalization, the error reply, the vanished turn and the unreadable log.
|
|
91
|
+
- The wait's progress notification now separates the two numbers a heartbeat needs: `turn 3: 2 tool
|
|
92
|
+
call(s), 41s elapsed, log quiet for 12s`. A long turn and a dead one used to look identical.
|
|
93
|
+
- `session_status` is covered in the live sweep (a `wait: false` prompt followed by a held status
|
|
94
|
+
call, then the settled ending and the absence of a failure block) and the forwarding test; both
|
|
95
|
+
README tables, the capability audit's tool count and `docs/LIVE-VERIFICATION.md` are updated.
|
|
96
|
+
Measured on 0.52.81, one boot with the search index open: live-checks **189/189**
|
|
97
|
+
(`--with-model --with-subagent`) and acceptance **166/166**; the status section's held call
|
|
98
|
+
reported `changed: true` while the turn ran and `idleMs` 3 ms after the newest event.
|
|
99
|
+
|
|
100
|
+
## 0.52.80
|
|
101
|
+
|
|
102
|
+
- A cancellation that lands in the window *after* the harness opened the turn but *before* the
|
|
103
|
+
prompt was committed no longer leaves the waiting `session_prompt` running to its timeout. The
|
|
104
|
+
durable log holds no `user/message` for that request (the turn was aborted with the message still
|
|
105
|
+
in the inbox), so `locateTurn` had nothing to anchor on and the wait reported `timedOut: true` on
|
|
106
|
+
a turn the caller had already cancelled. The driver now keeps a per-session cancellation
|
|
107
|
+
generation: `cancelSession` bumps it, and the wait settles with `turn.aborted: true` as soon as
|
|
108
|
+
it sees the bump. `cancelSession` is what both `session_cancel` and an MCP
|
|
109
|
+
`notifications/cancelled` call, so both paths are covered. Measured: a 30 s `timeoutMs` used to
|
|
110
|
+
return at 29 995 ms with `aborted: false`; after the fix the same cancellation settled 18 ms after
|
|
111
|
+
the notification in the full sweep. `tests/driver.test.ts` pins the pre-commit window (a stub
|
|
112
|
+
whose admission commits nothing).
|
|
113
|
+
- The full live sweep now runs with the session-search index open and
|
|
114
|
+
`DSH_PERMISSION_MODE=danger-full-access` (`tools/sweep-local.sh`), so the checks meet the shape a
|
|
115
|
+
deployment actually opts into instead of the stock `openAt: "never"` one. Two findings from
|
|
116
|
+
building it, both recorded in the script: a headless throwaway profile leaves the stock approval
|
|
117
|
+
policy `ask`, parking a turn on a question nobody answers (the cancellation check then has
|
|
118
|
+
nothing to stop — it sat at the 300 s prompt timeout with `turn.aborted: false`); and the
|
|
119
|
+
cancellation window above, which the earlier profiles happened not to hit. Measured after both
|
|
120
|
+
fixes, one boot with the search index open: live-checks **180/180** (`--with-model
|
|
121
|
+
--with-subagent`) and acceptance **166/166**.
|
|
122
|
+
- The caller-set truncation caps are asserted with receipts: one real turn with
|
|
123
|
+
`toolCalls: "full"`, `maxArgumentChars: 200` and `maxReplyChars: 40` must return a tool call
|
|
124
|
+
with `truncated: true` and its original `argumentsBytes` beside the cut arguments, and a reply
|
|
125
|
+
with `replyTruncated: true` whose `replyChars` is the original length. "The parameter was
|
|
126
|
+
accepted" would pass a silent cut. Measured in the sweep: 464 bytes → 200 chars and 299 → 40
|
|
127
|
+
characters, each with its original length beside it.
|
|
128
|
+
- New maintainer tool `tools/clean-probe-sessions.mjs` (not shipped in the package): the checks
|
|
129
|
+
create real sessions, and a long-lived checkout accumulates thousands of them in
|
|
130
|
+
`$DSH_HOME/sessions`. It removes only sessions it has *positive evidence* the checks created —
|
|
131
|
+
a throwaway workspace key, or a log whose every prompt carries the plugin's own `rpcId`
|
|
132
|
+
(`dsh-as-mcp-…`) — and never a session with any human prompt, whichever key it is in and
|
|
133
|
+
whatever its transcript says; a `user/message` without an `rpcId` counts as human (subagent
|
|
134
|
+
prompts look like that, so check subagents are left as noise rather than guessed at). It also
|
|
135
|
+
never touches a session written to in the last 10 minutes, and never changes anything on a dry
|
|
136
|
+
run. `--yes` moves removals to `$DSH_HOME/sessions-trash/` instead of deleting them; `--purge` is
|
|
137
|
+
the only permanent path. The first draft of this tool matched check-prompt *text* anywhere in a
|
|
138
|
+
log, and on 2026-11-03 that deleted two real developer sessions (one while the app had it open)
|
|
139
|
+
because their transcripts quoted the checks' own sources;
|
|
140
|
+
`docs/INCIDENT-2026-11-03-session-log-deletion.md` records the incident, what survived, and the
|
|
141
|
+
rule this entry states. `tests/clean-probe-sessions.test.ts` pins the behaviour with 15
|
|
142
|
+
synthetic-store cases, including a human session whose transcript contains every marker and a
|
|
143
|
+
symlinked workspace key that must not be followed.
|
|
144
|
+
|
|
145
|
+
## 0.52.79
|
|
146
|
+
|
|
147
|
+
- Two more options are asserted by effect: `session_list` folds titles only when `includeTitle` is
|
|
148
|
+
set (checked when the session has a generated title), and `session_usage { sinceSeq }` returns a
|
|
149
|
+
windowed fold — fewer turns and fewer tokens than the whole log — rather than ignoring the
|
|
150
|
+
parameter. Both reuse the turns the real-turn section already paid for. Measured: full
|
|
151
|
+
163/163, session-only 95/95 (the includeTitle check ran on a real generated title); README
|
|
152
|
+
tables and the matrix updated.
|
|
153
|
+
|
|
154
|
+
## 0.52.78
|
|
155
|
+
|
|
156
|
+
- Two more options are asserted by their effect rather than their acceptance:
|
|
157
|
+
`session_list { running: false }` must list an idle session that `running: true` excludes, and
|
|
158
|
+
`session_usage` with `includeTurns: false` / `includeCost: false` must omit the `byTurn` and
|
|
159
|
+
`byTurn` empty and cost unavailable. A filter that is accepted but ignored would pass a
|
|
160
|
+
one-directional test.
|
|
161
|
+
- The first version of the omission assertion expected the fields to be *absent*; the contract
|
|
162
|
+
is an empty `byTurn` array and `cost.available: false`, so the shape is asserted instead. The
|
|
163
|
+
failure was my assertion, not the code. Measured after the fix: full 161/161, session-only
|
|
164
|
+
93/93, cheap subset 110/47/64/68; README tables updated.
|
|
165
|
+
|
|
166
|
+
## 0.52.77
|
|
167
|
+
|
|
168
|
+
- `session_usage`'s `includeSubagents` is now asserted both ways: with it (the default), the
|
|
169
|
+
subagent is a row and the report total includes its spend; with an explicit
|
|
170
|
+
`includeSubagents: false`, no subagent row appears and the total is the session alone
|
|
171
|
+
(measured 55499 vs 36808 tokens). The option was exercised one way only, so "accepted but
|
|
172
|
+
ignored" would have passed.
|
|
173
|
+
- The first version of this check assumed the default was `false` and failed live — the code and
|
|
174
|
+
both READMEs already said `true`. The assertion now disables the default explicitly; the
|
|
175
|
+
mistake is recorded here because a doc-vs-code disagreement is exactly what this check exists
|
|
176
|
+
to catch, even when the code was right and the check was wrong.
|
|
177
|
+
|
|
178
|
+
## 0.52.76
|
|
179
|
+
|
|
180
|
+
- Documented a distinction found while probing what happens to a message queued behind a
|
|
181
|
+
cancelled turn: this endpoint runs **one turn at a time per session** (`enqueueTurn`), so a
|
|
182
|
+
second prompt from the same connection waits behind the running turn instead of entering the
|
|
183
|
+
harness inbox — measured at 21.9 s for a 20 s turn. Queuing *behind* a running turn needs a
|
|
184
|
+
second MCP client (and even then the queue view did not confirm the message while a waiting
|
|
185
|
+
request was in flight), which is now stated in the `session_prompt` row of both READMEs; the
|
|
186
|
+
probe's cancellation assertion was removed rather than kept as a check that depends on that
|
|
187
|
+
in-flight timing.
|
|
188
|
+
|
|
189
|
+
## 0.52.75
|
|
190
|
+
|
|
191
|
+
- After a cancelled wait returns, the check now also requires the turn to actually stop: the
|
|
192
|
+
session must leave `session_list { running: true }` (polled up to 20 seconds). The wait ending
|
|
193
|
+
and the turn stopping are two different facts, and only the first was asserted before.
|
|
194
|
+
Measured: full 159/159, session-only 91/91; READMEs and the matrix carry the numbers.
|
|
195
|
+
|
|
196
|
+
## 0.52.74
|
|
197
|
+
|
|
198
|
+
- The checks that must act while a turn is open (cancellation, steering into the running turn,
|
|
199
|
+
the mid-turn model switch) no longer sleep 1.5-3 seconds hoping the turn has started: they
|
|
200
|
+
poll the session listing's `running` filter (up to 20 seconds) and proceed the moment the turn
|
|
201
|
+
appears. Same flake discipline as the title check, applied to the remaining fixed waits.
|
|
202
|
+
The first attempt polled the wrong session variable in the prompt-steer block (the check then
|
|
203
|
+
saw 3 turns instead of 2); the assertion caught it immediately and the wait is now on
|
|
204
|
+
`steerSession`, with the full shape green at 158/158.
|
|
205
|
+
|
|
206
|
+
## 0.52.73
|
|
207
|
+
|
|
208
|
+
- The title-visibility check no longer sleeps a fixed 4 seconds waiting for the harness to write
|
|
209
|
+
its title request; it polls the session log for up to 20 seconds and stops as soon as the event
|
|
210
|
+
appears (or the log is missing). A fixed guess is how a check measures the machine and flakes
|
|
211
|
+
on a slow run — the same discipline the rest of the suite follows. Full shape with
|
|
212
|
+
`--with-model` re-run after the change: 158/158.
|
|
213
|
+
|
|
214
|
+
## 0.52.72
|
|
215
|
+
|
|
216
|
+
- Hybrid deployments now also check admission on the second entry: a request without a bearer is
|
|
217
|
+
refused with `401` + `WWW-Authenticate: Bearer`, and a wrong bearer is refused too. The two
|
|
218
|
+
entries share a token file, but that is configuration — the check is what the route does.
|
|
219
|
+
Hybrid shape measured at 116/116; both READMEs say so.
|
|
220
|
+
|
|
221
|
+
## 0.52.71
|
|
222
|
+
|
|
223
|
+
- The hybrid cross-entry section now also checks state: a session created through the plugin
|
|
224
|
+
listener is listed through the web-server entry, and a rename made through that entry is what
|
|
225
|
+
the listener reports. Same host, two doors — a client handed the wrong URL must not see a
|
|
226
|
+
different view of the same session. Hybrid shape measured at 114/114 (109 + 5 cross-entry
|
|
227
|
+
checks); both READMEs say so.
|
|
228
|
+
|
|
229
|
+
## 0.52.70
|
|
230
|
+
|
|
231
|
+
- Added a `hybrid` shape (listener **and** `mountOnWebServer`) and a live cross-entry check: a
|
|
232
|
+
second MCP client going in through DSH's web server must advertise the same tools, report the
|
|
233
|
+
same build and listener state, and see the same registered workspaces as the plugin's own
|
|
234
|
+
listener. A second entry that disagreed would be a deployment that half-works depending on
|
|
235
|
+
which URL a client was handed. Measured: hybrid 112/112 (109 + 3 cross-entry), mountonly
|
|
236
|
+
109/109. Building the shape surfaced a bug in my own patch template: the hybrid profile rendered
|
|
237
|
+
`mountOnWebServer: false` (the flag only switched on `mountedOnly`), so the second entry was the
|
|
238
|
+
SPA fallback — the new cross-entry check caught exactly the half-working deployment it exists to
|
|
239
|
+
catch, and the template is fixed.
|
|
240
|
+
|
|
241
|
+
## 0.52.69
|
|
242
|
+
|
|
243
|
+
- `tools/shapes.mjs` gained a `mountonly` shape: `http.enabled: false` +
|
|
244
|
+
`mountOnWebServer: true`, served on DSH's web server, checked over that route. live-checks
|
|
245
|
+
understands `--mounted-only` and then expects `listening: false`,
|
|
246
|
+
`mountedOnDshWebServer: true` and an endpoint string that describes where it lives instead of
|
|
247
|
+
echoing a URL. The mounted-only deployment previously had only a hand probe per release.
|
|
248
|
+
Measured on this build with `--with-model`: 158 full, 90 session, 63 narrow, 68 files+shell,
|
|
249
|
+
109 mounted-only; cheap subset 109/46/63/68. Both README tables updated.
|
|
250
|
+
|
|
251
|
+
## 0.52.68
|
|
252
|
+
|
|
253
|
+
- `dsh_info`'s endpoint/listening pair is now asserted as a relation in every live run: the
|
|
254
|
+
`endpoint` string is a dialable URL exactly when `listening` is true, and explicitly not a
|
|
255
|
+
URL when the endpoint is served elsewhere (the mounted-only explanation). The two fields were
|
|
256
|
+
recorded separately before, so a non-URL endpoint next to `listening: true` would have passed.
|
|
257
|
+
Live-verified the mounted-only configuration: `initialize` works on DSH's web server, the
|
|
258
|
+
plugin port has no listener, and `dsh_info` answers `listening: false`,
|
|
259
|
+
`mountedOnDshWebServer: true`, endpoint `/mcp (served by the DSH web server; ...)`. (A first
|
|
260
|
+
probe read a field name that does not exist — `mountedOnWebServer` instead of
|
|
261
|
+
`mountedOnDshWebServer` — and I re-ran it before recording anything.)
|
|
262
|
+
|
|
263
|
+
## 0.52.67
|
|
264
|
+
|
|
265
|
+
- Live-verified the `port: 0` configuration end to end: the plugin bound an OS-assigned port
|
|
266
|
+
(`61532`) and `dsh_info` reported `endpoint: "http://127.0.0.1:61532/mcp", listening: true`
|
|
267
|
+
— the port the OS chose, not `0`. The panel prefers that live URL since 0.52.66, so both
|
|
268
|
+
halves of the same problem are now covered. (Two earlier probe attempts failed on my own
|
|
269
|
+
script, not the plugin: an unbalanced parenthesis and a port discovered from the wrong
|
|
270
|
+
process.)
|
|
271
|
+
|
|
272
|
+
## 0.52.66
|
|
273
|
+
|
|
274
|
+
- The panel's client snippet now uses the endpoint a client can actually dial:
|
|
275
|
+
- the live `status.url` when the listener is up (so a `port: 0` configuration shows the
|
|
276
|
+
port the OS assigned, not `0`);
|
|
277
|
+
- `<this page's origin><path>` when the plugin listener is disabled and the endpoint is
|
|
278
|
+
served by DSH's own web server — the mounted-only configuration used to print the
|
|
279
|
+
plugin's unbound port;
|
|
280
|
+
- the configuration mirror last, which is the only source while the panel loads.
|
|
281
|
+
A non-URL status string (the mounted-only explanation) is never used as an endpoint. The
|
|
282
|
+
choice lives in `dialableEndpoint` with unit tests for each branch.
|
|
283
|
+
|
|
284
|
+
## 0.52.65
|
|
285
|
+
|
|
286
|
+
- A panel on a host that mounts no status route (or whose fetch fails) used to show no health
|
|
287
|
+
badge and no reason. It now says the endpoint status cannot be read here — health stays
|
|
288
|
+
hidden, which is the honest state, but silently missing looked like an unfinished panel.
|
|
289
|
+
|
|
290
|
+
## 0.52.64
|
|
291
|
+
|
|
292
|
+
- The panel's client configuration no longer appears while its configuration is half-read: it
|
|
293
|
+
used to render `http://127.0.0.1:` (port and path still missing) as if it were copyable. The
|
|
294
|
+
URL now comes from `endpointUrl`, which returns `undefined` until the port and path are both
|
|
295
|
+
known and rewrites a wildcard bind to a dialable host; the pane stays hidden until then.
|
|
296
|
+
Unit tests cover the half-read cases and the wildcard rewrite.
|
|
297
|
+
|
|
298
|
+
## 0.52.63
|
|
299
|
+
|
|
300
|
+
- Two views of one background job are now checked against each other: a row in
|
|
301
|
+
`shell_list { running: true }` must report `done: false` from `shell_output`, and after
|
|
302
|
+
`shell_kill` that same call must report `done: true`. The two filters were each trusted on
|
|
303
|
+
their own before.
|
|
304
|
+
|
|
305
|
+
## 0.52.62
|
|
306
|
+
|
|
307
|
+
- Found a real ambiguity while adding a cross-view check: `workspace_list`'s `sessionCount` is
|
|
308
|
+
the registry's **attach list** (sessions created with `workspaceId`), while a session created
|
|
309
|
+
by `cwd` alone is not attached — so the row can read 0 while `session_list { cwd }` shows a
|
|
310
|
+
session. The docs now say exactly that, and the live check asserts both halves: a session
|
|
311
|
+
created through the registration moves the count, one created by cwd alone does not.
|
|
312
|
+
- The first version of the check asserted the two tallies were equal, which failed live
|
|
313
|
+
("workspace 0 vs listing 1") and exposed the ambiguity instead of endorsing a wrong reading.
|
|
314
|
+
|
|
315
|
+
## 0.52.61
|
|
316
|
+
|
|
317
|
+
- `workspace_list`'s `sessionCount` is now checked against a session the endpoint actually
|
|
318
|
+
creates in that directory: it must move by one. The listing check alone only proved the field
|
|
319
|
+
was a number; this proves it is the harness's own tally for the right workspace.
|
|
320
|
+
|
|
321
|
+
## 0.52.60
|
|
322
|
+
|
|
323
|
+
- The configuration-sample check now covers **both** READMEs: the Chinese sample was compared
|
|
324
|
+
by nothing, so a value could drift there while the English one stayed honest. Proven by
|
|
325
|
+
changing the zh sample's `maxReadBytes` to 1048577 —
|
|
326
|
+
`DRIFT: README.zh.md sample says limits.maxReadBytes: 1048577, the default is "1048576"` —
|
|
327
|
+
then restoring.
|
|
328
|
+
|
|
329
|
+
## 0.52.59
|
|
330
|
+
|
|
331
|
+
- The README yaml configuration sample is now checked against `src/defaults.ts`: every scalar
|
|
332
|
+
(http host/port/path/toggles, all six tool toggles, session fields, limits, approval policy)
|
|
333
|
+
must equal the shipped default, because a profile patch **replaces** the row's config and a
|
|
334
|
+
sample that drifted would silently change behaviour for whoever copied it.
|
|
335
|
+
`agentTools.allow/deny: []` stay documented as "empty means the built-in allow-list" and are
|
|
336
|
+
asserted to be empty rather than compared with the default array.
|
|
337
|
+
- Proven by changing the sample's `promptTimeoutMs` to 900001 —
|
|
338
|
+
`DRIFT: README sample says session.promptTimeoutMs: 900001, the default is "900000"` — then
|
|
339
|
+
restoring. Building the check surfaced two of its own bugs (a `''` empty-string mismatch and
|
|
340
|
+
an inline `DEFAULT_APPROVAL` object it could not parse), both fixed.
|
|
341
|
+
|
|
342
|
+
## 0.52.58
|
|
343
|
+
|
|
344
|
+
- The drift check now compares five caps stated in README prose with the constants that
|
|
345
|
+
enforce them: image size (`MAX_IMAGE_BYTES`, 8 MiB), request body (`MAX_REQUEST_BYTES`,
|
|
346
|
+
64 MiB), prompt images (`MAX_PROMPT_IMAGES`, 4), background jobs (`MAX_SHELL_JOBS`, 16) and
|
|
347
|
+
remembered owned ids (`MAX_OWNED_SESSIONS`, 1000). Proven by editing "8 MiB" to "9 MiB" —
|
|
348
|
+
`DRIFT: README.md claims 9 for the MiB image cap, the code says 8` — then restoring. Both
|
|
349
|
+
READMEs list the check.
|
|
350
|
+
|
|
351
|
+
## 0.52.57
|
|
352
|
+
|
|
353
|
+
- `docs/CAPABILITY-GAPS.md` opened by claiming "25 tools" for several releases while the code
|
|
354
|
+
had 28. The page now says 28, and the drift check compares that opening sentence with the
|
|
355
|
+
registered tools — the appendix count was already checked, the introduction was not.
|
|
356
|
+
|
|
357
|
+
## 0.52.56
|
|
358
|
+
|
|
359
|
+
- A live turn now asserts that the usage fold agrees with the harness's own token projection:
|
|
360
|
+
`verifiedAgainstProjection === true` and no `projectionTotals` (that field appears only when
|
|
361
|
+
they disagree). Two independent implementations agreeing is the check unit tests cannot
|
|
362
|
+
provide — a stub projection just repeats whatever the test built. Verified live: full shape
|
|
363
|
+
153/153, session-only 89/89; READMEs and the matrix carry the numbers.
|
|
364
|
+
|
|
365
|
+
## 0.52.55
|
|
366
|
+
|
|
367
|
+
- Verified the documented blind spot: the session title is generated by an LLM call that
|
|
368
|
+
leaves no usage record. The live check reads the session's own durable log through
|
|
369
|
+
`shell_run` — it finds `session/title-llm-request` **with a route** and no usage-bearing
|
|
370
|
+
event for it, and the receipt's `samples` equals the number of `assistant/message` +
|
|
371
|
+
`assistant/attempt` events in the log. If the title call ever started writing usage, that
|
|
372
|
+
equality would break. Full shape after the addition: 152/152; the matrix records the
|
|
373
|
+
measurement and both READMEs carry the new count.
|
|
374
|
+
|
|
375
|
+
## 0.52.54
|
|
376
|
+
|
|
377
|
+
- The acceptance run now pins fork accounting instead of only "inherited > 0": the child's
|
|
378
|
+
`inherited` must equal the parent's total at the cut, a fresh child's `totals` must be zero,
|
|
379
|
+
and after the child runs one turn its `totals` must equal that reply's usage while
|
|
380
|
+
`inherited` stays the parent's figure (with exactly one `byTurn` row). The acceptance run
|
|
381
|
+
goes from 163 to 166 checks and is green; both READMEs carry the new count.
|
|
382
|
+
|
|
383
|
+
## 0.52.53
|
|
384
|
+
|
|
385
|
+
- Prompt image attachments now have live coverage: one real turn carries a 1×1 PNG and
|
|
386
|
+
settles (the path is read through the filesystem seam and becomes an image block), a
|
|
387
|
+
five-image prompt is refused before any turn runs, and an unreadable image path is an error
|
|
388
|
+
that names the file.
|
|
389
|
+
- The first attempt also documented a real distinction: the 1×1 PNG used by the image-reading
|
|
390
|
+
check (a valid header, enough for `file_read_image`'s byte sniffing) was refused as
|
|
391
|
+
"Unsupported or malformed image data" when attached to a prompt — the prompt path decodes
|
|
392
|
+
the image. The check now embeds a normally-encoded 2×2 PNG, and both READMEs say that
|
|
393
|
+
attachments must be decodable, not just sniffable. Full shape after the fix: 150/150.
|
|
394
|
+
|
|
395
|
+
## 0.52.52
|
|
396
|
+
|
|
397
|
+
- Live-verified `approval.policy`: with `allow`, `session_create` reports
|
|
398
|
+
`approvalPolicy: "never"` (the harness's unattended stance); with `inherit` the reply
|
|
399
|
+
carries no policy and the session keeps the default. `live-checks` now asserts that
|
|
400
|
+
relation against `dsh_info.config.approval`, so a run under either setting checks the
|
|
401
|
+
posture the caller is actually given. Both branches were run: a throwaway profile with
|
|
402
|
+
`allow` reports "approval.policy allow makes the session unattended — never" (104/104 the
|
|
403
|
+
cheap suite); the default `inherit` profile exercises the other branch. Re-measured shapes:
|
|
404
|
+
147/88 with `--with-model`, cheap subset 104/45/59/65; both READMEs updated.
|
|
405
|
+
|
|
406
|
+
## 0.52.51
|
|
407
|
+
|
|
408
|
+
- Live-verified the new host validation: a profile with `http.host: ''` starts **no
|
|
409
|
+
listener** and its output names the rule, while `0.0.0.0` binds as intended. The
|
|
410
|
+
non-loopback *warning* is in the code but plugin `log.warn` output is not captured in this
|
|
411
|
+
harness's stdout, so the matrix records the binding behaviour and says the warning itself
|
|
412
|
+
was not observed rather than implying it was.
|
|
413
|
+
|
|
414
|
+
## 0.52.50
|
|
415
|
+
|
|
416
|
+
- `http.host` is now validated: an empty value is refused (it would have bound on every
|
|
417
|
+
interface while the operator said nothing, with only a log warning) and so is a URL pasted
|
|
418
|
+
into the field; surrounding whitespace is trimmed instead of reaching `listen()`. The panel
|
|
419
|
+
example and README already said `127.0.0.1`, this is the config agreeing.
|
|
420
|
+
|
|
421
|
+
## 0.52.49
|
|
422
|
+
|
|
423
|
+
- `http.port` is now bounded at 65535 in config validation. The old check only required a
|
|
424
|
+
non-negative integer, so `70000` loaded fine and failed later as a listen error where the
|
|
425
|
+
operator has less to go on. The error says the range, and the panel's own 0-65535 bounds
|
|
426
|
+
now mirror the host rule exactly.
|
|
427
|
+
|
|
428
|
+
## 0.52.48
|
|
429
|
+
|
|
430
|
+
- The drift check now also compares each panel number's minimum with the host's
|
|
431
|
+
`positiveInt` rule: a panel field for one of those keys must declare `min: 1`. Proven by
|
|
432
|
+
lowering `session.promptTimeoutMs` to `min: 0` —
|
|
433
|
+
`DRIFT: panel number "session.promptTimeoutMs" has min 0, but the host requires a positive
|
|
434
|
+
integer (min 1)` — then restoring. Two bugs in the new check were caught while proving it:
|
|
435
|
+
it first matched only single-line field specs (two of the four are multi-line) and it
|
|
436
|
+
referenced the parsed sources before their declarations; both fixed.
|
|
437
|
+
- Both READMEs list the new check.
|
|
438
|
+
|
|
439
|
+
## 0.52.47
|
|
440
|
+
|
|
441
|
+
- The drift check now compares each panel `select` against the exported enum the config
|
|
442
|
+
schema validates with (`SESSION_SCOPES`, `APPROVAL_POLICIES`): offering a value the host
|
|
443
|
+
refuses looks like a supported choice, which is worse than free text. A new select without
|
|
444
|
+
a mapping fails the check rather than drifting silently. Proven by deleting the `allow`
|
|
445
|
+
option — `DRIFT: panel select "approval.policy" offers [inherit], APPROVAL_POLICIES is
|
|
446
|
+
[inherit, allow]` — then restoring. Both READMEs list the new check.
|
|
447
|
+
|
|
448
|
+
## 0.52.46
|
|
449
|
+
|
|
450
|
+
- The four positive-integer settings (`session.promptTimeoutMs`, `limits.maxReadBytes`,
|
|
451
|
+
`limits.shellTimeoutMs`, `limits.agentToolTimeoutMs`) had no minimum in the panel, so a
|
|
452
|
+
user could type `0` or a negative value and only learn it was refused after the write
|
|
453
|
+
failed. They now declare `min: 1`, matching the host's `positiveInt` rule.
|
|
454
|
+
- Added field invariants as tests: every path unique (no shadowing), each `select` carries
|
|
455
|
+
options, each `number` declares a minimum and never a maximum below it.
|
|
456
|
+
|
|
457
|
+
## 0.52.45
|
|
458
|
+
|
|
459
|
+
- Number fields no longer keep an unwritten value on screen: a draft the host would refuse
|
|
460
|
+
(empty, half-typed, out of range) is reset to the last committed value when the field
|
|
461
|
+
loses focus, instead of silently doing nothing while the input still showed e.g. `70000`.
|
|
462
|
+
The rule moved into `parseNumberDraft` so it is unit-tested (bounds accepted, invalid and
|
|
463
|
+
out-of-range rejected); text fields are unchanged.
|
|
464
|
+
|
|
465
|
+
## 0.52.44
|
|
466
|
+
|
|
467
|
+
- Wrote the **flake discipline** into `docs/LIVE-VERIFICATION.md`, with the four cases from the
|
|
468
|
+
0.52.4x rounds as the table: the progress check that demanded two rate-limited
|
|
469
|
+
notifications, the steer/switch that could miss a finished turn, the deadline check whose
|
|
470
|
+
command was too short to discriminate, and the shapes driver that read `harnessServices`
|
|
471
|
+
before the services mounted. Both READMEs point at the section.
|
|
472
|
+
|
|
473
|
+
## 0.52.43
|
|
474
|
+
|
|
475
|
+
- The same self-inflicted-flake audit applied to the acceptance script: the symlink-loop
|
|
476
|
+
walk's 5 s race is now 15 s (the project is tiny, so the window only catches an endless
|
|
477
|
+
following-walk rather than measuring speed), and the background-job output poll allows
|
|
478
|
+
10 s for a 0.3 s job instead of 4 s.
|
|
479
|
+
|
|
480
|
+
## 0.52.42
|
|
481
|
+
|
|
482
|
+
- Two more guards against self-inflicted flakiness. The steering checks start their long
|
|
483
|
+
turn with `sleep 10` (was 8) so the steer cannot arrive after the turn closed. The
|
|
484
|
+
`shell_run` deadline check now runs `sleep 15` under a 0.7 s timeout and accepts anything
|
|
485
|
+
under 8 s: the long command costs nothing (the call returns at the deadline) while the wide
|
|
486
|
+
window still proves the deadline cut the wait instead of the command finishing naturally.
|
|
487
|
+
|
|
488
|
+
## 0.52.41
|
|
489
|
+
|
|
490
|
+
- Removed the same class of flake risk from the mid-turn switch check: it switched routes
|
|
491
|
+
1.5 s after starting a turn made of three fast `echo` commands, so on a quick route the
|
|
492
|
+
turn could end first and the switch would apply to a *later* turn — the assertion would
|
|
493
|
+
fail without anything being wrong with the plugin. The prompt now starts with
|
|
494
|
+
`sleep 6`, so the turn is guaranteed to be open when the switch lands (and the check keeps
|
|
495
|
+
asserting that both routes answered the same turn).
|
|
496
|
+
|
|
497
|
+
## 0.52.40
|
|
498
|
+
|
|
499
|
+
- Hardened the progress-notification check after one full-shape run reported 144/1 that
|
|
500
|
+
could not be reproduced (three later runs: 145/0, 145/0, 145/0). The suspect assertion
|
|
501
|
+
required **two** notifications more than 500 ms apart, but the sender is rate-limited to
|
|
502
|
+
roughly one a second, so a fast turn can legitimately emit a single one. The check now
|
|
503
|
+
requires that the first notification arrived *during* the turn (the actual promise: not
|
|
504
|
+
batched at the end) and that, when several arrive, they are spread out. Two full-shape runs
|
|
505
|
+
and a session-shape run are green afterwards (146/146 and 87/87 — splitting the assertion
|
|
506
|
+
added one check), and both READMEs carry the new counts.
|
|
507
|
+
|
|
508
|
+
## 0.52.39
|
|
509
|
+
|
|
510
|
+
- `session_prompt { mode: "steer" }` now has live evidence, separate from the queue-steer
|
|
511
|
+
path: a steered prompt is answered inside the running turn (reply carries the marker,
|
|
512
|
+
no timeout, not aborted) and the session still records only two turns for two prompts
|
|
513
|
+
plus the steer — so steering does not buy a turn. Probed first, then encoded.
|
|
514
|
+
Re-measured shapes after the addition: all six 145, session+usage 86, narrow 59,
|
|
515
|
+
files+shell 65 (cheap subset 104/46/59/65). One shapes run reported 144/1 on the full
|
|
516
|
+
shape; an immediate rerun was 145/0 and the failing check could not be recovered from the
|
|
517
|
+
truncated output, so `tools/shapes.mjs` now prints the failing check names in its summary
|
|
518
|
+
— a recurrence will be identifiable instead of a bare count.
|
|
519
|
+
|
|
520
|
+
## 0.52.38
|
|
521
|
+
|
|
522
|
+
- Fixed a false claim in the settings panel: when the host mounts no status route (or the
|
|
523
|
+
status fetch fails), `readStatus` answers `undefined`, but the component still marked the
|
|
524
|
+
read as successful and rendered the "not listening" badge. Unknown is not a state anyone
|
|
525
|
+
observed — the badge now appears only when a status was actually read (and disappears
|
|
526
|
+
again if a later poll cannot read one). A source-level test keeps the rule from
|
|
527
|
+
regressing (`setStatusRead(next !== undefined)` plus the `statusRead &&` gate).
|
|
528
|
+
|
|
529
|
+
## 0.52.37
|
|
530
|
+
|
|
531
|
+
- `tools/release.sh --shapes` runs the shape driver as a release step (add `--with-model` to
|
|
532
|
+
spend turns). The driver also now picks **free ports** per shape instead of assuming
|
|
533
|
+
8821/8822 and killing whatever listens there — a release step must never kill an
|
|
534
|
+
operator's own endpoint. Both READMEs list the flag.
|
|
535
|
+
- Verified end to end: `release.sh --skip-tests --shapes` reports 103/44/59/65 for the four
|
|
536
|
+
shapes (the cheap subset), and the READMEs now say those differ from the 143/84 rows because
|
|
537
|
+
those are measured with `--with-model`.
|
|
538
|
+
|
|
539
|
+
## 0.52.36
|
|
540
|
+
|
|
541
|
+
- `tools/shapes.mjs` turns the hand-measured deployment matrix into one command: per shape it
|
|
542
|
+
boots a throwaway profile with that shape's `tools` block, installs the packaged tarball,
|
|
543
|
+
waits until the harness services are mounted, runs `live-checks`, prints the counts, and
|
|
544
|
+
cleans up. Verified end to end — it reproduces the table exactly (143/84/59/65) with
|
|
545
|
+
`--with-model`.
|
|
546
|
+
- Writing it exposed a real startup ordering fact: the HTTP socket answers **before** the
|
|
547
|
+
harness services are mounted, so a check run at first response sees
|
|
548
|
+
`harnessServices.sessionController: false`. The driver now polls `dsh_info` until the
|
|
549
|
+
controller is visible (fixed a spurious failure that the hand-walkthrough never hit because
|
|
550
|
+
it waited ~18 s). Both READMEs point at the script.
|
|
551
|
+
|
|
552
|
+
## 0.52.35
|
|
553
|
+
|
|
554
|
+
- Re-measured every row of the deployment-shape matrix on the current build instead of
|
|
555
|
+
trusting earlier runs: all six groups is **143/143** with `--with-model` (the table said
|
|
556
|
+
130 from a build before later checks were added); the narrowed shapes re-confirmed at
|
|
557
|
+
84/84, 59/59 and 65/65. Both READMEs carry the corrected number.
|
|
558
|
+
|
|
559
|
+
## 0.52.34
|
|
560
|
+
|
|
561
|
+
- Both READMEs now carry the deployment-shape matrix the last four rounds produced: all six
|
|
562
|
+
groups (28 tools, 130/130 with `--with-model`, 163/163 acceptance), `session+usage`
|
|
563
|
+
(13 tools, 84/84), `workspace+session+agentTools+usage` (18 tools, 59/59) and
|
|
564
|
+
`files+shell` (11 tools, 65/65), with the skip behaviour stated so an operator comparing a
|
|
565
|
+
narrowed deployment knows what was and was not exercised.
|
|
566
|
+
|
|
567
|
+
## 0.52.33
|
|
568
|
+
|
|
569
|
+
- Verified the **session-only** deployment shape — the one a client that just wants to drive
|
|
570
|
+
an agent would run: `workspace/files/shell/agentTools` off, `session` and `usage` on.
|
|
571
|
+
`live-checks --with-model` reports 84/84 checks, 0 failed: routing, `reasoningEffort`,
|
|
572
|
+
usage receipts, transcript turn windows, progress/cancellation, orchestration, steering
|
|
573
|
+
and idempotency all run, while every file/shell/workspace section degrades to an explicit
|
|
574
|
+
skip. The matrix records it.
|
|
575
|
+
|
|
576
|
+
## 0.52.32
|
|
577
|
+
|
|
578
|
+
- Verified the whole check suite against a **minimal** deployment (only `files` and
|
|
579
|
+
`shell` groups on): 11 tools advertised, 11 available, 17 unavailable with reasons, all
|
|
580
|
+
four disabled groups named, and the session/usage/queue/search sections degraded to
|
|
581
|
+
explicit skips — 65/65 checks, 0 failed. Both READMEs now say the script can verify a
|
|
582
|
+
narrowed deployment instead of only a fully-enabled one.
|
|
583
|
+
|
|
584
|
+
## 0.52.31
|
|
585
|
+
|
|
586
|
+
- The availability contract now checks the disabled-group path: on a profile with
|
|
587
|
+
`tools.files: false` and `tools.shell: false` it asserts each disabled group's reason
|
|
588
|
+
names the switch an operator would flip (`tools.files: false`), while `availableTools`
|
|
589
|
+
still equals the advertised `tools/list` set (probed first: 18 tools advertised, 18
|
|
590
|
+
available, `file_read` reason "the files tool group is disabled"). Default profiles skip
|
|
591
|
+
this check because no group is off.
|
|
592
|
+
- Running the suite against that narrowed profile also exposed a real defect in
|
|
593
|
+
`live-checks.mjs` itself: the workspace section wrote a file unconditionally and aborted
|
|
594
|
+
with "Tool file_write not found" when the files group was off. It now gates that part on
|
|
595
|
+
the advertised surface and reports it as skipped, so the script can verify a narrowed
|
|
596
|
+
deployment instead of crashing on it.
|
|
597
|
+
|
|
598
|
+
## 0.52.30
|
|
599
|
+
|
|
600
|
+
- Four edge contracts now have live checks, all probed first: `workspace_delete` of an
|
|
601
|
+
unknown id answers `deleted: false` rather than failing; `session_list` for a cwd with no
|
|
602
|
+
sessions returns an empty list with `total: 0`; a whitespace-only `session_rename` title
|
|
603
|
+
is refused before touching the harness; and `dsh_tool_list` on an unknown session returns
|
|
604
|
+
no tools plus a `scopeError` naming the session instead of a transport failure.
|
|
605
|
+
|
|
606
|
+
## 0.52.29
|
|
607
|
+
|
|
608
|
+
- The settings panel's token mask shows a four-character prefix only for tokens at least
|
|
609
|
+
**16 characters** long, the length below which the host already warns. The old threshold
|
|
610
|
+
was 9, so a 9–15 character token displayed four of its characters — nearly half of a
|
|
611
|
+
short token. Tests pin the boundary (15 masked fully, 16 shows `abcd••••••••`).
|
|
612
|
+
|
|
613
|
+
## 0.52.28
|
|
614
|
+
|
|
615
|
+
- The `cwd` contract shared by the file tools now has live checks: a relative write is
|
|
616
|
+
readable and listable through the same base (`file_list { path: ".", cwd }`), and a
|
|
617
|
+
missing relative path names the resolved file in its error. All cheap; no turns.
|
|
618
|
+
|
|
619
|
+
## 0.52.27
|
|
620
|
+
|
|
621
|
+
- `session_fork`'s two cut forms now have negative live checks: passing both `atSeq` and
|
|
622
|
+
`atTurn` is refused ("pass atSeq or atTurn, not both"), and an unknown `atTurn` lists the
|
|
623
|
+
turns the log actually records instead of failing vaguely. Both reuse the two-turn
|
|
624
|
+
session `--with-model` already has, so they cost no turns.
|
|
625
|
+
|
|
626
|
+
## 0.52.26
|
|
627
|
+
|
|
628
|
+
- `file_read_image` format coverage: the live check classifies a **GIF**, **WEBP** and
|
|
629
|
+
**BMP** header as their MIME types, alongside the existing PNG/text/missing/over-cap
|
|
630
|
+
cases. The first version assumed BMP was unsupported and failed against the running
|
|
631
|
+
plugin — it reads BMP fine; what refuses BMP is a *prompt attachment*, whose format list
|
|
632
|
+
is deliberately narrower. Both sides are now checked: `file_read_image` accepts it, and
|
|
633
|
+
`session_prompt { images: [bmp] }` refuses it with the accepted formats named (that call
|
|
634
|
+
spends no turn). Detection is by magic bytes, so a header is enough. A second script
|
|
635
|
+
mistake was found and fixed in the same round: the prompt-refusal assertion tried to
|
|
636
|
+
assert that the error did *not* mention bmp (it names the path, which contains "bmp");
|
|
637
|
+
it now asserts the harness wording — "cannot carry (supported: png, jpeg, webp, gif)".
|
|
638
|
+
|
|
639
|
+
## 0.52.25
|
|
640
|
+
|
|
641
|
+
- `idempotencyKey` now has cross-client evidence: the acceptance run covers one client's
|
|
642
|
+
retry, but two harness instances can drive one session. The live check sends with a key,
|
|
643
|
+
re-sends the same key through a *second* MCP client (expects the recorded turn, same
|
|
644
|
+
reply), tries the key with different content (refused), and asserts only one turn ran.
|
|
645
|
+
|
|
646
|
+
## 0.52.24
|
|
647
|
+
|
|
648
|
+
- Two boundary contracts now have live checks: a `file_read` offset at EOF is an empty
|
|
649
|
+
complete read (`truncated: false`, no `nextOffset`) and a past-EOF offset is empty rather
|
|
650
|
+
than an error, so a paging loop terminates cleanly; and `session_queue` with an unknown
|
|
651
|
+
`messageId` is refused with a readable message ("queued item is no longer pending")
|
|
652
|
+
instead of silently accepting.
|
|
653
|
+
- The first run failed both new checks for reasons in the *script*, not the plugin: the
|
|
654
|
+
EOF offset was computed from JavaScript string length while the tool counts **bytes**
|
|
655
|
+
('€' is one char, three bytes), and the queue regex did not accept the harness's
|
|
656
|
+
"no longer pending" wording. Both fixed, and the README now says the paging offsets are
|
|
657
|
+
byte offsets.
|
|
658
|
+
|
|
659
|
+
## 0.52.23
|
|
660
|
+
|
|
661
|
+
- The orchestration live check now edits a pending message while it waits (`action:
|
|
662
|
+
"edit"`): the preserved queue entry's text is replaced after the cancel, and the
|
|
663
|
+
transcript afterwards must contain the edited text and not the original — an edit that
|
|
664
|
+
only changed what a listing shows would fail this.
|
|
665
|
+
|
|
666
|
+
## 0.52.22
|
|
667
|
+
|
|
668
|
+
- Probed and asserted `session_queue { action: "steer" }`: queued while a sleeping turn
|
|
669
|
+
runs, the message is steered into that turn and its text appears inside the turn that
|
|
670
|
+
was already running rather than as a new one. The live check starts such a turn, steers,
|
|
671
|
+
settles it, and uses the turn count as the witness: three prompts produced **two** turns
|
|
672
|
+
(the steered message folded into the running one), and the steered text appears in the
|
|
673
|
+
transcript. Transcript rows do not always carry a turn number, so the count is the
|
|
674
|
+
honest evidence.
|
|
675
|
+
|
|
676
|
+
## 0.52.21
|
|
677
|
+
|
|
678
|
+
- The transcript's turn window now has live evidence: with the two turns
|
|
679
|
+
`--with-model` already runs, `session_messages { fromTurn }` returns only the later
|
|
680
|
+
turn (and its reply) while `toTurn` stops before it — the same parameter names as
|
|
681
|
+
`session_usage`, checked against the turn numbers the replies reported.
|
|
682
|
+
|
|
683
|
+
## 0.52.20
|
|
684
|
+
|
|
685
|
+
- `session_set_model { reasoningEffort }` now has live evidence: the selection is applied
|
|
686
|
+
and echoed back (`reasoningEffort: "high"`, same model). Probed first, then wired into
|
|
687
|
+
`live-checks --with-model` as a no-turn assertion (it skips rather than fails when a
|
|
688
|
+
route rejects the effort, so the check does not assume every provider has one).
|
|
689
|
+
|
|
690
|
+
## 0.52.19
|
|
691
|
+
|
|
692
|
+
- The file-edit live check now covers the compare-and-swap direction that only writes had:
|
|
693
|
+
a hash read before another client changed the file makes `file_edit` refuse with the
|
|
694
|
+
current hash and leaves the file intact, and the same edit succeeds once the hash is
|
|
695
|
+
re-read. This is the lost-update window for edits, stated in the docs and now exercised.
|
|
696
|
+
|
|
697
|
+
## 0.52.18
|
|
698
|
+
|
|
699
|
+
- Two more documented options now have live evidence: `dsh_tool_call { maxResultChars }`
|
|
700
|
+
reads a file through the agent's `read` tool with a 100-char cap and asserts `truncated`
|
|
701
|
+
plus the untruncated `resultBytes` (the agent's own formatting makes that size smaller
|
|
702
|
+
than the file — the check asserts "bigger than the cap", not the file size), and
|
|
703
|
+
`shell_kill { waitMs }` kills a running job with an explicit wait and still reports
|
|
704
|
+
`status: "killed"`. The read call accepts both `file_path` and `path` argument shapes so
|
|
705
|
+
the check is not pinned to one harness version.
|
|
706
|
+
|
|
707
|
+
## 0.52.17
|
|
708
|
+
|
|
709
|
+
- Live-checked the options documented in 0.52.16, and corrected the docs where the
|
|
710
|
+
behaviour was the opposite of the wording: `workspace_create` and `file_write` **create**
|
|
711
|
+
missing paths/parents by default and take `createDirectory: false` /
|
|
712
|
+
`createDirectories: false` as the strict mode. Checks now assert both directions
|
|
713
|
+
(default creates, `false` refuses), plus `file_list { maxDepth }` bounding a recursive
|
|
714
|
+
walk and `session_fork { atTurn }` stopping a child before the later turn — the last one
|
|
715
|
+
reuses the two turns `--with-model` already runs, so it adds no spend.
|
|
716
|
+
|
|
717
|
+
## 0.52.16
|
|
718
|
+
|
|
719
|
+
- Documented the non-obvious options that were absent from **both** READMEs, in parallel:
|
|
720
|
+
`session_fork { atTurn }`, `session_set_model { reasoningEffort }`, the compare-and-swap
|
|
721
|
+
`expectedSha256` of `file_write`/`file_edit`, `file_write { createDirectories }`,
|
|
722
|
+
`file_read`/`file_list { sha256 }`, `file_list { maxDepth }`, `shell_kill { jobId,
|
|
723
|
+
waitMs }`, `dsh_tool_call { maxResultChars }`, `workspace_create { createDirectory }`,
|
|
724
|
+
and the `oldText`/`newText` names of `file_edit`. The zh `file_read` row also gained the
|
|
725
|
+
`sha256` sentence the en row already had. Options that are self-describing
|
|
726
|
+
(`command`, `provider`, `content`, `timeoutMs`) are still described in prose only.
|
|
727
|
+
|
|
728
|
+
## 0.52.15
|
|
729
|
+
|
|
730
|
+
- The drift check now catches the class of defect 0.52.14 fixed by hand: a tool option
|
|
731
|
+
referenced in one README and not the other. Per tool it compares which `inputSchema`
|
|
732
|
+
option names each README mentions (as code spans) and fails when the sets differ — proven
|
|
733
|
+
by removing `` `probeSearch` `` from the en README, which now answers
|
|
734
|
+
`DRIFT: README.md never mentions option "probeSearch" of dsh_info, while README.zh.md
|
|
735
|
+
does`, then restoring. It does not demand every option be documented, only that coverage
|
|
736
|
+
be symmetric.
|
|
737
|
+
|
|
738
|
+
## 0.52.14
|
|
739
|
+
|
|
740
|
+
- Fixed an English/Chinese row-content drift: the zh `dsh_info` row documented
|
|
741
|
+
`probeSearch` (an option the schema really has, covered by tests and live checks) while
|
|
742
|
+
the en row did not. The en row now says what the option does. The drift check compares
|
|
743
|
+
the *set* of tool rows, not the content of a row, so this class of omission is not
|
|
744
|
+
machine-caught — noted here rather than silently fixed.
|
|
745
|
+
|
|
746
|
+
## 0.52.13
|
|
747
|
+
|
|
748
|
+
- Verified the other half of the ownership contract: a **corrupt** `owned.json` fails
|
|
749
|
+
closed. A session created under `session.scope: "owned"` became unreadable once the
|
|
750
|
+
record was replaced with `{not json at all` and the process restarted — the damaged
|
|
751
|
+
record hides sessions, it cannot expose them. The original record was copied before the
|
|
752
|
+
probe and restored byte-for-byte; the throwaway profile was deleted.
|
|
753
|
+
|
|
754
|
+
## 0.52.12
|
|
755
|
+
|
|
756
|
+
- Verified the restart behavior README already promised for `session.scope: "owned"`:
|
|
757
|
+
a session created through the endpoint is written to `$DSH_HOME/dsh-as-mcp/owned.json`,
|
|
758
|
+
and after killing and restarting the process the same session was still readable while
|
|
759
|
+
a session from another profile's logs was refused with the scope message. Unit tests
|
|
760
|
+
covered the record's round-trip; this covered the composition. The probe id was removed
|
|
761
|
+
from the real record afterwards; the throwaway profile was deleted.
|
|
762
|
+
|
|
763
|
+
## 0.52.11
|
|
764
|
+
|
|
765
|
+
- The drift check now owns one more number that had drifted before: the capability-probe
|
|
766
|
+
count in `docs/CAPABILITY-GAPS.md`'s appendix must equal what `describeCapabilities()`
|
|
767
|
+
reports (22 today). Proven by lowering the doc to 21 — the check answers
|
|
768
|
+
`DRIFT: ... says 21 capability probes, describeCapabilities() reports 22` — then
|
|
769
|
+
restoring it. Both READMEs list the new check.
|
|
770
|
+
|
|
771
|
+
## 0.52.10
|
|
772
|
+
|
|
773
|
+
- The subagent live check now proves the documented split between two views: a prompt
|
|
774
|
+
reply's `session` block excludes the subagent's tokens while `session_usage` includes
|
|
775
|
+
them as a row. The check asserts the reply total equals the self row and is strictly
|
|
776
|
+
smaller than the report total, so "the tool exists because the reply cannot see the
|
|
777
|
+
child" is evidence rather than prose.
|
|
778
|
+
|
|
779
|
+
## 0.52.9
|
|
780
|
+
|
|
781
|
+
- Fixed a drift the new in-test docs check caught (and that a `2>/dev/null | tail -1`
|
|
782
|
+
check in the same shell hid): the 0.52.8 matrix row cited the price plugin's own version
|
|
783
|
+
`1.9.41`, which the check rightly reads as a release of *this* plugin with no changelog
|
|
784
|
+
entry. The row now says "the current `dsh-cost-meter` (1.9.x)". Process note: the 0.52.8
|
|
785
|
+
install used `--skip-tests` while the suite had that failure, so from 0.52.9 on a round
|
|
786
|
+
that touches tests/docs runs the release *with* tests instead of skipping them.
|
|
787
|
+
|
|
788
|
+
## 0.52.8
|
|
789
|
+
|
|
790
|
+
- Closed the money row: installed `dsh-cost-meter@1.9.41` from its registry into a
|
|
791
|
+
throwaway profile and compared the receipt with the plugin's own ledger for the same
|
|
792
|
+
session — `costUsd 0.00008131200000000001, calls 2` against the ledger's
|
|
793
|
+
`cost 8.131200000000001e-05, calls 2`: the same float and the same call count, so the
|
|
794
|
+
figures are a faithful pass-through rather than a recomputation. (The earlier attempt
|
|
795
|
+
failed because a copied package directory loses its hoisted dependencies.)
|
|
796
|
+
- live-checks now asserts the implication when a price plugin is mounted: if
|
|
797
|
+
`dsh_info.usage.costAvailable` is true, the receipt must actually be priced by
|
|
798
|
+
`dsh-cost-meter`.
|
|
799
|
+
- Both READMEs say the money figures are the plugin's ledger values passed through.
|
|
800
|
+
|
|
801
|
+
## 0.52.7
|
|
802
|
+
|
|
803
|
+
- Attempted the money reconciliation in a throwaway profile and recorded why it did not
|
|
804
|
+
run: the only `dsh-cost-meter` install on this machine is hoisted inside the desktop
|
|
805
|
+
profile, so copying its package directory leaves its dependencies behind and its import
|
|
806
|
+
dies with `Cannot find package 'acorn'`; the desktop profile itself runs an older build.
|
|
807
|
+
The verification matrix now names that concrete blocker and the exact way to close it
|
|
808
|
+
(install the price plugin from its registry into a throwaway profile) instead of the
|
|
809
|
+
vaguer "compare on a known row". No code change.
|
|
810
|
+
|
|
811
|
+
## 0.52.6
|
|
812
|
+
|
|
813
|
+
- Documented what a `session_queue` row carries: id, text, image count, and the harness's
|
|
814
|
+
inbox `source` kind. A message queued through the endpoint reports `source: "user"` —
|
|
815
|
+
probed live — so the field is the harness's classification, **not** a client identity;
|
|
816
|
+
both READMEs say that, and the live orchestration check asserts the row shape.
|
|
817
|
+
|
|
818
|
+
## 0.52.5
|
|
819
|
+
|
|
820
|
+
- The orchestration live check now covers a **second MCP client**: it queues a message
|
|
821
|
+
from another connection, sees it in `session_queue` from the first, withdraws it there,
|
|
822
|
+
and asserts the withdrawn text never ran. The queue lives on the session, so a
|
|
823
|
+
connection-scoped id would be a bug a single-client test cannot see. (A message queued
|
|
824
|
+
from the DSH UI itself remains unverified — that needs the UI.)
|
|
825
|
+
|
|
826
|
+
## 0.52.4
|
|
827
|
+
|
|
828
|
+
- The two script-integration suites (`smoke-script`, `exercise-script`) allow one retry per
|
|
829
|
+
test. A full-suite run under load produced a single timeout in the smoke suite that
|
|
830
|
+
passed immediately when rerun; the assertions are deterministic, so the retry absorbs
|
|
831
|
+
scheduler flake without hiding a logic failure. (The 0.52.3 install went ahead with that
|
|
832
|
+
flake in the log because the failure grep was truncated — the suite was rerun green
|
|
833
|
+
twice before this entry.)
|
|
834
|
+
|
|
835
|
+
## 0.52.3
|
|
836
|
+
|
|
837
|
+
- `session_tool_result` now has owned-scope coverage: under `session.scope: "owned"` it
|
|
838
|
+
refuses a foreign session's tool result (the read that could otherwise expose output by
|
|
839
|
+
guessing call ids) and still answers for a session the endpoint created. The behavior
|
|
840
|
+
was already correct — `assertSessionVisible` — but every other session tool had a test
|
|
841
|
+
for the refusal path and this one did not.
|
|
842
|
+
|
|
843
|
+
## 0.52.2
|
|
844
|
+
|
|
845
|
+
- The documentation drift check now runs in the test suite too (`tests/docs-consistency.test.ts`
|
|
846
|
+
spawns `tools/check-docs.mjs`), so `vitest run` alone reports README/changelog/wiring
|
|
847
|
+
drift instead of leaving it for the release step. Both READMEs say where it runs.
|
|
848
|
+
|
|
849
|
+
## 0.52.1
|
|
850
|
+
|
|
851
|
+
- Documented and asserted where a model switch takes effect: a `session_set_model` during
|
|
852
|
+
a running turn applies to that turn's later model calls. The probe started a turn on
|
|
853
|
+
`deepseek-official/deepseek-flash`, switched to `deepseek-account/deepseek-flash`
|
|
854
|
+
mid-flight, and got one turn reporting both routes, whose per-turn `byModel` split
|
|
855
|
+
(18 062 + 18 586) summed to the row total (36 648) — the case 0.52.0's field exists for.
|
|
856
|
+
`session_set_model`'s description, both READMEs and the matrix now say so, and
|
|
857
|
+
live-checks `--with-model` reproduces it.
|
|
858
|
+
|
|
859
|
+
## 0.52.0
|
|
860
|
+
|
|
861
|
+
- Per-turn usage rows now carry a `byModel` split when the turn used more than one
|
|
862
|
+
route: a mid-turn escalation or fallback has a single row total, which hides how much
|
|
863
|
+
the expensive model charged. Single-route turns omit the field (the row and
|
|
864
|
+
`models[0]` already describe them fully), so the common case costs nothing.
|
|
865
|
+
- Usage-fold test covers the multi-route split, the single-route omission and the row
|
|
866
|
+
total; both READMEs describe the field.
|
|
867
|
+
|
|
868
|
+
## 0.51.11
|
|
869
|
+
|
|
870
|
+
- Verified two items that had been listed as unverified, and corrected one claim:
|
|
871
|
+
- a `read-only` sandbox really refuses plugin writes (`file_write` → "file access denied
|
|
872
|
+
under read-only mode"; `shell_run` reads fine but its redirect fails with "Operation
|
|
873
|
+
not permitted"), so the "the harness policy still applies" claim is evidence, not
|
|
874
|
+
architecture;
|
|
875
|
+
- chunked request bodies (no `content-length`) **are** size-bounded: 70 MB chunked
|
|
876
|
+
answered `413` in 71 ms while a valid chunked `initialize` answered 200. The README
|
|
877
|
+
said a chunked request was "not covered by this check", which understated the code
|
|
878
|
+
(`readJsonBody` counts streamed bytes); both READMEs now say what happens, and the zh
|
|
879
|
+
security section gained the missing bullet.
|
|
880
|
+
- Both rows moved from "Not verified here" to the verified matrix.
|
|
881
|
+
|
|
882
|
+
## 0.51.10
|
|
883
|
+
|
|
884
|
+
- `tools/release.sh` accepts `--with-model` and the `--full-live` shorthand for
|
|
885
|
+
`--with-model --with-subagent`. The documented desktop-closing command used to fail as
|
|
886
|
+
an unknown argument because only `--with-subagent` was parsed — the README and the
|
|
887
|
+
usage header now match what the script accepts.
|
|
888
|
+
|
|
889
|
+
## 0.51.9
|
|
890
|
+
|
|
891
|
+
- Refined the subagent route semantics: inheritance happens **at spawn time**, not at
|
|
892
|
+
session creation. A probe answered on `deepseek-official/deepseek-flash`, switched the
|
|
893
|
+
session to `deepseek-account/deepseek-flash`, then spawned — the child was billed on the
|
|
894
|
+
switched route. Both READMEs now say "the parent's route at spawn time", and the live
|
|
895
|
+
subagent check switches the parent's model before spawning and asserts the child follows
|
|
896
|
+
it (the earlier "child routes within parent routes" assertion is kept).
|
|
897
|
+
|
|
898
|
+
## 0.51.8
|
|
899
|
+
|
|
900
|
+
- Documented and asserted **subagent route inheritance**: a child spawned by a session
|
|
901
|
+
created on a non-default route spends on that same route (probe: parent
|
|
902
|
+
`deepseek-account/deepseek-flash` → child billed on the same route), so a caller
|
|
903
|
+
choosing the parent's model steers what its agents cost. Both READMEs say so, the live
|
|
904
|
+
subagent check asserts the child's `byModel` routes are within the parent's, and the
|
|
905
|
+
verification matrix records the probe.
|
|
906
|
+
|
|
907
|
+
## 0.51.7
|
|
908
|
+
|
|
909
|
+
- Full sweep recorded in `docs/LIVE-VERIFICATION.md`: one profile with the search index
|
|
910
|
+
enabled ran live-checks in every mode (`--with-model --with-subagent`, 115/115) and the
|
|
911
|
+
complete acceptance run (163/163) in the same boot, so the two suites agree on the same
|
|
912
|
+
process rather than each being green alone. Re-run before touching the desktop
|
|
913
|
+
composition.
|
|
914
|
+
|
|
915
|
+
## 0.51.6
|
|
916
|
+
|
|
917
|
+
- The acceptance run's phase-receipt check now cross-checks numbers, not just shape: a
|
|
918
|
+
`fromTurn`/`toTurn` receipt for one turn must equal the `byTurn` row's own total in
|
|
919
|
+
the same report *and* the same turn's row in the full receipt. A windowed report that
|
|
920
|
+
quietly folded the wrong events would pass a shape check; it cannot pass this.
|
|
921
|
+
|
|
922
|
+
## 0.51.5
|
|
923
|
+
|
|
924
|
+
- `tools/live-checks.mjs --with-model` cross-checks the token numbers too: each prompt
|
|
925
|
+
reply's per-turn `turn.usage.totalTokens` must equal that turn's row in the
|
|
926
|
+
`session_usage` receipt, and the row sum must equal the receipt's session total. The
|
|
927
|
+
route cross-check said *which* model ran; this says the number being billed agrees
|
|
928
|
+
across both views.
|
|
929
|
+
|
|
930
|
+
## 0.51.4
|
|
931
|
+
|
|
932
|
+
- `tools/live-checks.mjs --with-model` cross-checks two views of the same turns: the
|
|
933
|
+
`session_usage { includeTurns }` rows must name the same route each prompt reply's
|
|
934
|
+
`turn.models` reported, on both the initial and the switched turn. Billing and routing
|
|
935
|
+
disagreeing about which model ran is exactly the kind of drift a single-view test
|
|
936
|
+
cannot see.
|
|
937
|
+
|
|
938
|
+
## 0.51.3
|
|
939
|
+
|
|
940
|
+
- `tools/live-checks.mjs --with-model` closes the loop on the catalog: it creates a
|
|
941
|
+
session on a route dsh_info actually advertises, runs a real turn, checks the turn
|
|
942
|
+
reports that route, then switches to a second route when one exists and checks the
|
|
943
|
+
next turn follows. The catalog check alone proved the list was well formed; this
|
|
944
|
+
proves `session_create` and `session_set_model` consume it.
|
|
945
|
+
|
|
946
|
+
## 0.51.2
|
|
947
|
+
|
|
948
|
+
- `tools/live-checks.mjs` checks the catalog honesty in `dsh_info`: every route is a
|
|
949
|
+
named id, each per-route model list is well formed and belongs to a named route, a
|
|
950
|
+
catalogue that could not be read is named in `catalog.unavailable` instead of silently
|
|
951
|
+
missing, and the default agent preset (when present) names one of the listed presets.
|
|
952
|
+
That catalog is what `session_create`'s arguments are chosen from, so a silent hole
|
|
953
|
+
there sends callers guessing. The first run flagged the presets because this
|
|
954
|
+
profile's registry exposes ids only — the check now asserts the real contract (`id`
|
|
955
|
+
required, `name`/`description` optional) instead of an imagined one; 82/82 cheap
|
|
956
|
+
checks pass.
|
|
957
|
+
|
|
958
|
+
## 0.51.1
|
|
959
|
+
|
|
960
|
+
- `tools/live-checks.mjs` exercises the compare-and-swap lost-update window for real:
|
|
961
|
+
client A reads the hash, client B rewrites the file, A's guarded write is refused with
|
|
962
|
+
the new hash and leaves B's content intact, and A's retry with the current hash
|
|
963
|
+
succeeds. The earlier tests covered the one-client cases; this covers the interleaving
|
|
964
|
+
the guard exists for.
|
|
965
|
+
|
|
966
|
+
## 0.51.0
|
|
967
|
+
|
|
968
|
+
- `file_list { sha256: true }` hashes each file's content for bulk change detection; a
|
|
969
|
+
file above `limits.maxReadBytes` (or one that cannot be read as text) carries
|
|
970
|
+
`sha256Unavailable` with the reason instead of a silent omission, so a partial picture
|
|
971
|
+
is never passed off as complete. Directories carry neither field. Off by default.
|
|
972
|
+
- Test covers hashing, the unavailable reason, the directory case and the no-flag path.
|
|
973
|
+
The first live run hashed 0 files and caught the *third* instance of the same slip —
|
|
974
|
+
the schema gained the option but the handler did not forward it (after 0.46.0's turn
|
|
975
|
+
range and 0.50.0's read hash). `tools/check-docs.mjs` now has a systemic guard: it
|
|
976
|
+
parses every tool's `inputSchema` and its guard body and fails when an option never
|
|
977
|
+
reaches `args.<name>`, proven by injecting a synthetic dead key (DRIFT) and removing
|
|
978
|
+
it again. The re-run hashes 2 files and passes 163/163. Both READMEs updated.
|
|
979
|
+
|
|
980
|
+
## 0.50.0
|
|
981
|
+
|
|
982
|
+
- `file_read { sha256: true }` returns the SHA-256 of the **whole** file (not the
|
|
983
|
+
returned window) as the compare-and-swap value a later `file_write`/`file_edit` can
|
|
984
|
+
carry, so a read→decide→write cycle no longer needs a second call. The hash reads the
|
|
985
|
+
whole file, so it is opt-in and refuses a file above `limits.maxReadBytes`. Without
|
|
986
|
+
the flag nothing extra is read and the field is absent.
|
|
987
|
+
- Tests cover the whole-file-vs-window hash (with the hash ceiling taken from the
|
|
988
|
+
configured limit, not the window), the absent field and the over-ceiling refusal; the
|
|
989
|
+
acceptance run compares the hash with the one `file_write` returned and uses it for a
|
|
990
|
+
CAS edit (162/162). The first live run returned no hash and caught the same class of
|
|
991
|
+
wiring slip as 0.46.0: the new argument had not been forwarded by the tool handler.
|
|
992
|
+
Both READMEs updated.
|
|
993
|
+
|
|
994
|
+
## 0.49.0
|
|
995
|
+
|
|
996
|
+
- `file_write` and `file_edit` accept `expectedSha256` (compare-and-swap) and return the
|
|
997
|
+
resulting file's `sha256`: an external harness that reads a file, decides, then writes
|
|
998
|
+
it back no longer clobbers a concurrent edit — a mismatch names the actual hash and
|
|
999
|
+
writes nothing. `file_write` with `expectedSha256` refuses to create a missing file
|
|
1000
|
+
(omit the field to create one).
|
|
1001
|
+
- Tests cover the matching write, a stale write, a missing file, the matching edit and a
|
|
1002
|
+
stale edit; the acceptance run does a real CAS round-trip on disk. Both READMEs updated.
|
|
1003
|
+
|
|
1004
|
+
## 0.48.0
|
|
1005
|
+
|
|
1006
|
+
- `session_fork` accepts `atTurn`: cut the child at the end of that turn instead of a raw
|
|
1007
|
+
event seq — the unit a caller bills and reasons in. The reply echoes the resolved
|
|
1008
|
+
`atSeq` and the `atTurn`; an unknown turn lists the turns the log records, and passing
|
|
1009
|
+
both `atSeq` and `atTurn` is refused rather than silently preferring one.
|
|
1010
|
+
- Fork tests cover the resolution, the unknown-turn error and the mutual exclusion; the
|
|
1011
|
+
acceptance run forks at turn 1 and checks the child's inherited prefix. Both READMEs
|
|
1012
|
+
updated.
|
|
1013
|
+
|
|
1014
|
+
## 0.47.0
|
|
1015
|
+
|
|
1016
|
+
- `session_messages` accepts `fromTurn`/`toTurn`: read one phase of a long transcript
|
|
1017
|
+
by turn range, matching the `fromTurn`/`toTurn` receipt `session_usage` now offers.
|
|
1018
|
+
Attribution follows the existing rule — assistant and tool rows carry `turn`; the
|
|
1019
|
+
user prompt has no turn number and is not part of a turn-attributed read.
|
|
1020
|
+
- Driver and endpoint tests cover the range and its forwarding; the acceptance run
|
|
1021
|
+
compares a range read with the exact-turn read (same rows). Both READMEs updated.
|
|
1022
|
+
|
|
1023
|
+
## 0.46.0
|
|
1024
|
+
|
|
1025
|
+
- `session_usage` gained `fromTurn`/`toTurn`: fold only the turns in that inclusive
|
|
1026
|
+
range, so a caller can bill a task phase (say "the turns after the review started")
|
|
1027
|
+
instead of the whole session. The range is echoed as `turnRange`, totals/byModel/
|
|
1028
|
+
byTurn describe only those turns, and — like a `sinceSeq` window — the report is not
|
|
1029
|
+
projection-verified. An inverted range is refused with the inclusive-interval rule.
|
|
1030
|
+
- Fold and driver tests cover the subset totals, the echoed range, the single-turn
|
|
1031
|
+
case and the inverted-range refusal; the acceptance run bills the session's last
|
|
1032
|
+
turn as a one-turn phase (19 398 of ~77 500 tokens). The first live run returned the
|
|
1033
|
+
whole tally and exposed that the new arguments had been wired into the
|
|
1034
|
+
`session_messages` handler/schema rather than `session_usage` — caught before release
|
|
1035
|
+
and fixed. Both READMEs updated.
|
|
1036
|
+
|
|
1037
|
+
## 0.45.0
|
|
1038
|
+
|
|
1039
|
+
- `session_usage` gained `sinceSeq`: fold each row from that event seq onward, for an
|
|
1040
|
+
incremental receipt on a long session (or a segment of it) without re-reading spend
|
|
1041
|
+
already billed. The window is echoed as `sinceSeq`; `verifiedAgainstProjection` is
|
|
1042
|
+
false and no `projectionTotals` is emitted, because the harness projection covers the
|
|
1043
|
+
whole log and comparing a window with it would be meaningless. A window starting
|
|
1044
|
+
before a fork's inherited prefix is clamped to the prefix.
|
|
1045
|
+
- Driver test covers the full-vs-segment totals, the echoed window, the per-turn
|
|
1046
|
+
subset and the projection caveat; the acceptance run checks a real segment against
|
|
1047
|
+
the full tally. Both READMEs updated.
|
|
1048
|
+
|
|
1049
|
+
## 0.44.0
|
|
1050
|
+
|
|
1051
|
+
- Per-turn usage rows now also carry `models`: the `provider/model` routes that charged
|
|
1052
|
+
that turn, sorted, so a per-round bill can show which model spent the tokens (a route
|
|
1053
|
+
that changed mid-turn is exactly what a bare total hides). A turn whose only samples
|
|
1054
|
+
were unattributed retries carries none rather than an empty list.
|
|
1055
|
+
- Usage-fold test covers per-turn routes including the unattributed case; the
|
|
1056
|
+
acceptance run asserts real turns name their routes. Both READMEs updated.
|
|
1057
|
+
|
|
1058
|
+
## 0.43.0
|
|
1059
|
+
|
|
1060
|
+
- `session_usage { includeTurns: true }` per-turn rows now carry `durationMs` (from the
|
|
1061
|
+
turn's start to its end event) alongside their token buckets, so a caller can bill
|
|
1062
|
+
per turn and see which turn was slow. Absent when the log lacks a boundary inside the
|
|
1063
|
+
folded window (an inherited prefix, a turn still running) instead of guessed.
|
|
1064
|
+
- Usage-fold test covers the settled and still-running cases; the acceptance run
|
|
1065
|
+
asserts the real session's settled turn carries a duration. Both READMEs updated.
|
|
1066
|
+
|
|
1067
|
+
## 0.42.0
|
|
1068
|
+
|
|
1069
|
+
- `session_messages { includeToolResults: "errors" }` keeps only the tool results the
|
|
1070
|
+
harness marked as failures, so finding why a turn went wrong is one read instead of
|
|
1071
|
+
scanning every result. `true` still returns all rows; the boolean form is unchanged.
|
|
1072
|
+
- Driver test covers the filter; the acceptance run compares the returned failures
|
|
1073
|
+
with the error flags in the full result list. Both READMEs describe it.
|
|
1074
|
+
|
|
1075
|
+
## 0.41.0
|
|
1076
|
+
|
|
1077
|
+
- `session_messages` rows now carry their `turn` and `step` when the log attributes
|
|
1078
|
+
them, and `turn: N` narrows a read to one turn. Since assistant and tool rows are the
|
|
1079
|
+
ones the log attributes (`turn`/`step` on the event), a turn-filtered read is the
|
|
1080
|
+
turn's own rows; the user prompt has no turn number and is documented as excluded
|
|
1081
|
+
rather than guessed. Useful for "what did turn 3 do" and for grouping a long
|
|
1082
|
+
transcript without re-reading it all.
|
|
1083
|
+
- Driver and endpoint tests cover attribution and the filter; the acceptance run
|
|
1084
|
+
asserts real tool rows carry turn/step and that a turn-filtered read stays inside
|
|
1085
|
+
that turn. Both READMEs describe the fields.
|
|
1086
|
+
|
|
1087
|
+
## 0.40.0
|
|
1088
|
+
|
|
1089
|
+
- `session_messages` gained `includeToolResults`: each recorded `tool/result` becomes a
|
|
1090
|
+
`role: "tool"` row with `callId`, `text` and `error`, capped by `maxCharsPerMessage`
|
|
1091
|
+
like any other message. `includeToolCalls` says what was invoked and how large the
|
|
1092
|
+
outcome was; this says what the outcome *said*, so one read can reconstruct the whole
|
|
1093
|
+
timeline instead of pairing `session_tool_result` lookups with the transcript. Off by
|
|
1094
|
+
default, because a result can be as large as a file.
|
|
1095
|
+
- Covered by driver and endpoint tests and the acceptance run (real bash results appear
|
|
1096
|
+
as capped tool rows); both READMEs describe the option.
|
|
1097
|
+
|
|
1098
|
+
## 0.39.0
|
|
1099
|
+
|
|
1100
|
+
- `session_prompt`'s turn receipt now carries `turn.durationMs`: wall-clock time from
|
|
1101
|
+
the turn's start event to its end event. Tokens say what a turn cost; this says how
|
|
1102
|
+
long the caller waited, which is what a timeout or an SLO is tuned against. It is
|
|
1103
|
+
absent when the turn is still running at the wait boundary (no honest duration), and
|
|
1104
|
+
the driver test pins both cases. The first acceptance run reported no duration and
|
|
1105
|
+
exposed an ordering detail worth knowing: DSH commits `turn/start` *before* the user
|
|
1106
|
+
message, so the start event sits outside the transcript window — the summary now
|
|
1107
|
+
walks back to the owning turn's start, and the live run reports the real turn
|
|
1108
|
+
(3 489 ms for the FizzBuzz turn).
|
|
1109
|
+
- Both READMEs mention the field in the `session_prompt` row.
|
|
1110
|
+
|
|
1111
|
+
## 0.38.6
|
|
1112
|
+
|
|
1113
|
+
- `tools/live-checks.mjs` checks that `dsh_info` agrees with the surface it describes:
|
|
1114
|
+
the reported version equals this checkout's package version (so a stale installed
|
|
1115
|
+
build is caught instead of silently verified), the endpoint echo matches the URL the
|
|
1116
|
+
caller reached, `toolAvailability` names exactly the advertised tools with a reason
|
|
1117
|
+
for each unavailable one, `enabledToolGroups` and `harnessServices` are boolean maps
|
|
1118
|
+
covering every group/service, health reports the runtime and a live RSS figure, the
|
|
1119
|
+
effective session scope is echoed, and the caveats come back as notes.
|
|
1120
|
+
|
|
1121
|
+
## 0.38.5
|
|
1122
|
+
|
|
1123
|
+
- `tools/live-checks.mjs` checks the shell deadline and the job table: `shell_run`
|
|
1124
|
+
returns at its deadline with `timedOut: true` (~706 ms for a 5 s command), a quick
|
|
1125
|
+
command returns its output with `timedOut: false`, the 17th start evicts the oldest
|
|
1126
|
+
*finished* job and succeeds, and once 16 jobs are *running* the next start is
|
|
1127
|
+
refused with the "does not silently stop" explanation. Both README tables now state
|
|
1128
|
+
the retention rule.
|
|
1129
|
+
|
|
1130
|
+
## 0.38.4
|
|
1131
|
+
|
|
1132
|
+
- `tools/live-checks.mjs` covers the listing and image limits: a non-recursive listing
|
|
1133
|
+
is one level, a recursive one reaches nested files, `maxDepth` bounds the walk,
|
|
1134
|
+
listing a file is a legible error, a real 1x1 PNG comes back as a metadata text
|
|
1135
|
+
block plus an MCP image block, a text file is refused as "not an image", a missing
|
|
1136
|
+
path says so, and a 9 MiB file is refused with the 8 MiB cap named. Both READMEs
|
|
1137
|
+
note the metadata block that precedes the image.
|
|
1138
|
+
|
|
1139
|
+
## 0.38.3
|
|
1140
|
+
|
|
1141
|
+
- `file_read`'s description now says precisely what a mid-character window does: each
|
|
1142
|
+
orphaned byte decodes as one U+FFFD (a 3-byte character started one byte early yields
|
|
1143
|
+
two), and the rest stays aligned — the old wording implied a single replacement
|
|
1144
|
+
character. Found by the new live paging check, which also asserts that a capped read
|
|
1145
|
+
reports `truncated`/`bytes`/`nextOffset` and that the next window completes the file
|
|
1146
|
+
byte-for-byte text.
|
|
1147
|
+
|
|
1148
|
+
## 0.38.2
|
|
1149
|
+
|
|
1150
|
+
- `tools/live-checks.mjs` gained a prompt-input section: an empty prompt is refused
|
|
1151
|
+
with the accepted shapes, a non-image attachment is refused by name, more images
|
|
1152
|
+
than the cap is refused before any file is read, and a refused prompt is never
|
|
1153
|
+
half-admitted (the transcript does not grow). No model turn is spent.
|
|
1154
|
+
|
|
1155
|
+
## 0.38.1
|
|
1156
|
+
|
|
1157
|
+
- **Fixed silent model-selection handling in `session_create`.** Asking for only
|
|
1158
|
+
`provider` or only `model` used to create a session on the default route and hide
|
|
1159
|
+
the mistake in a log line (and could even pair a requested provider with a
|
|
1160
|
+
configured model). The schema says the two are paired, so a half pair is now
|
|
1161
|
+
refused *before* the session exists. A half pair in the *profile settings* still
|
|
1162
|
+
creates the session but reports `modelSelectionError`, so the reply stops hiding the
|
|
1163
|
+
reason. The tool description and both READMEs state the contract; driver tests
|
|
1164
|
+
cover both cases, and live-checks assert the error quality (half pair, missing
|
|
1165
|
+
directory, unroutable pair still creating the session with the route explanation,
|
|
1166
|
+
unknown preset).
|
|
1167
|
+
|
|
1168
|
+
## 0.38.0
|
|
1169
|
+
|
|
1170
|
+
- `session_fork`'s description (and both READMEs) now state the precondition the
|
|
1171
|
+
live check found: the source needs at least one completed turn. A session that has
|
|
1172
|
+
never answered has no prefix to branch from and the harness refuses it with
|
|
1173
|
+
"has no completed turn to fork from" — accurate, but previously undocumented, so a
|
|
1174
|
+
caller forking a freshly created session was surprised. The live check now asserts
|
|
1175
|
+
that refusal is legible; the positive fork path stays covered by the acceptance and
|
|
1176
|
+
smoke runs, which fork a session that has answered.
|
|
1177
|
+
- `tools/live-checks.mjs` gained a fork/rename section: titles are stored trimmed and
|
|
1178
|
+
come back through `session_list`, an unknown session id is a legible error, and an
|
|
1179
|
+
empty title is refused before it reaches the harness.
|
|
1180
|
+
|
|
1181
|
+
## 0.37.14
|
|
1182
|
+
|
|
1183
|
+
- `tools/live-checks.mjs` checks the `dsh_tool_list`/`dsh_tool_call` promises: the
|
|
1184
|
+
list is real for a session (15 agent tools here), an unlisted name is refused with
|
|
1185
|
+
the `agentTools.allow` hint, a listed tool with empty arguments fails with the
|
|
1186
|
+
tool's own validation message rather than a transport error, and a 30 KB `bash`
|
|
1187
|
+
result comes back capped at `maxResultChars` with `truncated`, `resultBytes` and no
|
|
1188
|
+
`value`.
|
|
1189
|
+
|
|
1190
|
+
## 0.37.13
|
|
1191
|
+
|
|
1192
|
+
- `tools/live-checks.mjs` checks the workspace promises: the same path registers once
|
|
1193
|
+
(returns the existing id), `workspace_list` reports path/session count/existence,
|
|
1194
|
+
`workspace_delete` removes only the registration and leaves the directory and files
|
|
1195
|
+
untouched, an unknown id answers `deleted: false` instead of failing, and
|
|
1196
|
+
`createDirectory: false` refuses a missing directory without creating it.
|
|
1197
|
+
|
|
1198
|
+
## 0.37.12
|
|
1199
|
+
|
|
1200
|
+
- `tools/live-checks.mjs` checks the file-tool promises that are easiest to get wrong:
|
|
1201
|
+
an ambiguous `oldText` is refused *and writes nothing*, `replaceAll` replaces every
|
|
1202
|
+
occurrence, `createDirectories: false` fails before creating the parent, and a
|
|
1203
|
+
relative path resolves against `cwd` for read as well as write. Cheap section, no
|
|
1204
|
+
model turn.
|
|
1205
|
+
|
|
1206
|
+
## 0.37.11
|
|
1207
|
+
|
|
1208
|
+
- **Fixed `shell_output.lossy`, which could never be true.** `drainShellJob` recorded a
|
|
1209
|
+
shell-service loss only as `truncated` and never set `job.lossy`, so a page for a job
|
|
1210
|
+
whose bytes the service had already dropped reported `truncated: true, lossy: false`
|
|
1211
|
+
— exactly the ambiguous receipt the field exists to prevent. Found by the new
|
|
1212
|
+
retention check: a 200 KiB writer came back as one 64 KiB page with no way to tell
|
|
1213
|
+
*why* the rest was missing. The flag is now persisted, a regression test covers it,
|
|
1214
|
+
and the live check requires both flags for that case.
|
|
1215
|
+
|
|
1216
|
+
## 0.37.10
|
|
1217
|
+
|
|
1218
|
+
- `tools/live-checks.mjs` checks the background-shell retention claim: a job that
|
|
1219
|
+
writes 200 KiB in one go must come back bounded to the retained 128 KiB buffer,
|
|
1220
|
+
report `truncated`, and finish reading; an unbounded or silently lossy job fails
|
|
1221
|
+
the check. Cheap section, no model turn.
|
|
1222
|
+
|
|
1223
|
+
## 0.37.9
|
|
1224
|
+
|
|
1225
|
+
- `tools/live-checks.mjs --with-model` now checks the claim `session_cancel` makes
|
|
1226
|
+
("queued work is preserved"): it queues a second message behind a long turn,
|
|
1227
|
+
cancels, and asserts the message is still pending and then runs. The withdrawn
|
|
1228
|
+
message from the same section must still never run.
|
|
1229
|
+
|
|
1230
|
+
## 0.37.8
|
|
1231
|
+
|
|
1232
|
+
- The `initialize` instructions now cover the surface added since they were written:
|
|
1233
|
+
`toolAvailability` before planning a sequence, the inspection trio
|
|
1234
|
+
(`includeToolCalls` / `session_tool_result` / `session_list origin`),
|
|
1235
|
+
`session_search` and its opt-in index, and `session_queue`/`session_cancel` with the
|
|
1236
|
+
`session.scope: "owned"` caveat. Instructions that stop at the first three tools
|
|
1237
|
+
leave the rest undiscovered. The endpoint test now asserts the phrases.
|
|
1238
|
+
|
|
1239
|
+
## 0.37.7
|
|
1240
|
+
|
|
1241
|
+
- `session_messages` now carries the admission `requestId` on user messages (the same
|
|
1242
|
+
id `idempotencyKey` derives and the log records as `source.rpcId`). A caller can
|
|
1243
|
+
correlate its own prompts and retries with transcript entries instead of matching
|
|
1244
|
+
on text; the acceptance run checks the awaited prompt's id.
|
|
1245
|
+
|
|
1246
|
+
## 0.37.6
|
|
1247
|
+
|
|
1248
|
+
- **Fixed a real duplicate-send race in idempotent retries.** `wait: false` returns
|
|
1249
|
+
when `controller.prompt()` resolves, but the durable user message can be committed
|
|
1250
|
+
a moment later; a retry inside that window did not find its own log entry and asked
|
|
1251
|
+
the agent to do the work twice. The acceptance check for "queue then await" caught
|
|
1252
|
+
it as two user messages carrying the same `rpcId`. The driver now keeps a small
|
|
1253
|
+
in-memory ledger of admissions it made (bounded, 24 h / 200 keys) as a fallback
|
|
1254
|
+
when the log has not caught up; the log remains the path for retries after a
|
|
1255
|
+
restart. A regression test defers the commit and asserts the retry waits instead of
|
|
1256
|
+
re-sending.
|
|
1257
|
+
|
|
1258
|
+
## 0.37.5
|
|
1259
|
+
|
|
1260
|
+
- Documented and verified the intended way to await queued work: queue a prompt with
|
|
1261
|
+
`wait: false` plus an `idempotencyKey`, then call again with the same key and
|
|
1262
|
+
`wait: true`. The retry replays the admission and *waits* for that turn instead of
|
|
1263
|
+
sending a second prompt. The tool description and both READMEs now say so, a driver
|
|
1264
|
+
test covers the pending-turn replay, and the acceptance run checks the reply and
|
|
1265
|
+
that only one such user prompt exists in the transcript.
|
|
1266
|
+
|
|
1267
|
+
## 0.37.4
|
|
1268
|
+
|
|
1269
|
+
- `tools/live-checks.mjs --with-model` gained an orchestration section: start a long
|
|
1270
|
+
turn with `wait: false`, queue work behind it, list and withdraw that queued
|
|
1271
|
+
message, cancel the running turn, then prompt the session again. Each tool exists
|
|
1272
|
+
on its own; this asserts they compose on one session without corrupting it (the
|
|
1273
|
+
follow-up prompt answers, and the withdrawn message never ran).
|
|
1274
|
+
|
|
1275
|
+
## 0.37.3
|
|
1276
|
+
|
|
1277
|
+
- `tools/live-checks.mjs --with-model` also runs two real turns in two independent
|
|
1278
|
+
sessions at once, and asserts each answered its own prompt, neither was aborted by
|
|
1279
|
+
the other, and each transcript kept its own conversation (a mixed-up correlation
|
|
1280
|
+
would show up as the other session's marker in the transcript). Serialization is
|
|
1281
|
+
per session, and a harness driving several tasks relies on that.
|
|
1282
|
+
|
|
1283
|
+
## 0.37.2
|
|
1284
|
+
|
|
1285
|
+
- `tools/live-checks.mjs` gained a concurrency section: 20 parallel `dsh_info`
|
|
1286
|
+
calls must all answer with one version, 8 parallel file writes then reads must
|
|
1287
|
+
stay consistent, and 12 parallel `session_list` calls must agree on the total.
|
|
1288
|
+
It is not a benchmark — it asserts that the shared-session and shared-file paths
|
|
1289
|
+
do not degrade into failures under parallel callers, which is how an external
|
|
1290
|
+
harness or an agent team actually drives the endpoint.
|
|
1291
|
+
|
|
1292
|
+
## 0.37.1
|
|
1293
|
+
|
|
1294
|
+
- `session_tool_result` gained `from: "end"`: when a result exceeds `maxChars` the
|
|
1295
|
+
tail is kept instead of the head, which is where a stack trace or the closing error
|
|
1296
|
+
lives. The reply echoes `from: "end"` so a caller can tell which end it holds.
|
|
1297
|
+
Surrogate pairs are not split. Acceptance compares the tail with the full result's
|
|
1298
|
+
last characters, and the driver test covers the cut.
|
|
1299
|
+
|
|
1300
|
+
## 0.37.0
|
|
1301
|
+
|
|
1302
|
+
- `session_tool_result` can now address a result by `toolName` as well as `callId`:
|
|
1303
|
+
it returns the most recent completed call of that tool and names the resolved call
|
|
1304
|
+
id. A caller watching a session usually knows "the last bash result is what I
|
|
1305
|
+
need" without tracking ids; an unknown name lists the tools that do have recorded
|
|
1306
|
+
calls, and passing both selectors (or neither) is refused.
|
|
1307
|
+
- Covered by driver tests and the acceptance run (by-id and by-name fetch of a real
|
|
1308
|
+
bash result); both READMEs updated.
|
|
1309
|
+
|
|
1310
|
+
## 0.36.1
|
|
1311
|
+
|
|
1312
|
+
- `tools/live-checks.mjs --with-subagent` also exercises the debug path end to end:
|
|
1313
|
+
it reads the subagent's transcript with `includeToolCalls`, takes a completed call
|
|
1314
|
+
id, fetches that result with `session_tool_result`, and checks the returned length
|
|
1315
|
+
equals the transcript's `resultChars`. Skips cleanly when the child made no call.
|
|
1316
|
+
|
|
1317
|
+
## 0.36.0
|
|
1318
|
+
|
|
1319
|
+
- **`session_tool_result`** (28th tool): read one recorded tool result by call id —
|
|
1320
|
+
the text, its original length, whether the harness marked it an error, and a
|
|
1321
|
+
`maxChars` cap. `includeToolCalls` already says how large a result was; this is the
|
|
1322
|
+
targeted follow-up for debugging the failure itself, without re-reading a long
|
|
1323
|
+
transcript. The call id comes from that transcript, so the lookup is bounded and
|
|
1324
|
+
scoped to one call.
|
|
1325
|
+
- Availability map, both README tool tables, the capability audit and the acceptance
|
|
1326
|
+
script's expected-tool list are updated; the exercise now fetches a real bash
|
|
1327
|
+
result by id and checks its length against the transcript's `resultChars`.
|
|
1328
|
+
|
|
1329
|
+
## 0.35.0
|
|
1330
|
+
|
|
1331
|
+
- `session_search` hits now carry `parentSessionId` and `origin: "subagent"` when the
|
|
1332
|
+
matched session is an agent child, from the same listing the enrichment already
|
|
1333
|
+
performs. A search result now says where in the tree it sits, not just which
|
|
1334
|
+
workspace and how recent.
|
|
1335
|
+
- Verified with a real subagent and the search index enabled (live-checks
|
|
1336
|
+
--with-subagent): the child's "SUBAGENT-OK" hit carried its parent.
|
|
1337
|
+
|
|
1338
|
+
## 0.34.0
|
|
1339
|
+
|
|
1340
|
+
- `session_list` gained an `origin` filter (`root` | `subagent`), applied before the
|
|
1341
|
+
limit so `total` stays honest. With the parentage fields this completes the tree
|
|
1342
|
+
view: "which sessions did I start" and "which are my agents' children", without
|
|
1343
|
+
reading transcripts.
|
|
1344
|
+
- Verified live with a real spawned subagent: `origin: "subagent"` returned only the
|
|
1345
|
+
child, `origin: "root"` only the parent (live-checks --with-subagent).
|
|
1346
|
+
|
|
1347
|
+
## 0.33.0
|
|
1348
|
+
|
|
1349
|
+
- `session_list` exposes parentage: a subagent session carries `parentSessionId` and
|
|
1350
|
+
`origin: "subagent"`, so a caller can reconstruct the agent tree from the flat list
|
|
1351
|
+
without reading each transcript. The fields come from the same session listing the
|
|
1352
|
+
call already performs, so there is no extra read.
|
|
1353
|
+
- Verified live with a real spawned subagent: the child row named the parent and
|
|
1354
|
+
origin, the root row carried neither (live-checks --with-subagent).
|
|
1355
|
+
|
|
1356
|
+
## 0.32.1
|
|
1357
|
+
|
|
1358
|
+
- `session_messages { includeToolCalls: true }` now also reports each call's
|
|
1359
|
+
outcome once it exists: `resultChars` (textual result length) and `error` when the
|
|
1360
|
+
harness marked the result as an error. A call whose tool has not answered yet
|
|
1361
|
+
carries neither, which is how a poller can tell "still running" from "done". The
|
|
1362
|
+
results are correlated by call id in one pass over the log, so a read stays O(events)
|
|
1363
|
+
and the arguments/results themselves are measured, not repeated.
|
|
1364
|
+
|
|
1365
|
+
## 0.32.0
|
|
1366
|
+
|
|
1367
|
+
- `session_messages` gained `includeToolCalls`: each assistant message then carries
|
|
1368
|
+
the tool calls the model requested (name, call id, argument JSON length). Text
|
|
1369
|
+
alone says "I'll run it"; the call list is what makes a poll of a running session
|
|
1370
|
+
actionable, and it stays bounded because arguments are measured, not repeated.
|
|
1371
|
+
- The first acceptance run failed this check with 0 calls, which exposed a real bug:
|
|
1372
|
+
a model message that only requests tools has no text, and the transcript loop had
|
|
1373
|
+
been dropping text-less messages — so tool-call-only steps never appeared. With
|
|
1374
|
+
`includeToolCalls` such a message is now kept (empty text, calls attached); without
|
|
1375
|
+
it, the historical prose transcript is unchanged. Covered by a driver regression
|
|
1376
|
+
test, and the acceptance run now sees 4 calls.
|
|
1377
|
+
|
|
1378
|
+
## 0.31.2
|
|
1379
|
+
|
|
1380
|
+
- `tools/live-checks.mjs` is now usable against older deployments: every check is
|
|
1381
|
+
gated on the endpoint's own `tools/list` (a build without `shell_start` or
|
|
1382
|
+
`session_search` reports those sections as skipped), and `--old-endpoint` turns a
|
|
1383
|
+
contract mismatch into a skip too. Against the desktop app's 0.2.0 build it
|
|
1384
|
+
reports 3 protocol checks passing and 4 skips — the Bearer challenge,
|
|
1385
|
+
`total`/`truncated`, paging metadata and workspace-scope usage — which is exactly
|
|
1386
|
+
the list of contracts that build predates. A strict run against the current build
|
|
1387
|
+
passes 16/16, so the gating does not hide a real regression.
|
|
1388
|
+
|
|
1389
|
+
## 0.31.1
|
|
1390
|
+
|
|
1391
|
+
- `dsh_info.toolAvailability` now also names the tools that passed
|
|
1392
|
+
(`availableTools`), so a caller can compare it with `tools/list` instead of
|
|
1393
|
+
trusting a count. It is the same set — disabled groups are absent from both, and
|
|
1394
|
+
group-enabled tools missing a service appear only in `unavailable` — which
|
|
1395
|
+
`tools/live-checks.mjs` now asserts at runtime (skipping endpoints that predate
|
|
1396
|
+
the field).
|
|
1397
|
+
- Unit and endpoint tests cover the new list; the count and the list length agree.
|
|
1398
|
+
|
|
1399
|
+
## 0.31.0
|
|
1400
|
+
|
|
1401
|
+
- `dsh_info` now reports **`toolAvailability`**: for every advertised tool, whether
|
|
1402
|
+
this deployment can serve it and, when not, the concrete reason (a disabled tool
|
|
1403
|
+
group, or the missing service — `session_queue` without `updateQueue`, background
|
|
1404
|
+
shell tools without a `start()`/`execute()` handle, `dsh_tool_*` without `agents`,
|
|
1405
|
+
...). The dependency map lives in `src/mcp/availability.ts` next to the probes it
|
|
1406
|
+
consumes, so it cannot drift from the capability names, and it is unit-tested for
|
|
1407
|
+
each rule plus the notes (search index caveat, empty allow-list).
|
|
1408
|
+
- The raw `harnessServices` booleans are unchanged; this is a pre-flight aid for
|
|
1409
|
+
callers planning calls, not an authorization layer.
|
|
1410
|
+
|
|
1411
|
+
## 0.30.9
|
|
1412
|
+
|
|
1413
|
+
- `tools/check-docs.mjs` now checks the settings panel's localization: the zh and
|
|
1414
|
+
en dictionaries must have the same key set, every key the panel references must
|
|
1415
|
+
exist in both, and unreferenced ("dead") keys are listed. Two parsing bugs were
|
|
1416
|
+
found and fixed while writing it: the zh scan swallowed the en block, which hid a
|
|
1417
|
+
missing zh key, and runtime-composed keys (`client.tokenSource.${source}`,
|
|
1418
|
+
`state.*`) were not counted as referenced.
|
|
1419
|
+
- Three genuinely dead keys were removed from both dictionaries
|
|
1420
|
+
(`field.approval.policy.allow.hint`, `field.auth.token.replace`,
|
|
1421
|
+
`client.statusLabel`). Negative-tested: deleting a zh key or misspelling a field
|
|
1422
|
+
label each make the check exit 1.
|
|
1423
|
+
|
|
1424
|
+
## 0.30.8
|
|
1425
|
+
|
|
1426
|
+
- `tools/check-docs.mjs` now checks release claims: changelog headings must be
|
|
1427
|
+
unique and strictly newest-first, every version cited in
|
|
1428
|
+
`docs/LIVE-VERIFICATION.md` must have a changelog entry, and the tool count
|
|
1429
|
+
restated in `docs/CAPABILITY-GAPS.md` must equal what the code registers.
|
|
1430
|
+
- It found one real drift: the verification record cited `0.1.0`, which predates
|
|
1431
|
+
the changelog (whose oldest entry is 0.2.0) — the two rows now read
|
|
1432
|
+
"≤0.2.0 (first documented release)". Negative-tested with a bogus 9.9.9
|
|
1433
|
+
citation: exit 1 naming it.
|
|
1434
|
+
|
|
1435
|
+
## 0.30.7
|
|
1436
|
+
|
|
1437
|
+
- `tools/check-docs.mjs` now also checks configuration: each README's YAML sample
|
|
1438
|
+
must set exactly the keys `normalizeConfig` accepts (minus the optional
|
|
1439
|
+
`auth.token`), and the settings panel must expose a field for every one of those
|
|
1440
|
+
keys and no field for an unknown key. Two parsing bugs surfaced while writing it
|
|
1441
|
+
and were fixed (the root `config` guard was read as a section, and the section /
|
|
1442
|
+
panel regexes only accepted lowercase names, so `agentTools` looked absent).
|
|
1443
|
+
Negative-tested: deleting `session.scope` from the sample makes the check exit 1
|
|
1444
|
+
naming it.
|
|
1445
|
+
|
|
1446
|
+
## 0.30.6
|
|
1447
|
+
|
|
1448
|
+
- Added `tools/check-docs.mjs` and a `docs consistency` step in
|
|
1449
|
+
`tools/release.sh`. It compares the two README tool tables with the tools the
|
|
1450
|
+
code registers, the package version with the newest changelog heading, the
|
|
1451
|
+
shipped `files` list with what exists on disk (and with maintainer-only
|
|
1452
|
+
directories), and every `docs/*.md` / script path the READMEs reference.
|
|
1453
|
+
- It immediately found real drift that prose review had missed: the en tool table
|
|
1454
|
+
was missing `session_queue` (the row edit had silently not applied), and the zh
|
|
1455
|
+
table had 24 rows, a duplicated `session_create`, and seven missing tools
|
|
1456
|
+
(`workspace_delete`, `file_edit`, `file_read_image`, `shell_start`,
|
|
1457
|
+
`shell_output`, `shell_kill`, `shell_list`). Both tables now list all 27, and
|
|
1458
|
+
the check was negative-tested (renaming a row makes it exit 1).
|
|
1459
|
+
|
|
1460
|
+
## 0.30.5
|
|
1461
|
+
|
|
1462
|
+
- `tools/release.sh` gained `--acceptance`: it boots a throwaway profile under a
|
|
1463
|
+
temporary `DSH_HOME` (OS-chosen free ports, isolated session store, the
|
|
1464
|
+
operator's provider credentials copied into the 0700 directory so real turns
|
|
1465
|
+
work), runs the full acceptance script against it, and tears everything down
|
|
1466
|
+
through an EXIT trap. Verified live: 142/142 checks, cwd runs, no leftover
|
|
1467
|
+
profile, process or `/tmp` directory.
|
|
1468
|
+
|
|
1469
|
+
## 0.30.4
|
|
1470
|
+
|
|
1471
|
+
- Added `tools/release.sh`: the release checklist as an executable (both
|
|
1472
|
+
typechecks, tests, both bundles, pack, an assertion that the tarball contains no
|
|
1473
|
+
maintainer-only directory, optional live checks with `--live`, optional desktop
|
|
1474
|
+
install with a rollback directory). It refuses to reinstall the version already
|
|
1475
|
+
in the desktop profile, because pnpm treats the same tarball path as
|
|
1476
|
+
already-installed — the trap that made a rebuilt plugin look installed while the
|
|
1477
|
+
profile still ran the previous build.
|
|
1478
|
+
|
|
1479
|
+
## 0.30.3
|
|
1480
|
+
|
|
1481
|
+
- Verified the last outstanding accounting question against a real harness: the
|
|
1482
|
+
agent spawned a subagent, `session_usage { includeSubagents: true }` returned it
|
|
1483
|
+
as its own row (parent 27,419 tokens, subagent 18,567) with the report total
|
|
1484
|
+
equal to their sum, and under `session.scope: "owned"` the subagent session was
|
|
1485
|
+
listed, billed and readable as part of the owned tree.
|
|
1486
|
+
- `tools/live-checks.mjs` gained `--with-subagent`, so that check is repeatable
|
|
1487
|
+
instead of a one-off probe: it spawns a helper, asserts the reply, the
|
|
1488
|
+
subagent row, the totals identity, and the tree access (23/23 with the model
|
|
1489
|
+
checks on). The desktop composition remains the one unverified path.
|
|
1490
|
+
|
|
1491
|
+
## 0.30.2
|
|
1492
|
+
|
|
1493
|
+
- `session.scope: "owned"` now treats **subagent sessions below an owned session**
|
|
1494
|
+
as part of the owned tree: listing, search, usage by id and transcript reads
|
|
1495
|
+
accept a child whose ancestor this endpoint created, and still refuse anything
|
|
1496
|
+
outside the tree. Subagents are the harness's own descendants of the session, so
|
|
1497
|
+
excluding them made an owned session's bill and listing quietly omit work it
|
|
1498
|
+
caused. The tree is resolved once per call from the session listing (no extra
|
|
1499
|
+
reads per row), and a listing failure fails closed.
|
|
1500
|
+
- `cancelSession` became async (the visibility check may need the listing), so its
|
|
1501
|
+
tool handler awaits it; a reply that used to be a bare `{accepted:true}` is
|
|
1502
|
+
unchanged.
|
|
1503
|
+
|
|
1504
|
+
## 0.30.1
|
|
1505
|
+
|
|
1506
|
+
- `session.scope: "owned"` now remembers what it created: created and forked
|
|
1507
|
+
session ids are written to `$DSH_HOME/dsh-as-mcp/owned.json` (`0600`, directory
|
|
1508
|
+
`0700`, newest 1000 ids, atomic tmp + rename), so ownership survives a plugin
|
|
1509
|
+
reload or DSH restart instead of lasting one process. A missing or corrupt
|
|
1510
|
+
record reads as "nothing is owned" — fail-closed, it can only hide sessions.
|
|
1511
|
+
Tests now run under their own temporary `DSH_HOME`, so a suite run can no longer
|
|
1512
|
+
write this record into the developer's real `~/.dsh`.
|
|
1513
|
+
|
|
1514
|
+
## 0.30.0
|
|
1515
|
+
|
|
1516
|
+
- **`session.scope`** (`all` | `owned`, default `all`): the containment option the
|
|
1517
|
+
security audit's F4/F11 asked for without changing the default. Under `owned`,
|
|
1518
|
+
every content-bearing session tool — prompt/steer, cancel, list, transcript,
|
|
1519
|
+
fork, rename, set-model, queue list/mutation and search — is limited to sessions
|
|
1520
|
+
this endpoint created or forked; a refusal names the setting. Search filters
|
|
1521
|
+
matches instead of failing and reports `hidden`; `session_usage` refuses explicit
|
|
1522
|
+
foreign ids and notes rows it left out of aggregate scopes; `session_list`
|
|
1523
|
+
filters before its limit, so `total` stays honest. Exposed in the settings panel
|
|
1524
|
+
and both READMEs. Two fail-closed consequences are documented: ownership is
|
|
1525
|
+
per plugin process (sessions from before a restart are no longer owned) and
|
|
1526
|
+
search filters the harness's global page of 20 after it arrives (`hidden` says
|
|
1527
|
+
when that page was entirely foreign). Verified live on a `scope: owned` profile
|
|
1528
|
+
— owned prompt worked, four foreign calls were refused, `session_list` returned
|
|
1529
|
+
only the owned session, usage noted the rows it left out, search reported
|
|
1530
|
+
`hidden: 20` — and the acceptance run passed 142/142 under that scope.
|
|
1531
|
+
|
|
1532
|
+
## 0.29.3
|
|
1533
|
+
|
|
1534
|
+
- Security audit caught up with the tools added after it was written. A third
|
|
1535
|
+
review round records two accepted consequences rather than findings:
|
|
1536
|
+
**F11** — session mutation (`prompt`/`steer`, `cancel`, `rename`,
|
|
1537
|
+
`set_model`, `fork`, `session_queue`) is instance-wide, so a token holder can
|
|
1538
|
+
alter sessions it did not create; **F12** — `session_search` discloses bounded
|
|
1539
|
+
transcript snippets from every visible session, which is why search is opt-in at
|
|
1540
|
+
the deployment level and why `dsh_info { probeSearch: true }` reports its state.
|
|
1541
|
+
Both READMEs' security sections now say the reach is not just reading. One
|
|
1542
|
+
unexercised case is recorded: withdrawing a message a *different* client queued.
|
|
1543
|
+
|
|
1544
|
+
## 0.29.2
|
|
1545
|
+
|
|
1546
|
+
- Added `docs/LIVE-VERIFICATION.md`: a record of which behaviours were checked
|
|
1547
|
+
against a real harness, the version that shipped each one, and how to reproduce
|
|
1548
|
+
the check — plus an explicit "not verified here" list (desktop-composition
|
|
1549
|
+
subagent accounting pending the app restart, panel visuals, chunked-body
|
|
1550
|
+
bounding, an observed sandbox refusal) and the reasons. Linked from both
|
|
1551
|
+
READMEs; versions were cross-checked against this changelog rather than
|
|
1552
|
+
remembered.
|
|
1553
|
+
|
|
1554
|
+
## 0.29.1
|
|
1555
|
+
|
|
1556
|
+
- Verified the other two `session_queue` actions against a live harness and
|
|
1557
|
+
documented their semantics: `edit` keeps the queued message id (a caller can
|
|
1558
|
+
keep using the id it listed), and `steer` really reaches the running turn — the
|
|
1559
|
+
steered text appears as a user message in the transcript and the assistant's
|
|
1560
|
+
reply followed it (`STEERED-ACCEPTED`), with the running turn's earlier work
|
|
1561
|
+
intact. No runtime change.
|
|
1562
|
+
|
|
1563
|
+
## 0.29.0
|
|
1564
|
+
|
|
1565
|
+
- **`session_queue`** (27th tool): edit, withdraw or steer one *pending* queued
|
|
1566
|
+
message, addressed by the message id now carried by `session_messages`. A caller
|
|
1567
|
+
that queued work with `wait: false` can retract it before it spends a turn —
|
|
1568
|
+
`session_cancel` stops the running turn, this changes the work waiting behind it.
|
|
1569
|
+
With only `sessionId` it lists the pending inbox from the live `inbox`
|
|
1570
|
+
projection — the only place a queued message exists before its turn starts.
|
|
1571
|
+
The capability is reported as `sessionQueue` in `dsh_info.harnessServices`.
|
|
1572
|
+
|
|
1573
|
+
## 0.28.0
|
|
1574
|
+
|
|
1575
|
+
- `session_messages` gained `beforeSeq`: a transcript longer than the 500-message
|
|
1576
|
+
window is now reachable by paging backwards. Pass the seq of the oldest message
|
|
1577
|
+
you hold and the tail of the older window comes back, so repeated calls walk the
|
|
1578
|
+
whole log without a server-side cursor. `total` keeps counting everything the
|
|
1579
|
+
filters match, so a caller can tell how much further back it can go.
|
|
1580
|
+
|
|
1581
|
+
## 0.27.0
|
|
1582
|
+
|
|
1583
|
+
- `dsh_info { probeSearch: true }` answers "does session search really work here?"
|
|
1584
|
+
instead of only "does the controller expose search": it runs one random-token
|
|
1585
|
+
search (which matches nothing) so a disabled index is reported as
|
|
1586
|
+
`search.available: false` with the enabling instruction. Opt-in because the
|
|
1587
|
+
first search may open and reconcile the index.
|
|
1588
|
+
- `tools/live-checks.mjs` uses that probe and asserts `session_search` agrees with
|
|
1589
|
+
it, for both enabled and disabled deployments.
|
|
1590
|
+
|
|
1591
|
+
## 0.26.0
|
|
1592
|
+
|
|
1593
|
+
- `session_search` hits now carry context: `cwd`, `updatedAt` and `running` from
|
|
1594
|
+
one session listing, so a caller can tell which workspace a match came from and
|
|
1595
|
+
how recent it is without a second call. A listing failure does not fail the
|
|
1596
|
+
search — the matches are still returned, just without that context.
|
|
1597
|
+
|
|
1598
|
+
## 0.25.1
|
|
1599
|
+
|
|
1600
|
+
- Added a **Deployment requirements** section to both READMEs: one table of what
|
|
1601
|
+
each tool group needs (service, and where the capability is configurable —
|
|
1602
|
+
`session-query`'s `openAt`, `costMeter`, `approval`, `webServer`), so an
|
|
1603
|
+
operator can see why a tool is unavailable without reading the source.
|
|
1604
|
+
- `tools/live-checks.mjs` now checks session search too, accepting both legitimate
|
|
1605
|
+
deployments: a fresh token returns no matches when the index is open, and a
|
|
1606
|
+
disabled index reports how to enable it. Verified live on both (stock profile
|
|
1607
|
+
and `openAt: first-search`), 11/11 each.
|
|
1608
|
+
|
|
1609
|
+
## 0.25.0
|
|
1610
|
+
|
|
1611
|
+
- **`session_search`** (26th tool): a literal search over session *message content*
|
|
1612
|
+
that returns each match as `sessionId` plus a snippet, bounded at 20 with
|
|
1613
|
+
`hasMore`. It answers "which conversation discussed this?" without listing,
|
|
1614
|
+
reading and grepping transcripts, and it needs `sessionController.search` (the
|
|
1615
|
+
capability is reported as `sessionSearch` in `dsh_info.harnessServices`).
|
|
1616
|
+
|
|
1617
|
+
## 0.24.3
|
|
1618
|
+
|
|
1619
|
+
- Added `tools/live-checks.mjs` (maintainer-only, not in the published package): a
|
|
1620
|
+
reusable live verification run covering the protocol contract, list/paging
|
|
1621
|
+
contracts, and — with `--with-model` — progress streaming and MCP cancellation.
|
|
1622
|
+
Verified against a real 0.24.2 instance: 10/10 cheap checks and 16/16 with the
|
|
1623
|
+
model checks (progress notifications arriving about a second apart; a
|
|
1624
|
+
cancellation settling the wait in 12 ms with `turn.aborted: true`).
|
|
1625
|
+
|
|
1626
|
+
## 0.24.2
|
|
1627
|
+
|
|
1628
|
+
- Documentation consistency audit against everything verified in the 0.11.0–0.24.1
|
|
1629
|
+
rounds: CAPABILITY-GAPS now records those findings (bounded replies, usage
|
|
1630
|
+
receipt options, list contracts, `workspace_delete`, the `approval.policy`
|
|
1631
|
+
implementation, protocol/cancellation work, the `mountOnWebServer` fix),
|
|
1632
|
+
its service-probe table lists the real twenty probes, and its "the plugin
|
|
1633
|
+
cannot address the session's agent" conclusion is marked closed by
|
|
1634
|
+
`resolveAgent` (which is what made `dsh_tool_call` and `approval.policy`
|
|
1635
|
+
possible). README/README.zh cancellation and `partial` wording now matches the
|
|
1636
|
+
verified behaviour, and the zh design note gained the cancellation sentence the
|
|
1637
|
+
en one already had.
|
|
1638
|
+
|
|
1639
|
+
## 0.24.1
|
|
1640
|
+
|
|
1641
|
+
- Verified the web-server mount against the same protocol behaviours: with
|
|
1642
|
+
`enabled: false, mountOnWebServer: true` the only listener is DSH's own, and
|
|
1643
|
+
progress notifications stream roughly once a second (not batched at the end)
|
|
1644
|
+
while a cancellation notification settled the wait 11 ms after it arrived, with
|
|
1645
|
+
`turn.aborted: true` and the session still usable.
|
|
1646
|
+
|
|
1647
|
+
## 0.24.0
|
|
1648
|
+
|
|
1649
|
+
- **MCP `notifications/cancelled` is now honored.** The SDK version in use
|
|
1650
|
+
accepts the notification and does nothing with it, so a cancelled
|
|
1651
|
+
`session_prompt` used to keep waiting until its turn ended. The endpoint now
|
|
1652
|
+
reads the JSON body itself (passing it to the transport through `parsedBody`),
|
|
1653
|
+
maps in-flight `session_prompt` request ids to their sessions, and cancels the
|
|
1654
|
+
turn when the matching cancellation arrives — the waiting call settles as
|
|
1655
|
+
aborted/partial.
|
|
1656
|
+
- Same layer answers an unparseable body with the JSON-RPC `-32700` parse error
|
|
1657
|
+
and keeps the 64 MiB declared-body refusal.
|
|
1658
|
+
|
|
1659
|
+
## 0.23.2
|
|
1660
|
+
|
|
1661
|
+
- Verified the settings panel's live-write chain end to end (the same path the
|
|
1662
|
+
panel uses, driven by a throwaway plugin that calls `ctx.settings.update`): the
|
|
1663
|
+
write landed in the composed config without a restart, `dsh_info` reported the
|
|
1664
|
+
new `limits.maxReadBytes`, and the very next `file_read` was capped by it
|
|
1665
|
+
(2048 instead of the previous 1 MiB). Unit tests already covered the per-call
|
|
1666
|
+
read; this is the first time the settings service itself was in the loop.
|
|
1667
|
+
- Also confirmed `file_read` paging metadata is complete for a capped read:
|
|
1668
|
+
`offset`, `bytes` (returned), `nextOffset` (continuation) and `size` (the whole
|
|
1669
|
+
file) are all present, so a clamped default read is still continuable.
|
|
1670
|
+
|
|
1671
|
+
## 0.23.1
|
|
1672
|
+
|
|
1673
|
+
- Verified live (not only in unit tests) that tool-group toggles work end to end:
|
|
1674
|
+
with workspace/files/shell/agentTools/usage all off, `tools/list` advertises
|
|
1675
|
+
exactly `dsh_info` plus the session group (9 tools), calling a disabled tool
|
|
1676
|
+
answers JSON-RPC `-32602 Tool ... not found` rather than running and being
|
|
1677
|
+
refused, `dsh_info` reports the toggles, and `session_prompt` replies omit the
|
|
1678
|
+
`session`/`usage` blocks cleanly (`usageUnavailable` is not raised).
|
|
1679
|
+
- Added a test that disables each group in turn and asserts exactly that group's
|
|
1680
|
+
tools leave `tools/list` while every other group stays.
|
|
1681
|
+
|
|
1682
|
+
## 0.23.0
|
|
1683
|
+
|
|
1684
|
+
- `session_usage { cwd }` totals every session that ran in one workspace
|
|
1685
|
+
directory (canonicalised), newest first and bounded by `limit`. Subagent rows
|
|
1686
|
+
fold in like every other scope, an empty directory is explained in `notes`, and
|
|
1687
|
+
mixing it with the id selectors is refused. This is "what did this project
|
|
1688
|
+
cost?" in one call instead of listing then batching.
|
|
1689
|
+
|
|
1690
|
+
## 0.22.0
|
|
1691
|
+
|
|
1692
|
+
- `shell_list` gained a `running` filter (`true` only live jobs, `false` only
|
|
1693
|
+
settled ones). Settled jobs stay in the list until a new start prunes them, so a
|
|
1694
|
+
caller polling one long-lived session had to filter the page itself.
|
|
1695
|
+
- Verified live first: the 16-job cap refuses a 17th start with an explicit
|
|
1696
|
+
message instead of silently evicting a running job, and a job whose output
|
|
1697
|
+
exceeds the 128 KiB buffer reports `truncated`/`lossy` plus the spill paths
|
|
1698
|
+
rather than losing bytes silently.
|
|
1699
|
+
|
|
1700
|
+
## 0.21.4
|
|
1701
|
+
|
|
1702
|
+
- Steer and queue semantics verified live: a `mode: "steer"` message sent into a
|
|
1703
|
+
running turn is folded into that turn (one turn, usage covering the whole turn),
|
|
1704
|
+
while `mode: "queue"` becomes the next turn. Titled docs now also state
|
|
1705
|
+
explicitly that different sessions are not serialized against each other
|
|
1706
|
+
(measured concurrently: 6.3 s for two prompts each blocked on a 4 s tool step),
|
|
1707
|
+
and that cancelling one session leaves another untouched.
|
|
1708
|
+
- Added the missing unit test for per-session serialization: a second prompt is
|
|
1709
|
+
not even submitted until the first wait settles.
|
|
1710
|
+
|
|
1711
|
+
## 0.21.3
|
|
1712
|
+
|
|
1713
|
+
- Timeout boundaries verified live: `shell_run` returns `timedOut: true` with
|
|
1714
|
+
SIGTERM and leaves the shell usable; a `session_prompt` wait timeout returns
|
|
1715
|
+
`timedOut`/`partial` while the turn keeps running, the session stays usable and
|
|
1716
|
+
the final usage settles; `dsh_tool_call` with a small `timeoutMs` aborts the
|
|
1717
|
+
call and the session stays usable.
|
|
1718
|
+
- An agent-tool abort now names the deadline: the harness reports both a client
|
|
1719
|
+
cancellation and our timeout as "tool call aborted", so the reply says
|
|
1720
|
+
"aborted after <N>ms because timeoutMs elapsed" instead.
|
|
1721
|
+
|
|
1722
|
+
## 0.21.2
|
|
1723
|
+
|
|
1724
|
+
- Verified the plugin on a deliberately degraded profile (workspace and
|
|
1725
|
+
session-controller rows disabled): every missing-service tool names the service
|
|
1726
|
+
and the bundle that provides it, while `file_read`, `shell_run` and `dsh_info`
|
|
1727
|
+
keep working. One dead-end was fixed: with no session listing, the empty usage
|
|
1728
|
+
report told the caller to try `all: true`, which cannot work there; it now says
|
|
1729
|
+
to pass a known sessionId instead.
|
|
1730
|
+
|
|
1731
|
+
## 0.21.1
|
|
1732
|
+
|
|
1733
|
+
- `session_messages` reports `total` (messages matched by `sinceSeq`/`role`) and
|
|
1734
|
+
`truncated` when the newest-`limit` window hid older ones. Like `session_list`,
|
|
1735
|
+
the reply could previously not tell a caller whether it had seen the
|
|
1736
|
+
conversation from the start or only its tail.
|
|
1737
|
+
|
|
1738
|
+
## 0.21.0
|
|
1739
|
+
|
|
1740
|
+
- `session_list` reports `total` (matches before the limit) and `truncated` when
|
|
1741
|
+
the limit hid some. A bare list could not distinguish "these are all my
|
|
1742
|
+
sessions" from "the first page of them", which matters to a caller scanning for
|
|
1743
|
+
its sessions before acting.
|
|
1744
|
+
|
|
1745
|
+
## 0.20.7
|
|
1746
|
+
|
|
1747
|
+
- Acceptance covers more filesystem and shell edges, all verified live first:
|
|
1748
|
+
`maxBytes` reads report `truncated`/`nextOffset`; a symlink loop is listed but
|
|
1749
|
+
never followed (a walk that recurses into one never ends); `file_read_image`
|
|
1750
|
+
sniffs the bytes rather than the extension; and `shell_output` does not
|
|
1751
|
+
re-deliver output already read.
|
|
1752
|
+
|
|
1753
|
+
## 0.20.6
|
|
1754
|
+
|
|
1755
|
+
- A 401 now carries `WWW-Authenticate: Bearer realm="dsh-as-mcp"`. Without the
|
|
1756
|
+
challenge a standard MCP client cannot tell that presenting a bearer token is
|
|
1757
|
+
the fix and reports a dead endpoint instead of retrying.
|
|
1758
|
+
- Acceptance gained protocol-contract checks (malformed JSON -> parse error,
|
|
1759
|
+
unknown method -> method-not-found, missing token -> Bearer challenge,
|
|
1760
|
+
declared oversized body -> 413) and model-selection failure checks (a bad
|
|
1761
|
+
route is reported by name with the known routes and the discovery hint).
|
|
1762
|
+
|
|
1763
|
+
## 0.20.5
|
|
1764
|
+
|
|
1765
|
+
- **Fixed `http.mountOnWebServer: true` mounting nothing on a real install.** The
|
|
1766
|
+
route was never registered because the DSH web-server service mounts after the
|
|
1767
|
+
plugins that use it and the code sampled it once at boot; requests fell through
|
|
1768
|
+
to the SPA fallback and answered 405. The plugin now waits for the service
|
|
1769
|
+
(`internal/service`) and mounts as soon as it appears. `dsh_info` reports
|
|
1770
|
+
`mountNote` while it is still waiting.
|
|
1771
|
+
|
|
1772
|
+
## 0.20.4
|
|
1773
|
+
|
|
1774
|
+
- `session_usage` now also carries `partial: true` on the report itself when any
|
|
1775
|
+
included row was read mid-turn, plus a note saying the totals will still grow.
|
|
1776
|
+
Previously only the individual rows were marked, so a caller reading `totals`
|
|
1777
|
+
could take a snapshot for a final bill.
|
|
1778
|
+
|
|
1779
|
+
## 0.20.3
|
|
1780
|
+
|
|
1781
|
+
- Docs and tests only. Recorded and verified post-restart continuity: a session
|
|
1782
|
+
created before a DSH restart stays readable, `session_usage { sessionId }`
|
|
1783
|
+
returns the same totals, and `session_prompt` continues it with cumulative
|
|
1784
|
+
usage spanning both processes; only ownership-based defaults reset. Added a
|
|
1785
|
+
regression test that owning a session never gates prompting or scoped usage.
|
|
1786
|
+
|
|
1787
|
+
## 0.20.2
|
|
1788
|
+
|
|
1789
|
+
- `workspace_create` on a path that exists as a file now says "exists but is not
|
|
1790
|
+
a directory" instead of echoing mkdir's `EEXIST` syscall message.
|
|
1791
|
+
- The acceptance script gained an error-path section: unknown session ids and job
|
|
1792
|
+
ids, a missing file, a file registered as a workspace, forking an empty
|
|
1793
|
+
session, and an unknown-id usage receipt (row error + totals exclusion), then a
|
|
1794
|
+
health read to prove the endpoint survived the barrage.
|
|
1795
|
+
|
|
1796
|
+
## 0.20.1
|
|
1797
|
+
|
|
1798
|
+
- Settings panel: the timeout fields offered a minimum of `0`, which the host
|
|
1799
|
+
validator rejects; they now start at `1`. Added tests that every panel field
|
|
1800
|
+
path points at a real config leaf and that numeric bounds never offer a value
|
|
1801
|
+
the host refuses.
|
|
1802
|
+
|
|
1803
|
+
## 0.20.0
|
|
1804
|
+
|
|
1805
|
+
- `session_prompt` replies carry `turn.models`: the `provider/model` routes that
|
|
1806
|
+
answered the turn (unique, first-seen order), so a caller can confirm which
|
|
1807
|
+
model ran without reading token buckets.
|
|
1808
|
+
- Requests that declare a body over 64 MiB are refused with `413` before the body
|
|
1809
|
+
is read (defence in depth; tools that carry large payloads have their own caps).
|
|
1810
|
+
|
|
1811
|
+
## 0.19.1
|
|
1812
|
+
|
|
1813
|
+
- Documentation and metadata only. Added this changelog; no runtime change.
|
|
1814
|
+
|
|
1815
|
+
## 0.19.0
|
|
1816
|
+
|
|
1817
|
+
- **`workspace_delete`** (25th tool): unregister a workspace without touching its
|
|
1818
|
+
files or session history; idempotent (`deleted: false` for an unknown id).
|
|
1819
|
+
|
|
1820
|
+
## 0.18.0
|
|
1821
|
+
|
|
1822
|
+
- `session_list` gained `cwd` (canonicalised) and `running` filters, applied
|
|
1823
|
+
before the limit so "the newest N of mine" means exactly that.
|
|
1824
|
+
- `session_usage` gained `sessionIds` for one receipt over several known
|
|
1825
|
+
sessions: duplicates collapse, subagents fold per id, and mixing it with
|
|
1826
|
+
`sessionId`/`all` is refused.
|
|
1827
|
+
|
|
1828
|
+
## 0.17.0
|
|
1829
|
+
|
|
1830
|
+
- `session_messages` gained `maxCharsPerMessage`; a cut message reports
|
|
1831
|
+
`truncated` and its original `chars`.
|
|
1832
|
+
- `session_prompt` gained `maxReplyChars`; a cut reply reports `replyTruncated`
|
|
1833
|
+
and its original `replyChars`. `TurnResult.replyChars` is always present.
|
|
1834
|
+
Both caps are opt-in, so the default behaviour is unchanged.
|
|
1835
|
+
|
|
1836
|
+
## 0.16.0
|
|
1837
|
+
|
|
1838
|
+
- `dsh_tool_call` now caps the result text: 20 000 characters by default,
|
|
1839
|
+
`maxResultChars` up to 200 000. The reply always carries `resultBytes`, sets
|
|
1840
|
+
`truncated` when cut, and drops the structured `value` in that case — an agent
|
|
1841
|
+
tool can return a whole file.
|
|
1842
|
+
|
|
1843
|
+
## 0.15.1
|
|
1844
|
+
|
|
1845
|
+
- `dsh_info.harnessServices` now reports `approval`, `sessionRename` and
|
|
1846
|
+
`sessionModel`, so a caller can tell "unavailable" from "refused".
|
|
1847
|
+
- Documentation audit: corrected the `approval.policy` description, added image
|
|
1848
|
+
and background-shell notes to the security section, documented mid-turn
|
|
1849
|
+
`partial`, and corrected the claim that `dsh plugin --profile desktop add` is
|
|
1850
|
+
refused on the current harness (it works; only managed operations are refused).
|
|
1851
|
+
|
|
1852
|
+
## 0.15.0
|
|
1853
|
+
|
|
1854
|
+
- **`approval.policy: "allow"` is now implemented** (it was documented and
|
|
1855
|
+
parsed but never applied): sessions this plugin creates — and forked children —
|
|
1856
|
+
are switched to the harness's unattended stance through the approval service.
|
|
1857
|
+
The create/fork reply reports `approvalPolicy: "never"` or
|
|
1858
|
+
`approvalPolicyError`.
|
|
1859
|
+
|
|
1860
|
+
## 0.14.2
|
|
1861
|
+
|
|
1862
|
+
- Metadata and exports: package description reflects the real tool surface and
|
|
1863
|
+
the public driver types added since 0.2.0 are exported from the package root.
|
|
1864
|
+
|
|
1865
|
+
## 0.14.1
|
|
1866
|
+
|
|
1867
|
+
- Acceptance-script fixes: cross-section state was block-scoped, so the run threw
|
|
1868
|
+
`usageReported is not defined` and never reached the later sections; the fork
|
|
1869
|
+
check read the wrong reply field. Both fixed, with a regression test.
|
|
1870
|
+
|
|
1871
|
+
## 0.14.0
|
|
1872
|
+
|
|
1873
|
+
- `session_usage` (and every usage read) marks a report `partial: true` when the
|
|
1874
|
+
session's newest turn is still running, so a mid-flight read is never mistaken
|
|
1875
|
+
for a final bill.
|
|
1876
|
+
|
|
1877
|
+
## 0.13.0
|
|
1878
|
+
|
|
1879
|
+
- Structured output: six data tools declare an `outputSchema`, and every
|
|
1880
|
+
object-shaped reply also carries `structuredContent` beside the text block.
|
|
1881
|
+
|
|
1882
|
+
## 0.12.0
|
|
1883
|
+
|
|
1884
|
+
- `session_prompt` gained `idempotencyKey`: a retried call returns the existing
|
|
1885
|
+
turn instead of sending the task twice (measured: 0 extra tokens, one user
|
|
1886
|
+
message). Reusing a key with different content is refused.
|
|
1887
|
+
|
|
1888
|
+
## 0.11.0
|
|
1889
|
+
|
|
1890
|
+
- **`session_set_model`**: switch a session's provider route/model for later
|
|
1891
|
+
turns; per-model usage shows which route spent what.
|
|
1892
|
+
- Shared, actionable model-selection failures (requested pair, known routes, how
|
|
1893
|
+
to list models).
|
|
1894
|
+
|
|
1895
|
+
## 0.10.0
|
|
1896
|
+
|
|
1897
|
+
- **`session_rename`**: explicit user titles, visible in the DSH UI and readable
|
|
1898
|
+
back through `session_list { includeTitle }`.
|
|
1899
|
+
- `dsh_info` gained redacted `config` (limits, approval, defaults — never the
|
|
1900
|
+
token) and endpoint `health` (node, platform, memory).
|
|
1901
|
+
|
|
1902
|
+
## 0.9.0
|
|
1903
|
+
|
|
1904
|
+
- `session_prompt` tool calls are summarised by default (`name`, `callId`,
|
|
1905
|
+
`argumentsBytes`) instead of echoing full arguments; `toolCalls: full|none` and
|
|
1906
|
+
`maxArgumentChars` control the detail. Measured: a 5.8 KB `write` call no longer
|
|
1907
|
+
appears in the reply.
|
|
1908
|
+
|
|
1909
|
+
## 0.8.0
|
|
1910
|
+
|
|
1911
|
+
- `session_prompt` accepts up to four images (`png`/`jpeg`/`webp`/`gif`) read
|
|
1912
|
+
through the filesystem seam and passed to the agent as prompt attachments.
|
|
1913
|
+
- A client abort now ends a waiting `session_prompt` with `aborted`/partial
|
|
1914
|
+
instead of running to the timeout.
|
|
1915
|
+
|
|
1916
|
+
## 0.7.0
|
|
1917
|
+
|
|
1918
|
+
- **`file_read_image`**: images returned as MCP image blocks, format sniffed from
|
|
1919
|
+
the bytes, 8 MiB cap.
|
|
1920
|
+
- `dsh_info { includeModels: true }` lists each provider route's advertised
|
|
1921
|
+
models, time-boxed per provider.
|
|
1922
|
+
|
|
1923
|
+
## 0.6.0
|
|
1924
|
+
|
|
1925
|
+
- **`session_fork`**: branch a session at an exact event prefix; the inherited
|
|
1926
|
+
prefix is reported separately by `session_usage`. Uses the controller's fork
|
|
1927
|
+
(cold-readable, persists the child); the store's fork was rejected after live
|
|
1928
|
+
testing showed its child cannot be prompted.
|
|
1929
|
+
|
|
1930
|
+
## 0.5.0
|
|
1931
|
+
|
|
1932
|
+
- **Fixed a live bug**: `shell_run` failed on harness 0.2.1 with "Cannot read
|
|
1933
|
+
properties of undefined (reading 'text')" because `execute()` resolves a live
|
|
1934
|
+
process handle whose foreground result is `result()`.
|
|
1935
|
+
- **`shell_start` / `shell_output` / `shell_kill` / `shell_list`**: background
|
|
1936
|
+
commands with bounded buffers, watchdogs, and support for both the 0.1.5
|
|
1937
|
+
(`start`) and 0.1.7+ (`execute`) shell shapes.
|
|
1938
|
+
|
|
1939
|
+
## 0.4.0
|
|
1940
|
+
|
|
1941
|
+
- `session_prompt` reports `notifications/progress` (at most once a second) when
|
|
1942
|
+
the client supplies a `progressToken`.
|
|
1943
|
+
- `session_list { includeTitle }` folds each session's latest title.
|
|
1944
|
+
|
|
1945
|
+
## 0.3.0
|
|
1946
|
+
|
|
1947
|
+
- **`file_edit`**: exact literal replacement, unique unless `replaceAll`, nothing
|
|
1948
|
+
written on an ambiguous match — editing no longer costs a read plus a write of
|
|
1949
|
+
the whole file.
|
|
1950
|
+
- `file_list { recursive }` with relative paths, depth bound, cycle-safe walk and
|
|
1951
|
+
an entry cap.
|
|
1952
|
+
- `session_messages` gained `sinceSeq`/`role` for incremental transcript reads.
|
|
1953
|
+
|
|
1954
|
+
## 0.2.1
|
|
1955
|
+
|
|
1956
|
+
- `dsh_info` gained `version`/`uptimeMs`, a `catalog` block (providers, agent
|
|
1957
|
+
presets, sandbox mode, permission presets, session count) and an opt-in model
|
|
1958
|
+
list.
|
|
1959
|
+
- MCP `initialize` carries workflow instructions for the calling model.
|
|
1960
|
+
- The prompt wait wakes on `session/event` instead of re-reading the log on a
|
|
1961
|
+
fixed 200 ms cadence.
|
|
1962
|
+
- `session_usage { includeCost: false }` skips the price ledger on wide scans;
|
|
1963
|
+
list-price totals are aggregated beside real money.
|
|
1964
|
+
- `file_read` gained `offset`/`nextOffset`/`maxBytes` for cheap paging.
|
|
1965
|
+
|
|
1966
|
+
## 0.2.0
|
|
1967
|
+
|
|
1968
|
+
- **Usage accounting**: every `session_prompt` reply carries exact per-turn and
|
|
1969
|
+
cumulative tokens (plus money when `dsh-cost-meter` is installed), and
|
|
1970
|
+
**`session_usage`** reports a receipt after the fact — the harness has no
|
|
1971
|
+
session-end event, so the tally rides the reply and an explicit tool.
|
|
1972
|
+
Subagents fold in as their own rows; fork-inherited prefixes are separated;
|
|
1973
|
+
compaction is reported but excluded from totals; the fold is verified against
|
|
1974
|
+
the harness's own `tokenUsage` projection.
|