dsh-any-agent 0.6.2 → 0.6.3
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 +60 -1
- package/README.md +47 -6
- package/README.zh.md +48 -7
- package/SECURITY.md +46 -0
- package/USAGE-NOTES.en.md +6 -2
- package/USAGE-NOTES.md +10 -2
- package/bin/dsh-any-agent.mjs +2 -1
- package/docs/en/architecture.md +12 -0
- package/docs/en/limits.md +6 -0
- package/docs/en/live-view-design.md +177 -0
- package/docs/en/reference.md +1 -0
- package/docs/en/release.md +21 -14
- package/docs/en/tests.md +6 -5
- package/docs/en/usage.md +2 -1
- package/docs/zh/architecture.md +12 -0
- package/docs/zh/limits.md +6 -0
- package/docs/zh/live-view-design.md +11 -6
- package/docs/zh/reference.md +1 -0
- package/docs/zh/release.md +20 -11
- package/docs/zh/tests.md +6 -5
- package/docs/zh/usage.md +2 -1
- package/package.json +6 -5
- package/src/doctor.mjs +63 -2
- package/src/live-cli.mjs +52 -15
- package/src/live-dashboard.html +191 -79
- package/src/live-view.mjs +111 -24
- package/DESIGN.md +0 -484
- package/docs/brand/README.md +0 -58
- package/docs/brand/header.jpg +0 -0
- package/scripts/check-release.mjs +0 -73
- package/scripts/commit.mjs +0 -122
- package/scripts/detect-dsh.mjs +0 -55
- package/scripts/lint.mjs +0 -595
- package/scripts/sync-test-counts.mjs +0 -699
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
> Back to [README](../../README.md) · 中文版:[实时查看设计](../zh/live-view-design.md)
|
|
2
|
+
|
|
3
|
+
# Design: let users see what DSH is doing (live event stream + viewers)
|
|
4
|
+
|
|
5
|
+
> Status: **shipped.** The event stream, `watch` and `dashboard` landed in 0.6.1; token usage and the viewer improvements (in-place `watch`, inspector column, scrollable runs) in 0.6.2; English UI, `--open` and `dsh_doctor`'s live check after that (see the CHANGELOG). This document describes the design as built.
|
|
6
|
+
>
|
|
7
|
+
> Code: `src/live.mjs` (writing), `src/live-view.mjs` (reading, merging, terminal rendering), `src/live-cli.mjs` (the `watch` / `dashboard` subcommands), `src/live-dashboard.mjs` + `src/live-dashboard.html` (the page). Tests: `test/live-events.mjs`, `test/live-view.mjs`.
|
|
8
|
+
>
|
|
9
|
+
> Scope: a **lightweight feature** — an event stream plus two read-only viewers. No status line, no host-embedded view, no control plane.
|
|
10
|
+
|
|
11
|
+
## 1. The problem
|
|
12
|
+
|
|
13
|
+
This server dispatches work to DSH. For the **calling agent** the process is visible: it can `dsh_read` incrementally, wait for the sentinel, `dsh_get` the result. For the **person sitting at Claude Code / Codex / Cursor**, it is mostly a black box:
|
|
14
|
+
|
|
15
|
+
- The screen shows one `dsh_send` call and a receipt (run_id, sentinel path), then waiting.
|
|
16
|
+
- Which files DSH read, which commands it ran, whether an approval escalated the sandbox to `danger-full-access` — the user cannot see any of it unless the agent calls `dsh_read` and relays it, which costs tokens and depends on the agent choosing to.
|
|
17
|
+
- When something goes wrong (stuck on a tool, failing repeatedly, waiting on an approval) the user cannot tell whether to wait or interrupt.
|
|
18
|
+
|
|
19
|
+
The goal is a **side view that does not go through the agent**.
|
|
20
|
+
|
|
21
|
+
## 2. Goals and non-goals
|
|
22
|
+
|
|
23
|
+
**Goals**
|
|
24
|
+
|
|
25
|
+
1. See every conversation live: whether it is running, which tool it is on, which tools this run has called and their status.
|
|
26
|
+
2. Make approvals — especially auto-allowed ones and sandbox escalations — stand out.
|
|
27
|
+
3. See conversations dispatched by every client and every server instance at once.
|
|
28
|
+
4. Keep the privacy red line: **reasoning never travels this path**.
|
|
29
|
+
5. A write failure never affects dispatching.
|
|
30
|
+
|
|
31
|
+
**Non-goals**
|
|
32
|
+
|
|
33
|
+
- No control plane: the viewers cannot approve, interject or interrupt (see §9, item 4).
|
|
34
|
+
- No status-line output and no host-embedded view (Claude Code side panel, MCP Apps). If they come later they only read the same stream; the server does not change.
|
|
35
|
+
- `dsh_read`, sentinels and the audit log keep their exact semantics.
|
|
36
|
+
- DSH's own `~/.dsh/sessions` is never read.
|
|
37
|
+
|
|
38
|
+
## 3. Where the data was
|
|
39
|
+
|
|
40
|
+
DSH's tool activity reaches this server through ACP `session/update` (`tool_call` / `tool_call_update`). Before the live view it went to:
|
|
41
|
+
|
|
42
|
+
| Where | What | Live | Who can see it |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| the `Conversation.out` ring buffer (`onUpdate` → `pushOut` in `src/conversation.mjs`) | tool names + status snapshots, answer text, turn boundaries | yes | only `dsh_read`, i.e. only the agent |
|
|
45
|
+
| the run collector `collector.tools` / `autoApprovals` | tool inputs, auto-approval summaries | yes | sentinel / `dsh_get` after the run |
|
|
46
|
+
| the sentinel `<RUNS_DIR>/<conv>/<run>.json` | answer, tool inputs (redacted) | no, written at the end | a file, 7-day TTL |
|
|
47
|
+
| the audit log `DSH_MCP_AUDIT_FILE` | who did what to which conversation (no tools) | yes | off by default |
|
|
48
|
+
| DSH's `~/.dsh/sessions` | everything, including reasoning | written by DSH | internal format |
|
|
49
|
+
|
|
50
|
+
So the live data existed only in the server process's memory, and its only exit was an MCP tool the agent calls.
|
|
51
|
+
|
|
52
|
+
## 4. Constraints
|
|
53
|
+
|
|
54
|
+
1. **The server is a stdio child.** Every MCP client spawns its own `dsh-any-agent`; there is no port for a page to connect to.
|
|
55
|
+
2. **Several instances.** With Claude Code, Codex and Cursor in use at once there are several server processes whose memory does not overlap. The view has to merge them.
|
|
56
|
+
3. **Shared ACP processes.** Conversations with the same `(cwd, permission)` share one DSH process (`src/host.mjs`), but events are routed to each `Conversation` by `sessionId`, so organising data per conversation is unaffected.
|
|
57
|
+
4. **The write lock.** One writer per conversation at a time (see [write lock](locking.md)), which makes "one file per conversation, single appender" hold naturally.
|
|
58
|
+
5. **No MCP notifications.** The existing design never relies on a client handling pushes; neither should this.
|
|
59
|
+
6. **Privacy.** Logs, sentinels and the registry never hold reasoning; anything new written to disk follows the same rule and the redaction in `src/redact.mjs`.
|
|
60
|
+
7. **Test isolation.** `test/cleanup.mjs` removes test sessions, so real runs and test runs must not share a directory.
|
|
61
|
+
|
|
62
|
+
## 5. Options
|
|
63
|
+
|
|
64
|
+
| Option | How | Pros | Main problem | Verdict |
|
|
65
|
+
|---|---|---|---|---|
|
|
66
|
+
| A. HTTP/SSE inside the server process | push to a local port from `pushOut` | live, small change | instances race for ports and need discovery; lifetime tied to the client; a local port is a CSRF / DNS-rebinding surface needing a token; Windows firewall prompts | no |
|
|
67
|
+
| B. Viewer reads `~/.dsh/sessions` | no server change | smallest change | couples to DSH's internal (pre-release) format; contains reasoning | no |
|
|
68
|
+
| C. Server appends an event file; viewers only read | JSONL per conversation, any viewer tails it | works across instances and restarts; same "files as IPC" idea as sentinels; viewers evolve independently | needs a stable schema, rotation and cleanup | **chosen** |
|
|
69
|
+
| D. MCP progress notifications / MCP Apps | the host's native UI | nice UX | `dsh_send` returns immediately, so there is no long-lived call to carry progress; host support varies | no (beyond the lightweight scope) |
|
|
70
|
+
|
|
71
|
+
## 6. Data contract: the live event stream
|
|
72
|
+
|
|
73
|
+
### 6.1 Location and switch
|
|
74
|
+
|
|
75
|
+
- Directory: `DSH_MCP_LIVE_DIR`, default `<dirname(STATE_FILE)>/live/`, next to `runs/` and `reviews/`.
|
|
76
|
+
- File: `<live>/<conversation_id>/events.jsonl`, one event per line, UTF-8, `\n`-terminated. One sub-directory per conversation so the existing `pruneDirTree` cleans it; the rolled `events.jsonl.1` lives there too.
|
|
77
|
+
- Level: `DSH_MCP_LIVE=off | meta | detail`, **default `detail`**.
|
|
78
|
+
- `meta`: tool name, kind, status, timing, counts, approval mode and escalation, token counts. **No** tool inputs, commands, paths, prompts or answer text.
|
|
79
|
+
- `detail`: `meta` plus redacted short previews (command / path, prompt, tool title). The sentinels already store full answers and untruncated tool inputs by default, so `detail` exposes no more than they do — and without the previews the call chain cannot say *which file* or *which command*.
|
|
80
|
+
- `off`: write nothing.
|
|
81
|
+
|
|
82
|
+
### 6.2 Common fields
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{"v":1,"ts":"2026-11-08T09:12:03.120Z","conv":"…","run":"run-…","turn":3,"w":"<pid>:<instance>","n":42,"type":"tool"}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- `v`: schema version; a viewer that does not know it shows only the common fields.
|
|
89
|
+
- `w`: writer id (server pid + a random id per start), used to tell whether the writer is still alive.
|
|
90
|
+
- `n`: per-writer sequence number; ordering across writers uses `ts`.
|
|
91
|
+
|
|
92
|
+
### 6.3 Event types
|
|
93
|
+
|
|
94
|
+
| type | When | `meta` fields | `detail` adds |
|
|
95
|
+
|---|---|---|---|
|
|
96
|
+
| `conv.open` | `dsh_start` created the conversation | `cwd`, `permission`, `on_approval`, `model`, `client` | `title_hint` |
|
|
97
|
+
| `conv.close` | released without `forget` | `stopped` | — |
|
|
98
|
+
| `run.queued` | `dsh_send(queue=true)` | `position`, `cwd`, `client` | `prompt_preview` |
|
|
99
|
+
| `run.start` | a run starts | `via` (send / queue / interject), `permission`, `on_approval`, `cwd`, `client` | `prompt_preview` (≤200, redacted) |
|
|
100
|
+
| `tool` | `tool_call` / `tool_call_update`, **only when the status changes** or the input first arrives | `tool_call_id`, `name`, `kind`, `status`, `elapsed_ms` | `title`, `input_preview` (≤300, redacted) |
|
|
101
|
+
| `approval` | `request_permission` arrived | `mode` (auto-allow / auto-deny / ask / read-only-deny), `escalation`, `approval_id` (ask), `tool`, `tool_call_id` | `title`, `command_preview` |
|
|
102
|
+
| `approval.decided` | `dsh_approval_decide`, timeout, run end or close | `approval_id`, `by` (caller / timeout / turn_end / close), `decision`, `effective` | — |
|
|
103
|
+
| `interject` / `interrupt` | interjecting, interrupting | `mode`, `pending_run` / `busy` | `text_preview` |
|
|
104
|
+
| `usage` | during a run (≤ every 2 s, only on change) and once more with `final: true` after it ends | `tokens` (this run's delta: input, output, cache_read, cache_write), `session_tokens`, `context {used, window}` | — |
|
|
105
|
+
| `run.end` | the run ended (done / error / cancelled) | `status`, `stop_reason`, `duration_ms`, `tool_count`, `auto_approvals`, `auto_escalations` | `error` |
|
|
106
|
+
|
|
107
|
+
Notes from building it:
|
|
108
|
+
|
|
109
|
+
- **No `text` events**: answer text stays in the sentinel.
|
|
110
|
+
- **No writer hand-over events**: every event carries `w`, and the viewer checks whether the last writer's pid is alive (§6.5).
|
|
111
|
+
- A cancelled queued run, an interject that timed out and a run that could not start all get a `run.end`, so no run stays "queued" or "running" forever.
|
|
112
|
+
- **Token numbers** come from DSH's projection cache (`tokenUsage.totals`, `contextPressure`). Measured against real DSH: it updates them after every model step, so the server records a baseline when the run starts and emits the delta; the final value is emitted after the git snapshot, by which time DSH has written its totals.
|
|
113
|
+
- **Real DSH `tool_call`s carry no `name`**: the tool name is in `title` (`read`, `glob`, `write`, `pwsh`) and `kind` is always `other`. An identifier-only `title` is therefore used as the `name` at `meta` level; a `title` with spaces (which may contain a command) stays `detail`-only. Viewers show paths relative to the conversation's directory and hide `other`.
|
|
114
|
+
|
|
115
|
+
### 6.4 Writing
|
|
116
|
+
|
|
117
|
+
- Same discipline as `src/audit.mjs`: append one line synchronously, swallow every error, never throw into the main path.
|
|
118
|
+
- A line over 4 KB drops its `detail` fields and is marked `truncated: true`.
|
|
119
|
+
- All writes go through `emitLive(conv, type, fields, detail)`, called from existing hooks in `Conversation` — not generically from `pushOut`, so the internal buffer format never becomes an external contract.
|
|
120
|
+
- Reasoning (`agent_thought_chunk`) **has no event type**, so it cannot be written by construction; `DSH_MCP_SENTINEL_INCLUDE_REASONING` does not affect this.
|
|
121
|
+
|
|
122
|
+
### 6.5 Consistency and edge cases
|
|
123
|
+
|
|
124
|
+
- **Takeover**: the viewer trusts the `w` of the latest event per run / conversation; a writer that lost the lock emits nothing more.
|
|
125
|
+
- **Crash**: a run without `run.end` might still be running; if the pid in `w` is dead the viewer shows it as *interrupted*. No heartbeat needed.
|
|
126
|
+
- **Half lines**: viewers split on bytes and keep an incomplete trailing line for the next read.
|
|
127
|
+
- **Rotation**: over 5 MB the file rolls to `.1` (one generation kept); a new viewer reads `.1` first.
|
|
128
|
+
- **Cleanup**: pruned at startup with `DSH_MCP_SENTINEL_TTL_MS` (a separate `pruneLive` call, leaving `Hub.pruneSentinels`' return shape alone); deleted on `forget`; test-residue cleanup (`src/residue.mjs`) removes `live/<id>/` too.
|
|
129
|
+
|
|
130
|
+
## 7. Viewers
|
|
131
|
+
|
|
132
|
+
Both viewers only read `live/` and `conversations.json` (for titles), are independent processes, never touch the write lock or DSH, and add no dependencies. Titles prefer the caller's `title_hint` over DSH's native title, which is only a truncated prefix of the first prompt.
|
|
133
|
+
|
|
134
|
+
### 7.1 Terminal: `dsh-any-agent watch [conversation id prefix]`
|
|
135
|
+
|
|
136
|
+
A tree — client → conversation → run → tool calls / approvals — with per-run tokens. It runs on the terminal's alternate screen like `top` and rewrites only changed lines (clearing the screen each tick piled frames into Windows Terminal's scrollback). Lines are clipped to the terminal width (CJK counts as two columns) and the frame to its height; leaving restores the screen. `--once` prints once.
|
|
137
|
+
|
|
138
|
+
### 7.2 Page: `dsh-any-agent dashboard [--open]`
|
|
139
|
+
|
|
140
|
+
- A **separate process**, so option A's port and lifetime problems do not apply.
|
|
141
|
+
- Binds `127.0.0.1` on a random port; a one-time token is generated at start and put in the printed URL; the `Host` header is checked against DNS rebinding; GET only; a strict CSP.
|
|
142
|
+
- One inline static HTML file, no CDN; data over SSE. Files are polled (`fs.watch` is unreliable on Windows).
|
|
143
|
+
- Layout: conversations grouped by client on the left; the run timeline in the middle, with a timing bar per tool call and long runs scrolling inside their card; an inspector on the right (overview, token usage, tools, files read/written, approvals).
|
|
144
|
+
|
|
145
|
+
### 7.3 Language
|
|
146
|
+
|
|
147
|
+
Both viewers come in Chinese and English: `--lang zh|en`, else `DSH_MCP_LANG`, `LC_ALL` / `LC_MESSAGES` / `LANG`, then the OS locale. The page can also switch from its top-right corner and remembers the choice.
|
|
148
|
+
|
|
149
|
+
## 8. Relation to existing mechanisms
|
|
150
|
+
|
|
151
|
+
| Mechanism | Relation |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `dsh_read` | Unchanged. It serves the agent; the live view serves the user. Same source, different exit. |
|
|
154
|
+
| Sentinels | Unchanged. The sentinel is the "run finished" latch and result carrier; the live stream is the process. `run.end` does not replace it. |
|
|
155
|
+
| Audit log | Unchanged. Audit answers "who did what to the conversation"; the live stream answers "what the conversation itself is doing". |
|
|
156
|
+
| Write lock | The live stream holds no lock; viewers never touch it, so looking never affects the GUI or a takeover. |
|
|
157
|
+
| `dsh_doctor` | Its `live` check reports the level, whether the directory is writable, the number of conversations, size and the last event — warnings only. |
|
|
158
|
+
|
|
159
|
+
## 9. Privacy and security
|
|
160
|
+
|
|
161
|
+
1. Reasoning has no place in the schema (§6.4).
|
|
162
|
+
2. Every string field goes through `sanitizeApprovalContext`, then is truncated per field.
|
|
163
|
+
3. The default `detail` level adds only redacted short previews, no more than the sentinels hold; `meta` drops them and `off` writes nothing.
|
|
164
|
+
4. **Why no control plane**: pending approvals, interjects and interrupts live in some server process's memory (`pendingApprovals` and friends). A separate dashboard process could only act on them through a reverse channel (say, a command directory the server watches), which would hand "anything that can write a local file" a way to approve escalations. That needs its own authentication design and does not fit the lightweight scope.
|
|
165
|
+
5. The dashboard listens on loopback only; the token is printed at start and never written to disk.
|
|
166
|
+
|
|
167
|
+
## 10. Tests
|
|
168
|
+
|
|
169
|
+
- Offline, through a fake hub and a fake ACP process: event order and fields, status de-duplication, level filtering, redaction, the 4 KB cap, approval decisions, failure / queue / cancel / forget, token deltas against a fake projection cache, and that a non-writable live directory does not fail a run.
|
|
170
|
+
- Reasoning regression: an `agent_thought_chunk` never appears in the file at any level.
|
|
171
|
+
- Viewers: byte-level line splitting, half lines, rotation, removal, unknown types, interrupted runs, rendering in both languages, in-place frame diffs, the dashboard's token / Host / read-only checks and SSE.
|
|
172
|
+
- Every suite uses its own temporary live directory; suites that drive `Conversation` directly import `test/live-sandbox.mjs` so running them alone never writes into the real `~/.dsh`.
|
|
173
|
+
|
|
174
|
+
## 11. Open questions
|
|
175
|
+
|
|
176
|
+
1. **Are DSH subagents' tool calls reported over ACP?** Not verified. If not, the view shows a subagent as one tool.
|
|
177
|
+
2. **Remote / multi-machine**: the live directory is local; remote or cloud sessions are out of scope.
|
package/docs/en/reference.md
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
| `DSH_MCP_REVIEWS_DIR` | `<state file dir>/reviews` | Where review verdicts are written (`<this>/<conversation_id>/<run_id>.review.json`). Must be writable, or startup fails loudly |
|
|
33
33
|
| `DSH_MCP_LIVE` | `detail` | Live event stream level (the data behind `watch` / `dashboard`). `detail` adds redacted prompt / command / path previews; `meta` keeps tool names, status, timing and approval kinds only; `off` writes nothing. Reasoning is never written at any level |
|
|
34
34
|
| `DSH_MCP_LIVE_DIR` | `<state file dir>/live` | Live event stream directory (`<this>/<conversation_id>/events.jsonl`, rolled to `.1` at 5 MB). Write failures are skipped silently and never affect a run; pruned by `DSH_MCP_SENTINEL_TTL_MS` |
|
|
35
|
+
| `DSH_MCP_LANG` | follows the system | Interface language of `watch` / `dashboard`: `zh` or `en`. When unset: `LC_ALL` / `LC_MESSAGES` / `LANG`, then the OS locale; the `--lang` flag wins over it |
|
|
35
36
|
| `DSH_MCP_PERMISSION` | `workspace-write` | Default tier for `dsh_start`. `danger-full-access` additionally requires `DSH_MCP_ALLOW_FULL_ACCESS=1` |
|
|
36
37
|
| `DSH_MCP_ALLOW_FULL_ACCESS` | `0` | Whether `permission=danger-full-access` is allowed. Denied by default: it hands the caller whole-machine write access |
|
|
37
38
|
| `DSH_MCP_REASONING_EFFORT` | `max` | Default reasoning effort |
|
package/docs/en/release.md
CHANGED
|
@@ -7,31 +7,38 @@ The order matters; two of these steps exist because a bad release shipped withou
|
|
|
7
7
|
```bash
|
|
8
8
|
# 1. Bump the version. SERVER_VERSION reads package.json, nothing else.
|
|
9
9
|
npm version patch --no-git-tag-version
|
|
10
|
-
#
|
|
10
|
+
# Bump both versions in server.json too (top level and packages[0]); check:release verifies them.
|
|
11
|
+
# Then edit CHANGELOG.md:
|
|
12
|
+
# - turn [Unreleased] into the new version (that section IS the GitHub Release notes, written for users)
|
|
13
|
+
# - link block at the bottom: [Unreleased] → compare/vx.y.z...HEAD, plus [x.y.z]: …/compare/vprevious...vx.y.z
|
|
11
14
|
|
|
12
15
|
# 2. Run the offline suites locally (same set as CI, no tokens, no DSH needed for most).
|
|
13
16
|
node test/run.mjs
|
|
14
17
|
node test/package-check.mjs
|
|
15
|
-
npm run check:release # version / CHANGELOG / doc links / package contents
|
|
18
|
+
npm run check:release # version / this CHANGELOG section and its links / release notes build / doc links / package contents
|
|
16
19
|
npm run docs:counts:check # check counts in tests.md and the CI comment = the report node test/run.mjs just wrote
|
|
17
20
|
# A mismatch means tests changed without the numbers being refreshed:
|
|
18
21
|
# node test/run.mjs --all --report .state/test-report.json && npm run docs:counts
|
|
19
22
|
# (model-suite numbers only move with a report made using --all; see "After changing tests" in docs/en/tests.md)
|
|
23
|
+
node scripts/release-notes.mjs x.y.z # preview the notes the Release page will show
|
|
20
24
|
|
|
21
25
|
# 3. Verify the tarball itself: builds, installs, starts.
|
|
22
26
|
npm pack --dry-run
|
|
23
27
|
# Then in a temp dir: npm install <tgz> && node_modules/.bin/dsh-any-agent --version
|
|
24
28
|
|
|
25
|
-
# 4.
|
|
26
|
-
git
|
|
27
|
-
git
|
|
28
|
-
# The pushed tag triggers .github/workflows/release.yml, which creates the
|
|
29
|
-
# GitHub Release with generated notes; no local gh CLI is required.
|
|
30
|
-
|
|
31
|
-
# 5. Publish to npm and verify.
|
|
32
|
-
npm publish --access public
|
|
33
|
-
npm view dsh-any-agent version
|
|
34
|
-
# A 404 right after publish is usually propagation, not failure: npm returns
|
|
35
|
-
# 202 and the registry can lag briefly. Confirm the local npm log shows the
|
|
36
|
-
# version before retrying.
|
|
29
|
+
# 4. Merge into main (via PR, after CI is green), then tag that main commit and push the tag.
|
|
30
|
+
git tag -a vx.y.z <commit on main> -m "vx.y.z"
|
|
31
|
+
git push origin vx.y.z
|
|
37
32
|
```
|
|
33
|
+
|
|
34
|
+
**Everything after the tag push is done by `.github/workflows/release.yml` — no local `npm publish`:**
|
|
35
|
+
|
|
36
|
+
1. **GitHub Release**: notes are this version's CHANGELOG section (`scripts/release-notes.mjs`), followed by the install command and a compare link. If the Release already exists only its notes are updated, so re-running is safe.
|
|
37
|
+
2. **npm publish**: checks the tag matches `package.json`, runs `check:release` and `package-check`, then publishes with **npm Trusted Publishing** (GitHub OIDC) and provenance — no `NPM_TOKEN`, and the npm page shows the package was built by this repository's GitHub Actions. Skipped if the version is already on npm.
|
|
38
|
+
3. **MCP Registry**: after npm, the workflow logs in to the official MCP Registry with its own GitHub OIDC token (the `io.github.xmtlzzz/*` namespace) and publishes `server.json`. The registry checks ownership through the `mcpName` field inside the npm package, which is why it runs after npm. `mcp-publisher` is pinned and its signature is verified with cosign before it runs.
|
|
39
|
+
|
|
40
|
+
**One-time setup**: npmjs.com → package `dsh-any-agent` → Settings → Trusted Publisher → GitHub Actions, repository `xmtlzzz/dsh-any-agent`, workflow file `release.yml`. Until then the npm job fails with an auth error (the GitHub Release is unaffected); publish locally with `npm publish` as a fallback, and a later re-run simply skips npm.
|
|
41
|
+
|
|
42
|
+
**Refreshing an old Release's notes**: Actions → release → Run workflow with an existing tag (e.g. `v0.6.2`). The npm job skips because the version exists.
|
|
43
|
+
|
|
44
|
+
**A 404 right after publishing is not a failure.** npm ingests new versions asynchronously (`PUT 202`, not `200`), so the registry can lag for a minute. For a CI publish read the npm job's log; for a local publish confirm the npm log shows `http fetch PUT 202`, then wait and check `npm view dsh-any-agent dist-tags`.
|
package/docs/en/tests.md
CHANGED
|
@@ -46,8 +46,9 @@ Why model-backed suites do not point `DSH_HOME` at a throwaway sandbox: DSH keep
|
|
|
46
46
|
| `line-decoder` | 4 | **UTF-8 line framing**: multi-byte characters split across MCP/ACP stream chunks are preserved (no U+FFFD), including CRLF and EOF-without-newline (no tokens) |
|
|
47
47
|
| `read-cursor` | 26 | **`dsh_read` cursor semantics**: text appended to an already-read entry is delivered again (revision cursor, match by `seq`); **each tool call gets its own entry** and a status update replaces that entry in place (they used to be concatenated into one entry under the first call's id); an empty turn's start/end stay two entries; **message segments separated by tool calls get a blank line between them in `answer`** (they used to run together); and `limit` is clamped to 1..500 so `0`/negative cannot spin or drop the tail (no tokens) |
|
|
48
48
|
| `sentinel-redaction` | 19 | **sentinel disk hygiene**: `result.thinking` is stripped, `thinking_omitted` is always a boolean, and `answer`/`error`/`title`/`tools_used[].input` all pass the same credential redaction as approvals (no tokens) |
|
|
49
|
-
| `live-events` |
|
|
50
|
-
| `live-view` |
|
|
49
|
+
| `live-events` | 43 | **live event stream, write side**: a full run through a fake hub and fake ACP process; event order and fields, tools written only on status change, reasoning never written, redacted previews, `meta`/`off` levels, write failures don't affect runs, ask-approval decisions, failure/queue/cancel/forget (no tokens) |
|
|
50
|
+
| `live-view` | 66 | **live viewers**: byte-level line splitting (no broken CJK), call-chain merge, exited writer shown as interrupted, incremental tail (half lines/rotation/removal), `watch --once` through the bin entry, `dashboard` token / Host / read-only checks and SSE (no tokens) |
|
|
51
|
+
| `release-notes` | 12 | **Release notes**: the CHANGELOG section for a version (`1.1.1` never matches `1.1.10`, stops at the link definitions), empty or missing sections fail, install command and compare link appended, CLI exit codes (no tokens) |
|
|
51
52
|
| `review` | 70 | **caller review**: default `advisory` tickets carry a checklist + evidence + `dsh_review` entry; `dsh_review` writes the verdict to disk and back into the run and sentinel; `fail`/`partial` require notes; running/unknown runs are refused; review still works after a restart (sentinel-backed); queue and `client_run_id` replay receipts carry the ticket; review records share the sentinel TTL; notes, previews, evidence **and checklist items** are credential-redacted; **path-traversal ids are rejected before any file I/O** (no tokens) |
|
|
52
53
|
| `forget-tombstone` | 10 | **`forget` is terminal**: `release(forget=true)` writes a tombstone, by-id lookups fail with a recovery path, lists exclude it, and deleting the tombstone restores the normal unknown-session path (no tokens) |
|
|
53
54
|
| `bootstrap-profile` | 24 | **first-start profile works in the published zero-dependency form**: copies routing with the built-in YAML subset parser, strips `apiKey`/`headers`/literal secrets, never writes `defaultPreset`, is idempotent, and leaves a user-maintained profile byte-identical (no tokens) |
|
|
@@ -64,7 +65,7 @@ Why model-backed suites do not point `DSH_HOME` at a throwaway sandbox: DSH keep
|
|
|
64
65
|
| `test-counts` | 92 | **check-count sync**: parsing suite output (Chinese/English summary lines, whole-suite skip, `✓`/`✗` fallback, `[report]` environment tags), report assembly, round-trips of the "main number (other environments)" cell, rewriting the Chinese and English docs and `tests.yml`, `--check` diffs and `--portable`, model-suite numbers left alone, failing reports refused (no tokens) |
|
|
65
66
|
| `roots` | 12 | `dsh_start` without `cwd` asks the client for MCP roots; a client without roots gets an actionable error (no tokens) |
|
|
66
67
|
| `workspace-titles` | 18 (17 with TEMP outside the home directory, 16 case-sensitive with TEMP outside home) | home-as-workspace **and filesystem-root cwd** are rejected before DSH starts; `title_hint` is returned separately from DSH's automatic title (no tokens) |
|
|
67
|
-
| `package-check` |
|
|
68
|
+
| `package-check` | 15 | `files` whitelist, zero runtime dependencies, Node floor, and bin shebangs (no tokens) |
|
|
68
69
|
| `lint` | 22 | **Regression for the `npm run lint` static check itself**: throwaway trees prove that a syntax error, a missing or wrongly-cased import path, a missing named export (including `export { x } from`, `await import()` destructuring and `node:` builtins) and an unused import are each reported; a tree of tricky-but-valid syntax (uses inside template literals, quotes inside regex literals, `export *`) produces zero false positives; and this repo itself is lint-clean (no tokens) |
|
|
69
70
|
| `timeout` | 40 | **timeout semantics**: `wait=false` is unaffected by `timeout_ms`; a `wait=true` expiry merely downgrades to background (turn not cancelled, result not lost, `busy` never lies); `timeout_ms=0` waits forever and a **negative `timeout_ms` is rejected with a clear error** (no run dispatched, conversation still usable); a failed resume invalidates the process instead of wedging the conversation; an empty prompt yields a clear error |
|
|
70
71
|
| `lock` | 79 | **write lock and preemption**: four holder classifications, `writeMarker` never overwriting a live holder, malformed/missing registration edge cases; **end-to-end** with two real MCP instances fighting over one conversation → clear error → refusal → `force` takeover; **a crash releases the lock automatically**, **a wedged holder is preempted without `force`**; plus the "one turn, one sentinel" invariant (inline turns and idle interjects included) |
|
|
@@ -81,13 +82,13 @@ Why model-backed suites do not point `DSH_HOME` at a throwaway sandbox: DSH keep
|
|
|
81
82
|
> **2026-11-06**: `node test/run.mjs` now runs **every** suite and lists all failures at the end (`--bail` restores stop-at-first-failure) — a single stale assertion in `timeout.mjs` used to hide eleven model-backed suites. Model-backed suites re-run in full against DSH 0.2.2-rc.1 on the desktop app's bundled runtime.
|
|
82
83
|
|
|
83
84
|
<!-- counts:begin (generated by scripts/sync-test-counts.mjs; do not edit by hand) -->
|
|
84
|
-
**`node test/run.mjs` ran
|
|
85
|
+
**`node test/run.mjs` ran 1539 checks in 34 offline suites on this machine (Windows, DSH installed, 5/9 client config fixtures), all passing.** Parenthesised numbers in the table depend on the environment (DSH present or not, platform, filesystem case sensitivity, how many client configs the machine has), so the total moves with it; this line records the environment of the report used for the last sync.
|
|
85
86
|
|
|
86
87
|
That is **524 assertions in the client-registration suites alone** (`setup` 45, `removal-scope` 24, `config-safety` 407, `real-configs` 48), which is where the 0.3 work actually is.
|
|
87
88
|
|
|
88
89
|
The model-backed suites (`boundary` 46, `integration` 30, `async` 21, `sentinel` 36, `timeout` 40, `lock` 79, `multi` 19, `list` 17, `permission` 11, `shared-process` 8, `cycle` 36, `concurrency` 31, `capability` 22, `workspace-effect` 24, `acceptance` 36, `reasoning-check` 8) total **464 checks** nominally; with DSH present `smoke` runs 13 more checks in its session sections. They need a working DSH and provider, so re-run them yourself before trusting a release: `node test/run.mjs --all`. `acceptance` **skips explicitly** when the DSH profile has no IDA MCP (`mcp__ida__*`); `shared-process` also skips without DSH.
|
|
89
90
|
|
|
90
|
-
> Three different totals are intentional: `node test/run.mjs` on this machine (
|
|
91
|
+
> Three different totals are intentional: `node test/run.mjs` on this machine (1539), the CI offline job (nominal 1471 on Linux/macOS, 1477 on Windows; hosted runners have no DSH and no client fixtures, so `smoke` skips its session sections and `real-configs` skips entirely), and the model-backed set (464 nominal). Counts are the numbers each suite prints itself, written back from the report by `npm run docs:counts`; don't copy them by hand and don't use one number for a different runner.
|
|
91
92
|
<!-- counts:end -->
|
|
92
93
|
|
|
93
94
|
### 🔢 After changing tests: counts are computed
|
package/docs/en/usage.md
CHANGED
|
@@ -93,7 +93,8 @@ dsh-any-agent dashboard
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
- `watch [conversation id prefix]`: live call chain in the terminal (`--once` prints once and exits, `--runs N` shows the last N runs per conversation).
|
|
96
|
-
- `dashboard`: a **read-only** local page; prints a URL with a one-time token (binds `127.0.0.1` only, checks the Host header, has no control that changes state).
|
|
96
|
+
- `dashboard`: a **read-only** local page; prints a URL with a one-time token (binds `127.0.0.1` only, checks the Host header, has no control that changes state); add `--open` to launch it in your default browser.
|
|
97
|
+
- **Interface language**: both follow your system language (`DSH_MCP_LANG` / `LANG` / the OS locale); pass `--lang en|zh` to choose. The page also has a switch in its top-right corner, and remembers your choice.
|
|
97
98
|
- Both are separate processes: they don't start the server, never touch the write lock and never talk to DSH, and they show conversations dispatched from every client (Claude Code, Codex, Cursor, …) together.
|
|
98
99
|
- A run whose writer process has exited without finishing is shown as interrupted.
|
|
99
100
|
- **Token usage**: each run's input / output / cache read-write delta, the session total and context-window use, updated while the run is going (numbers come from DSH's own session record). The dashboard's side column has a "Token 消耗" panel and a per-turn chart.
|
package/docs/zh/architecture.md
CHANGED
|
@@ -38,6 +38,18 @@ DSH 自带两个可编程入口。`sdk` profile 看着更简单,但它的 JSON
|
|
|
38
38
|
|
|
39
39
|
**进程模型。** 会话的身份是 `sessionId`,进程只是可丢弃的载体。0.5.0 起同一 `(cwd, permission)` 的会话默认共用一个 ACP 进程(`src/host.mjs`):DSH 的 ACP 服务端支持一条连接上多会话、每个 session 有自己的 Agent,但沙箱根与审批策略是**进程级**的,所以只能按这两项分组。收益是第二个会话建起来约 300ms(独立进程约 4s),内存也按组只付一份;代价是这**一个**进程崩了,组里所有会话的进行中回合会一起断(会话本体无损,下次派活各自 resume)——因此 `dsh_takeover` 不会在不带 `force` 的情况下杀共享进程。要回到「一会话一进程」就设 `DSH_MCP_SHARED_PROCESS=0`。
|
|
40
40
|
|
|
41
|
+
**实时查看(0.6.1 起)。** 本服务是每个 MCP 客户端各自拉起的 stdio 子进程,可能同时有好几个实例、内存互不相通,也都没有端口。所以实时查看走文件而不是端口:每个实例在派活过程中把会话的活动(回合、工具调用、审批、token 用量)追加到 `<状态目录>/live/<会话id>/events.jsonl`。写锁本来就保证一个会话同一时刻只有一个写者,所以"一会话一文件"不需要再加锁。`dsh-any-agent watch` 和 `dsh-any-agent dashboard` 是独立的只读进程,tail 这些文件,因此能同时看到所有客户端、所有实例派出的活,服务重启也不丢,并且完全不碰写锁和 DSH:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
MCP 客户端 ─▶ dsh-any-agent(每个客户端一个)──追加──▶ live/<id>/events.jsonl
|
|
45
|
+
│ tail(只读)
|
|
46
|
+
┌─────────────┴─────────────┐
|
|
47
|
+
dsh-any-agent watch dsh-any-agent dashboard
|
|
48
|
+
(终端,原地刷新) (127.0.0.1 + token,SSE)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
token 数字取自 DSH 自己的投影缓存(`tokenUsage` / `contextPressure`),DSH 每走一个模型步就会更新它。设计细节见 [实时查看设计](live-view-design.md)。
|
|
52
|
+
|
|
41
53
|
**MCP 兼容性。** 服务端只使用 `initialize`、`ping`、`tools/list`、`tools/call` 四个标准方法。已用一个协商旧版协议、从不发送 `notifications/initialized` 的原始客户端验证。
|
|
42
54
|
|
|
43
55
|
</details>
|
package/docs/zh/limits.md
CHANGED
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
7. **`session/list` 只返回 `{sessionId, cwd}`,且排除已打开的会话** —— 对话名由本服务从 DSH 投影缓存补全。
|
|
13
13
|
8. **内存(实测 300–480MB/活跃进程)** —— 0.5.0 起同 `(cwd, permission)` 的会话**默认共用一个 ACP 进程**,所以这份开销是**每组一份**,不再按会话翻倍;再加上 0.4.0 起的默认 **空闲 10 分钟回收 + `MAX_LIVE=4`**,最坏约 1.2–1.9GB。`keep_alive=true` 的会话不因空闲被回收(代价是常驻)。还可以:① `DSH_MCP_MAX_RSS_MB` 按实际 RSS 预算回收;② `DSH_MCP_CHILD_MAX_OLD_SPACE_MB` 给子进程设堆上限。一批活干完想立刻归零:**`dsh_release(all=true)`** ✓(默认只释放空闲会话,**正在跑的回合会跳过、继续跑**;要连它们一起停掉传 `force=true`。之后照样 resume)。
|
|
14
14
|
9. **共享进程会连坐** —— 默认开启的共享宿主让同 `(cwd, permission)` 的会话共用一个 PID:会话之间互不干扰(各自有 Agent 与上下文),但**进程一旦崩溃/被杀,同组所有会话的进行中回合会一起断**(会统一写 error 哨兵并唤醒等待者,下次派活各自 resume,会话本体无损)。所以:① `dsh_takeover` 杀这类进程前要求 `force=true`,并列出"车上还有谁";② 需要严格隔离(分析不可信样本、跑互不信任的任务)就设 `DSH_MCP_SHARED_PROCESS=0`;③ 不同工作区/不同权限档本来就各起一个进程 —— 那是 `dsh-base` 的进程级沙箱与审批配置决定的,不是可选项。
|
|
15
|
+
10. **实时查看(`watch` / `dashboard`)的边界** ——
|
|
16
|
+
① **只看本机**:事件写在本机的 `$DSH_HOME/.dsh-any-agent/live/`,远程或云端的会话看不到;
|
|
17
|
+
② **只读**:不能在页面上批准、插话或中断,这些仍走 MCP 工具(`dsh_approval_decide` / `dsh_interject` / `dsh_interrupt`);
|
|
18
|
+
③ **只看得到经 ACP 上报的工具调用**:DSH 子代理内部的调用是否上报由 DSH 决定,本服务不额外推断;
|
|
19
|
+
④ **token 数字来自 DSH 的会话记录**:DSH 还没写入时显示"暂无数据";会话累计包含 live 之前的回合;
|
|
20
|
+
⑤ **事件不追溯**:0.6.1 之前跑过的回合没有调用链,0.6.2 之前的回合没有 token。
|
|
15
21
|
|
|
16
22
|
## ❓ 为什么 profile 还叫 `dsh-mcp`?
|
|
17
23
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
> 返回 [README](../../README.zh.md)
|
|
1
|
+
> 返回 [README](../../README.zh.md) · English: [live view design](../en/live-view-design.md)
|
|
2
2
|
|
|
3
3
|
# 设计稿:让用户看见 DSH 在做什么(live 事件流 + 查看器)
|
|
4
4
|
|
|
5
|
-
> 状态:**P1 + P2 已实现**,随 0.6.1 发布;token 用量与查看端改进随 0.6.2
|
|
5
|
+
> 状态:**P1 + P2 已实现**,随 0.6.1 发布;token 用量与查看端改进随 0.6.2 发布;之后又加了英文界面、`--open` 与 `dsh_doctor` 的 live 检查(见 CHANGELOG)。实现与本文最初草案的差异已在对应小节标注"实现"。
|
|
6
6
|
>
|
|
7
7
|
> 代码:`src/live.mjs`(写入)、`src/live-view.mjs`(读取 / 归并 / 终端渲染)、`src/live-cli.mjs`(`watch` / `dashboard` 子命令)、`src/live-dashboard.mjs` + `src/live-dashboard.html`(页面)。测试:`test/live-events.mjs`、`test/live-view.mjs`。
|
|
8
8
|
>
|
|
@@ -144,8 +144,13 @@ tail 一个或全部会话,按会话分组、工具行原地刷新、审批高
|
|
|
144
144
|
- **独立进程**,与 MCP 服务进程无关,所以 §5 方案 A 的端口、生命周期问题都不存在。
|
|
145
145
|
- 只绑定 `127.0.0.1`、随机端口;启动时生成一次性 token,放在打印出来的 URL 里;校验 `Host` 头防 DNS rebinding;只提供 GET。
|
|
146
146
|
- 单个内联静态 HTML,不依赖 CDN;数据走 SSE。
|
|
147
|
-
-
|
|
147
|
+
- 文件变更检测:(实现)只用定时轮询,不用 `fs.watch`(Windows 上不可靠)。
|
|
148
148
|
- 视图:左侧会话列表(状态、cwd、标题、最近活动),右侧当前会话的时间线(轮次 → 工具 → 审批 → 结束摘要)。
|
|
149
|
+
- 实现:中间是回合时间线,每次工具调用带一根时间条,长回合在卡片内部滚动;右栏是概览、Token 消耗、工具分布、涉及文件、审批。`--open` 直接用默认浏览器打开。
|
|
150
|
+
|
|
151
|
+
### 7.3 界面语言
|
|
152
|
+
|
|
153
|
+
(实现)两个查看端都有中英文:`--lang zh|en`,否则依次看 `DSH_MCP_LANG`、`LC_ALL` / `LC_MESSAGES` / `LANG`、系统区域设置。网页右上角也能切换,并记住选择。
|
|
149
154
|
|
|
150
155
|
## 8. 与现有机制的关系
|
|
151
156
|
|
|
@@ -160,7 +165,7 @@ tail 一个或全部会话,按会话分组、工具行原地刷新、审批高
|
|
|
160
165
|
|
|
161
166
|
1. 思考内容在 schema 里没有位置(§6.4)。
|
|
162
167
|
2. 所有字符串字段过 `sanitizeApprovalContext`,再按字段截断。
|
|
163
|
-
3.
|
|
168
|
+
3. (实现)默认 `detail`:只多出脱敏后的短预览,暴露面不超过哨兵;`meta` 去掉这些预览,`off` 完全不写。
|
|
164
169
|
4. **为什么不做控制面**:待决审批、插话、中断的状态都在某个服务进程的内存里(`pendingApprovals` 等)。独立的 dashboard 进程要操作它们,只能再开一条反向通道(例如服务进程监视一个命令目录),这等于给"能写本地文件的任何东西"开了一个批准提权的口子,需要单独的鉴权设计,和"轻量"的定位不符。
|
|
165
170
|
5. dashboard 只监听回环地址,token 只在启动时打印,不落盘。
|
|
166
171
|
|
|
@@ -169,7 +174,7 @@ tail 一个或全部会话,按会话分组、工具行原地刷新、审批高
|
|
|
169
174
|
- 离线(`test/fake-acp.mjs` 驱动):发 `tool_call` / `tool_call_update` / `request_permission`,断言 live 文件的行、级别过滤、脱敏、状态去重、单行截断。
|
|
170
175
|
- 思考内容回归:fake ACP 发 `agent_thought_chunk`,断言 live 文件里不出现其文本(任何级别)。
|
|
171
176
|
- 写入失败:live 目录不可写时派活仍成功。
|
|
172
|
-
-
|
|
177
|
+
- 崩溃:无 `run.end` 且写者进程已退出时,查看器显示为中断(实现:不写交接事件,见 §6.5)。
|
|
173
178
|
- 轮转与 TTL 清理、forget 时删除。
|
|
174
179
|
- 查看器解析:半行、未知 `v`、未知 `type`。
|
|
175
180
|
- 所有测试设置 `DSH_MCP_LIVE_DIR` 到各自临时目录;live 用例不并发跑真实 DSH(与现有 live 测试同一规则)。
|
|
@@ -181,7 +186,7 @@ tail 一个或全部会话,按会话分组、工具行原地刷新、审批高
|
|
|
181
186
|
3. ~~默认级别~~ 已定:默认 `detail`(理由见 §6.1)。
|
|
182
187
|
4. ~~正文要不要进 live~~ 已定:不进,正文只在哨兵里。
|
|
183
188
|
5. **多机 / 远程**:live 目录在本机;远程或云端会话的可见性不在本文范围。
|
|
184
|
-
6.
|
|
189
|
+
6. ~~英文版文档~~ 已补:[docs/en/live-view-design.md](../en/live-view-design.md)。
|
|
185
190
|
|
|
186
191
|
## 12. 分期
|
|
187
192
|
|
package/docs/zh/reference.md
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
| `DSH_MCP_REVIEWS_DIR` | `<状态文件同目录>/reviews` | 审核裁决的落盘目录(`<此目录>/<会话id>/<run_id>.review.json`)。不可写会让服务启动时明确失败 |
|
|
33
33
|
| `DSH_MCP_LIVE` | `detail` | live 事件流级别(`watch` / `dashboard` 的数据源)。`detail` 带脱敏后的 prompt / 命令 / 路径预览;`meta` 只有工具名、状态、计时、审批类型;`off` 不写。思考内容任何级别都不写 |
|
|
34
34
|
| `DSH_MCP_LIVE_DIR` | `<状态文件同目录>/live` | live 事件流目录(`<此目录>/<会话id>/events.jsonl`,5MB 滚动到 `.1`)。写不进去只会静默跳过,不影响派活;按 `DSH_MCP_SENTINEL_TTL_MS` 清理 |
|
|
35
|
+
| `DSH_MCP_LANG` | 跟随系统 | `watch` / `dashboard` 的界面语言:`zh` 或 `en`。未设置时依次看 `LC_ALL` / `LC_MESSAGES` / `LANG`、系统区域设置;命令行 `--lang` 优先于它 |
|
|
35
36
|
| `DSH_MCP_PERMISSION` | `workspace-write` | `dsh_start` 未指定时的权限档。设为 `danger-full-access` 还需要 `DSH_MCP_ALLOW_FULL_ACCESS=1` |
|
|
36
37
|
| `DSH_MCP_ALLOW_FULL_ACCESS` | `0` | 是否允许 `permission=danger-full-access`。默认拒绝:该档把整机写权限交给调用方 |
|
|
37
38
|
| `DSH_MCP_REASONING_EFFORT` | `max` | 默认思考深度 |
|
package/docs/zh/release.md
CHANGED
|
@@ -8,31 +8,40 @@
|
|
|
8
8
|
# 1. 升版本号。SERVER_VERSION 读的是 package.json,别处不用改
|
|
9
9
|
# (以前这里有两份数字,漂移过)
|
|
10
10
|
npm version patch --no-git-tag-version
|
|
11
|
-
#
|
|
11
|
+
# 同步 server.json 里的两个 version(顶层与 packages[0]),check:release 会核对
|
|
12
|
+
# 然后手工改 CHANGELOG.md:
|
|
13
|
+
# - 把 [Unreleased] 变成新版本一节(这一节就是 GitHub Release 的说明,写给用户看)
|
|
14
|
+
# - 文末链接区:[Unreleased] 改成 compare/vx.y.z...HEAD,并加一行 [x.y.z]: …/compare/v上一版...vx.y.z
|
|
12
15
|
|
|
13
16
|
# 2. 本地跑一遍离线套件(和 CI 相同,不花 token、不需要 DSH)
|
|
14
17
|
node test/run.mjs
|
|
15
18
|
node test/package-check.mjs
|
|
16
|
-
npm run check:release # 版本号 / CHANGELOG / 文档链接 /
|
|
19
|
+
npm run check:release # 版本号 / CHANGELOG 这一节与文末链接 / 能否生成 Release 说明 / 文档链接 / 打包清单
|
|
17
20
|
npm run docs:counts:check # tests.md 与 CI 注释里的检查数 = 刚才 node test/run.mjs 写出的报告
|
|
18
21
|
# 不一致就是改过测试没更新数字:node test/run.mjs --all --report .state/test-report.json && npm run docs:counts
|
|
19
22
|
# (模型套件的数字只有带 --all 的报告才会更新,见 docs/zh/tests.md「改了测试之后」)
|
|
23
|
+
node scripts/release-notes.mjs x.y.z # 预览 Release 页面上会显示的说明
|
|
20
24
|
|
|
21
25
|
# 3. 确认 tarball 本身:打得出来、装得上、起得来
|
|
22
26
|
npm pack --dry-run
|
|
23
27
|
# 再在临时目录里:npm install <tgz> && node_modules/.bin/dsh-any-agent --version
|
|
24
28
|
|
|
25
|
-
# 4.
|
|
26
|
-
git
|
|
27
|
-
git
|
|
28
|
-
# 推送 tag 后,.github/workflows/release.yml 会自动创建 GitHub Release
|
|
29
|
-
# (使用 GitHub 生成的 release notes);没有 gh CLI 也不影响。
|
|
30
|
-
|
|
31
|
-
# 5. 发布
|
|
32
|
-
npm publish
|
|
29
|
+
# 4. 合进 main(走 PR,CI 绿了再合),然后在 main 的那个提交上打 tag 并推送
|
|
30
|
+
git tag -a vx.y.z <main 上的提交> -m "vx.y.z"
|
|
31
|
+
git push origin vx.y.z
|
|
33
32
|
```
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
**推送 tag 之后的事都由 `.github/workflows/release.yml` 完成,不需要再在本机 `npm publish`:**
|
|
35
|
+
|
|
36
|
+
1. **GitHub Release**:说明取自 CHANGELOG 里这个版本的那一节(`scripts/release-notes.mjs`),末尾附安装命令和 compare 链接。Release 已存在时只更新说明,所以重跑是安全的。
|
|
37
|
+
2. **npm 发布**:先核对 tag 与 `package.json` 版本一致,再跑 `check:release` 与 `package-check`,然后用 **npm Trusted Publishing**(GitHub OIDC)发布并附带 provenance —— 不需要 `NPM_TOKEN`,npm 页面上会显示"由这个仓库的 GitHub Actions 构建"。这个版本已经在 npm 上就跳过。
|
|
38
|
+
3. **MCP Registry**:npm 发布之后,用工作流自己的 GitHub OIDC 登录官方 MCP Registry(`io.github.xmtlzzz/*` 命名空间),把 `server.json` 登记上去。Registry 通过 npm 包里的 `mcpName` 字段确认归属,所以必须在 npm 之后。`mcp-publisher` 版本固定,并用 cosign 校验签名后才执行。
|
|
39
|
+
|
|
40
|
+
**一次性配置(只需做一次)**:npmjs.com → 包 `dsh-any-agent` → Settings → Trusted Publisher → GitHub Actions,填仓库 `xmtlzzz/dsh-any-agent`、工作流文件 `release.yml`。没配之前,npm 那一步会报认证错误(GitHub Release 不受影响);这时可以在本机手动 `npm publish` 兜底,之后在 Actions 里重跑也只会跳过 npm。
|
|
41
|
+
|
|
42
|
+
**重发某个旧版本的 Release 说明**:Actions → release → Run workflow,填已有的 tag(如 `v0.6.2`)。npm 那一步会因为版本已存在而跳过。
|
|
43
|
+
|
|
44
|
+
**发布后看到 404,不要当成失败。** npm 对新版本是异步入库(返回 `PUT 202` 而不是 `200`),几秒后去查 registry 仍然是 404,看起来像没发成功。CI 发布时看 npm 那一步的日志;本机手动发布时确认 npm 日志里有这一行:
|
|
36
45
|
|
|
37
46
|
```
|
|
38
47
|
grep 'http fetch PUT 202' "$LOCALAPPDATA/npm-cache/_logs/<最新的>.log"
|
package/docs/zh/tests.md
CHANGED
|
@@ -45,8 +45,9 @@ npm run docs:counts:check # 只检查文档与报告是否一致,不一致 exi
|
|
|
45
45
|
| `read-cursor` | 26 | **`dsh_read` 游标语义**:追加到已读条目里的文本必须再次返回(修订号游标,按 `seq` 替换全文);**每个工具调用各占一条**、状态更新原地替换(以前会拼成一条、只剩第一个工具的 id);空回合的开始/结束仍是两条;**`answer` 里被工具调用隔开的正文之间补空行**(以前首尾相接);`limit` 钳到 1..500,`0`/负数不会空转或丢尾(不花 token) |
|
|
46
46
|
| `line-decoder` | 4 | **UTF-8 行分帧**:MCP/ACP 的 chunk 边界不能把中文/emoji 截成 U+FFFD;覆盖逐字节、任意 chunk、CRLF 与 EOF 残留(不花 token) |
|
|
47
47
|
| `sentinel-redaction` | 19 | **哨兵落盘卫生**:`result.thinking` 被剥掉、`thinking_omitted` 始终是布尔值、`answer`/`error`/`title`/`tools_used[].input` 都走与审批相同的凭据脱敏(不花 token) |
|
|
48
|
-
| `live-events` |
|
|
49
|
-
| `live-view` |
|
|
48
|
+
| `live-events` | 43 | **live 事件流写入侧**:假 hub + 假 ACP 进程跑完整回合;事件序列与字段、工具只在状态变化时写、思考内容不落盘、预览脱敏、`meta`/`off` 级别、写失败不影响派活、ask 审批裁决、失败/排队/取消/forget(不花 token) |
|
|
49
|
+
| `live-view` | 66 | **live 查看端**:按字节切行(不切坏中文)、调用链归并、写者退出显示中断、增量 tail(半行/滚动/删除)、`watch --once` 走 bin 入口、`dashboard` 的 token / Host / 只读校验与 SSE(不花 token) |
|
|
50
|
+
| `release-notes` | 12 | **Release 说明**:从 CHANGELOG 取对应一节(`1.1.1` 不误匹配 `1.1.10`、到链接定义区为止)、空节与缺失版本直接报错、附安装命令与 compare 链接、命令行退出码(不花 token) |
|
|
50
51
|
| `review` | 70 | **调用方审核**:默认 `advisory` 审核单带清单 + 证据 + `dsh_review` 入口;`dsh_review` 把裁决落盘并回写内存 run 与哨兵;`fail`/`partial` 必须给理由;未完成/未知 run 被拒绝;重启后仍可凭哨兵审核;队列与 `client_run_id` 重放收据也带审核单;审核记录与哨兵共享 TTL;理由、预览、证据**与清单项**都凭据脱敏;**路径穿越 id 在触盘前就被拒绝**(不花 token) |
|
|
51
52
|
| `forget-tombstone` | 10 | **`forget` 是终态**:`release(forget=true)` 写墓碑,按 id 查询明确失败并给出恢复路径,列表不再展示;删除墓碑后回到常规“未知会话”路径(不花 token) |
|
|
52
53
|
| `bootstrap-profile` | 24 | **首启 profile 在零依赖发布形态下可用**:内置 YAML 子集解析器复制路由、剥掉 `apiKey`/`headers`/字面密钥、绝不写 `defaultPreset`、幂等;用户手写文件逐字节不动(不花 token) |
|
|
@@ -63,7 +64,7 @@ npm run docs:counts:check # 只检查文档与报告是否一致,不一致 exi
|
|
|
63
64
|
| `test-counts` | 92 | **检查数同步**:套件输出解析(中英文汇总行、整套 skip、`✓`/`✗` 兜底、`[report]` 环境标注)、报告拼装、「主数字(其它环境的数字)」单元格往返、对中英文文档与 `tests.yml` 的重写、`--check` 差异与 `--portable`、模型套件数字不动、失败的报告被拒绝(不花 token) |
|
|
64
65
|
| `roots` | 12 | 不传 `cwd` 时通过 MCP roots 取客户端当前项目;不支持 roots 的客户端得到可操作的报错(不花 token) |
|
|
65
66
|
| `workspace-titles` | 18(TEMP 在家目录外时 17,区分大小写且 TEMP 在家目录外时 16) | 家目录**与文件系统根目录**当工作区都会在 DSH 启动前被拦住;`title_hint` 与 DSH 自动标题分开返回(不花 token) |
|
|
66
|
-
| `package-check` |
|
|
67
|
+
| `package-check` | 15 | `files` 白名单、零运行时依赖、Node 下限、bin shebang(不花 token) |
|
|
67
68
|
| `lint` | 22 | **静态检查 `npm run lint` 自身的回归**:在临时目录造一棵有问题的树,逐条断言语法错、导入路径不存在 / 大小写不一致、具名导出不存在(含 `export { x } from`、`await import()` 解构、`node:` 内置模块)、未使用导入都会报;写法刁钻但合法的树(模板字符串里的用法、正则里的引号、`export *`)零误报;并断言本仓库自己是 lint 干净的(不花 token) |
|
|
68
69
|
| `timeout` | 40 | **超时语义**:`wait=false` 不受 `timeout_ms` 影响、`wait=true` 到期只转后台(不取消/不丢结果、`busy` 不说谎)、`timeout_ms=0` 一直等、**负 `timeout_ms` 明确报错**(不派出回合、会话照常可用)、resume 失败会作废进程而不永久卡死、空 prompt 给清晰错误 |
|
|
69
70
|
| `lock` | 79 | **写锁与抢占**:四类持有者判定、`writeMarker` 不覆盖活着的持有者、登记文件损坏/目录缺失等边界;**端到端**两实例抢锁 → 清晰报错 → 拒绝 → `force` 夺锁;**崩溃自动释放锁**、**卡死持有者无 force 自动抢占**;以及"一个回合 = 一个哨兵"的不变量(含内联回合与空闲插话) |
|
|
@@ -80,13 +81,13 @@ npm run docs:counts:check # 只检查文档与报告是否一致,不一致 exi
|
|
|
80
81
|
> **2026-11-06**:`node test/run.mjs` 现在**跑完全部套件**再在末尾列出所有失败项(`--bail` 恢复遇错即停)—— 以前 `timeout.mjs` 一条过时断言就让后面 11 个真机套件全部没跑。需要模型的套件已对桌面版自带运行时上的 DSH 0.2.2-rc.1 全量重跑。
|
|
81
82
|
|
|
82
83
|
<!-- counts:begin (generated by scripts/sync-test-counts.mjs; do not edit by hand) -->
|
|
83
|
-
**`node test/run.mjs` 在本机(Windows,装了 DSH,5/9 个客户端配置 fixture)跑了
|
|
84
|
+
**`node test/run.mjs` 在本机(Windows,装了 DSH,5/9 个客户端配置 fixture)跑了 34 个离线套件、1539 项检查,全部通过。**表中带括号的数字随环境变化(有没有 DSH、哪个平台、文件系统是否区分大小写、本机装了几个客户端配置),套件总数也随之变化;这一行记录的是上次同步所用报告的环境。
|
|
84
85
|
|
|
85
86
|
其中 **524 项属于客户端注册器套件**(`setup` 45、`removal-scope` 24、`config-safety` 407、`real-configs` 48)—— 0.3 的活其实都在这里。
|
|
86
87
|
|
|
87
88
|
需要模型的套件(`boundary` 46、`integration` 30、`async` 21、`sentinel` 36、`timeout` 40、`lock` 79、`multi` 19、`list` 17、`permission` 11、`shared-process` 8、`cycle` 36、`concurrency` 31、`capability` 22、`workspace-effect` 24、`acceptance` 36、`reasoning-check` 8)名义合计 **464 项**;有 DSH 时 `smoke` 还会多跑 13 项会话段。它们要可用的 DSH 与 provider,发版前请自己重跑一遍:`node test/run.mjs --all`。`acceptance` 在没有 IDA MCP(`mcp__ida__*`)时会明确 **skip**;`shared-process` 没有 DSH 时也会 skip。
|
|
88
89
|
|
|
89
|
-
> 三个总数是刻意分开的:本机 `node test/run.mjs`(
|
|
90
|
+
> 三个总数是刻意分开的:本机 `node test/run.mjs`(1539)、CI 离线 job(名义 Linux/macOS 1471,Windows 1477;hosted runner 没有 DSH 也没有客户端 fixture,`smoke` 跳过会话段、`real-configs` 整体跳过)、需要模型的集合(名义 464)。数字取各套件自己打印的汇总,由 `npm run docs:counts` 从报告写回,不在这里手抄;不要把一个数字套到另一个 runner 上。
|
|
90
91
|
<!-- counts:end -->
|
|
91
92
|
|
|
92
93
|
### 🔢 改了测试之后:检查数是算出来的
|
package/docs/zh/usage.md
CHANGED
|
@@ -93,7 +93,8 @@ dsh-any-agent dashboard
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
- `watch [会话id前缀]`:在终端里实时显示调用链(`--once` 打印一次就退出,`--runs N` 每个会话显示最近几轮)。
|
|
96
|
-
- `dashboard`:起一个**只读**本地页面,打印出带一次性 token 的地址(只监听 `127.0.0.1`,校验 Host
|
|
96
|
+
- `dashboard`:起一个**只读**本地页面,打印出带一次性 token 的地址(只监听 `127.0.0.1`,校验 Host 头,没有任何能改变状态的按钮);加 `--open` 直接用默认浏览器打开。
|
|
97
|
+
- **界面语言**:两者默认跟随系统语言(`DSH_MCP_LANG` / `LANG` / 系统区域设置),`--lang zh|en` 可指定;网页右上角也能切换,选择会被记住。
|
|
97
98
|
- 两者都是独立进程:不启动服务、不碰写锁、不连 DSH,所有客户端(Claude Code、Codex、Cursor…)派出去的会话都能一起看到。
|
|
98
99
|
- 写者进程已退出而回合没有结束的,显示为"中断"。
|
|
99
100
|
- **token 用量**:每个回合的输入 / 输出 / 缓存读写增量、会话累计和上下文窗口占用,回合进行中就会更新(数字取自 DSH 自己的会话记录)。dashboard 右栏有"Token 消耗"面板和每回合用量图。
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-any-agent",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Drive DeepSeek Harness (DSH) as a long-lived coding agent from any MCP client — Claude Code, Claude Desktop, Codex, Cursor, Windsurf, Zed, Gemini CLI, opencode, VS Code. Resume-aware, interruptible, approval-capable, and it only ever touches the one config entry it owns.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"mcpName": "io.github.xmtlzzz/dsh-any-agent",
|
|
6
7
|
"license": "MIT",
|
|
7
8
|
"author": "xmtlz (https://github.com/xmtlzzz)",
|
|
8
9
|
"keywords": [
|
|
@@ -31,7 +32,8 @@
|
|
|
31
32
|
"bin/",
|
|
32
33
|
"src/",
|
|
33
34
|
"demo/",
|
|
34
|
-
"docs/",
|
|
35
|
+
"docs/zh/",
|
|
36
|
+
"docs/en/",
|
|
35
37
|
"profile-example/",
|
|
36
38
|
"README.md",
|
|
37
39
|
"README.zh.md",
|
|
@@ -40,9 +42,8 @@
|
|
|
40
42
|
"USAGE-MIGRATION.md",
|
|
41
43
|
"USAGE-MIGRATION.en.md",
|
|
42
44
|
"CHANGELOG.md",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"scripts/"
|
|
45
|
+
"SECURITY.md",
|
|
46
|
+
"LICENSE"
|
|
46
47
|
],
|
|
47
48
|
"scripts": {
|
|
48
49
|
"start": "node ./bin/dsh-any-agent.mjs",
|