dsh-deeppilot 0.9.4 → 0.9.5
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/COMPATIBILITY.md +52 -11
- package/PRIVACY.md +2 -2
- package/README.md +26 -33
- package/README.zh-CN.md +19 -24
- package/SECURITY.md +2 -1
- package/cordis.patch.yml +1 -1
- package/docs/DEVICE_REGISTRY_RECOVERY.md +87 -0
- package/docs/DEVICE_REGISTRY_RECOVERY.zh-CN.md +73 -0
- package/docs/DSH_RELEASE_MEMORY.md +329 -12
- package/docs/NPM_PUBLISHING.md +83 -0
- package/docs/SECURITY_ROADMAP.md +9 -4
- package/docs/SECURITY_ROADMAP.zh-CN.md +9 -4
- package/lib/client.js +5 -5
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +57 -6
- package/lib/index.js.map +1 -1
- package/locale/en.json +1 -1
- package/package.json +17 -14
|
@@ -5,6 +5,78 @@
|
|
|
5
5
|
|
|
6
6
|
## Current decision
|
|
7
7
|
|
|
8
|
+
- **Audited release:** `dsh-v0.2.1-alpha.3` (official release page:
|
|
9
|
+
<https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.3>),
|
|
10
|
+
published to npm 2026-10-14T13:32:15Z on the `alpha` dist-tag. It is **not**
|
|
11
|
+
`latest`; `latest`/`next` still point at `0.2.0-rc.2`. Baseline commit
|
|
12
|
+
`2fbf4359b82fb94c03dcce0b831660d87f12ea5c`, compared against alpha.2's
|
|
13
|
+
`d743267388641bc76f17c45ce8b4c231aed1d32c` (**852 commits, 3311 changed files,
|
|
14
|
+
120,978 insertions, 16,632 deletions**).
|
|
15
|
+
- **Comparison baseline:** `dsh-v0.2.1-alpha.2` (the previous audited tag).
|
|
16
|
+
- **Plugin runtime verdict: four confirmed breaks with one root cause — the
|
|
17
|
+
alpha.3 Peer model — and the resident interaction client does not start.**
|
|
18
|
+
**Fixed in this audit; see "The fix" below.**
|
|
19
|
+
This release makes every in-process carrier name a `PeerScope`
|
|
20
|
+
(`principal` / `signal` / `interactions`) explicitly; nothing defaults to the
|
|
21
|
+
operator any more. The four landing points, each with its own evidence:
|
|
22
|
+
|
|
23
|
+
| # | Upstream change | Plugin seam | Evidence |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| 1 | `TypertGatewayWireStream.open`'s 4th parameter `peer` is required; `assertPeerLive()` reads `peer.signal.aborted` before anything else | `src/dsh-remote-interactions.ts:180` passes `undefined, undefined` | Reproduced against the published bundles: alpha.2 returns normally, alpha.3 throws `TypeError: Cannot read properties of undefined (reading 'signal')` |
|
|
26
|
+
| 2 | `$events` additionally requires `peer.interactions === 'answer'` (`requireInteractions`) | same call | Reproduced: a peer with `interactions: 'none'` gets `gateway/interactions-forbidden`; a full owner/answer peer passes on **both** versions |
|
|
27
|
+
| 3 | `ConnectionFetchHandler.fetch(request, peer)` now takes a required Peer | `src/dsh-remote-interactions.ts:204` calls `fetchHandler.fetch(request)` | `lib/types/rpc.d.ts` declares `peer: PeerScope` required, "never defaulted" |
|
|
28
|
+
| 4 | `./client` gained `node`/`browser` conditions; Node now resolves `lib/client.node.js`, which carries **no** `window.__ModuleLoader__.load` banner | `loadClientFaces()` (`src/dsh-remote-interactions.ts:117`) builds its module table from that banner | `grep -c __ModuleLoader__` is 0 in `client.node.js`, 1 in `client.browser.js` and 1 in alpha.2's `client.js` |
|
|
29
|
+
|
|
30
|
+
A fifth, latent one: `installConnection` derives
|
|
31
|
+
`interactions ?? (location === undefined ? 'none' : 'answer')`, and the
|
|
32
|
+
plugin's Node context has no `location`, so `apply(ctx)` alone always selects
|
|
33
|
+
`'none'`. Passing an explicit Peer is not enough by itself — the install call
|
|
34
|
+
must name `interactions: 'answer'` too.
|
|
35
|
+
- **Failure mode is degradation, not a crash — but only partly contained.**
|
|
36
|
+
`src/dsh-api-proxy.ts:316` wraps `startDshRemoteInteractions` in `try/catch`
|
|
37
|
+
and logs `[deeppilot] DSH Remote interaction client unavailable`. Breaks 3 and
|
|
38
|
+
4 surface while the resident client is being assembled, so they land in that
|
|
39
|
+
catch. Whether break 1/2 (the Gateway opens its Remote Events generation
|
|
40
|
+
asynchronously) also lands there is **not confirmed** — an uncaught rejection
|
|
41
|
+
there would be fatal under Node's default `--unhandled-rejections=throw`.
|
|
42
|
+
The phone loses approval/question delivery either way.
|
|
43
|
+
- **Fix direction (verified compatible with both hosted versions):** build one
|
|
44
|
+
owner/answer Peer (`new Peer(ownerCtx, { principal: { kind: 'owner' },
|
|
45
|
+
interactions: 'answer' })`, falling back to `connection.operator` on alpha.2)
|
|
46
|
+
and name it in every carrier call — alpha.2 ignores a second `fetch` argument
|
|
47
|
+
and defaults an absent `open` peer to the operator, so one call form serves
|
|
48
|
+
both. `loadClientFaces` additionally has to accept the Node bundle's named
|
|
49
|
+
exports when the banner did not fire; both shapes export `apply`/`inject`
|
|
50
|
+
(`installConnection`/`transportError` are extra on Node).
|
|
51
|
+
- **Install verdict: the peer range now names this release explicitly.** It moved
|
|
52
|
+
from the open-ended `>=0.2.0-rc.2 <0.3.0-0` to `0.2.0-rc.2 || 0.2.1-alpha.3`
|
|
53
|
+
across all ten `@deepseek-ai/dsh*` peers, and the two vendor peers widened to
|
|
54
|
+
`~4.0.4 || ~4.0.5-alpha.1` and `~3.18.4 || ~3.18.5-alpha.1`. Because DSH
|
|
55
|
+
evaluates that range on every start, the next DSH release deactivates this
|
|
56
|
+
plugin until the list is widened deliberately — see `docs/决策记录.md`
|
|
57
|
+
(章节一, 条目2), which records this as a reversal of the 2026-09-29 policy.
|
|
58
|
+
The published CLI composed the plugin's patch cleanly before the change (see
|
|
59
|
+
the 2026-10-14 entry). The plugin's own `lib/client.js` shape was not
|
|
60
|
+
re-verified against the alpha.3 client-modules scanner.
|
|
61
|
+
- **Protocol verdict: no phone-protocol change.** Protocol v2, pairing state,
|
|
62
|
+
and device records are untouched by this release; no phone needs to re-pair.
|
|
63
|
+
- **Two host-side migrations this machine must apply** (independent of the
|
|
64
|
+
plugin): `~/.dsh/profiles/web/cordis.patch.yml` carries
|
|
65
|
+
`llm-deepseek-account.config.models: []`, and alpha.3 throws on any residual
|
|
66
|
+
`models` key — **including an empty array** (`resolveAccountOptions`). Also
|
|
67
|
+
`dsh --profile <name> --from-default-profile <template>` now *replaces* an
|
|
68
|
+
existing profile directory (it deletes the old `node_modules` and
|
|
69
|
+
`cordis.patch.yml`); the audit procedure recorded below used that flag.
|
|
70
|
+
- **Capability gaps, not regressions:** the `contextBreakdown` projection was
|
|
71
|
+
reshaped (`nodes[]` → `systems[]` + `surfaceTokens` + `claim`, stateVersion
|
|
72
|
+
5→6) and the plugin forwards it by key, so the phone may render empty values;
|
|
73
|
+
the model catalog shrank (account routes return only the version-gated
|
|
74
|
+
`offered` subset, pi-ai routes without a resolvable key return `[]`); and
|
|
75
|
+
`CLIENT_UPGRADE_REQUIRED` / `MODEL_UNSUPPORTED` are new codes with no mapping
|
|
76
|
+
in `src/wire-errors.ts` yet.
|
|
77
|
+
|
|
78
|
+
### Previous decision — `dsh-v0.2.1-alpha.2`
|
|
79
|
+
|
|
8
80
|
- **Audited release:** `dsh-v0.2.1-alpha.2` (official release page:
|
|
9
81
|
<https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.2>),
|
|
10
82
|
published to npm 2026-10-09T08:18:00Z on the `alpha` dist-tag. It is **not**
|
|
@@ -43,9 +115,21 @@
|
|
|
43
115
|
|
|
44
116
|
### Plugin release verdicts
|
|
45
117
|
|
|
46
|
-
- **Release
|
|
47
|
-
|
|
48
|
-
|
|
118
|
+
- **Release candidate (`0.9.5`, 2026-10-15):** carries the alpha.3 Peer/client
|
|
119
|
+
compatibility repair, the exact two-host peer policy, cross-language auth
|
|
120
|
+
vectors and recovery guidance, and the manual OIDC/provenance workflow.
|
|
121
|
+
The package and both lockfile version entries are `0.9.5`; the apply baseline
|
|
122
|
+
records that version. Clean install, 562 passing unit tests (zero failures,
|
|
123
|
+
one existing TODO), typecheck, build, the real pinned-host schema loader,
|
|
124
|
+
Go tests, all six helper checksums, and package inspection pass locally.
|
|
125
|
+
The altered-challenge test now mutates the challenge from its own socket,
|
|
126
|
+
eliminating the timestamp collision, and passes 20 consecutive focused runs.
|
|
127
|
+
Registry/provenance and clean published-profile checks remain release gates;
|
|
128
|
+
no physical-iPhone validation is claimed. Notes: `docs/RELEASE_NOTES-0.9.5.md`.
|
|
129
|
+
|
|
130
|
+
- **Release verdict (`0.9.4`, published 2026-10-10, tag `v0.9.4` @ `e2f77af`):**
|
|
131
|
+
carries the `projections` adapter fix and the Automation tasks documentation
|
|
132
|
+
correction described in the 2026-10-09 entry below. `package.json`, both root
|
|
49
133
|
`package-lock.json` version entries, and `tests/apply-baseline.snapshot.json`
|
|
50
134
|
are on `0.9.4`, and `lib/` is regenerated. The full checklist passed locally:
|
|
51
135
|
`npm ci`, 538 unit tests, typecheck, build, the config-schema check against a
|
|
@@ -53,9 +137,24 @@
|
|
|
53
137
|
`npm pack --dry-run` (65 files, 50,916,470 bytes packed). The fix was also
|
|
54
138
|
verified on a live `0.2.1-alpha.2` host against the published `0.9.3` as an
|
|
55
139
|
A/B control (25 pass / 0 fail either way, with the `sessions.projections
|
|
56
|
-
failed: internal` diagnostic present only on `0.9.3`).
|
|
57
|
-
|
|
58
|
-
|
|
140
|
+
failed: internal` diagnostic present only on `0.9.3`).
|
|
141
|
+
- **Registry verification after publishing.** `dist-tags.latest` moved off
|
|
142
|
+
`0.9.3`; `dist.shasum` `85b3c71a802e45e42f5fb1b23444ed2e396d39dc` is
|
|
143
|
+
byte-identical to the local `npm pack` output at the tag; 65 files,
|
|
144
|
+
`dist.unpackedSize` 130,789,604. **`gitHead` is `e2f77af`, the tag commit** —
|
|
145
|
+
the 0.9.3 ordering deviation did not repeat, because the release notes and
|
|
146
|
+
every other documentation change were committed before the pack. The publish
|
|
147
|
+
itself needed the maintainer's one-time password, as in 0.9.3; everything up
|
|
148
|
+
to and after it was done here.
|
|
149
|
+
- **Clean-profile verification.** A fresh isolated home, built from the shipped
|
|
150
|
+
Web template and installed with the published `0.2.1-alpha.2` CLI
|
|
151
|
+
(`dsh plugin --profile verify add dsh-deeppilot@0.9.4`), composed a real
|
|
152
|
+
profile: `--dump-config` printed the plugin's three rows
|
|
153
|
+
(`deeppilot-directory-picker-browse`, `…-browse-client`, `deeppilot`) with no
|
|
154
|
+
missing-module or load error, the installed package's six helper checksums
|
|
155
|
+
verified, every helper binary embedded `go1.26.9`, and the installed
|
|
156
|
+
`lib/index.js` carried the projections fix. Only then was the GitHub Release
|
|
157
|
+
taken out of draft and marked Latest.
|
|
59
158
|
- **The release also had to fix the helper's toolchain and its refresh
|
|
60
159
|
workflow.** CI's `govulncheck` job reported 11 reachable Go standard-library
|
|
61
160
|
vulnerabilities for the pinned `go 1.26.6`; this is time-triggered, not
|
|
@@ -207,6 +306,224 @@
|
|
|
207
306
|
bundle" below. The plugin needs no code change; the phone's copy now points
|
|
208
307
|
at the bundle.
|
|
209
308
|
|
|
309
|
+
## 2026-10-14 — audit of `dsh-v0.2.1-alpha.3`
|
|
310
|
+
|
|
311
|
+
### Scope and method
|
|
312
|
+
|
|
313
|
+
Compared `dsh-v0.2.1-alpha.2...dsh-v0.2.1-alpha.3` (**852 commits, 3311 changed
|
|
314
|
+
files, 120,978 insertions, 16,632 deletions**) by fetching the new tag into the
|
|
315
|
+
existing blobless clone (`.audit-dsh-alpha2`, whose HEAD was already the alpha.2
|
|
316
|
+
audit's target SHA `d7432673`), then `git diff --name-status` and per-path
|
|
317
|
+
content diffs into `.audit-alpha3/diffs/`. Four parallel read-only passes
|
|
318
|
+
covered `packages/api` + `apps/cli` + `packages/typert` + `packages/settings` +
|
|
319
|
+
`vendor`, `packages/client/{connection,locale,ui-settings,ui-slots}` +
|
|
320
|
+
`packages/boot` + `packages/bundle`, `packages/{credentials,subagent,session,
|
|
321
|
+
schedule}`, and `packages/{core,host,skill,llm}`. All **14** upgrade guides
|
|
322
|
+
added by this release (under `docs/upgrade-guide/v0.2.0-rc.2/`,
|
|
323
|
+
`v0.2.1-alpha.1/`, `v0.2.1-alpha.2/`) were read in Chinese in full.
|
|
324
|
+
|
|
325
|
+
The alpha.2 audit's lesson — judge the seams reached through `ctx.get`, not just
|
|
326
|
+
the peer list — is what caught this one: the peer list is *not* where the breaks
|
|
327
|
+
are. `@deepseek-ai/dsh-api-remote-access` is new, `packages/schedule/schedule`
|
|
328
|
+
has **zero** changed source files, and the peer packages' own exported surfaces
|
|
329
|
+
moved far less than the carrier contracts around them.
|
|
330
|
+
|
|
331
|
+
### The four confirmed breaks
|
|
332
|
+
|
|
333
|
+
Every one is the same root cause: alpha.3 makes the Peer explicit. `PeerScope`
|
|
334
|
+
(`@deepseek-ai/dsh-typert-protocol`) gained required `principal`, `signal`, and
|
|
335
|
+
`interactions`; `client/connection` added a `Peer` class and deleted
|
|
336
|
+
`OperatorPeer`; the Gateway deleted `operatorPeer()` and its
|
|
337
|
+
`request.peer ?? this.operatorPeer()` fallback.
|
|
338
|
+
|
|
339
|
+
Reproduction used the **published** bundles, the same technique as the alpha.2
|
|
340
|
+
`projections` break, with the plugin's exact argument list
|
|
341
|
+
(`open('$events', payload, undefined, peer, signal)`):
|
|
342
|
+
|
|
343
|
+
```sh
|
|
344
|
+
# .audit-alpha3/repro-peer-contract.mjs, against
|
|
345
|
+
# alpha.2: ~/.npm/_npx/b5c6312874079fe8/node_modules/@deepseek-ai/dsh-api-gateway/lib/index.js
|
|
346
|
+
# alpha.3: .audit-alpha3/cli-install/node_modules/@deepseek-ai/dsh-api-gateway/lib/index.js
|
|
347
|
+
# A. peer = undefined (what the plugin sends today)
|
|
348
|
+
alpha.2: 未抛错
|
|
349
|
+
alpha.3: 抛错 TypeError: Cannot read properties of undefined (reading 'signal')
|
|
350
|
+
# B. peer = { principal: owner, signal, interactions: 'none' }
|
|
351
|
+
alpha.2: 未抛错
|
|
352
|
+
alpha.3: 抛错 TypertGatewayError: $events: forwarded Remote events require interactions=answer
|
|
353
|
+
# C. peer = { principal: owner, signal, interactions: 'answer' }
|
|
354
|
+
alpha.2: 未抛错
|
|
355
|
+
alpha.3: 未抛错
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Break 4 was settled by reading the shipped artifacts rather than by inference:
|
|
359
|
+
`@deepseek-ai/dsh-client-connection`'s `./client` now maps `node` →
|
|
360
|
+
`lib/client.node.js`, and that file contains **zero** `__ModuleLoader__`
|
|
361
|
+
occurrences while `client.browser.js` and alpha.2's `client.js` each contain
|
|
362
|
+
one. `loadClientFaces` builds its module table *only* from that banner, so on
|
|
363
|
+
Node the table stays empty. Both shapes export `apply` and `inject`, which is
|
|
364
|
+
what makes a banner-optional fallback possible.
|
|
365
|
+
|
|
366
|
+
### The fix
|
|
367
|
+
|
|
368
|
+
One call form serves both audited hosts, because alpha.2 ignores a second
|
|
369
|
+
`fetch` argument and defaults an absent `open` peer to the operator Peer it
|
|
370
|
+
holds:
|
|
371
|
+
|
|
372
|
+
- `src/dsh-remote-interactions.ts` mints one Peer through
|
|
373
|
+
`connection.createPeer({ principal: { kind: 'owner' }, interactions: 'answer' })`
|
|
374
|
+
and falls back to `connection.operator` on a Host without that entry point.
|
|
375
|
+
The same Peer is named on `fetch` and on `wireStream.open`.
|
|
376
|
+
- The Connection install prefers the explicit
|
|
377
|
+
`installConnection(ctx, { transport, interactions: 'answer' })`, because
|
|
378
|
+
`apply(ctx)` cannot name the participation and therefore always selects
|
|
379
|
+
`'none'` for this caller; `apply` remains the fallback for a host that predates
|
|
380
|
+
the installer.
|
|
381
|
+
- `loadClientFaces` accepts a Node bundle's named exports when the browser
|
|
382
|
+
banner did not register them. A banner registration still wins, because the
|
|
383
|
+
bundle's own internal `require()` resolves against it.
|
|
384
|
+
|
|
385
|
+
Six regression tests in `tests/dsh-wire-stream-contract.test.ts` pin the
|
|
386
|
+
contract: the operator fallback, the alpha.3 `createPeer` request shape and the
|
|
387
|
+
Peer reaching `wireStream.open`, both installer branches, and both module-table
|
|
388
|
+
branches. The file fails against the previous call form — verified by restoring
|
|
389
|
+
the source and re-running (1 failure) — and passes after the fix.
|
|
390
|
+
|
|
391
|
+
### Peer range narrowed to the two audited hosts
|
|
392
|
+
|
|
393
|
+
By explicit user direction the DSH peer range stopped being open-ended:
|
|
394
|
+
`>=0.2.0-rc.2 <0.3.0-0` became `0.2.0-rc.2 || 0.2.1-alpha.3` across all ten
|
|
395
|
+
`@deepseek-ai/dsh*` peers, with `@deepseek-ai/cordis` widened to
|
|
396
|
+
`~4.0.4 || ~4.0.5-alpha.1` and `@deepseek-ai/schemastery` to
|
|
397
|
+
`~3.18.4 || ~3.18.5-alpha.1`. The two vendor peers are outside DSH's own peer
|
|
398
|
+
check — it reads only `@deepseek-ai/dsh` and `@deepseek-ai/dsh-*` — so their
|
|
399
|
+
ranges matter only to npm's install-time resolution, which runs without
|
|
400
|
+
`includePrerelease`; naming both vendored prereleases keeps a clean install on
|
|
401
|
+
either host. `devDependencies` stay pinned to `0.2.0-rc.2`.
|
|
402
|
+
|
|
403
|
+
The consequence is deliberate: `evaluatePluginCompatibility` runs before a
|
|
404
|
+
profile is composed on every start, so the next DSH release will refuse to
|
|
405
|
+
activate this plugin until the list is widened by an audit. That reversal of the
|
|
406
|
+
2026-09-29 policy is recorded in `docs/决策记录.md` (章节一, 条目2).
|
|
407
|
+
`tests/compatibility-metadata.test.ts` now asserts both admissions, fifteen
|
|
408
|
+
rejections, and the two vendor ranges.
|
|
409
|
+
|
|
410
|
+
### Host-side changes with no plugin impact
|
|
411
|
+
|
|
412
|
+
`packages/schedule/schedule/**` has no changed source; the schedule error-code
|
|
413
|
+
table is identical, so `src/wire-errors.ts`'s twelve mappings (including
|
|
414
|
+
`subagent_session → E_PROTOCOL`) stay correct. No session-log format migration
|
|
415
|
+
exists — `session-format*`, `v0-to-v1`…`v3-to-v4`, `session-persistence`,
|
|
416
|
+
`session-projection*`, `session-log-deepseek` changed only their version and
|
|
417
|
+
locale files — so phone history rendering is unaffected. The subagent descriptor
|
|
418
|
+
is still version 3. `host/webserver`'s `port` widening, the macOS directory
|
|
419
|
+
picker, the PTC call registry, and the new `job/registered` / `job/settled`
|
|
420
|
+
events are all unreachable or safely skipped by this plugin (`projectEvent`'s
|
|
421
|
+
`default: return null`). Settings surfaces are unchanged at the contract level:
|
|
422
|
+
`ui-settings`'s `settings.section` slot has the same SHA as alpha.2, and
|
|
423
|
+
`configForms.get()` kept its name. The Web composition still mounts
|
|
424
|
+
`schedule`/`ui-schedule`, and `directory-picker` is still disabled in place, so
|
|
425
|
+
the plugin's three `cordis.patch.yml` rows still land.
|
|
426
|
+
|
|
427
|
+
### Validation evidence
|
|
428
|
+
|
|
429
|
+
- **Config schema on the published CLI:** isolated `DSH_HOME`, `dsh
|
|
430
|
+
--profile deeppilot-audit --from-default-profile sdk-minimal
|
|
431
|
+
--dump-config-schema --patch cordis.patch.yml` against
|
|
432
|
+
`@deepseek-ai/dsh@0.2.1-alpha.3` → exit 0, 179,851 bytes, the plugin entry at
|
|
433
|
+
`/37` reports `status: 'schema'` with `configRef: #/$defs/config24`, all seven
|
|
434
|
+
volatile assertions pass, no `deeppilot`-attributed diagnostic, and the only
|
|
435
|
+
warning is the `sdk-minimal` template's own missing `directory-picker` row.
|
|
436
|
+
- **Web composition:** the same CLI composed a real Web profile with the plugin
|
|
437
|
+
patch → **1225 rows versus 1214 without it**, the plugin's three rows present
|
|
438
|
+
at `deeppilot-directory-picker-browse` / `…-browse-client` / `deeppilot`, and
|
|
439
|
+
zero `unrecognized Loader tree carrier` entries on either side.
|
|
440
|
+
- **Plugin unit tests:** `npx tsx --test tests/*.test.ts` → **562 tests, 561
|
|
441
|
+
pass, 0 fail, 1 todo**, including the six new contract tests. `npm run
|
|
442
|
+
typecheck`, `npm run build`, and `npm run check:config-schema` (against the
|
|
443
|
+
pinned `0.2.0-rc.2` CLI) all pass with regenerated `lib/`.
|
|
444
|
+
- **Live A/B on a real `0.2.1-alpha.3` host — the evidence that matters.** An
|
|
445
|
+
isolated `DSH_HOME` profile built from the shipped Web template and linked to
|
|
446
|
+
this working tree booted under the published alpha.3 CLI (`--port 3180`; the
|
|
447
|
+
default 3080 was already held by this machine's own Host). The **unfixed**
|
|
448
|
+
`0.9.4` build printed
|
|
449
|
+
`[deeppilot] DSH Remote interaction client unavailable:
|
|
450
|
+
@deepseek-ai/dsh-typert-registry/client Client bundle returned no exports`
|
|
451
|
+
and the fixed build printed no such line. The checklist itself scored
|
|
452
|
+
**23 pass / 1 fail / 4 skipped in both runs** — see the blind spot below.
|
|
453
|
+
- **The regression checklist does not cover this seam.** `scripts/smoke-live.mts`
|
|
454
|
+
scored identically before and after the fix, because the resident Client it
|
|
455
|
+
exercises is only reachable through the approval/question waterfall, and step
|
|
456
|
+
10 is skipped without `--interactions`. The observable difference is the Host
|
|
457
|
+
log line and the phone's approval/question delivery, not a checklist step. A
|
|
458
|
+
future revision should assert on that diagnostic directly.
|
|
459
|
+
- **Backward compatibility, same working tree:** the same profile booted under
|
|
460
|
+
the published `0.2.1-alpha.2` CLI (`--port 3181`) scored **25 pass / 0 fail /
|
|
461
|
+
0 warn / 3 skipped** with no interaction-client diagnostic — the same result
|
|
462
|
+
the alpha.2 audit recorded, so the fix does not regress the host users run
|
|
463
|
+
today.
|
|
464
|
+
- **One checklist difference between the two host versions is real.**
|
|
465
|
+
`模型目录可读` passes on alpha.2 and fails on alpha.3 in the same isolated
|
|
466
|
+
home (`0 provider / 0 model, routable=true`): this release stops listing a
|
|
467
|
+
pi-ai route before its credential resolves and returns only the version-gated
|
|
468
|
+
subset for the account route. The home has no model credential, so the phone's
|
|
469
|
+
model list is empty there. That is the documented behavior change, not a
|
|
470
|
+
plugin defect.
|
|
471
|
+
- **Artifact inspection:** the `node`/`browser` conditional exports and the
|
|
472
|
+
banner counts above were read from the installed alpha.3 packages, not from
|
|
473
|
+
the diff.
|
|
474
|
+
|
|
475
|
+
### Host-side migrations this machine must apply
|
|
476
|
+
|
|
477
|
+
Neither is a plugin defect, and neither is fixed by the plugin change:
|
|
478
|
+
|
|
479
|
+
1. **`llm-deepseek-account`'s retired `models` key.**
|
|
480
|
+
`~/.dsh/profiles/web/cordis.patch.yml:77-79` sets `models: []`.
|
|
481
|
+
`resolveAccountOptions` throws for *any* own `models` key
|
|
482
|
+
(`llm-deepseek-account: models is no longer configurable…`), and the
|
|
483
|
+
upstream test pins `[{ models: [] }, /models is no longer configurable/]`.
|
|
484
|
+
The empty array is not exempt. The `desktop` profile's row carries no
|
|
485
|
+
`config` and is unaffected. Whether the throw takes the whole Host down or
|
|
486
|
+
only retires that route was **not** measured — the guide says the account
|
|
487
|
+
models stop appearing either way.
|
|
488
|
+
2. **`--from-default-profile` now replaces.** It stages a fresh copy over the
|
|
489
|
+
target and deletes the previous directory, including `node_modules` and the
|
|
490
|
+
user's own `cordis.patch.yml`. The re-audit procedure below and the alpha.2
|
|
491
|
+
entry's schema command both use that flag; from now on it must be pointed at
|
|
492
|
+
a scratch name.
|
|
493
|
+
|
|
494
|
+
Checked and **not** required: Azure provider rename (no `azure` /
|
|
495
|
+
`azure-openai-responses` key exists under `~/.dsh/profiles`), credential-file
|
|
496
|
+
extra keys (the file's top level is already `version`/`refs`/`records`), and
|
|
497
|
+
the stored-credential precedence flip (no `refs` name is exported in the shell,
|
|
498
|
+
and the stored value won precedence over `.env` on both sides of the change).
|
|
499
|
+
|
|
500
|
+
### Still unverified
|
|
501
|
+
|
|
502
|
+
- Whether breaks 1 and 2 surface inside `dsh-api-proxy.ts`'s catch or escape it
|
|
503
|
+
as an unhandled rejection — the Gateway opens its Remote Events generation
|
|
504
|
+
asynchronously, after `startDshRemoteInteractions` has already returned.
|
|
505
|
+
- Whether an old profile that predates `@deepseek-ai/dsh-api-remote-access`
|
|
506
|
+
still gets the row (the shipped Web template does; `dsh-base` carries it).
|
|
507
|
+
- How the reshaped `contextBreakdown` renders on iOS, and what the phone shows
|
|
508
|
+
for the unmapped `CLIENT_UPGRADE_REQUIRED` / `MODEL_UNSUPPORTED` codes.
|
|
509
|
+
- Whether the plugin's own `lib/client.js` (banner shape, `dsh.client.inject`,
|
|
510
|
+
`platform: web`) is still accepted by alpha.3's client-modules scanner; that
|
|
511
|
+
scanner lives in `packages/client/modules` and `packages/client/web`, which
|
|
512
|
+
were not read in full.
|
|
513
|
+
|
|
514
|
+
### Re-audit procedure used
|
|
515
|
+
|
|
516
|
+
Fetch the new tag into the existing blobless clone at `.audit-dsh-alpha2`
|
|
517
|
+
(`git fetch --filter=blob:none --no-tags origin
|
|
518
|
+
'+refs/tags/<tag>:refs/tags/<tag>'`, ~3 s), slice
|
|
519
|
+
`git diff --name-status` per package, and write each slice's content diff to a
|
|
520
|
+
file before parallel reading — blobless clones fetch blobs on demand, so one
|
|
521
|
+
`git diff` per region is far cheaper than many small reads. Reproduce a
|
|
522
|
+
suspected carrier break against the **published** `lib/` bundles with a stub
|
|
523
|
+
`this`, as above, rather than trusting either the diff or the release notes.
|
|
524
|
+
`npm --cache <workspace dir>` is still required; `~/.npm/_cacache` holds
|
|
525
|
+
root-owned files and fails `npm view` with `EPERM`.
|
|
526
|
+
|
|
210
527
|
## 2026-10-09 — audit of `dsh-v0.2.1-alpha.2`
|
|
211
528
|
|
|
212
529
|
### Scope and method
|
|
@@ -316,12 +633,12 @@ the mirror's `signal` is required, so the compiler keeps it that way.
|
|
|
316
633
|
own, so a dedicated long-lived controller is not warranted; following the
|
|
317
634
|
existing convention in the same file is enough. `lib/index.js` was regenerated.
|
|
318
635
|
|
|
319
|
-
The fix ships in `0.9.4`,
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
636
|
+
The fix ships in `0.9.4`, published 2026-10-10 (see "Plugin release verdicts").
|
|
637
|
+
A host on `0.2.1-alpha.2` left on the published `0.9.3` still loses the on-open
|
|
638
|
+
projection baseline. That was observed directly: on a live `0.2.1-alpha.2` host
|
|
639
|
+
running published `0.9.3` the diagnostic appeared, and on the same host version
|
|
640
|
+
running this build it did not (see "Live run on a real `0.2.1-alpha.2` host"
|
|
641
|
+
under Validation evidence).
|
|
325
642
|
|
|
326
643
|
`tests/dsh-api-proxy.test.ts` adds "projection baseline reads pass the
|
|
327
644
|
AbortSignal the Host now requires": a controller stub that calls
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# npm 可信发布说明
|
|
2
|
+
|
|
3
|
+
## 一、发布方式
|
|
4
|
+
|
|
5
|
+
1. `.github/workflows/publish.yml` 使用 GitHub 托管的运行器,通过
|
|
6
|
+
OpenID Connect(OIDC,开放身份连接)向 npm 获取临时发布凭据;无需配置
|
|
7
|
+
`NPM_TOKEN` 或 `NODE_AUTH_TOKEN`。来源证明(Provenance)将关联公开仓库、
|
|
8
|
+
工作流和本次运行的源码提交。
|
|
9
|
+
2. 工作流仅由 `workflow_dispatch`(手动触发)启动;推送提交、标签或创建
|
|
10
|
+
GitHub Release(发布页)不会自动上传 npm 包。手动运行须使用对应版本标签,
|
|
11
|
+
不能在 `main` 上运行后另行检出标签,否则来源证明的提交会与源码不一致。
|
|
12
|
+
3. 发布环境使用 Node.js 22 与固定的 npm `11.21.0`;npm 官方要求可信发布至少使用
|
|
13
|
+
npm `11.5.1` 和 Node.js `22.14.0`。稳定版使用 `latest`(稳定分发标签),
|
|
14
|
+
预发布版使用 `alpha`(预发布分发标签),与原有发布策略一致。
|
|
15
|
+
|
|
16
|
+
## 二、npm 网站绑定
|
|
17
|
+
|
|
18
|
+
1. 将工作流合入默认分支 `main` 后,打开
|
|
19
|
+
[dsh-deeppilot 包设置](https://www.npmjs.com/package/dsh-deeppilot/access),
|
|
20
|
+
在 Trusted Publisher(可信发布者)区域选择 GitHub Actions(GitHub 工作流)。
|
|
21
|
+
2. 按下表配置,字段大小写必须一致:
|
|
22
|
+
|
|
23
|
+
| npm 字段 | 含义 | 填写值 |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Organization or user | GitHub 组织或用户 | `Mars-Sea` |
|
|
26
|
+
| Repository | 仓库名称 | `dsh-deeppilot` |
|
|
27
|
+
| Workflow filename | 工作流文件名 | `publish.yml` |
|
|
28
|
+
| Environment name | 部署环境名称 | 留空,本工作流未使用部署环境 |
|
|
29
|
+
| Allow npm publish | 允许直接发布 | 勾选 |
|
|
30
|
+
| Allow npm dist-tag | 允许独立修改分发标签 | 无需勾选,工作流通过发布命令设置标签 |
|
|
31
|
+
|
|
32
|
+
3. 工作流文件名只填 `publish.yml`,不填 `.github/workflows/` 前缀。
|
|
33
|
+
新建绑定默认只允许暂存发布(`npm stage publish`),须明确允许 `npm publish`。
|
|
34
|
+
npm 不在保存配置时验证绑定;首次发布成功才证明认证可用。
|
|
35
|
+
4. 新绑定必须在 **2 天内完成首次成功发布**。因此准备好新版本后再绑定;若过期,
|
|
36
|
+
删除过期绑定并重新创建。先验证可信发布成功,再按需调整原有令牌发布权限。
|
|
37
|
+
|
|
38
|
+
## 三、每次发布
|
|
39
|
+
|
|
40
|
+
1. 按项目 `AGENTS.md` 的发布清单完成验证,同时更新 `package.json`、
|
|
41
|
+
`package-lock.json` 顶层与根包条目的版本;提交并推送,等待持续集成(CI)通过。
|
|
42
|
+
版本提交必须已经进入 `main`,且该提交包含 `publish.yml`。
|
|
43
|
+
2. 经用户授权发布该版本后,从精确的发布提交创建并推送附注标签 `v<版本>`,
|
|
44
|
+
准备包含英文与简体中文说明的 GitHub 发布草稿。
|
|
45
|
+
3. 在对应标签上手动运行工作流。下例的 `0.9.5` 仅为示例,替换为已授权发布的版本:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
gh workflow run publish.yml --repo Mars-Sea/dsh-deeppilot --ref v0.9.5 \
|
|
49
|
+
-f version=0.9.5 -f dist_tag=latest
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
预发布示例:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
gh workflow run publish.yml --repo Mars-Sea/dsh-deeppilot --ref v0.9.5-alpha.1 \
|
|
56
|
+
-f version=0.9.5-alpha.1 -f dist_tag=alpha
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
4. 工作流验证输入、标签、两处锁文件版本、包仓库与运行提交一致;拒绝轻量标签、
|
|
60
|
+
尚未合入 `main` 的提交、分发标签错配和构建元数据。随后执行依赖安全检查、
|
|
61
|
+
插件测试、类型检查、构建产物一致性检查、DSH 配置校验、Go 测试和辅助程序校验和。
|
|
62
|
+
5. 工作流检查包内容并生成压缩包,再发布该压缩包,显式要求来源证明。
|
|
63
|
+
压缩包发布跳过生命周期脚本(`--ignore-scripts`);测试、类型检查和构建已在前序
|
|
64
|
+
步骤执行,打包仍执行 `prepack`(打包前构建)。发布后核对版本、分发标签、
|
|
65
|
+
压缩包完整性和来源证明元数据。
|
|
66
|
+
6. 确认 npm 包可安装,在全新 DSH 配置中完成安装验证后,再公开 GitHub 发布草稿。
|
|
67
|
+
工作流不自动公开发布草稿;npm 上传和发布后的核对分别记录在步骤中。
|
|
68
|
+
|
|
69
|
+
## 四、失败处理与证据边界
|
|
70
|
+
|
|
71
|
+
1. 认证失败先核对 npm 绑定中的仓库、大小写、`publish.yml` 和直接发布权限。
|
|
72
|
+
`npm whoami`(当前账号查询)不能验证 OIDC 发布权限。
|
|
73
|
+
2. 若 npm 上传成功而后续核对失败,先检查注册表;已存在的版本不能通过重跑覆盖。
|
|
74
|
+
排查来源证明或分发标签后再决定下一步,禁止把核对失败直接当作上传失败。
|
|
75
|
+
3. 本地语法检查、测试和打包不能证明 npm 绑定已生效;真实 OIDC 认证与来源证明
|
|
76
|
+
需要首次获授权的新版本发布才能验证。已有旧版本的认证状态不因新增工作流改变。
|
|
77
|
+
|
|
78
|
+
## 五、官方资料
|
|
79
|
+
|
|
80
|
+
1. [npm 可信发布配置与限制](https://docs.npmjs.com/trusted-publishers/)
|
|
81
|
+
2. [npm 来源证明要求](https://docs.npmjs.com/generating-provenance-statements/)
|
|
82
|
+
3. [GitHub 手动运行工作流](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/manually-run-a-workflow)
|
|
83
|
+
4. [GitHub 工作流调度接口:运行引用支持分支或标签](https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event)
|
package/docs/SECURITY_ROADMAP.md
CHANGED
|
@@ -14,11 +14,16 @@ Status: implemented on the development branch. `PROTOCOL.md` is normative. Proto
|
|
|
14
14
|
- minimal unauthenticated health response; no Bearer or URL credentials;
|
|
15
15
|
- privacy-preserving audit identifiers, a global connection switch, and per-device deletion; scopes remain enforced at the protocol layer.
|
|
16
16
|
|
|
17
|
+
- shared canonical/signature vectors (`tests/fixtures/auth-vectors.json`) read by both the TypeScript tests and the iOS tests, with signatures from Node and from CryptoKit;
|
|
18
|
+
- abuse tests for malformed requests, connection churn, replay, expired and altered challenges, and many-source pressure (`tests/auth-hardening.test.ts`);
|
|
19
|
+
- documented local recovery when `devices-v2.json` is lost ([DEVICE_REGISTRY_RECOVERY.md](./DEVICE_REGISTRY_RECOVERY.md)).
|
|
20
|
+
|
|
21
|
+
## Decided
|
|
22
|
+
|
|
23
|
+
- High-impact approval responses do **not** require a fresh local biometric/user-presence confirmation (decided 2026-10-12). Revisit if the threat model changes, for example a phone shared between people.
|
|
24
|
+
|
|
17
25
|
## Remaining release validation
|
|
18
26
|
|
|
19
|
-
- share fixed canonical/signature vectors between TypeScript and Swift tests;
|
|
20
27
|
- exercise pairing, reconnect/resume, and per-device deletion through both real LAN and Funnel paths;
|
|
21
|
-
-
|
|
22
|
-
- document local recovery when `devices-v2.json` is lost;
|
|
23
|
-
- decide whether high-impact approval responses should require fresh local biometric/user-presence confirmation;
|
|
28
|
+
- an authenticated socket still accepts a second `c2s.auth.prove` and answers it with another welcome and resume replay; the gate should answer `E_PROTOCOL` instead (tracked as a `todo` test);
|
|
24
29
|
- keep dependency scanning and CodeQL green, then sign and notarize every distributed helper before claiming production readiness.
|
|
@@ -14,11 +14,16 @@
|
|
|
14
14
|
- 最小化匿名探活响应,不接受 Bearer 或 URL 凭据;
|
|
15
15
|
- 隐私化审计标识、全局连接开关和逐设备删除入口;scope 继续由协议层强制执行。
|
|
16
16
|
|
|
17
|
+
- TypeScript 与 Swift 测试共用的固定规范化编码/签名向量(`tests/fixtures/auth-vectors.json`),签名分别来自 Node 与 CryptoKit;
|
|
18
|
+
- 畸形请求、连接抖动、重放、挑战过期/被篡改和多来源压力的滥用测试(`tests/auth-hardening.test.ts`);
|
|
19
|
+
- `devices-v2.json` 丢失后的本机恢复流程([DEVICE_REGISTRY_RECOVERY.zh-CN.md](./DEVICE_REGISTRY_RECOVERY.zh-CN.md))。
|
|
20
|
+
|
|
21
|
+
## 已决定
|
|
22
|
+
|
|
23
|
+
- 高影响审批响应**不**要求新的本地生物识别/用户在场确认(2026-10-12 决定)。如威胁模型变化(例如手机多人共用)再重新评估。
|
|
24
|
+
|
|
17
25
|
## 发布前剩余验证
|
|
18
26
|
|
|
19
|
-
- 在 TypeScript 与 Swift 测试间共享固定的规范化编码/签名向量;
|
|
20
27
|
- 通过真实 LAN 与 Funnel 验证配对、重连/恢复和逐设备删除;
|
|
21
|
-
-
|
|
22
|
-
- 记录 `devices-v2.json` 丢失后的本机恢复流程;
|
|
23
|
-
- 决定高影响审批是否要求新的本地生物识别/用户在场确认;
|
|
28
|
+
- 已认证的 socket 仍会接受第二次 `c2s.auth.prove`,并再回一次 welcome 与重放;门应改为回 `E_PROTOCOL`(以 `todo` 测试跟踪);
|
|
24
29
|
- 保持依赖扫描与 CodeQL 通过,并在宣称生产可用前签名、公证所有分发 helper。
|
package/lib/client.js
CHANGED
|
@@ -545,7 +545,7 @@ window.__ModuleLoader__.load({
|
|
|
545
545
|
"push.testPush": "发送测试通知",
|
|
546
546
|
"push.relayTesting": "测试中…",
|
|
547
547
|
"push.pushSending": "发送中…",
|
|
548
|
-
"push.relayDefault": "
|
|
548
|
+
"push.relayDefault": "验证这台电脑到推送中继的连通性与自动注册是否正常。",
|
|
549
549
|
"push.pushDefault": "向所有已注册设备强制发送一条真实推送(不受在线状态与分类开关影响)。",
|
|
550
550
|
"push.relayOk": "通过",
|
|
551
551
|
"push.relayBad": "存在问题",
|
|
@@ -656,7 +656,7 @@ window.__ModuleLoader__.load({
|
|
|
656
656
|
en: {
|
|
657
657
|
"nav": "DeepPilot",
|
|
658
658
|
"meta.title": "DeepPilot",
|
|
659
|
-
"meta.intro": "Connect your iPhone to DeepSeek Harness (DSH) on this
|
|
659
|
+
"meta.intro": "Connect your iPhone to DeepSeek Harness (DSH) on this computer (protocol v2).",
|
|
660
660
|
"meta.refresh": "Refresh",
|
|
661
661
|
"master.title": "DeepPilot connection",
|
|
662
662
|
"master.loading": "Loading settings…",
|
|
@@ -763,7 +763,7 @@ window.__ModuleLoader__.load({
|
|
|
763
763
|
"push.testPush": "Send test notification",
|
|
764
764
|
"push.relayTesting": "Testing…",
|
|
765
765
|
"push.pushSending": "Sending…",
|
|
766
|
-
"push.relayDefault": "Verify
|
|
766
|
+
"push.relayDefault": "Verify this computer → push relay connectivity and zero-touch enrollment.",
|
|
767
767
|
"push.pushDefault": "Force one real push to every registered device (ignores online state and category switches).",
|
|
768
768
|
"push.relayOk": "OK",
|
|
769
769
|
"push.relayBad": "Issues found",
|
|
@@ -814,7 +814,7 @@ window.__ModuleLoader__.load({
|
|
|
814
814
|
"help.step2": "When the status shows \"Awaiting Tailscale authorization\", click \"Open authorization page\".",
|
|
815
815
|
"help.step3": "Sign in with an account that can manage this Tailnet and follow the on-page prompts to enable Funnel. Owner / Admin / Network admin is usually required.",
|
|
816
816
|
"help.step4": "Come back to this page and click \"Refresh\". When a green dot appears next to the remote title, scan the QR code below to add your phone.",
|
|
817
|
-
"help.funnelHint": "The plugin bundles the Tailscale networking stack, so neither this
|
|
817
|
+
"help.funnelHint": "The plugin bundles the Tailscale networking stack, so neither this computer nor the phone needs the Tailscale app — but a Tailnet administrator must approve first-time use.",
|
|
818
818
|
"help.httpsTitle": "If authorization does not complete: enable HTTPS",
|
|
819
819
|
"help.httpsStep1": "Open the Tailscale admin console → Network → DNS.",
|
|
820
820
|
"help.httpsStep2": "Confirm MagicDNS is enabled.",
|
|
@@ -826,7 +826,7 @@ window.__ModuleLoader__.load({
|
|
|
826
826
|
"help.faqTitle": "Frequently asked questions",
|
|
827
827
|
"help.faq1": "\"HTTPS must be enabled\" — finish the HTTPS Certificates steps above.",
|
|
828
828
|
"help.faq2": "\"Funnel not available\" — make sure the nodeAttrs snippet is saved and this node is in the rule's target.",
|
|
829
|
-
"help.faq3": "Public URL exists but the phone times out: wait a few minutes, confirm the
|
|
829
|
+
"help.faq3": "Public URL exists but the phone times out: wait a few minutes, confirm the computer is awake and DSH is running, then re-scan the latest QR code.",
|
|
830
830
|
"help.faq4": "No public URL: check that Tailscale authorization completed, then click \"Refresh\" or restart DSH.",
|
|
831
831
|
"help.securityTitle": "Security notes",
|
|
832
832
|
"help.security1": "A Funnel URL is public, but every connection requires a signature from a paired device P-256 private key. Do not share a pairing QR while it is valid.",
|