@ran-sh/dsh-crew 0.3.0 → 0.3.1
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/README.md +72 -292
- package/README.zh.md +71 -236
- package/package.json +44 -42
- package/scripts/verify-npm-install.mjs +276 -0
- package/src/runtime-identity.mjs +1 -1
package/README.md
CHANGED
|
@@ -5,356 +5,136 @@
|
|
|
5
5
|
<h1 align="center">DSH Crew</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>Use Codex Desktop or Claude Code as the orchestrator, and dispatch coding work to DeepSeek Harness workers
|
|
8
|
+
<strong>Use Codex Desktop or Claude Code as the orchestrator, and dispatch coding work to isolated DeepSeek Harness workers and reviewers.</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="./README.md"><b>English</b></a> · <a href="./README.zh.md">简体中文</a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## What it does
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
- **Roles**: worker (implementation / fixes / tests / search) and reviewer (independent review + verdict)
|
|
19
|
-
- **Model Policy**: each role resolves its own ordered provider/model candidates (preferred → priority → escalation → Harness Default)
|
|
20
|
-
- Legacy Flash Only · Pro Only · Balanced · Review Pipeline modes migrate onto the role model automatically
|
|
21
|
-
- Follow DSH Provider
|
|
22
|
-
- First-class DSH Hub sessions (visible in the Web UI, async jobs with progress)
|
|
23
|
-
- Optional Vision / Image Generation (independently switchable)
|
|
17
|
+
DSH Crew adds a small orchestration layer between your coding host and DeepSeek Harness:
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
- **worker** — implements, fixes, tests, searches, and analyzes;
|
|
20
|
+
- **reviewer** — independently reviews the worker result and returns a verdict;
|
|
21
|
+
- **model policy** — resolves provider/model priorities per role and can escalate on evidence;
|
|
22
|
+
- **isolated execution** — coding workers run in temporary git worktrees by default;
|
|
23
|
+
- **live jobs** — Hub sessions expose progress, results, cancellation, routing trace, and readiness data.
|
|
26
24
|
|
|
27
|
-
```
|
|
25
|
+
```text
|
|
28
26
|
Codex Desktop / Claude Code
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Model Policy │ (each role resolves its own provider/model candidates;
|
|
40
|
-
cheap→strong │ escalation steps on evidence: FAIL tests, missing
|
|
41
|
-
│ │ delivery, blocked tasks)
|
|
42
|
-
└────┬────┘
|
|
43
|
-
│
|
|
44
|
-
▼
|
|
45
|
-
DeepSeek Harness
|
|
46
|
-
│
|
|
47
|
-
▼
|
|
48
|
-
DSH selected provider/model
|
|
27
|
+
│
|
|
28
|
+
▼
|
|
29
|
+
DSH Crew
|
|
30
|
+
┌────┴────┐
|
|
31
|
+
│ │
|
|
32
|
+
worker reviewer
|
|
33
|
+
│ │
|
|
34
|
+
└────┬────┘
|
|
35
|
+
▼
|
|
36
|
+
DeepSeek Harness
|
|
49
37
|
```
|
|
50
38
|
|
|
51
|
-
The orchestrator picks **what** to do and decides accept / reject / revise.
|
|
52
|
-
The workflow decides **when / state**. **Roles** decide **who** (worker executes,
|
|
53
|
-
reviewer reviews). **Model Policy** decides **which model** (never fixed to a
|
|
54
|
-
role). Workspace isolation decides **where** (each coding worker runs in its
|
|
55
|
-
own temporary git worktree, so parallel workers never write the same working
|
|
56
|
-
tree). Verification / reviewer decide **whether to accept**.
|
|
57
|
-
|
|
58
39
|
## Install
|
|
59
40
|
|
|
60
|
-
Prerequisites: Node.js
|
|
61
|
-
the safe cross-platform path because it controls the Crew DSH home and profile.
|
|
62
|
-
|
|
63
|
-
> **Official Harness isolation** — dsh-crew installs into its own dedicated DSH
|
|
64
|
-
> home (`~/.config/dsh-crew/harness`) and profile (`dsh-crew`), never into the
|
|
65
|
-
> official DSH home (`~/.dsh`) or the official `web` profile. Your normal
|
|
66
|
-
> DeepSeek Harness installation is never modified.
|
|
41
|
+
Prerequisites: **Node.js**, **Git**, and **pnpm**.
|
|
67
42
|
|
|
68
|
-
|
|
43
|
+
Recommended source setup:
|
|
69
44
|
|
|
70
45
|
```bash
|
|
46
|
+
git clone https://github.com/Ran-sh/dsh-crew.git
|
|
47
|
+
cd dsh-crew
|
|
71
48
|
node scripts/setup.mjs install
|
|
72
49
|
```
|
|
73
50
|
|
|
74
|
-
|
|
51
|
+
Windows can use:
|
|
75
52
|
|
|
76
53
|
```bat
|
|
77
54
|
install.cmd
|
|
78
55
|
```
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
integration is optional and has its own Install button.
|
|
57
|
+
DSH Crew uses its own Harness home and profile:
|
|
82
58
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
to the npm registry.
|
|
87
|
-
|
|
88
|
-
### Update
|
|
89
|
-
|
|
90
|
-
Re-run the source installer after pulling:
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
git pull
|
|
94
|
-
node scripts/setup.mjs install
|
|
59
|
+
```text
|
|
60
|
+
~/.config/dsh-crew/harness
|
|
61
|
+
profile: dsh-crew
|
|
95
62
|
```
|
|
96
63
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
### Migrating an older fork install
|
|
100
|
-
|
|
101
|
-
Legacy releases installed Crew into the official DSH `web` profile. Those
|
|
102
|
-
installs are outside the supported path. Uninstall any legacy Crew (see
|
|
103
|
-
Uninstall), then run the source installer above, which installs into the
|
|
104
|
-
dedicated Crew home/profile.
|
|
64
|
+
Supported Crew tooling does **not** modify the normal `~/.dsh` home, the official `web` profile, or official DSH credential stores.
|
|
105
65
|
|
|
106
|
-
|
|
66
|
+
## Quick start
|
|
107
67
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
68
|
+
1. Start DeepSeek Harness normally.
|
|
69
|
+
2. Open **Settings → DSH Crew**.
|
|
70
|
+
3. Install the Codex integration; Claude Code integration is optional.
|
|
71
|
+
4. Refresh Harness models and choose role priorities if needed.
|
|
72
|
+
5. Restart the coding host after integration changes.
|
|
112
73
|
|
|
113
|
-
|
|
114
|
-
npx -y @deepseek-ai/dsh plugin --profile web add github:Ran-sh/dsh-crew
|
|
115
|
-
npx -y @deepseek-ai/dsh plugin --profile web remove @ran-sh/dsh-crew
|
|
116
|
-
npx -y @deepseek-ai/dsh plugin --profile web remove @zseven-w/dsh-crew
|
|
117
|
-
```
|
|
74
|
+
Then ask the host naturally, for example:
|
|
118
75
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
### Migrating from Flash / Pro (v0.1 → v0.2 roles)
|
|
124
|
-
|
|
125
|
-
v0.2 keeps every old configuration field working. `collaboration_mode`,
|
|
126
|
-
`tier_policy`, `flash_state` / `pro_state`, `flash_model_priority` /
|
|
127
|
-
`pro_model_priority`, `escalate_on_failure` and `pro_reviews_flash` are all
|
|
128
|
-
still read, and a centralized migration rebuilds the role model from them:
|
|
129
|
-
|
|
130
|
-
- `flash-only` → worker auto, reviewer disabled, `economy` strategy.
|
|
131
|
-
- `pro-only` → worker auto (strong model class), reviewer disabled.
|
|
132
|
-
- `balanced` → worker auto, reviewer manual (available on request).
|
|
133
|
-
- `review-pipeline` → worker auto + reviewer auto (`auto_review` on);
|
|
134
|
-
reviewer uses the old Pro priority.
|
|
135
|
-
- `escalate_on_failure` → worker escalation policy `enabled`.
|
|
136
|
-
- `pro_reviews_flash` → automatic review after a successful worker run.
|
|
137
|
-
|
|
138
|
-
The old `ds-flash` / `ds-pro` subagents remain as **deprecated aliases**: they
|
|
139
|
-
map to the worker role with a legacy model-class hint, so existing prompts
|
|
140
|
-
keep working, but new prompts should use `ds-worker` / `ds-reviewer`. A
|
|
141
|
-
request that names both a role and a contradicting legacy tier is rejected
|
|
142
|
-
with a clear `ROLE_TIER_CONFLICT` error — never silently guessed.
|
|
143
|
-
|
|
144
|
-
### Troubleshooting: `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`
|
|
145
|
-
|
|
146
|
-
pnpm 11 enforces a default supply-chain policy (`minimum-release-age`, 24h):
|
|
147
|
-
a lockfile entry published within the last day is rejected unless listed under
|
|
148
|
-
`minimumReleaseAgeExclude`. Installing Crew into a fresh dedicated `dsh-crew`
|
|
149
|
-
profile is unaffected, but if the Crew profile already contains a just-published
|
|
150
|
-
DSH-plugin (for example the optional `dsh-plugin-image-mind`), the install fails
|
|
151
|
-
with this error.
|
|
152
|
-
|
|
153
|
-
Unblock it by adding the package (bare name, no version) under
|
|
154
|
-
`minimumReleaseAgeExclude` in the **Crew profile's** `pnpm-workspace.yaml`, then
|
|
155
|
-
re-run the installer:
|
|
156
|
-
|
|
157
|
-
```yaml
|
|
158
|
-
# ~/.config/dsh-crew/harness/profiles/dsh-crew/pnpm-workspace.yaml
|
|
159
|
-
minimumReleaseAgeExclude:
|
|
160
|
-
- dsh-plugin-image-mind
|
|
161
|
-
- '@ran-sh/dsh-vision@0.1.0'
|
|
76
|
+
```text
|
|
77
|
+
Use ds-worker to implement this change.
|
|
78
|
+
Use ds-reviewer to review the implementation.
|
|
162
79
|
```
|
|
163
80
|
|
|
164
|
-
|
|
165
|
-
by Crew tooling. Use a bare package name — listing multiple `pkg@version`
|
|
166
|
-
entries for the same name only honors the first (first-match-wins), which can
|
|
167
|
-
leave newer versions blocked. Keep the entries in `pnpm-workspace.yaml`; the
|
|
168
|
-
verifier does not read `.npmrc`. The policy only applies to registry packages
|
|
169
|
-
younger than one day (git-hosted installs like Crew itself are never
|
|
170
|
-
age-checked), so the failure is transient and self-heals within 24 hours.
|
|
81
|
+
## Roles and routing
|
|
171
82
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
1. In **Settings → DSH Crew**, click **Restore** for Codex and Claude Code integrations that you installed.
|
|
177
|
-
2. Uninstall the Crew plugin from the dedicated Crew profile:
|
|
178
|
-
|
|
179
|
-
```bash
|
|
180
|
-
node scripts/setup.mjs uninstall
|
|
181
|
-
```
|
|
83
|
+
| Role | Purpose |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `worker` | Implementation, fixes, tests, search, analysis |
|
|
86
|
+
| `reviewer` | Independent review and verdict |
|
|
182
87
|
|
|
183
|
-
|
|
184
|
-
profile must be removed for that profile using the DSH CLI against the official
|
|
185
|
-
home, without changing anything else there. Removing the Crew plugin does not
|
|
186
|
-
implicitly edit `~/.codex` or `~/.claude`. Crew configuration, backups,
|
|
187
|
-
credentials, and other DSH bundles are preserved.
|
|
88
|
+
Each role resolves its own ordered provider/model candidates. Explicit user priorities remain authoritative; automatic escalation only happens when the workflow has evidence that stronger execution or review is needed.
|
|
188
89
|
|
|
189
|
-
|
|
90
|
+
Legacy `ds-flash` / `ds-pro` aliases remain for compatibility, but new prompts should use `ds-worker` / `ds-reviewer`.
|
|
190
91
|
|
|
191
|
-
|
|
92
|
+
## Isolation
|
|
192
93
|
|
|
193
|
-
|
|
94
|
+
`execution.isolation` defaults to `worktree`.
|
|
194
95
|
|
|
195
|
-
|
|
196
|
-
git clone https://github.com/Ran-sh/dsh-crew.git
|
|
197
|
-
cd dsh-crew
|
|
198
|
-
install.cmd
|
|
199
|
-
```
|
|
96
|
+
A coding worker receives a temporary git worktree at the requested revision, so parallel workers do not write into the same working tree. The worker returns an auditable change candidate for the orchestrator to accept, reject, or revise; Crew does not silently merge changes into your primary workspace.
|
|
200
97
|
|
|
201
|
-
|
|
98
|
+
## Update / uninstall
|
|
202
99
|
|
|
203
|
-
|
|
204
|
-
git pull
|
|
205
|
-
install.cmd
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
Cross-platform:
|
|
100
|
+
Update a source install:
|
|
209
101
|
|
|
210
102
|
```bash
|
|
103
|
+
git pull --ff-only
|
|
211
104
|
node scripts/setup.mjs install
|
|
212
105
|
```
|
|
213
106
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
- links this local checkout into the dedicated Crew profile (`dsh-crew`) under
|
|
217
|
-
the Crew-owned DSH home (`~/.config/dsh-crew/harness`), never the official
|
|
218
|
-
`web` profile (`link:<repo>`)
|
|
219
|
-
- installs the Codex Desktop integration (no `codex` CLI required)
|
|
220
|
-
- installs the Claude Code integration automatically when the `claude` CLI is detected (optional)
|
|
221
|
-
- is idempotent — safe to re-run
|
|
222
|
-
|
|
223
|
-
Source uninstall on Windows:
|
|
224
|
-
|
|
225
|
-
```bat
|
|
226
|
-
uninstall.cmd
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
Source uninstall cross-platform:
|
|
107
|
+
Uninstall Crew and its integrations:
|
|
230
108
|
|
|
231
109
|
```bash
|
|
232
110
|
node scripts/setup.mjs uninstall
|
|
233
111
|
```
|
|
234
112
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
- DSH Crew from the dedicated Crew profile (official web profile is never touched)
|
|
238
|
-
- Codex Desktop integration
|
|
239
|
-
- Claude Code integration
|
|
240
|
-
|
|
241
|
-
It keeps:
|
|
242
|
-
|
|
243
|
-
- the repository
|
|
244
|
-
- Crew config (`~/.config/dsh-crew`)
|
|
245
|
-
- backups and credentials
|
|
246
|
-
|
|
247
|
-
## Quick Start
|
|
248
|
-
|
|
249
|
-
1. Start the official DeepSeek Harness as usual (its home and `web` profile are
|
|
250
|
-
never modified by Crew).
|
|
251
|
-
2. Open **Settings → DSH Crew** (the Crew Settings surface is served by the
|
|
252
|
-
Crew-owned Hub).
|
|
253
|
-
3. Keep the fresh default workflow: Codex → **worker** role → Codex (reviewer off).
|
|
254
|
-
4. Use **Refresh Harness Models** to choose ordered per-role model priorities when needed.
|
|
255
|
-
5. Restart Codex Desktop / Claude Code.
|
|
256
|
-
|
|
257
|
-
Then just say:
|
|
258
|
-
|
|
259
|
-
- “Use ds-worker to implement this change.”
|
|
260
|
-
- “Use ds-reviewer to review this implementation.”
|
|
261
|
-
|
|
262
|
-
## Roles
|
|
263
|
-
|
|
264
|
-
- **worker** — the executing role: implementation, fixes, tests, search, analysis. The default role for any coding request. It is a thin dispatcher; which model backs it is the Worker Model Policy's job.
|
|
265
|
-
- **reviewer** — the independent review role: inspects the implementation outcome, the workspace diff, tests and risks, and returns a verdict. Review-only by default; it does not re-implement.
|
|
266
|
-
- **ds-flash / ds-pro** remain as deprecated aliases for compatibility (see migration above).
|
|
267
|
-
|
|
268
|
-
A role's state is `disabled | manual | auto`: a disabled role refuses every
|
|
269
|
-
request, a manual role runs only when explicitly named, an auto role may be
|
|
270
|
-
chosen by the orchestrator. The effective state comes from the legacy
|
|
271
|
-
collaboration mode until you adopt the canonical config.
|
|
272
|
-
|
|
273
|
-
## Model Policy
|
|
274
|
-
|
|
275
|
-
Each role resolves its own ordered provider/model candidates through the live
|
|
276
|
-
Harness catalog:
|
|
277
|
-
|
|
278
|
-
- attempt 0 → the role's primary (cheap/fast) priority;
|
|
279
|
-
- attempt ≥ 1 → the escalation (strong) priority — escalation happens on
|
|
280
|
-
*evidence* (FAIL tests, incomplete delivery, blocked/incomplete task,
|
|
281
|
-
workspace diff that disagrees with the worker's report), not on failure
|
|
282
|
-
alone, and never beyond `max_attempts` (0..max-1, total attempts);
|
|
283
|
-
- otherwise → Harness Default.
|
|
284
|
-
|
|
285
|
-
Infrastructure failures (missing API key, unreachable hub, non-git workspace
|
|
286
|
-
under worktree isolation) are **never** "fixed" by a stronger model — they
|
|
287
|
-
fail with a stable error code instead. Flash / Pro survive only as legacy
|
|
288
|
-
model-class hints (`deepseek-v4-flash` / `deepseek-v4-pro`); they are not
|
|
289
|
-
roles.
|
|
290
|
-
|
|
291
|
-
## Workflow
|
|
292
|
-
|
|
293
|
-
Every dispatch — `dsh_run_worker` (blocking) and `dsh_spawn_worker` (async),
|
|
294
|
-
Hub or Standalone — runs the **same** workflow runtime:
|
|
113
|
+
Windows shortcuts:
|
|
295
114
|
|
|
115
|
+
```bat
|
|
116
|
+
install.cmd
|
|
117
|
+
uninstall.cmd
|
|
296
118
|
```
|
|
297
|
-
CREATED -> (QUEUED when busy) -> RUNNING -> VERIFYING
|
|
298
|
-
-> ESCALATING (evidence) -> RUNNING -> VERIFYING
|
|
299
|
-
-> REVIEWING (automatic reviewer) -> READY -> COMPLETED
|
|
300
|
-
(or FAILED / CANCELLED)
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
The only difference between blocking and async is whether the caller awaits.
|
|
304
|
-
A coding worker runs in its own temporary git worktree (isolated execution);
|
|
305
|
-
a `change candidate` (base revision, committed + uncommitted + new-file
|
|
306
|
-
changes, bounded redacted patch, fingerprint) is returned for the orchestrator
|
|
307
|
-
to accept / reject / revise — the runtime never auto-merges into your working
|
|
308
|
-
tree.
|
|
309
119
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
`execution.isolation` is `worktree` by default: a worker role converges on a
|
|
313
|
-
fresh detached worktree at HEAD and the primary workspace is never modified
|
|
314
|
-
(even while it is dirty; uncommitted primary changes are not folded in). If
|
|
315
|
-
the requested workspace is **not** a git repository, worktree-isolated jobs
|
|
316
|
-
fail closed (`NOT_GIT_REPOSITORY`) instead of silently sharing — set
|
|
317
|
-
`execution.isolation: "shared"` to allow the legacy in-place behavior.
|
|
318
|
-
|
|
319
|
-
## Modes
|
|
320
|
-
|
|
321
|
-
| Mode | Mapped role behavior |
|
|
322
|
-
|---|---|
|
|
323
|
-
| Flash Only | worker auto, reviewer disabled, economy model strategy |
|
|
324
|
-
| Pro Only | worker auto on the strong model class, reviewer disabled |
|
|
325
|
-
| Balanced | worker auto, reviewer manual (available on request) |
|
|
326
|
-
| Review Pipeline | worker auto + reviewer auto (`auto_review` on) |
|
|
120
|
+
Crew config and backups under `~/.config/dsh-crew` are preserved unless you remove them yourself.
|
|
327
121
|
|
|
328
|
-
|
|
122
|
+
## Development
|
|
329
123
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
Credentials stay in the DSH provider configuration only. Tested with an OpenAI-compatible OpenCode Go gateway. Standalone mode (no DSH running) always uses DeepSeek Official + `DEEPSEEK_API_KEY`.
|
|
338
|
-
|
|
339
|
-
## Hosts
|
|
340
|
-
|
|
341
|
-
- **Codex Desktop** is directly supported through the shared `~/.codex` configuration. The Codex CLI is **not required**; it is an optional extra host / management interface.
|
|
342
|
-
- **Claude Code** is optional; the one-click setup installs its integration automatically when the Claude CLI is detected.
|
|
343
|
-
|
|
344
|
-
## Notes
|
|
124
|
+
```bash
|
|
125
|
+
pnpm install --frozen-lockfile
|
|
126
|
+
node --test test/*.test.mjs
|
|
127
|
+
pnpm run build:client
|
|
128
|
+
pnpm run verify:npm-install
|
|
129
|
+
```
|
|
345
130
|
|
|
346
|
-
|
|
347
|
-
- Standalone uses DeepSeek Official only.
|
|
348
|
-
- Crew Vision registration changes may require a DSH restart.
|
|
349
|
-
- Restart Codex Desktop after integration changes.
|
|
350
|
-
- Every worker returns an auditable Delivery Report (`## Diff` / `## Tests` / `## Risks`) and the isolated candidate captures a bounded, redacted patch so you can verify what changed before accepting.
|
|
351
|
-
- Blocking and async jobs execute the same workflow (evidence-driven escalation + automatic reviewer pass); async just returns a workflow id immediately and continues in the background.
|
|
352
|
-
- `dsh_worker_status` / `dsh_worker_result` / `dsh_worker_cancel` operate on workflow ids (`wf-…`); legacy `hub-…` / `job-…` ids are still accepted.
|
|
353
|
-
- Standalone launches the worker as `node <dsh-sdk-jsonrpc-demo/lib/bin.js>` (Windows-safe; the pnpm `.bin` sh shim is not spawnable). Standalone defaults to the DeepSeek Official provider + `DEEPSEEK_API_KEY`, but honors `DEEPSEEK_BASE_URL` so an OpenAI-compatible gateway (e.g. an `opencode-*` gateway already configured in `~/.dsh`) can back standalone workers by setting both env vars.
|
|
354
|
-
- The Settings UI still presents the legacy Flash/Pro compatibility controls in this transition build; the new worker/reviewer role policy and `execution.isolation` write-through is a follow-up.
|
|
131
|
+
Useful docs:
|
|
355
132
|
|
|
356
|
-
|
|
133
|
+
- [Architecture roadmap](./docs/v0.3-architecture-roadmap.md)
|
|
134
|
+
- [Readiness matrix](./docs/readiness-matrix.md)
|
|
135
|
+
- [Agent Workflow](./docs/agent-workflow.md)
|
|
136
|
+
- [Changelog](./CHANGELOG.md)
|
|
357
137
|
|
|
358
|
-
|
|
138
|
+
## License
|
|
359
139
|
|
|
360
|
-
MIT
|
|
140
|
+
MIT
|
package/README.zh.md
CHANGED
|
@@ -5,301 +5,136 @@
|
|
|
5
5
|
<h1 align="center">DSH Crew</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>让 Codex Desktop
|
|
8
|
+
<strong>让 Codex Desktop 或 Claude Code 做总控,把编码任务分发给隔离运行的 DeepSeek Harness worker 与 reviewer。</strong>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
12
|
<a href="./README.md">English</a> · <a href="./README.zh.md"><b>简体中文</b></a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 它做什么
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
- **角色(Role)**:worker(实现 / 修复 / 测试 / 搜索)与 reviewer(独立审查 + 结论)
|
|
19
|
-
- **模型策略(Model Policy)**:每个角色独立解析有序 provider/model 候选(首选 → 优先级 → 升级 → Harness Default)
|
|
20
|
-
- 旧版 Flash Only · Pro Only · Balanced · Review Pipeline 自动迁移到角色模型
|
|
21
|
-
- Follow DSH Provider
|
|
22
|
-
- DSH Hub 一等公民会话(Web UI 可见、异步任务带进度)
|
|
23
|
-
- 可选的视觉 / 生图(独立开关)
|
|
17
|
+
DSH Crew 在你的编码宿主和 DeepSeek Harness 之间增加一层轻量编排:
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
- **worker** — 实现、修复、测试、搜索、分析;
|
|
20
|
+
- **reviewer** — 独立审查 worker 的结果并给出结论;
|
|
21
|
+
- **模型策略** — 每个角色独立解析 provider / model 优先级,并可按证据升级;
|
|
22
|
+
- **隔离执行** — 编码 worker 默认运行在临时 git worktree;
|
|
23
|
+
- **实时任务** — Hub 可查看进度、结果、取消、路由轨迹和 readiness 信息。
|
|
26
24
|
|
|
27
|
-
```
|
|
25
|
+
```text
|
|
28
26
|
Codex Desktop / Claude Code
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Model Policy │ (各角色独立解析 provider/model;
|
|
40
|
-
cheap→strong │ 升级基于证据:FAIL 测试、缺失交付、阻塞任务)
|
|
41
|
-
│ │
|
|
42
|
-
└────┬────┘
|
|
43
|
-
│
|
|
44
|
-
▼
|
|
45
|
-
DeepSeek Harness
|
|
46
|
-
│
|
|
47
|
-
▼
|
|
48
|
-
DSH 当前选择的 provider/model
|
|
27
|
+
│
|
|
28
|
+
▼
|
|
29
|
+
DSH Crew
|
|
30
|
+
┌────┴────┐
|
|
31
|
+
│ │
|
|
32
|
+
worker reviewer
|
|
33
|
+
│ │
|
|
34
|
+
└────┬────┘
|
|
35
|
+
▼
|
|
36
|
+
DeepSeek Harness
|
|
49
37
|
```
|
|
50
38
|
|
|
51
|
-
主 Agent 决定**做什么**并负责接受 / 拒绝 / 要求修改;Workflow 决定**何时 / 状态**;
|
|
52
|
-
**角色**决定**谁来做**(worker 执行、reviewer 审查);**模型策略**决定**用哪个模型**
|
|
53
|
-
(永不与角色绑定);工作区隔离决定**在哪里做**(每个 coding worker 跑在独立临时
|
|
54
|
-
git worktree,并行 worker 不会互相踩 working tree);验证 / reviewer 决定**是否接受**。
|
|
55
|
-
|
|
56
39
|
## 安装
|
|
57
40
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
> **官方 Harness 隔离** — dsh-crew 安装到它自己的专用 DSH home
|
|
61
|
-
> (`~/.config/dsh-crew/harness`)和专用 profile(`dsh-crew`),永远不会安装
|
|
62
|
-
> 到官方 DSH home(`~/.dsh`)或官方 `web` profile。你的正常 DeepSeek Harness
|
|
63
|
-
> 安装不会被修改。
|
|
41
|
+
需要:**Node.js、Git、pnpm**。
|
|
64
42
|
|
|
65
|
-
|
|
43
|
+
推荐从源码安装:
|
|
66
44
|
|
|
67
45
|
```bash
|
|
46
|
+
git clone https://github.com/Ran-sh/dsh-crew.git
|
|
47
|
+
cd dsh-crew
|
|
68
48
|
node scripts/setup.mjs install
|
|
69
49
|
```
|
|
70
50
|
|
|
71
|
-
Windows
|
|
51
|
+
Windows 也可以直接运行:
|
|
72
52
|
|
|
73
53
|
```bat
|
|
74
54
|
install.cmd
|
|
75
55
|
```
|
|
76
56
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
安装器会把本检出以 `@ran-sh/dsh-crew` 方式链接到专用 `dsh-crew` profile,并把 Crew Hub 指向自己的端口;不会改动官方 web profile 或任何官方凭据存储。本 fork 不发布到 npm registry。
|
|
80
|
-
|
|
81
|
-
### 更新
|
|
57
|
+
DSH Crew 使用自己独立的 Harness home 和 profile:
|
|
82
58
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
git pull
|
|
87
|
-
node scripts/setup.mjs install
|
|
59
|
+
```text
|
|
60
|
+
~/.config/dsh-crew/harness
|
|
61
|
+
profile: dsh-crew
|
|
88
62
|
```
|
|
89
63
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
### 迁移旧版 fork 安装
|
|
64
|
+
受支持的 Crew 工具不会修改正常的 `~/.dsh`、官方 `web` profile 或官方 DSH 凭据存储。
|
|
93
65
|
|
|
94
|
-
|
|
66
|
+
## 30 秒上手
|
|
95
67
|
|
|
96
|
-
|
|
68
|
+
1. 正常启动 DeepSeek Harness。
|
|
69
|
+
2. 打开 **Settings → DSH Crew**。
|
|
70
|
+
3. 安装 Codex 集成;Claude Code 集成为可选项。
|
|
71
|
+
4. 需要时刷新 Harness 模型,并设置各角色优先级。
|
|
72
|
+
5. 集成变更后重启编码宿主。
|
|
97
73
|
|
|
98
|
-
|
|
74
|
+
然后直接说:
|
|
99
75
|
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
npx -y @deepseek-ai/dsh plugin --profile web remove @zseven-w/dsh-crew
|
|
76
|
+
```text
|
|
77
|
+
Use ds-worker to implement this change.
|
|
78
|
+
Use ds-reviewer to review the implementation.
|
|
104
79
|
```
|
|
105
80
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
### 从 Flash / Pro 迁移(v0.1 → v0.2 角色)
|
|
81
|
+
## 角色与路由
|
|
109
82
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`
|
|
113
|
-
|
|
114
|
-
- `flash-only` → worker auto、reviewer disabled、`economy` 策略。
|
|
115
|
-
- `pro-only` → worker auto(强模型类)、reviewer disabled。
|
|
116
|
-
- `balanced` → worker auto、reviewer manual(点名时可用)。
|
|
117
|
-
- `review-pipeline` → worker auto + reviewer auto(`auto_review` 开启);
|
|
118
|
-
reviewer 使用旧 Pro priority。
|
|
119
|
-
- `escalate_on_failure` → worker 升级策略 `enabled`。
|
|
120
|
-
- `pro_reviews_flash` → worker 成功后自动追加一次复查。
|
|
121
|
-
|
|
122
|
-
旧的 `ds-flash` / `ds-pro` 子代理保留为**弃用别名**:它们映射到 worker 角色并带上
|
|
123
|
-
历史模型类提示,旧 prompt 照常可用;新 prompt 请改用 `ds-worker` / `ds-reviewer`。
|
|
124
|
-
同时传 role 与冲突的 legacy tier 会被明确拒绝(`ROLE_TIER_CONFLICT`),绝不静默猜测。
|
|
125
|
-
|
|
126
|
-
### 故障排查:`ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`
|
|
83
|
+
| 角色 | 用途 |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `worker` | 实现、修复、测试、搜索、分析 |
|
|
86
|
+
| `reviewer` | 独立审查与 verdict |
|
|
127
87
|
|
|
128
|
-
|
|
129
|
-
lockfile 条目会被拒绝,除非列入 `minimumReleaseAgeExclude`。全新 profile 安装
|
|
130
|
-
dsh-crew 不受影响;但如果你 profile 里已带刚发布的 DSH 插件(例如可选的
|
|
131
|
-
`dsh-plugin-image-mind`),安装会报此错。
|
|
88
|
+
每个角色都有自己的 provider / model 候选顺序。用户显式配置始终优先;只有工作流拿到明确证据时才会自动升级到更强模型。
|
|
132
89
|
|
|
133
|
-
|
|
90
|
+
旧的 `ds-flash` / `ds-pro` 仍保留兼容,但新提示词建议使用 `ds-worker` / `ds-reviewer`。
|
|
134
91
|
|
|
135
|
-
|
|
136
|
-
# ~/.dsh/profiles/web/pnpm-workspace.yaml
|
|
137
|
-
minimumReleaseAgeExclude:
|
|
138
|
-
- dsh-plugin-image-mind
|
|
139
|
-
- '@ran-sh/dsh-vision@0.1.0'
|
|
140
|
-
```
|
|
92
|
+
## 隔离
|
|
141
93
|
|
|
142
|
-
|
|
143
|
-
新版本漏掉。排除项要放在 `pnpm-workspace.yaml`,校验器不读 `.npmrc`。该策略只针对
|
|
144
|
-
发布不到一天的 registry 包(git 托管的 dsh-crew 本身从不做 age 校验),所以问题通常
|
|
145
|
-
在 24 小时内自动消失。
|
|
94
|
+
`execution.isolation` 默认是 `worktree`。
|
|
146
95
|
|
|
147
|
-
|
|
96
|
+
编码 worker 会在目标 revision 上创建临时 git worktree,因此并行 worker 不会写入同一个工作目录。worker 返回可审计的 change candidate,由总控决定接受、拒绝或继续修改;Crew 不会偷偷把改动合进主工作区。
|
|
148
97
|
|
|
149
|
-
|
|
98
|
+
## 更新 / 卸载
|
|
150
99
|
|
|
151
|
-
|
|
152
|
-
2. 移除 profile plugin:
|
|
100
|
+
更新源码安装:
|
|
153
101
|
|
|
154
102
|
```bash
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
只移除 profile plugin 不会隐式修改 `~/.codex` 或 `~/.claude`。Crew 配置、备份、凭据和其他 DSH bundle 都会保留。
|
|
159
|
-
|
|
160
|
-
## 开发 / 源码安装
|
|
161
|
-
|
|
162
|
-
源码安装器继续保留,供贡献者与本地 checkout 开发使用。
|
|
163
|
-
|
|
164
|
-
Windows:
|
|
165
|
-
|
|
166
|
-
```bat
|
|
167
|
-
git clone https://github.com/Ran-sh/dsh-crew.git
|
|
168
|
-
cd dsh-crew
|
|
169
|
-
install.cmd
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
以后更新:
|
|
173
|
-
|
|
174
|
-
```bat
|
|
175
|
-
git pull
|
|
176
|
-
install.cmd
|
|
103
|
+
git pull --ff-only
|
|
104
|
+
node scripts/setup.mjs install
|
|
177
105
|
```
|
|
178
106
|
|
|
179
|
-
|
|
107
|
+
卸载 Crew 与集成:
|
|
180
108
|
|
|
181
109
|
```bash
|
|
182
|
-
node scripts/setup.mjs
|
|
110
|
+
node scripts/setup.mjs uninstall
|
|
183
111
|
```
|
|
184
112
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- 把本仓库以 link 方式装进 DSH web profile(`link:<repo>`)
|
|
188
|
-
- 安装 Codex Desktop 集成(**不需要** `codex` CLI)
|
|
189
|
-
- 检测到 `claude` CLI 时自动安装 Claude Code 集成(可选)
|
|
190
|
-
- 幂等,可安全重复执行
|
|
191
|
-
|
|
192
|
-
Windows 源码卸载:
|
|
113
|
+
Windows:
|
|
193
114
|
|
|
194
115
|
```bat
|
|
116
|
+
install.cmd
|
|
195
117
|
uninstall.cmd
|
|
196
118
|
```
|
|
197
119
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
node scripts/setup.mjs uninstall
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
它会移除:
|
|
205
|
-
|
|
206
|
-
- DSH web profile 中的 DSH Crew
|
|
207
|
-
- Codex Desktop 集成
|
|
208
|
-
- Claude Code 集成
|
|
209
|
-
|
|
210
|
-
它会保留:
|
|
211
|
-
|
|
212
|
-
- 仓库
|
|
213
|
-
- Crew 配置(`~/.config/dsh-crew`)
|
|
214
|
-
- 备份与凭据
|
|
215
|
-
|
|
216
|
-
## 快速开始
|
|
217
|
-
|
|
218
|
-
1. 照常启动 DSH:`npx -y @deepseek-ai/dsh web`
|
|
219
|
-
2. 打开 **设置 → DSH Crew**。
|
|
220
|
-
3. 保持新安装默认工作流:Codex → **worker** 角色 → Codex(reviewer 关闭)。
|
|
221
|
-
4. 需要时用 **刷新 Harness 模型** 为各角色分别设置有序模型优先级。
|
|
222
|
-
5. 重启 Codex Desktop / Claude Code。
|
|
223
|
-
|
|
224
|
-
然后直接说:
|
|
225
|
-
|
|
226
|
-
- “用 ds-worker 实现这个改动。”
|
|
227
|
-
- “用 ds-reviewer 审查这个实现。”
|
|
228
|
-
|
|
229
|
-
## 角色
|
|
230
|
-
|
|
231
|
-
- **worker** — 执行角色:实现、修复、测试、搜索、分析。任意编码请求的默认角色。它是薄派发器;后台用哪个模型由 Worker Model Policy 决定。
|
|
232
|
-
- **reviewer** — 独立审查角色:检查实现结果、工作区 diff、测试与风险,给出结论。默认只读,不做二次实现。
|
|
233
|
-
- **ds-flash / ds-pro** 保留为弃用别名(见上面的迁移说明)。
|
|
234
|
-
|
|
235
|
-
角色状态为 `disabled | manual | auto`:disabled 拒绝一切请求;manual 只在显式点名时运行;auto 可被 orchestrator 自动选择。未采用 canonical 配置时,生效状态来自旧协作模式。
|
|
236
|
-
|
|
237
|
-
## 模型策略
|
|
120
|
+
`~/.config/dsh-crew` 下的 Crew 配置和备份默认保留。
|
|
238
121
|
|
|
239
|
-
|
|
122
|
+
## 开发
|
|
240
123
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
## 工作流
|
|
248
|
-
|
|
249
|
-
每次派发——`dsh_run_worker`(阻塞)与 `dsh_spawn_worker`(异步)、Hub 或 Standalone——都走**同一套** workflow runtime:
|
|
250
|
-
|
|
251
|
-
```
|
|
252
|
-
CREATED -> (繁忙时 QUEUED) -> RUNNING -> VERIFYING
|
|
253
|
-
-> ESCALATING(证据驱动)-> RUNNING -> VERIFYING
|
|
254
|
-
-> REVIEWING(自动审查)-> READY -> COMPLETED
|
|
255
|
-
(或 FAILED / CANCELLED)
|
|
124
|
+
```bash
|
|
125
|
+
pnpm install --frozen-lockfile
|
|
126
|
+
node --test test/*.test.mjs
|
|
127
|
+
pnpm run build:client
|
|
128
|
+
pnpm run verify:npm-install
|
|
256
129
|
```
|
|
257
130
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
### 隔离
|
|
261
|
-
|
|
262
|
-
`execution.isolation` 默认为 `worktree`:worker 角色在 HEAD 的分离 worktree 上工作,主工作区永不被动(即使它是 dirty 的;未提交的主工作区改动不会混入 candidate)。如果目标工作区**不是** git 仓库,worktree 隔离任务会 **fail closed**(`NOT_GIT_REPOSITORY`)而不是静默共享——需要旧的就地行为请显式设 `execution.isolation: "shared"`。
|
|
263
|
-
|
|
264
|
-
## 模式
|
|
265
|
-
|
|
266
|
-
| 模式 | 迁移后的角色行为 |
|
|
267
|
-
|---|---|
|
|
268
|
-
| Flash Only | worker auto、reviewer disabled、economy 模型策略 |
|
|
269
|
-
| Pro Only | worker auto(强模型类)、reviewer disabled |
|
|
270
|
-
| Balanced | worker auto、reviewer manual(点名时可用) |
|
|
271
|
-
| Review Pipeline | worker auto + reviewer auto(`auto_review` 开启) |
|
|
272
|
-
|
|
273
|
-
Custom 模式可分别配置 worker / reviewer 状态。
|
|
274
|
-
|
|
275
|
-
## Provider
|
|
276
|
-
|
|
277
|
-
Crew 会读取 DeepSeek Harness 当前注册的全部 provider 与模型。各角色独立解析候选:**worker** 使用首选(便宜)优先级 + 升级(强)模型池,**reviewer** 有独立审查优先级——旧版新鲜偏好为 `deepseek-v4-flash` / `deepseek-v4-pro`,以 Harness Default 兜底。
|
|
278
|
-
|
|
279
|
-
- **Follow DSH Provider** — 从 Harness 目录解析角色的 provider/model 优先级(新配置默认值)。
|
|
280
|
-
- **DeepSeek Official** — 为兼容旧配置保留内置固定路由。
|
|
281
|
-
|
|
282
|
-
凭据始终由 DSH 的 provider 配置管理。已在 OpenAI 兼容的 OpenCode Go 网关实测。Standalone 模式(无 DSH 运行)始终使用 DeepSeek Official + `DEEPSEEK_API_KEY`。
|
|
283
|
-
|
|
284
|
-
## 宿主
|
|
285
|
-
|
|
286
|
-
- **Codex Desktop** 通过共享的 `~/.codex` 配置直接受支持,**不要求** `codex` CLI;CLI 只是可选的补充宿主 / 管理接口。
|
|
287
|
-
- **Claude Code** 为可选;一键安装检测到 Claude CLI 时自动安装其集成。
|
|
288
|
-
|
|
289
|
-
## 备注
|
|
290
|
-
|
|
291
|
-
- Main Agent Mode 是路由指引,不是对宿主工具的硬沙箱。
|
|
292
|
-
- Standalone 只使用 DeepSeek Official。
|
|
293
|
-
- Crew Vision 的工具注册改动可能需要重启 DSH。
|
|
294
|
-
- 集成内容改动后请重启 Codex Desktop。
|
|
295
|
-
- 每个 worker 都会返回可审查的交付报告(`## Diff` / `## Tests` / `## Risks`),隔离候选捕获有界、脱敏的 patch,方便在接受前核对改动。
|
|
296
|
-
- 阻塞与异步任务执行**同一套** workflow(证据驱动升级 + 自动审查);异步只是立刻返回 workflow id 并在后台继续。
|
|
297
|
-
- `dsh_worker_status` / `dsh_worker_result` / `dsh_worker_cancel` 以 workflow id(`wf-…`)为主;旧的 `hub-…` / `job-…` id 仍兼容。
|
|
298
|
-
- Standalone 以 `node <dsh-sdk-jsonrpc-demo/lib/bin.js>` 启动 worker(Windows 安全;pnpm 的 `.bin` sh shim 无法被 Node spawn)。Standalone 默认 DeepSeek Official + `DEEPSEEK_API_KEY`,但支持 `DEEPSEEK_BASE_URL`——已配置在 `~/.dsh` 的 OpenAI 兼容网关(如 `opencode-*`)可通过同时设置这两个环境变量来支撑 Standalone worker。
|
|
299
|
-
- 设置 UI 在本过渡构建里仍展示旧的 Flash/Pro 兼容控件;新 worker/reviewer 角色策略与 `execution.isolation` 的写回是后续项。
|
|
131
|
+
更多文档:
|
|
300
132
|
|
|
301
|
-
|
|
133
|
+
- [架构路线图](./docs/v0.3-architecture-roadmap.md)
|
|
134
|
+
- [Readiness Matrix](./docs/readiness-matrix.md)
|
|
135
|
+
- [Agent Workflow](./docs/agent-workflow.md)
|
|
136
|
+
- [Changelog](./CHANGELOG.md)
|
|
302
137
|
|
|
303
|
-
|
|
138
|
+
## License
|
|
304
139
|
|
|
305
|
-
MIT
|
|
140
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ran-sh/dsh-crew",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/hub/entry.mjs",
|
|
6
6
|
"description": "DeepSeek Harness plugin: dispatch work to DSH agents from Claude Code / Codex, as native subagents with live progress",
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"mcp": "node src/server.mjs",
|
|
23
|
-
"build:client": "tsdown src/client/entry.tsx --format cjs --platform browser --target es2022 --tsconfig tsconfig.client.json --deps.never-bundle react --deps.never-bundle react/jsx-runtime --deps.never-bundle react-dom --deps.never-bundle react-dom/client --out-dir .client-build --clean --logLevel warn && node scripts/build-client.mjs"
|
|
23
|
+
"build:client": "tsdown src/client/entry.tsx --format cjs --platform browser --target es2022 --tsconfig tsconfig.client.json --deps.never-bundle react --deps.never-bundle react/jsx-runtime --deps.never-bundle react-dom --deps.never-bundle react-dom/client --out-dir .client-build --clean --logLevel warn && node scripts/build-client.mjs",
|
|
24
|
+
"verify:npm-install": "node scripts/verify-npm-install.mjs",
|
|
25
|
+
"verify:npm-install:official": "node scripts/verify-npm-install.mjs --with-official-dsh"
|
|
24
26
|
},
|
|
25
27
|
"dependencies": {
|
|
26
28
|
"@modelcontextprotocol/sdk": "^1.17.5",
|
|
@@ -34,26 +36,26 @@
|
|
|
34
36
|
"peerDependencies": {
|
|
35
37
|
"react": "^18.0.0",
|
|
36
38
|
"react-dom": "^18.0.0",
|
|
37
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
38
|
-
"@deepseek-ai/dsh-agent-presets": "0.1.
|
|
39
|
-
"@deepseek-ai/dsh-agent-spine-demo": "0.1.
|
|
40
|
-
"@deepseek-ai/dsh-bash-local": "0.1.
|
|
41
|
-
"@deepseek-ai/dsh-compaction-basic": "0.1.
|
|
42
|
-
"@deepseek-ai/dsh-fs-local": "0.1.
|
|
43
|
-
"@deepseek-ai/dsh-fs-observation-policy": "0.1.
|
|
44
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
45
|
-
"@deepseek-ai/dsh-llm-deepseek": "0.1.
|
|
46
|
-
"@deepseek-ai/dsh-sandbox-local": "0.1.
|
|
47
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.1.
|
|
48
|
-
"@deepseek-ai/dsh-sdk-client": "
|
|
49
|
-
"@deepseek-ai/dsh-sdk-jsonrpc-demo": "
|
|
50
|
-
"@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.
|
|
51
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
52
|
-
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.
|
|
53
|
-
"@deepseek-ai/dsh-subprocess-local": "0.1.
|
|
54
|
-
"@deepseek-ai/dsh-token-meter": "0.1.
|
|
55
|
-
"@deepseek-ai/dsh-tool-fs": "0.1.
|
|
56
|
-
"@deepseek-ai/dsh-tool-todo": "0.1.
|
|
39
|
+
"@deepseek-ai/dsh-agent": "0.1.1-rc.2",
|
|
40
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.1-rc.2",
|
|
41
|
+
"@deepseek-ai/dsh-agent-spine-demo": "0.1.1-rc.2",
|
|
42
|
+
"@deepseek-ai/dsh-bash-local": "0.1.1-rc.2",
|
|
43
|
+
"@deepseek-ai/dsh-compaction-basic": "0.1.1-rc.2",
|
|
44
|
+
"@deepseek-ai/dsh-fs-local": "0.1.1-rc.2",
|
|
45
|
+
"@deepseek-ai/dsh-fs-observation-policy": "0.1.1-rc.2",
|
|
46
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
47
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
|
|
48
|
+
"@deepseek-ai/dsh-sandbox-local": "0.1.1-rc.2",
|
|
49
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.1.1-rc.2",
|
|
50
|
+
"@deepseek-ai/dsh-sdk-client": "0.1.1-rc.2",
|
|
51
|
+
"@deepseek-ai/dsh-sdk-jsonrpc-demo": "0.1.1-rc.2",
|
|
52
|
+
"@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.1-rc.2",
|
|
53
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.2",
|
|
54
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
|
|
55
|
+
"@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
|
|
57
|
+
"@deepseek-ai/dsh-tool-fs": "0.1.1-rc.2",
|
|
58
|
+
"@deepseek-ai/dsh-tool-todo": "0.1.1-rc.2"
|
|
57
59
|
},
|
|
58
60
|
"devDependencies": {
|
|
59
61
|
"react": "^18.3.1",
|
|
@@ -61,26 +63,26 @@
|
|
|
61
63
|
"tsdown": "^0.12.0",
|
|
62
64
|
"typescript": "^5.6.0",
|
|
63
65
|
"@types/react": "^18.3.0",
|
|
64
|
-
"@deepseek-ai/dsh-agent": "0.1.
|
|
65
|
-
"@deepseek-ai/dsh-agent-presets": "0.1.
|
|
66
|
-
"@deepseek-ai/dsh-agent-spine-demo": "0.1.
|
|
67
|
-
"@deepseek-ai/dsh-bash-local": "0.1.
|
|
68
|
-
"@deepseek-ai/dsh-compaction-basic": "0.1.
|
|
69
|
-
"@deepseek-ai/dsh-fs-local": "0.1.
|
|
70
|
-
"@deepseek-ai/dsh-fs-observation-policy": "0.1.
|
|
71
|
-
"@deepseek-ai/dsh-llm": "0.1.
|
|
72
|
-
"@deepseek-ai/dsh-llm-deepseek": "0.1.
|
|
73
|
-
"@deepseek-ai/dsh-sandbox-local": "0.1.
|
|
74
|
-
"@deepseek-ai/dsh-sandbox-policy": "0.1.
|
|
75
|
-
"@deepseek-ai/dsh-sdk-client": "
|
|
76
|
-
"@deepseek-ai/dsh-sdk-jsonrpc-demo": "
|
|
77
|
-
"@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.
|
|
78
|
-
"@deepseek-ai/dsh-session": "0.1.
|
|
79
|
-
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.
|
|
80
|
-
"@deepseek-ai/dsh-subprocess-local": "0.1.
|
|
81
|
-
"@deepseek-ai/dsh-token-meter": "0.1.
|
|
82
|
-
"@deepseek-ai/dsh-tool-fs": "0.1.
|
|
83
|
-
"@deepseek-ai/dsh-tool-todo": "0.1.
|
|
66
|
+
"@deepseek-ai/dsh-agent": "0.1.1-rc.2",
|
|
67
|
+
"@deepseek-ai/dsh-agent-presets": "0.1.1-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-agent-spine-demo": "0.1.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-bash-local": "0.1.1-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-compaction-basic": "0.1.1-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-fs-local": "0.1.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-fs-observation-policy": "0.1.1-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-llm": "0.1.1-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.1.1-rc.2",
|
|
75
|
+
"@deepseek-ai/dsh-sandbox-local": "0.1.1-rc.2",
|
|
76
|
+
"@deepseek-ai/dsh-sandbox-policy": "0.1.1-rc.2",
|
|
77
|
+
"@deepseek-ai/dsh-sdk-client": "0.1.1-rc.2",
|
|
78
|
+
"@deepseek-ai/dsh-sdk-jsonrpc-demo": "0.1.1-rc.2",
|
|
79
|
+
"@deepseek-ai/dsh-sdk-jsonrpc-server": "0.1.1-rc.2",
|
|
80
|
+
"@deepseek-ai/dsh-session": "0.1.1-rc.2",
|
|
81
|
+
"@deepseek-ai/dsh-session-persistence-jsonl": "0.1.1-rc.2",
|
|
82
|
+
"@deepseek-ai/dsh-subprocess-local": "0.1.1-rc.2",
|
|
83
|
+
"@deepseek-ai/dsh-token-meter": "0.1.1-rc.2",
|
|
84
|
+
"@deepseek-ai/dsh-tool-fs": "0.1.1-rc.2",
|
|
85
|
+
"@deepseek-ai/dsh-tool-todo": "0.1.1-rc.2"
|
|
84
86
|
},
|
|
85
87
|
"license": "MIT",
|
|
86
88
|
"repository": {
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Regression gate for the published-package peer contract.
|
|
4
|
+
// This intentionally uses npm's default resolver for the candidate install:
|
|
5
|
+
// no peer-resolution bypass or equivalent override is permitted here.
|
|
6
|
+
//
|
|
7
|
+
// The official DSH check is deliberately bounded. It audits authoritative
|
|
8
|
+
// public registry manifests and cross-cohort ranges instead of materializing
|
|
9
|
+
// or fully resolving the very large official DSH dependency graph.
|
|
10
|
+
|
|
11
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
19
|
+
const registry = process.env.NPM_REGISTRY ?? 'https://registry.npmjs.org/';
|
|
20
|
+
export const candidateVersion = '0.3.1';
|
|
21
|
+
export const supportedDshVersion = '0.1.1-rc.2';
|
|
22
|
+
const verifyOfficialDsh = process.argv.includes('--with-official-dsh');
|
|
23
|
+
const DSH_PACKAGE = '@deepseek-ai/dsh';
|
|
24
|
+
const DSH_PREFIX = '@deepseek-ai/dsh-';
|
|
25
|
+
const MAX_DIRECT_PEERS = 64;
|
|
26
|
+
|
|
27
|
+
function isDshPackage(name) {
|
|
28
|
+
return name === DSH_PACKAGE || name.startsWith(DSH_PREFIX);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseVersion(value) {
|
|
32
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
|
|
33
|
+
if (!match) return null;
|
|
34
|
+
return {
|
|
35
|
+
major: Number(match[1]),
|
|
36
|
+
minor: Number(match[2]),
|
|
37
|
+
patch: Number(match[3]),
|
|
38
|
+
prerelease: match[4] ? match[4].split('.') : [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function compareVersions(left, right) {
|
|
43
|
+
for (const key of ['major', 'minor', 'patch']) {
|
|
44
|
+
if (left[key] !== right[key]) return left[key] > right[key] ? 1 : -1;
|
|
45
|
+
}
|
|
46
|
+
if (left.prerelease.length === 0 && right.prerelease.length === 0) return 0;
|
|
47
|
+
if (left.prerelease.length === 0) return 1;
|
|
48
|
+
if (right.prerelease.length === 0) return -1;
|
|
49
|
+
const length = Math.max(left.prerelease.length, right.prerelease.length);
|
|
50
|
+
for (let index = 0; index < length; index += 1) {
|
|
51
|
+
const a = left.prerelease[index];
|
|
52
|
+
const b = right.prerelease[index];
|
|
53
|
+
if (a === undefined) return -1;
|
|
54
|
+
if (b === undefined) return 1;
|
|
55
|
+
if (a === b) continue;
|
|
56
|
+
const aNumber = /^\d+$/.test(a);
|
|
57
|
+
const bNumber = /^\d+$/.test(b);
|
|
58
|
+
if (aNumber && bNumber) return Number(a) > Number(b) ? 1 : -1;
|
|
59
|
+
if (aNumber !== bNumber) return aNumber ? -1 : 1;
|
|
60
|
+
return a > b ? 1 : -1;
|
|
61
|
+
}
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function upperBoundForCaret(version) {
|
|
66
|
+
if (version.major > 0) return { major: version.major + 1, minor: 0, patch: 0, prerelease: [] };
|
|
67
|
+
if (version.minor > 0) return { major: 0, minor: version.minor + 1, patch: 0, prerelease: [] };
|
|
68
|
+
return { major: 0, minor: 0, patch: version.patch + 1, prerelease: [] };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function upperBoundForTilde(version) {
|
|
72
|
+
return { major: version.major, minor: version.minor + 1, patch: 0, prerelease: [] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function satisfiesComparator(version, operator, expected) {
|
|
76
|
+
const comparison = compareVersions(version, expected);
|
|
77
|
+
if (operator === '>') return comparison > 0;
|
|
78
|
+
if (operator === '>=') return comparison >= 0;
|
|
79
|
+
if (operator === '<') return comparison < 0;
|
|
80
|
+
if (operator === '<=') return comparison <= 0;
|
|
81
|
+
return comparison === 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function satisfiesAlternative(version, alternative) {
|
|
85
|
+
const normalized = alternative.trim();
|
|
86
|
+
if (!normalized || /[*xX]|workspace:|^npm:|^file:|^git:/.test(normalized)) return false;
|
|
87
|
+
const tokens = normalized.replace(/,/g, ' ').split(/\s+/).filter(Boolean);
|
|
88
|
+
return tokens.every((token) => {
|
|
89
|
+
const prefix = /^(\^|~|>=|<=|>|<|=)/.exec(token)?.[1] ?? '';
|
|
90
|
+
const raw = prefix ? token.slice(prefix.length) : token;
|
|
91
|
+
const expected = parseVersion(raw);
|
|
92
|
+
if (!expected) return false;
|
|
93
|
+
if (prefix === '^') {
|
|
94
|
+
return compareVersions(version, expected) >= 0 && compareVersions(version, upperBoundForCaret(expected)) < 0;
|
|
95
|
+
}
|
|
96
|
+
if (prefix === '~') {
|
|
97
|
+
return compareVersions(version, expected) >= 0 && compareVersions(version, upperBoundForTilde(expected)) < 0;
|
|
98
|
+
}
|
|
99
|
+
return satisfiesComparator(version, prefix, expected);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function satisfiesCohortRange(versionString, range) {
|
|
104
|
+
const version = parseVersion(versionString);
|
|
105
|
+
if (!version || typeof range !== 'string') return false;
|
|
106
|
+
return range.split(/\s*\|\|\s*/).some((alternative) => satisfiesAlternative(version, alternative));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function assertManifest(manifest, expectedName, expectedVersion) {
|
|
110
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
111
|
+
throw new Error(`invalid registry manifest for ${expectedName}@${expectedVersion}`);
|
|
112
|
+
}
|
|
113
|
+
if (manifest.name !== expectedName || manifest.version !== expectedVersion) {
|
|
114
|
+
throw new Error(`registry manifest identity mismatch for ${expectedName}@${expectedVersion}`);
|
|
115
|
+
}
|
|
116
|
+
return manifest;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function manifestUrl(registryUrl, name, version) {
|
|
120
|
+
const encodedName = name.startsWith('@') ? name.replace('/', '%2f') : encodeURIComponent(name);
|
|
121
|
+
return `${registryUrl.replace(/\/+$/, '')}/${encodedName}/${encodeURIComponent(version)}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function fetchPublicManifest(name, version, {
|
|
125
|
+
registryUrl = registry,
|
|
126
|
+
fetchImpl = globalThis.fetch,
|
|
127
|
+
} = {}) {
|
|
128
|
+
if (typeof fetchImpl !== 'function') throw new Error('global fetch is unavailable');
|
|
129
|
+
const response = await fetchImpl(manifestUrl(registryUrl, name, version), {
|
|
130
|
+
headers: { accept: 'application/json', 'user-agent': 'dsh-crew-v031-cohort-audit' },
|
|
131
|
+
});
|
|
132
|
+
if (!response?.ok) throw new Error(`public manifest unavailable for ${name}@${version} (HTTP ${response?.status ?? 'unknown'})`);
|
|
133
|
+
let manifest;
|
|
134
|
+
try {
|
|
135
|
+
manifest = await response.json();
|
|
136
|
+
} catch {
|
|
137
|
+
throw new Error(`public manifest was not valid JSON for ${name}@${version}`);
|
|
138
|
+
}
|
|
139
|
+
return assertManifest(manifest, name, version);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function dshReferences(manifest) {
|
|
143
|
+
return [
|
|
144
|
+
...Object.entries(manifest.dependencies ?? {}),
|
|
145
|
+
...Object.entries(manifest.optionalDependencies ?? {}),
|
|
146
|
+
...Object.entries(manifest.peerDependencies ?? {}),
|
|
147
|
+
].filter(([name]) => isDshPackage(name));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function assertCrossCohortRanges(manifest, label) {
|
|
151
|
+
const references = dshReferences(manifest);
|
|
152
|
+
for (const [name, range] of references) {
|
|
153
|
+
if (!satisfiesCohortRange(supportedDshVersion, range)) {
|
|
154
|
+
throw new Error(`${label} has incompatible ${name} range ${JSON.stringify(range)} for ${supportedDshVersion}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return references.length;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function auditOfficialDshCohort({
|
|
161
|
+
candidateManifest,
|
|
162
|
+
registryUrl = registry,
|
|
163
|
+
fetchManifest = (name, version) => fetchPublicManifest(name, version, { registryUrl }),
|
|
164
|
+
} = {}) {
|
|
165
|
+
if (!candidateManifest || typeof candidateManifest !== 'object') throw new Error('candidate package manifest is missing');
|
|
166
|
+
if (candidateManifest.version !== candidateVersion) throw new Error(`package.json must be ${candidateVersion}`);
|
|
167
|
+
|
|
168
|
+
const directPeers = Object.entries(candidateManifest.peerDependencies ?? {})
|
|
169
|
+
.filter(([name]) => isDshPackage(name));
|
|
170
|
+
if (directPeers.length === 0 || directPeers.length > MAX_DIRECT_PEERS) {
|
|
171
|
+
throw new Error(`candidate DSH peer count is outside the bounded audit limit: ${directPeers.length}`);
|
|
172
|
+
}
|
|
173
|
+
for (const [name, range] of directPeers) {
|
|
174
|
+
if (range !== supportedDshVersion) {
|
|
175
|
+
throw new Error(`candidate peer ${name} must pin exact cohort ${supportedDshVersion}, got ${JSON.stringify(range)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const officialManifest = assertManifest(
|
|
180
|
+
await fetchManifest(DSH_PACKAGE, supportedDshVersion),
|
|
181
|
+
DSH_PACKAGE,
|
|
182
|
+
supportedDshVersion,
|
|
183
|
+
);
|
|
184
|
+
const manifests = [officialManifest];
|
|
185
|
+
let crossCohortChecks = assertCrossCohortRanges(officialManifest, `${DSH_PACKAGE}@${supportedDshVersion}`);
|
|
186
|
+
|
|
187
|
+
for (const [name] of directPeers) {
|
|
188
|
+
const manifest = assertManifest(await fetchManifest(name, supportedDshVersion), name, supportedDshVersion);
|
|
189
|
+
manifests.push(manifest);
|
|
190
|
+
crossCohortChecks += assertCrossCohortRanges(manifest, `${name}@${supportedDshVersion}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
marker: 'PASS',
|
|
195
|
+
candidate: candidateVersion,
|
|
196
|
+
officialDsh: supportedDshVersion,
|
|
197
|
+
directPeerCount: directPeers.length,
|
|
198
|
+
manifestCount: manifests.length,
|
|
199
|
+
crossCohortChecks,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function run(args, cwd = root) {
|
|
204
|
+
const result = spawnSync(npmCommand, args, {
|
|
205
|
+
cwd,
|
|
206
|
+
encoding: 'utf8',
|
|
207
|
+
shell: process.platform === 'win32',
|
|
208
|
+
windowsHide: true,
|
|
209
|
+
});
|
|
210
|
+
if (result.error) throw result.error;
|
|
211
|
+
return {
|
|
212
|
+
status: result.status ?? 1,
|
|
213
|
+
stdout: result.stdout ?? '',
|
|
214
|
+
stderr: result.stderr ?? '',
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function fail(label, result) {
|
|
219
|
+
const detail = `${result.stdout}\n${result.stderr}`.trim().replace(/\s+/g, ' ').slice(0, 800);
|
|
220
|
+
throw new Error(`${label} failed (exit ${result.status}): ${detail}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function assertInstalled(prefix, label) {
|
|
224
|
+
const packageRoot = path.join(prefix, 'node_modules', '@ran-sh', 'dsh-crew');
|
|
225
|
+
const packageJson = JSON.parse(await readFile(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
226
|
+
const runtimeSource = await readFile(path.join(packageRoot, 'src', 'runtime-identity.mjs'), 'utf8');
|
|
227
|
+
const runtimeVersion = runtimeSource.match(/RUNTIME_VERSION\s*=\s*'([^']+)'/)?.[1];
|
|
228
|
+
const entryExists = await readFile(path.join(packageRoot, 'src', 'hub', 'entry.mjs')).then(() => true, () => false);
|
|
229
|
+
const clientExists = await readFile(path.join(packageRoot, 'lib', 'client.js')).then(() => true, () => false);
|
|
230
|
+
if (packageJson.version !== candidateVersion || runtimeVersion !== candidateVersion || !entryExists || !clientExists) {
|
|
231
|
+
throw new Error(`${label} did not install the expected ${candidateVersion} package/runtime entries`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async function main() {
|
|
236
|
+
const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'dsh-crew-v031-install-'));
|
|
237
|
+
const paths = {
|
|
238
|
+
pack: await mkdtemp(path.join(tempRoot, 'pack-')),
|
|
239
|
+
standalone: await mkdtemp(path.join(tempRoot, 'standalone-')),
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
|
|
244
|
+
if (packageJson.version !== candidateVersion) throw new Error(`package.json must be ${candidateVersion}`);
|
|
245
|
+
|
|
246
|
+
const packed = run(['pack', '--json', '--pack-destination', paths.pack]);
|
|
247
|
+
if (packed.status !== 0) fail('npm pack', packed);
|
|
248
|
+
const packInfo = JSON.parse(packed.stdout)[0];
|
|
249
|
+
const tarball = path.join(paths.pack, packInfo.filename);
|
|
250
|
+
|
|
251
|
+
const standalone = run([
|
|
252
|
+
'install', '--prefix', paths.standalone, '--no-save', '--package-lock=false', '--ignore-scripts', '--no-audit', '--no-fund',
|
|
253
|
+
'--registry', registry, tarball,
|
|
254
|
+
]);
|
|
255
|
+
if (standalone.status !== 0) fail('plain npm candidate install', standalone);
|
|
256
|
+
await assertInstalled(paths.standalone, 'standalone candidate');
|
|
257
|
+
|
|
258
|
+
let officialMarker = 'not-run';
|
|
259
|
+
if (verifyOfficialDsh) {
|
|
260
|
+
const audit = await auditOfficialDshCohort({ candidateManifest: packageJson, registryUrl: registry });
|
|
261
|
+
officialMarker = `${supportedDshVersion}:manifest-audit`;
|
|
262
|
+
console.log(`DSH_COHORT_AUDIT=PASS official_dsh=${audit.officialDsh} direct_peers=${audit.directPeerCount} manifests=${audit.manifestCount} cross_cohort_checks=${audit.crossCohortChecks}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
console.log(`NPM_INSTALL_CONTRACT=PASS candidate=${candidateVersion} official_dsh=${officialMarker}`);
|
|
266
|
+
} finally {
|
|
267
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
272
|
+
main().catch((error) => {
|
|
273
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
274
|
+
process.exitCode = 1;
|
|
275
|
+
});
|
|
276
|
+
}
|
package/src/runtime-identity.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Keep this module pure and dependency-free so Hub, MCP and tests all use the
|
|
9
9
|
// exact same compatibility rules.
|
|
10
10
|
|
|
11
|
-
export const RUNTIME_VERSION = '0.3.
|
|
11
|
+
export const RUNTIME_VERSION = '0.3.1';
|
|
12
12
|
export const HUB_PROTOCOL_VERSION = 1;
|
|
13
13
|
|
|
14
14
|
export const HUB_CAPABILITIES = Object.freeze([
|