dsh-deeppilot 0.9.2 → 0.9.4

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 CHANGED
@@ -12,7 +12,7 @@ range before the plugin will install there.
12
12
  | Component | Current contract | Evidence |
13
13
  |---|---|---|
14
14
  | Node.js | 22 or newer | Package engine and CI |
15
- | DSH CLI and Host API | `>=0.2.0-rc.2 <0.3.0-0` (audited against `0.2.0-rc.2`) | Peer metadata, source typecheck, unit tests, bundle build, and config schema projection against a real 0.2.0-rc.2 CLI |
15
+ | DSH CLI and Host API | `>=0.2.0-rc.2 <0.3.0-0` (dev pins `0.2.0-rc.2`; source audits through `0.2.1-alpha.2`) | Peer metadata, source typecheck, unit tests, bundle build, and config schema projection against a real 0.2.0-rc.2 CLI, plus an isolated compose with the published 0.2.1-alpha.2 CLI |
16
16
  | iOS bridge | DeepPilot protocol v2 | Bridge protocol tests; device behavior must be checked with the running Host and app |
17
17
  | Remote access | Tailscale Funnel on ports 443, 8443, or 10000 | Helper and supervisor tests |
18
18
 
@@ -21,7 +21,7 @@ range before the plugin will install there.
21
21
  - The Host adapter uses Session and Workspace controllers, Gateway Remote Events, and `typertGateway.wireStream.open(endpoint, payload, uplink, peer, signal)`. Its in-process carrier passes `undefined` for `uplink` and `peer` and the cancellation signal in argument five.
22
22
  - The resident Client sends relative RPC paths such as `api/$events/result`. The in-process transport resolves those paths to a local URL before constructing a Node `Request`; the shared Fetch handler dispatches it without a network request. This is required for iOS approval and question answers to settle at the Gateway.
23
23
  - The client settings page binds `configForms.get('deeppilot')` when the service becomes available. The Host reads volatile config references from its profile entry and reconciles transports on `loader/volatile-update`.
24
- - Typert strict codecs publish `create()` factories. Session opening reads `session.projections` for a complete baseline.
24
+ - Typert strict codecs publish `create()` factories. Session opening reads `session.projections` for a complete baseline; that controller requires a cancellation signal from `0.2.1-alpha.2` on, so the adapter always passes one.
25
25
  - The Host adapter retains a stable bridge-facing API so the phone protocol does not depend directly on DSH controller shapes. This adapter is an internal design boundary, not support for an older DSH Host.
26
26
 
27
27
  ## Separate protocol and data boundaries
@@ -31,28 +31,30 @@ range before the plugin will install there.
31
31
  - The plugin's persisted device and Funnel state migration remains separate from DSH API version support. Session logs written by earlier DSH builds are readable only when the current Host's own history reader accepts them; this plugin does not rewrite those logs.
32
32
  - An unavailable optional OS facility, transport, or controller disables its dependent capability without crashing the Host.
33
33
 
34
- ## Schedules require an optional DSH bundle
34
+ ## Schedules ship with the Web composition
35
35
 
36
- DSH 0.2.0 moved automation out of the shipped Web composition. The shipped
37
- `packages/bundle/web-app/cordis.patch.yml` no longer carries the `time-context`,
38
- `schedule`, or `ui-schedule` rows; they are supplied by the optional
39
- `@deepseek-ai/dsh-experimental-schedule-bundle`, which ships switched off and is
40
- enabled by the user through **Automation tasks** in the plugin manager.
36
+ Reminders need no user action on a current host. DSH 0.2.0 briefly moved
37
+ automation out of the shipped Web composition into the optional
38
+ `@deepseek-ai/dsh-experimental-schedule-bundle`, and the following release
39
+ removed that bundle again: it is listed in `RETIRED_BUNDLES`,
40
+ `@deepseek-ai/dsh-web-app` mounts `schedule` and `ui-schedule` in every Web
41
+ profile, and loading a profile strips the retired entry from
42
+ `dsh.profile.bundles`. The plugin manager no longer shows an **Automation
43
+ tasks** switch, so no profile is expected to enable anything for reminders.
41
44
 
42
- The plugin needs no change for this. It still resolves the service through
45
+ The plugin needed no change for either step. It resolves the service through
43
46
  `ctx.get('schedule')`, never declares `schedule` in `inject`, and reports
44
47
  `welcome.capabilities.schedules = false` with a stable `E_UNSUPPORTED` for every
45
- schedule frame when the service is absent. What changes is that a stock 0.2.0
46
- Host now always takes that degraded path, so the phone tells the user to enable
47
- Automation tasks instead of reporting a generic capability gap.
48
+ schedule frame when the service is absent. On a Web host the service is mounted,
49
+ so the capability bit is `true` by default; a profile that drops the rows still
50
+ reports `false` and still degrades cleanly.
48
51
 
49
52
  The capability is resolved lazily rather than captured when the bridge is
50
53
  built. The bridge mounts on `sessionController` / `connection` /
51
- `typertGateway`, all of which become ready before the optional Schedule service
52
- finishes its own initialization, so a probe taken at that moment would report
53
- `false` for the bridge's whole lifetime. With the bundle enabled, a live
54
- 0.2.0-rc.1 host advertises `schedules=true` and the schedule
55
- list/create/history/delete flow passes.
54
+ `typertGateway`, all of which become ready before the Schedule service finishes
55
+ its own initialization, so a probe taken at that moment would report `false` for
56
+ the bridge's whole lifetime. On a live host with the service mounted the bit is
57
+ `true` and the schedule list/create/history/delete flow passes.
56
58
 
57
59
  ## Configuration migration
58
60
 
@@ -62,6 +64,30 @@ carries the old top-level `debug: true` is accepted but that value is ignored:
62
64
  verbose logging must be turned on again from the settings page. No other config
63
65
  field changed shape, and the old key is not read anywhere in this version.
64
66
 
67
+ ## 0.2.1-alpha.2 audit outcome
68
+
69
+ The source-level audit of `dsh-v0.2.1-alpha.2` against `dsh-v0.2.1-alpha.1`
70
+ (669 commits, 3226 changed files) found the peer set almost untouched — 10 of
71
+ the 12 peer packages have zero changed source files — but one real break
72
+ outside it: `SessionController.projections` now aborts before it reads
73
+ anything, so the adapter's signal-less call lost the entire projection baseline.
74
+ That is fixed here: `src/dsh-api-proxy.ts` passes a signal, its controller
75
+ mirror declares one required, and a regression test fails against the previous
76
+ call form. Install and phone-protocol risk are unchanged — the peer range admits
77
+ `0.2.1-alpha.2`, and protocol v2, pairing state, and device records are
78
+ untouched.
79
+
80
+ A live `0.2.1-alpha.2` host has now exercised the bridge. An isolated profile
81
+ built from the shipped Web template, booted by the published CLI, passed the
82
+ phone-protocol checklist over the LAN TLS listener — health, SPKI, pairing
83
+ rejection, WSS upgrade, challenge/prove with all six scopes, session and
84
+ workspace RPCs, archive/unarchive, the schedule create/history/delete flow, and
85
+ reconnect replay (25 pass, 0 fail, 3 skipped). Running the published `0.9.3` on
86
+ the same host version instead logged
87
+ `[deeppilot] sessions.projections failed: internal` while still passing every
88
+ step, which is the silent degradation this release fixes. A real model turn, the
89
+ Funnel transport, and a real iPhone are still unverified.
90
+
65
91
  ## 0.2.0-rc.2 audit outcome
66
92
 
67
93
  The source-level audit of `dsh-v0.2.0-rc.2` against `dsh-v0.2.0-rc.1` found no
package/bin/SHA256SUMS CHANGED
@@ -1,6 +1,6 @@
1
- 69f2d9793f750ca68863eae0aaf40a2c670ed52579349e907c213529754356de ./darwin-amd64/dsh-deeppilot-tunnel
2
- f9fe205fb8d97684ddb9350e02cb766ffb53e8610a3c5dfce0d265872ac6a8b3 ./darwin-arm64/dsh-deeppilot-tunnel
3
- d5c97703970e595a4f341fb446fa3337f87fefbf766ab81aea12d983318b98c1 ./linux-amd64/dsh-deeppilot-tunnel
4
- f1eb837e02eb9ee63801d12be9df45351a895963b9f635079eb2287a7f59954a ./linux-arm64/dsh-deeppilot-tunnel
5
- 88cca9341aa9f87215c0de2ebb69d0971bc4fa39548df332a000c63560c830f2 ./windows-amd64/dsh-deeppilot-tunnel.exe
6
- 5b81075d986fd387192084f916964d2c4a51ca494ce331e0346a8a75fc3e7b8d ./windows-arm64/dsh-deeppilot-tunnel.exe
1
+ c70401fd9530f2fae6abe7aec5eacacca239ba315cedc7197db530df8c6f97bd ./darwin-amd64/dsh-deeppilot-tunnel
2
+ 45ff6b53d3e27fe6242e4904055d647d8b220a5c56ca76e542ffcd861ad3bbad ./darwin-arm64/dsh-deeppilot-tunnel
3
+ 8d4937de90564327dcf8392dba6241e4facc30044a48447b22de99f3e62c9808 ./linux-amd64/dsh-deeppilot-tunnel
4
+ cd4638f9aa3e60340d4f446301b2b6f6452f5cb89fc012884e5a893ab8dbd577 ./linux-arm64/dsh-deeppilot-tunnel
5
+ d5435d4e0c02056b3fac39a5f289acb404e6f060dc566e5dea724ee2b86a4296 ./windows-amd64/dsh-deeppilot-tunnel.exe
6
+ 7d79c848b2b74286764bd284005edbcfdf539be90ce0b160ea464ae3af70e005 ./windows-arm64/dsh-deeppilot-tunnel.exe
@@ -5,50 +5,173 @@
5
5
 
6
6
  ## Current decision
7
7
 
8
- - **Audited release:** `dsh-v0.2.1-alpha.1` (official release page:
9
- <https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.1>),
10
- published 2026-10-03 on the npm `alpha` dist-tag. It is **not** `latest`;
11
- `latest`/`next` still point at `0.2.0-rc.2`, which is what the current Host runs.
12
- - **Comparison baseline:** `dsh-v0.2.0-rc.2` (the previous plugin baseline).
13
- - **Plugin runtime verdict:** **no breaking change in the DSH APIs that DeepPilot
14
- currently calls.** All 12 peer packages have **zero** changed `src/` files
15
- between the two tags — each changed only its `package.json` version field and
16
- its READMEs. See the 2026-10-03 entry below for the full finding-by-finding
17
- table and for the three official upgrade guides checked against this plugin.
18
- - **Nothing was changed to support this release.** `package.json`,
19
- `package-lock.json`, generated `lib/`, `COMPATIBILITY.md`, and `PROTOCOL.md`
20
- deliberately still describe the `0.2.0-rc.2` baseline. The release-audit-only
21
- scope was agreed before the audit ran, so no version bump, no dependency
22
- install, and no `lib/` regeneration was performed.
23
- - **Peer range still admits it:** `>=0.2.0-rc.2 <0.3.0-0` satisfies
24
- `0.2.1-alpha.1` under `semver.satisfies(..., { includePrerelease: true })`,
25
- which is how DSH's `evaluatePluginCompatibility` gates activation. The same
26
- holds for the `~4.0.4` / `~3.18.4` vendor ranges against `4.0.5-alpha.1` /
27
- `3.18.5-alpha.1`. Under npm's **default** prerelease handling those three do
28
- not match, so installing on an alpha host from npm reports peer warnings.
8
+ - **Audited release:** `dsh-v0.2.1-alpha.2` (official release page:
9
+ <https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.2.1-alpha.2>),
10
+ published to npm 2026-10-09T08:18:00Z on the `alpha` dist-tag. It is **not**
11
+ `latest`; `latest`/`next` still point at `0.2.0-rc.2`.
12
+ - **Comparison baseline:** `dsh-v0.2.1-alpha.1` (the previous audited tag).
13
+ - **Plugin runtime verdict: one confirmed break, fixed in this audit.**
14
+ `SessionController.projections` now requires its cancellation signal — the
15
+ published implementation calls `signal.throwIfAborted()` as the first
16
+ statement of its `try` and reads `signal.aborted` in its `catch`, while the
17
+ adapter called it with no signal at all, so every projection baseline read
18
+ failed with a `TypeError` instead of returning values. The adapter now passes
19
+ a signal and its hand-written mirror declares `signal` required. See "The one
20
+ confirmed break" in the 2026-10-09 entry for the reproduction, the fix, and
21
+ the regression test that pins it.
22
+ - **What is unaffected:** `list`, `inspect(sessionId)` (its signal is still
23
+ optional upstream), `create`, `fork`, `rename`, `cancel`, `attachment`,
24
+ `prompt`, `selectModel`, `modelCatalog`, the workspace controller, the Gateway
25
+ wire-stream seam the resident approval/question client depends on, the shared
26
+ Fetch carrier, and every peer package's exported runtime surface.
27
+ - **Install verdict:** the peer range `>=0.2.0-rc.2 <0.3.0-0` admits
28
+ `0.2.1-alpha.2` under `semver.satisfies(..., { includePrerelease: true })`,
29
+ which is how DSH's `evaluatePluginCompatibility` gates activation; npm's
30
+ **default** prerelease handling still reports peer warnings for
31
+ `@deepseek-ai/dsh`, `cordis`, and `schemastery`. Composing a real Web profile
32
+ with this plugin's `cordis.patch.yml` on the published CLI adds the plugin's
33
+ three rows plus its own config schema and no plugin-attributed diagnostic; the
34
+ four base-profile schema errors it prints reproduce identically without the
35
+ patch.
29
36
  - **Protocol verdict:** no required DeepPilot phone-protocol break. Protocol v2,
30
37
  pairing state, and device records are unchanged; already-paired phones need no
31
38
  action.
32
- - **Release verdict (`0.9.2`, shipped 2026-10-04):** `scripts/smoke-live.mts`
33
- was run against a live **`dsh-v0.2.1-alpha.1`** web profile and passed with
34
- **0 failures**. This is the first live-host verification since the 0.9.1
35
- entry, and it retires the gate that entry left open ("pending separate
36
- authorization before the `0.9.1` release ships") — on a newer host than the
39
+ - **Capability gaps, not regressions:** the new `workingDirectory` projection,
40
+ the new `formatStatus` list field, the `mode: 'external'` subagent catalog, and
41
+ the `session/migration-required` fork refusal are all things this plugin does
42
+ not consume, and none of them change a call it already makes.
43
+
44
+ ### Plugin release verdicts
45
+
46
+ - **Release verdict (`0.9.4`, prepared 2026-10-10, NOT published):** carries the
47
+ `projections` adapter fix and the Automation tasks documentation correction
48
+ described in the 2026-10-09 entry below. `package.json`, both root
49
+ `package-lock.json` version entries, and `tests/apply-baseline.snapshot.json`
50
+ are on `0.9.4`, and `lib/` is regenerated. The full checklist passed locally:
51
+ `npm ci`, 538 unit tests, typecheck, build, the config-schema check against a
52
+ real `0.2.0-rc.2` CLI, Go helper tests, helper checksums, and
53
+ `npm pack --dry-run` (65 files, 50,916,470 bytes packed). The fix was also
54
+ verified on a live `0.2.1-alpha.2` host against the published `0.9.3` as an
55
+ A/B control (25 pass / 0 fail either way, with the `sessions.projections
56
+ failed: internal` diagnostic present only on `0.9.3`). Nothing was
57
+ published, tagged, or pushed; npm `latest` still points at `0.9.3`, so users on
58
+ `0.2.1-alpha.2` keep hitting the projections defect until this ships.
59
+ - **The release also had to fix the helper's toolchain and its refresh
60
+ workflow.** CI's `govulncheck` job reported 11 reachable Go standard-library
61
+ vulnerabilities for the pinned `go 1.26.6`; this is time-triggered, not
62
+ caused by the release — `0.9.3`'s CI was green on 2026-10-05 with the same
63
+ helper source. `helper/go.mod` now pins `go 1.26.9`, and the six `bin/`
64
+ binaries were rebuilt with it. That rebuild only became correct after fixing
65
+ `.github/workflows/build-helpers.yml`: each build job uploaded the whole
66
+ `bin/` tree, so every artifact also carried the checked-out copies of the
67
+ targets its runner never rebuilt, and `merge-multiple` let those stale files
68
+ overwrite the fresh ones (the first refresh PR mixed two toolchains — only
69
+ darwin-arm64 embedded `go1.26.9`). The workflow now uploads only the
70
+ per-matrix target globs; PR #19 then carried all six at `go1.26.9` and was
71
+ merged as `f0f243b`.
72
+ - **Checklist environment notes for the next release.**
73
+ (a) `~/.npm/_cacache` contains root-owned files on this machine, so `npm view`
74
+ and `npm pack` fail with `EPERM` unless `npm_config_cache` points at a
75
+ writable cache — the run above used the workspace audit cache.
76
+ (b) Go's default build cache lives outside the workspace and the file sandbox
77
+ blocks it (`open .../Library/Caches/go-build/...: operation not permitted`,
78
+ reported as `[setup failed]`); point `GOCACHE` at a workspace directory.
79
+ (c) `gh` writes its run-log cache under `~/.cache/gh` and fails the same way;
80
+ point `XDG_CACHE_HOME` at a workspace directory.
81
+ None of the three is a repository defect.
82
+ - **Release verdict (`0.9.3`, published 2026-10-05, tag `v0.9.3` @ `b85869b`):**
83
+ shipped to `latest`. Verified on the registry: version present, `dist-tags.latest`
84
+ moved off `0.9.2`, 65 files, `dist.shasum` `90f0c665c395a7043eb3689a291cf28c88bc39ab`
85
+ byte-identical to the locally verified `npm pack` output, and a clean install plus
86
+ a real `dsh --profile web --dump-config` compose (188 entries) with the plugin's
87
+ three `cordis.patch.yml` entries present and no missing-module error. Ships
88
+ issue #24 (`100.64.0.0/10` becomes a selectable pairing target) and the Windows
89
+ journal `fsync` tolerance fix. Protocol v2 unchanged; paired phones need no action.
90
+ - **`gitHead` deviation, recorded rather than hidden.** The registry records
91
+ `gitHead` `fbda6a5`, the docs-only commit that added `RELEASE_NOTES-0.9.3.md`,
92
+ while tag `v0.9.3` points at `b85869b`. The cause is ordering, not content:
93
+ the tarball was packed before that docs commit, and npm stamps `gitHead`
94
+ from HEAD at publish time. The shipped bytes are provably `b85869b`'s tree —
95
+ the published `dist.shasum` equals the local `npm pack` output and the tarball
96
+ does not contain `RELEASE_NOTES-0.9.3.md`. `fbda6a5` is `b85869b`'s child and
97
+ changes no packaged file. Versions are immutable on npm, so this cannot be
98
+ corrected retroactively; **do it differently next time: commit every doc file
99
+ before packing, and pack after the last commit.** The 0.9.2 entry below had
100
+ `gitHead` matching the tag because no commit landed between pack and publish.
101
+ - **Install caveat users will hit — DSH's release-age policy.** Confirmed again
102
+ while verifying this release: `dsh plugin add --profile web` writes a
103
+ `minimumReleaseAgeExclude` entry in `pnpm-workspace.yaml` and an explicit
104
+ `dsh-deeppilot@0.9.3` installs the new version. Expect the unversioned form to
105
+ resolve to a slightly older release for a while; this is expected DSH
106
+ behaviour, not a publishing failure.
107
+ - **Release verdict (`0.9.2`, published 2026-10-05, tag `v0.9.2` @ `4b43671`):**
108
+ shipped to `latest`. Verified on the registry: version present, `dist-tags.latest`
109
+ moved off `0.9.1`, `gitHead` `4b436715` matches the tag and HEAD, 65 files,
110
+ and a clean DSH profile install of `dsh-deeppilot@0.9.2` composes with the
111
+ plugin's three `cordis.patch.yml` entries present and no missing-module error.
112
+ `scripts/smoke-live.mts` passed with **0 failures** against a live
113
+ **`dsh-v0.2.1-alpha.1`** web profile — the first live-host verification since
114
+ the 0.9.1 entry, retiring the gate it left open ("pending separate
115
+ authorization before the `0.9.1` release ships"), on a newer host than the
37
116
  rc.2 that entry contemplated, which is strictly stronger evidence: the
38
117
  `0.2.1-alpha.1` source-diff above found zero changed `src/` files across all
39
118
  12 peer packages, and the live run agrees. The plugin's own build and type
40
- baselines stay pinned to `0.2.0-rc.2`; nothing about the pin changed for this
41
- release.
119
+ baselines stay pinned to `0.2.0-rc.2`; nothing about the pin changed.
120
+ - **Install caveat users will hit — DSH's release-age policy.** On a clean
121
+ profile, `dsh plugin add dsh-deeppilot` installed **`0.9.1`**, not the just
122
+ published `0.9.2`, even though `latest` already pointed at `0.9.2`. DSH
123
+ deliberately prefers a slightly older release and offers an exact-version
124
+ path instead; `dsh plugin add dsh-deeppilot@0.9.2` installs the new one.
125
+ This is expected DSH behaviour, not a publishing failure — do not read the
126
+ older version in the installer as a broken release.
127
+ - **Peer-range note for future audits.** `evaluatePluginCompatibility` checks
128
+ each peer against the *running DSH version*, but `cordis` and `schemastery`
129
+ are host-vendored third parties and `react` comes from the Web app. Judging
130
+ those three against a DSH version number reports false mismatches. They are
131
+ also `optional` in `peerDependenciesMeta`. Confirm installability by
132
+ composing a real profile, not by iterating the peer table.
42
133
  - **Two tracked follow-ups:**
43
- 1. **Schedule documentation is now stale.** Automation tasks became a
44
- built-in Web capability and `@deepseek-ai/dsh-experimental-schedule-bundle`
45
- is in DSH's new `RETIRED_BUNDLES` set, so the user instruction "enable
46
- Automation tasks in the plugin manager" points at a retired bundle.
47
- Affects `PROTOCOL.md`, `COMPATIBILITY.md`, and the iOS copy. **Open.**
48
- 2. ~~`subagent_session` is unmapped.~~ **Resolved 2026-10-03:** mapped to
134
+ 1. ~~Projection signal.~~ **Resolved 2026-10-10:** the adapter passes
135
+ `new AbortController().signal` to `session.projections` and the mirror
136
+ declares `signal` required, so the compiler enforces it. Covered by a new
137
+ regression test that fails against the previous call form. 538 unit tests,
138
+ typecheck, and build pass with regenerated `lib/`.
139
+ 2. **Automation tasks documentation: public docs fixed, iOS copy open.**
140
+ Automation was a short-lived optional bundle: DSH 0.2.0 moved it out of the
141
+ shipped Web composition, and the following release retired
142
+ `@deepseek-ai/dsh-experimental-schedule-bundle` again — `dsh-web-app` now
143
+ mounts `schedule` and `ui-schedule` in every Web profile. `COMPATIBILITY.md`,
144
+ `PROTOCOL.md`, `docs/DEEPPILOT_FEATURE_PLAN.md`, and the schedule-service
145
+ comment in `src/dsh-api-proxy.ts` now state that; the private app still
146
+ ships `error.scheduleUnsupported` and `schedule.unsupported` copy telling
147
+ the user to enable it (`app-private/ios/L10nFragments/schedule.json` and the
148
+ generated `zh-Hans.lproj` / `en.lproj` string tables). **iOS copy still
149
+ open** — it belongs to the private repository and to its own release flow.
150
+ 3. ~~`subagent_session` is unmapped.~~ **Resolved 2026-10-03:** mapped to
49
151
  `E_PROTOCOL` in `src/wire-errors.ts` and added to the frozen expectation in
50
152
  `tests/wire-errors.test.ts`. 525 unit tests, typecheck, and build pass.
51
153
 
154
+ ### Previous decision — `dsh-v0.2.1-alpha.1`
155
+
156
+ - **Audited release:** `dsh-v0.2.1-alpha.1`, published 2026-10-03 (npm
157
+ `alpha` dist-tag, also not `latest`). Comparison baseline was
158
+ `dsh-v0.2.0-rc.2`.
159
+ - **Plugin runtime verdict:** no breaking change in the DSH APIs DeepPilot calls.
160
+ All 12 peer packages had **zero** changed `src/` files; each changed only its
161
+ `package.json` version field and its READMEs. Full table in the 2026-10-03
162
+ entry below.
163
+ - **Nothing was changed to support that release.** `package.json`,
164
+ `package-lock.json`, generated `lib/`, `COMPATIBILITY.md`, and `PROTOCOL.md`
165
+ stayed on the `0.2.0-rc.2` baseline — no version bump, no dependency install,
166
+ no `lib/` regeneration. The same held for its source diff; the alpha.2 audit
167
+ that followed then produced one adapter fix (see the 2026-10-09 entry).
168
+ - **Peer range:** `>=0.2.0-rc.2 <0.3.0-0` admitted `0.2.1-alpha.1` through
169
+ `semver.satisfies(..., { includePrerelease: true })`; the `~4.0.4` / `~3.18.4`
170
+ vendor ranges admitted `4.0.5-alpha.1` / `3.18.5-alpha.1` the same way, and
171
+ npm's default prerelease rules reported warnings for those three.
172
+ - **Protocol verdict:** no required phone-protocol break; protocol v2, pairing
173
+ state, and device records unchanged.
174
+
52
175
  ### Previous decision — `dsh-v0.2.0-rc.2`
53
176
 
54
177
  - **Audited release:** `dsh-v0.2.0-rc.2` (official release page:
@@ -84,6 +207,252 @@
84
207
  bundle" below. The plugin needs no code change; the phone's copy now points
85
208
  at the bundle.
86
209
 
210
+ ## 2026-10-09 — audit of `dsh-v0.2.1-alpha.2`
211
+
212
+ ### Scope and method
213
+
214
+ Compared `dsh-v0.2.1-alpha.1...dsh-v0.2.1-alpha.2` (**669 commits, 3226 changed
215
+ files, 114,987 insertions, 28,177 deletions**) from a full clone
216
+ (`git clone --filter=blob:none --no-checkout`, unshallowed before measuring).
217
+ Baseline SHA `5badb150`, target SHA `d7432673`, annotated tag object `6851e496`.
218
+ Peer package names were mapped to monorepo paths by reading every `package.json`
219
+ in the tree, then each peer package's `src/**/*.ts` was diffed with
220
+ `git diff --name-status`. Every changed source file under `packages/api`,
221
+ `packages/session*`, `packages/client`, and `packages/core` was read in full —
222
+ including the complete 836-line session-controller diff — rather than being
223
+ summarised from release notes. All **20** upgrade guides added since the last
224
+ audit (19 under `docs/upgrade-guide/v0.2.1-alpha.1/` plus `bundled-python-tests`
225
+ under `v0.2.0-rc.2/`) were read in Chinese in full, and each one that could touch
226
+ an integration seam was traced to its implementation before being judged. No
227
+ guide from an earlier release was modified between the two tags.
228
+
229
+ ### Peer packages: 2 of 12 changed source
230
+
231
+ | Package | Path | Changed `src/` files |
232
+ | --- | --- | --- |
233
+ | `@deepseek-ai/dsh` | `apps/cli` | 1 (`src/args.ts`) |
234
+ | `@deepseek-ai/dsh-client-connection` | `packages/client/connection` | 5 (`api-request-trust.ts`, `browser-auth.ts`, `index.ts`, `rpc-host.ts`, `rpc.ts`) |
235
+ | `@deepseek-ai/dsh-api-gateway` | `packages/api/gateway` | 0 |
236
+ | `@deepseek-ai/dsh-api-remotes` | `packages/api/remotes` | 0 |
237
+ | `@deepseek-ai/dsh-client-locale` | `packages/client/locale` | 0 |
238
+ | `@deepseek-ai/dsh-client-ui-settings` | `packages/client/ui-settings` | 0 |
239
+ | `@deepseek-ai/dsh-client-ui-slots` | `packages/client/ui-slots` | 0 |
240
+ | `@deepseek-ai/dsh-settings` | `packages/settings/settings` | 0 |
241
+ | `@deepseek-ai/dsh-typert-protocol` | `packages/typert/protocol` | 0 |
242
+ | `@deepseek-ai/dsh-typert-registry` | `packages/typert/registry` | 0 |
243
+ | `@deepseek-ai/cordis` | `vendor/cordis` | 0 |
244
+ | `@deepseek-ai/schemastery` | `vendor/schemastery` | 0 |
245
+
246
+ `react` is not an upstream package and was not measured. Two non-peer packages
247
+ that the bridge reaches through `ctx.get` did change and are covered below:
248
+ `packages/api/session-controller` and `packages/client/connection`.
249
+
250
+ ### The one confirmed break
251
+
252
+ `packages/api/session-controller/src/index.ts` started dereferencing a parameter
253
+ that upstream's own published declaration already marks as required:
254
+
255
+ ```ts
256
+ async projections(request: SessionProjectionsRequest, signal: AbortSignal): Promise<SessionProjectionsValue> {
257
+ const { sessionId } = request
258
+ if (sessionId.length === 0) throw new RemoteError('gateway/bad-request', ...)
259
+ try {
260
+ signal.throwIfAborted() // now the first statement
261
+ const live = this.liveProjections(sessionId)
262
+ ...
263
+ } catch (error) {
264
+ ...
265
+ if (signal.aborted || ...) throw new RemoteError('gateway/cancelled', ...) // now unconditional
266
+ ...
267
+ }
268
+ }
269
+ ```
270
+
271
+ In alpha.1 the same method only forwarded `signal` into `observeSession` and read
272
+ `signal.aborted` inside the `catch`, so an omitted signal still survived the happy
273
+ path. In alpha.2 the omission throws before any work is done — reproduced against
274
+ the published `0.2.1-alpha.2` bundle with the real class, a stub context whose
275
+ live-projection path returns a value, and no signal:
276
+
277
+ ```sh
278
+ # published @deepseek-ai/dsh-api-session-controller@0.2.1-alpha.2, lib/index.js
279
+ SessionController.prototype.projections.call(receiver, { sessionId: 'x' })
280
+ # → TypeError (signal is undefined at signal.throwIfAborted(), then again at signal.aborted)
281
+ SessionController.prototype.projections.call(receiver, { sessionId: 'x' }, new AbortController().signal)
282
+ # → { kind: 'sequenced', asOfSeq: 7, values: { title: 'audit' } }
283
+ ```
284
+
285
+ DeepPilot's call site is `src/dsh-api-proxy.ts:235`:
286
+
287
+ ```ts
288
+ projections: async (request) => this.call(() => this.session.projections(request.payload!)),
289
+ ```
290
+
291
+ The hand-written mirror at `src/dsh-api-proxy.ts:45` declares `signal?` optional,
292
+ which is exactly what let the omission typecheck; the upstream declaration
293
+ (`@deepseek-ai/dsh-api-session-controller@0.2.1-alpha.2`,
294
+ `lib/types/index.d.ts:209`) requires it. `DshApiProxy.call()`
295
+ (`src/dsh-api-proxy.ts:413`) converts the `TypeError` into
296
+ `{ ok: false, error: { code: 'internal' } }`, so this degrades rather than
297
+ crashes: `HostBridge.refreshProjections` (`src/host-bridge.ts:799`) already treats
298
+ a failed baseline as a diagnostic and logs
299
+ `sessions.projections failed: internal`.
300
+
301
+ **Observable effect.** The phone's session summary loses the on-open complete
302
+ projection baseline. It still receives title, todo counts, and token usage from
303
+ the session list rows' cached projections (`src/dsh-api-proxy.ts:458`,
304
+ `src/host-bridge.ts:1546`) and from live `session/projection` frames
305
+ (`src/host-bridge.ts:453`), so nothing breaks outright; the loss shows up on
306
+ hosts where the baseline read was the only source — a session whose list row
307
+ carries no cached projections stays without those values longer than it should.
308
+ Cold history reads are **not** affected: they run through
309
+ `inspect(sessionId)`, whose signal is still optional upstream.
310
+
311
+ **Fix (applied 2026-10-10).** The adapter now passes a signal the way `list` and
312
+ `prompt` already do —
313
+ `this.session.projections(request.payload!, new AbortController().signal)` — and
314
+ the mirror's `signal` is required, so the compiler keeps it that way.
315
+ `refreshProjections` is fire-and-forget and has no cancellation source of its
316
+ own, so a dedicated long-lived controller is not warranted; following the
317
+ existing convention in the same file is enough. `lib/index.js` was regenerated.
318
+
319
+ The fix ships in `0.9.4`, which is prepared but not published (see "Plugin release
320
+ verdicts"); until it is published, a host on `0.2.1-alpha.2` running the
321
+ published `0.9.3` still loses the on-open projection baseline. That was observed
322
+ directly: on a live `0.2.1-alpha.2` host running published `0.9.3` the diagnostic
323
+ appeared, and on the same host version running this build it did not (see "Live
324
+ run on a real `0.2.1-alpha.2` host" under Validation evidence).
325
+
326
+ `tests/dsh-api-proxy.test.ts` adds "projection baseline reads pass the
327
+ AbortSignal the Host now requires": a controller stub that calls
328
+ `signal.throwIfAborted()` before returning, plus an assertion that the adapter
329
+ handed it a real `AbortSignal`. The test was verified red against the previous
330
+ call form (1 failure) and green after the fix, so it pins the behaviour rather
331
+ than the implementation coincidence.
332
+
333
+ ### Other session-controller changes, checked against this plugin
334
+
335
+ `Where` names the upstream `packages/api/session-controller/src/` file unless it
336
+ says otherwise; `lib/types` means the published declarations of
337
+ `@deepseek-ai/dsh-api-session-controller@0.2.1-alpha.2`.
338
+
339
+ | Change | Where | DeepPilot impact |
340
+ | --- | --- | --- |
341
+ | `projections` return type is now a union: `{ kind: 'sequenced', asOfSeq, values }` / `{ kind: 'migration-required', values }` / `null` | `src/index.ts:506-541` | The mirror's flat `{ asOfSeq, values }` type is now narrower than upstream, but the plugin reads only `values`, so a successful read still folds. Type-mirror drift, not a runtime defect. |
342
+ | `inspect(sessionId, signal?)` | `packages/session/session-query` | Signal is still optional in the published declaration, so the plugin's `inspect(sessionId)` call keeps working. Cold log reading, scanner row decoding, and the V4 codec path were read and are unchanged for this caller. |
343
+ | `search(request, signal)` requires a signal | `lib/types/index.d.ts:93` | The plugin never calls `search`. |
344
+ | `page(request, signal)` requires a signal | `src/index.ts:1412` | Not called; history paging still goes through `inspect`. |
345
+ | `fork` gains `allowMigration` (default `true`) | `src/index.ts` | The plugin does not send it, so the upstream default keeps automatic migration. The new `session/migration-required` error is unreachable from the phone. |
346
+ | `skill-catalog` injects `workingDirectory` and lists skills by execution directory | `src/skill-catalog.ts` | Not consumed; the plugin lists no skills. |
347
+ | Session ownership helper | `packages/api/session-controller` | Unchanged; `session/agent-busy` still maps to `E_BUSY`. |
348
+ | Subagent catalog v2 `mode: 'external'` | `packages/subagent/subagent` | Descriptor version is still 3 and the earlier one-shot read types are retained; the plugin filters by origin/parent and does not fold catalogs. |
349
+
350
+ ### Connection carrier: unchanged on the paths this plugin uses
351
+
352
+ `packages/client/connection` gained five changed source files, including a new
353
+ `api-request-trust.ts` and browser-auth changes. Those trust, authentication, and
354
+ TLS checks run through `requestRejection`/`admit`, which guard **physical**
355
+ requests. The plugin's resident client uses
356
+ `connection.createSharedFetchHandler('/api')`, which dispatches routes directly
357
+ and never calls `admit` (`packages/client/connection/src/rpc-host.ts:136-154`), and
358
+ its stream call keeps the
359
+ five-argument `wireStream.open(endpoint, payload, uplink, peer, signal)` order.
360
+ The plugin's own LAN TLS pin check and `/phone` listener are unaffected.
361
+
362
+ ### Four-way comparison at `0.2.1-alpha.2`
363
+
364
+ | Surface | Both sides | Upstream has, plugin does not consume | Plugin has, upstream does not |
365
+ | --- | --- | --- | --- |
366
+ | Session RPCs | `list`, `inspect`, `create`, `fork`, `rename`, `cancel`, `prompt`, `attachment`, `selectModel`, `modelCatalog`, `projections` | `search`, `page`, `follow`, `updateQueue`, `initializeDefaultModel`, `canOpenWorkspacePath`, `openWorkspacePath`, `workspacePathApplications` | phone-shaped `sessions.*` envelope, history windowing, projection folding, notification suppression |
367
+ | Workspace RPCs | `create`, `archiveSession`, `unarchiveSession`, `follow` | `initializeDefault`, `rename`, `delete`, `insertBefore`, `insertSessionBefore`, `pinSession`, `unpinSession` | archived mirror, `sessionRestore` capability bit |
368
+ | Directory picker | `list`, `pick` | `createDirectory` | browse host/client patch rows, phone-side directory browsing |
369
+ | Schedule | `list`, `history`, `create`, `update`, `delete` | `catalog`, stream delivery modes | phone schedule frames, `schedule.manage` scope, prompt-free mutation journal |
370
+ | Projections | reading `values` | `workingDirectory`, `formatStatus`, the migration-required branch | `title` / `todos` / `sessionStats` / `tokenUsage` folding |
371
+ | Subagents | — | `prompt`, `interruptByParent`, catalog v2 | subagent-session exclusion and notification policy |
372
+ | Trust and auth | shared Fetch route dispatch, TLS pin check | `webStartup.trustedHosts`, listener-address validation | device records, pairing tokens, signed challenge |
373
+
374
+ Rows marked "upstream has, plugin does not consume" are capability gaps by
375
+ choice, not regressions: none of them changes a call the bridge already makes.
376
+ `neither side` is empty for every row that matters to the phone.
377
+
378
+ ### Validation evidence
379
+
380
+ - **Unit tests:** `tsx --test tests/*.test.ts` → **537 pass, 0 fail, 0 skipped**
381
+ on the source diff alone. After the adapter fix and its new regression test the
382
+ same command reports **538 pass, 0 fail, 0 skipped**, and `npm run typecheck`
383
+ and `npm run build` pass with regenerated `lib/`.
384
+ - **Config schema on the published CLI:** an isolated `DSH_HOME` and npm cache ran
385
+ `dsh --profile deeppilot-audit --from-default-profile sdk-minimal
386
+ --dump-config-schema --patch cordis.patch.yml` against
387
+ `@deepseek-ai/dsh@0.2.1-alpha.2` → the plugin entry reports
388
+ `status: 'schema'` with its own `$defs` config, the volatile-field checks pass,
389
+ and the only warning is the `sdk-minimal` template's own missing
390
+ `directory-picker` row.
391
+ - **Web composition:** the same CLI composed a real Web profile with the plugin
392
+ patch → 188 rows versus 185 without it; the plugin's three rows
393
+ (`deeppilot-directory-picker-browse`, `…-browse-client`, `deeppilot`) are
394
+ present with the plugin config schema at `#/$defs/config100`. The four
395
+ `unrecognized Loader tree carrier` errors the dump prints at `/181`–`/184`
396
+ reproduce **identically without** the patch, so they belong to the base Web
397
+ profile and not to this plugin.
398
+ - **Published client bundles:** the two resident-client tests
399
+ (`tests/dsh-wire-stream-contract.test.ts`) were re-run against the actual
400
+ published `0.2.1-alpha.2` client bundles, resolved through a temporary
401
+ resolution hook instead of the rc.2 dev pins → pass. This covers client-face
402
+ composition and the stream's abort-signal argument contract with a **mocked**
403
+ Host gateway.
404
+ - **Documentation corrected in the same change:** `COMPATIBILITY.md`,
405
+ `PROTOCOL.md`, `docs/DEEPPILOT_FEATURE_PLAN.md`, and the schedule-service
406
+ comment in `src/dsh-api-proxy.ts` no longer tell users to enable a retired
407
+ bundle. The private app's schedule-unsupported strings still do; that copy
408
+ lives in the other repository and is listed as an open follow-up above.
409
+ - **Live run on a real `0.2.1-alpha.2` host (2026-10-10).** An isolated
410
+ `DSH_HOME` (`.audit-smoke-home`, workspace root) was given a `smoke` profile
411
+ built from the shipped Web template with this working tree linked in, plus a
412
+ second profile pinning the published `dsh-deeppilot@0.9.3`. The published
413
+ `0.2.1-alpha.2` CLI booted each one in turn and
414
+ `scripts/smoke-live.mts run` drove the phone protocol against it over the LAN
415
+ TLS listener (no `--prompt`, so no model turn). **Both runs: 25 pass / 0 fail /
416
+ 0 warn / 3 skipped** — health, TLS SPKI, pairing rejection, WSS upgrade,
417
+ challenge/prove with all six scopes, session list/create/open/history,
418
+ model catalog and switch, workspace list/create, archive/unarchive, pending
419
+ snapshot, the full schedule create/history/delete flow, and reconnect replay.
420
+ The skips are the log-pin cross-check (needs `SMOKE_LOG`), the real prompt, and
421
+ the approval/question round trip.
422
+ - **The A/B result is the evidence that matters.** The published `0.9.3` host
423
+ logged `[deeppilot] sessions.projections failed: internal` while still passing
424
+ every checklist step, which is exactly the silent degradation predicted above;
425
+ the working-tree build, same checklist, same host version, logged **zero**
426
+ `sessions.projections` lines. The defect and the fix are therefore both
427
+ confirmed at runtime on `0.2.1-alpha.2`, not only in unit tests.
428
+ - **The live run also settles the automation question.** The welcome frame on a
429
+ stock Web profile advertised `schedules: true` with no bundle enabled and no
430
+ user action, and the full schedule create/history/delete flow passed against
431
+ it. That is the behaviour the corrected `COMPATIBILITY.md`, `PROTOCOL.md`, and
432
+ feature-plan copy now describe.
433
+ - **Still untested:** a real model turn inside the plugin's live path (the
434
+ isolated home has no model credentials), the Funnel/remote transport (the
435
+ isolated host ran `remote.enabled` at its default `false`; the live host's
436
+ phone path goes through the Funnel helper), and a real iPhone. The live smoke
437
+ gate still refuses to run against the machine's own host without a restart,
438
+ because `DeviceStore` snapshots `devices-v2.json` once at mount.
439
+ - **Repository pins unchanged:** `npm run check:config-schema` and the CI
440
+ `config-schema` job still target the `0.2.0-rc.2` CLI. The alpha.2 schema dump
441
+ above was run separately in an isolated home instead of re-pinning the
442
+ repository's own gate, so the gate keeps reporting its `0.2.0-rc.2` baseline
443
+ and nothing in the working tree depends on alpha.2.
444
+
445
+ ### Re-audit procedure used
446
+
447
+ The same full-clone method as the alpha.1 entry, with one addition: because two
448
+ non-peer packages (`api/session-controller`, `client/connection`) carry the
449
+ integration seams this plugin depends on, the diff was extended past the peer
450
+ set and every changed source file in `packages/api`, `packages/client`,
451
+ `packages/session*`, and `packages/core` was read in full. Judging only the peer
452
+ list would have missed the `projections` break entirely — the peer-list check
453
+ reported 10 of 12 packages clean while the defect sat in a package reached
454
+ through `ctx.get('sessionController')`.
455
+
87
456
  ## 2026-10-03 — audit of `dsh-v0.2.1-alpha.1`
88
457
 
89
458
  ### Scope and method
@@ -201,6 +570,31 @@ in the same class as the other input-shaped Schedule refusals. Without the
201
570
  mapping `wireErrorOf` falls through to `WIRE_ERROR_FALLBACK` (`E_INTERNAL`), which
202
571
  would tell the client the failure was worth retrying.
203
572
 
573
+ ### Cross-platform trap found while shipping 0.9.2
574
+
575
+ CI's `plugin` job hung on `npm test` four times while shipping 0.9.2. Worth
576
+ recording because macOS could never reproduce it and the fix is one line each.
577
+
578
+ `tests/journal-parity.test.ts` used `join('/proc/definitely/not/writable', …)`
579
+ as its "unwritable path" probe, in two places. `/proc` is a Linux-only pseudo
580
+ filesystem: on macOS the path simply does not exist, so the branch had only ever
581
+ been exercised on macOS and had never run on Linux at all. On Linux, writing
582
+ under `/proc` does not return an error — and because `dispatch-journal`'s
583
+ `save()` is entirely synchronous `fs` calls (`mkdirSync`/`openSync`/
584
+ `writeFileSync`/`fsyncSync`/`renameSync`), a single write blocks the whole event
585
+ loop. Even `setTimeout` stops firing, and node's `--test-timeout` can only report
586
+ a file-level timeout. Both sites now build the unwritable target inside the
587
+ scratch directory (a file-with-a-child shape that yields `ENOTDIR` identically on
588
+ every platform); recorded values are unchanged because all three candidate paths
589
+ returned the same status on macOS.
590
+
591
+ Two lessons for future audits: the workflow sets no `timeout-minutes`, so a hang
592
+ costs the full 6-hour default before failing — cancel and re-run rather than
593
+ waiting; and reproduce Linux-only behaviour in a container instead of inferring
594
+ it. `docker run --rm --cpus=2 -v $PWD:/app -w /app node:22` matches the runner
595
+ closely enough (2 cores, and the same `v22.23.x` that `node-version: 22` floats
596
+ to).
597
+
204
598
  ### Re-audit procedure used
205
599
 
206
600
  Same blobless-clone method as the rc.2 and rc.1 entries, with two refinements: