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 CHANGED
@@ -1,24 +1,30 @@
1
1
  # Compatibility
2
2
 
3
- This branch targets the **DSH `0.2.x` line, from `0.2.0-rc.2` onward**. The DSH
4
- peer dependencies accept `>=0.2.0-rc.2 <0.3.0-0`: any later `0.2.x` host,
5
- including future release candidates and the eventual `0.2.0` GA, installs
6
- without a plugin update. The development packages this branch builds and
7
- typechecks against are pinned to the exact audited release, currently
8
- `0.2.0-rc.2`. Every `0.1.x` build and any `0.3.0` or later release are outside
9
- this branch's support scope; a `0.3.x` line requires its own audit and its own
10
- range before the plugin will install there.
3
+ This branch targets exactly **two DSH releases**: `0.2.0-rc.2`, which the npm
4
+ `latest`/`next` channels point at, and `0.2.1-alpha.3`, which the `alpha` channel
5
+ points at. Both are named in `peerDependencies`; no other version is accepted.
6
+ DSH evaluates that range before composing a profile — on every start, not only on
7
+ install — so a newly published DSH release deactivates this plugin until the
8
+ range is widened deliberately. That is the intended trade: a plugin that cannot
9
+ run on an unaudited host fails visibly instead of misbehaving quietly. The
10
+ decision and the reversal it represents are recorded in
11
+ [`docs/决策记录.md`](./docs/决策记录.md) (章节一), with the supporting audit in
12
+ [`docs/DSH_RELEASE_MEMORY.md`](./docs/DSH_RELEASE_MEMORY.md).
13
+
14
+ The development packages this branch builds and typechecks against stay pinned
15
+ to the exact release-candidate baseline, currently `0.2.0-rc.2`; the alpha host
16
+ is covered by the isolated compose and live-runs recorded in the release memory.
11
17
 
12
18
  | Component | Current contract | Evidence |
13
19
  |---|---|---|
14
20
  | Node.js | 22 or newer | Package engine and CI |
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 |
21
+ | DSH CLI and Host API | `0.2.0-rc.2 \|\| 0.2.1-alpha.3` (dev pins `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, plus isolated composes and live phone-protocol runs with the published 0.2.1-alpha.2 and 0.2.1-alpha.3 CLIs |
16
22
  | iOS bridge | DeepPilot protocol v2 | Bridge protocol tests; device behavior must be checked with the running Host and app |
17
23
  | Remote access | Tailscale Funnel on ports 443, 8443, or 10000 | Helper and supervisor tests |
18
24
 
19
25
  ## DSH interfaces used here
20
26
 
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.
27
+ - 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`, a live owner/answer Peer for `peer`, and the cancellation signal in argument five. That Peer comes from `connection.createPeer({ principal: { kind: 'owner' }, interactions: 'answer' })` where the Host exposes it, and from the `connection.operator` instance otherwise. The same Peer is named on `fetchHandler.fetch(request, peer)` and the Connection install goes through `installConnection(ctx, { transport, interactions: 'answer' })` when the Client face offers it. alpha.3 requires all three; alpha.2 ignores the extra `fetch` argument, defaults an absent `open` peer to its operator, and ignores the participation.
22
28
  - 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
29
  - 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
30
  - 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.
@@ -64,6 +70,36 @@ carries the old top-level `debug: true` is accepted but that value is ignored:
64
70
  verbose logging must be turned on again from the settings page. No other config
65
71
  field changed shape, and the old key is not read anywhere in this version.
66
72
 
73
+ ## 0.2.1-alpha.3 audit outcome
74
+
75
+ The source-level audit of `dsh-v0.2.1-alpha.3` against `dsh-v0.2.1-alpha.2`
76
+ (852 commits, 3311 changed files) found the first release on this line that
77
+ breaks the plugin at runtime. One root cause — alpha.3 makes every in-process
78
+ carrier name a `PeerScope` with a `principal`, a `signal`, and an `interactions`
79
+ participation — landed in four places: `wireStream.open`'s `peer` argument became
80
+ required, the `$events` endpoint began requiring `interactions: 'answer'`,
81
+ `ConnectionFetchHandler.fetch` gained a required Peer, and the `node` export
82
+ condition started resolving the Client faces to bundles that export them as ESM
83
+ instead of registering the browser module banner the resident Client's loader
84
+ reads. The failure mode was silent: the resident interaction client did not
85
+ start, so the phone lost approval and question delivery while every other
86
+ capability kept working.
87
+
88
+ All four are fixed in one call form both hosts accept (see "DSH interfaces used
89
+ here"). The peer range was narrowed to name `0.2.0-rc.2` and `0.2.1-alpha.3`
90
+ explicitly, and protocol v2, pairing state, and device records are untouched, so
91
+ no paired phone needs to re-pair. The live runs behind this verdict are recorded
92
+ in [`docs/DSH_RELEASE_MEMORY.md`](./docs/DSH_RELEASE_MEMORY.md): alpha.3 with the
93
+ unfixed build logs `DSH Remote interaction client unavailable`, alpha.3 with this
94
+ build does not, and alpha.2 still scores the checklist result it did before the
95
+ change.
96
+
97
+ Two host-side migrations in this release belong to the operator rather than to
98
+ the plugin: a residual `models` key on an `llm-deepseek-account` entry now fails
99
+ that plugin's load, **including an empty array**, and `--from-default-profile`
100
+ now replaces an existing profile directory — its `node_modules` and the user's
101
+ own `cordis.patch.yml` included — instead of leaving it in place.
102
+
67
103
  ## 0.2.1-alpha.2 audit outcome
68
104
 
69
105
  The source-level audit of `dsh-v0.2.1-alpha.2` against `dsh-v0.2.1-alpha.1`
@@ -106,6 +142,11 @@ install the plugin. A source diff is still expected for each new DSH release
106
142
  and recorded in `docs/DSH_RELEASE_MEMORY.md`, but a clean result now only
107
143
  needs a documentation update, not a peer-range widening.
108
144
 
145
+ > **Superseded 2026-10-14.** That open-ended range was narrowed back to the two
146
+ > versions actually audited, `0.2.0-rc.2` and `0.2.1-alpha.3`; see the
147
+ > 0.2.1-alpha.3 outcome above and `docs/决策记录.md` (章节一). The paragraphs in
148
+ > this historical entry describe the policy as it stood on 2026-09-29.
149
+
109
150
  ## 0.2.0-rc.1 audit outcome
110
151
 
111
152
  The source-level audit of `dsh-v0.2.0-rc.1` against the previous baseline
@@ -135,4 +176,4 @@ The complete audit evidence and re-audit procedure remain in [`docs/DSH_RELEASE_
135
176
 
136
177
  ## Evidence limits
137
178
 
138
- Unit tests and config schema projection do not prove every network, Mac, Linux, Windows, or iPhone combination. In particular, physical-device performance, signed helper distribution, and production APNs delivery need their own checks. Include the exact DSH version, plugin commit, Node version, OS, connection mode, and sanitized status output when reporting a compatibility issue; never include credentials or message content.
179
+ Unit tests and config schema projection do not prove every network, macOS, Linux, Windows, or iPhone combination. In particular, physical-device performance, signed helper distribution, and production APNs delivery need their own checks. Include the exact DSH version, plugin commit, Node version, OS, connection mode, and sanitized status output when reporting a compatibility issue; never include credentials or message content.
package/PRIVACY.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  DeepPilot connects directly to the DSH Host controlled by the user. The plugin does not upload complete conversations to a DeepPilot application server.
4
4
 
5
- ## Data stored on the Mac
5
+ ## Data stored on your computer
6
6
 
7
7
  By default, runtime state is stored under `$DSH_HOME/deeppilot/`:
8
8
 
@@ -10,7 +10,7 @@ By default, runtime state is stored under `$DSH_HOME/deeppilot/`:
10
10
  - `devices-v2.json`: paired public keys, fingerprints, scopes, device metadata, revocation/last-seen timestamps, notification preferences, APNs alert registrations, WidgetKit push registrations (a separate token per device used only for content-free widget refresh invalidations), and at most one Live Activity registration per device (activity identifier, session identifier, APNs Live Activity update token, environment, and expiry);
11
11
  - `tailscale/`: local state for the optional embedded tsnet node.
12
12
 
13
- The directory is owner-only mode `0700`. The Mac never receives or stores iPhone private signing keys. Pairing codes exist only in plugin memory for up to five minutes and are not written to disk.
13
+ The directory is owner-only mode `0700`. The computer never receives or stores iPhone private signing keys. Pairing codes exist only in plugin memory for up to five minutes and are not written to disk.
14
14
 
15
15
  The settings report exposes public-key fingerprints, scopes, revocation state, and masked notification diagnostics. It does not expose full APNs tokens, pairing codes, or message bodies.
16
16
 
package/README.md CHANGED
@@ -32,15 +32,18 @@ Host on your own computer and does not replace or modify the DSH Web UI.
32
32
 
33
33
  ## Install from npm
34
34
 
35
- This source checkout targets Node.js 22+ and the **DSH `0.2.x` line, `0.2.0-rc.2`
36
- or later** (up to, but excluding, `0.3.0`), with a `web` profile. The package
37
- includes Funnel helpers for macOS, Linux, and Windows on amd64/arm64;
38
- trusted-LAN mode does not require the helper.
39
-
40
- The current working tree accepts any DSH release in `>=0.2.0-rc.2 <0.3.0-0`
41
- and is built/typechecked against `0.2.0-rc.2`. A previously published plugin
42
- package may still carry its own older, exact-pinned compatibility declaration
43
- until a release is made from this tree.
35
+ This source checkout targets Node.js 22+ and the two DSH releases it has been
36
+ audited against — `0.2.0-rc.2` (the `latest`/`next` channels) and `0.2.1-alpha.3`
37
+ (the `alpha` channel) — with a `web` profile. DSH checks a plugin's declared peer
38
+ range before composing a profile, on every start rather than only on install, so
39
+ any other host version refuses to activate this plugin. The package includes
40
+ Funnel helpers for macOS, Linux, and Windows on amd64/arm64; trusted-LAN mode
41
+ does not require the helper.
42
+
43
+ The current working tree names exactly those two versions in
44
+ `peerDependencies` and is built/typechecked against `0.2.0-rc.2`. A previously
45
+ published plugin package may still carry its own older compatibility
46
+ declaration until a release is made from this tree.
44
47
 
45
48
  ```sh
46
49
  dsh plugin --profile web add dsh-deeppilot
@@ -83,30 +86,20 @@ DeepPilot state under `$DSH_HOME/deeppilot/`.
83
86
 
84
87
  ## Publishing (maintainers)
85
88
 
86
- Release from this branch only after validating against DSH `0.2.0-rc.2`:
87
-
88
- 1. Bump `version` in `package.json` and in the root `""` entry of
89
- `package-lock.json`, then run `npm test && npm run typecheck && npm run build`
90
- and inspect `npm pack --dry-run --json`. The compatibility metadata test
91
- enforces the audited DSH peer range and the exact development dependency
92
- version.
93
- 2. Commit the release and push it. `npm publish` runs `prepack` (build) and
94
- `prepublishOnly` (test + typecheck) automatically.
95
- 3. Publish pre-releases without touching `latest`:
96
-
97
- ```sh
98
- npm publish --tag alpha
99
- ```
100
-
101
- After a successful publish, verify the dist tag and install the package in
102
- a DSH `0.2.0-rc.2` profile before pointing users at it.
103
- 4. Tag the release commit `vX.Y.Z` and prepare a GitHub Release
104
- (English + 简体中文 notes) that links this README section.
105
- 5. Publish stable releases with `npm publish --tag latest`, which moves
106
- `latest` to the new version. Stable releases must never be published
107
- with `--tag alpha`.
108
-
109
- Never run `npm publish` from a copy that still has an older version.
89
+ 采用可信发布(Trusted Publishing):GitHub Actions(GitHub 工作流)通过 OIDC
90
+ 身份认证发布,并生成来源证明(Provenance),无需长期 npm 发布令牌。
91
+
92
+ 1. 同步包版本与锁文件版本,按 `AGENTS.md` 完成发布验证;提交、推送并等待
93
+ 持续集成(CI)通过。
94
+ 2. 从已合入 `main` 的发布提交创建附注标签 `v<版本>`,准备英文与简体中文的
95
+ GitHub Release(发布页)草稿。
96
+ 3. 经用户授权后,在该版本标签上手动运行 `publish.yml`。稳定版使用
97
+ `latest`(稳定分发标签),预发布版使用 `alpha`(预发布分发标签)。
98
+ 4. 核对 npm 版本、分发标签、包完整性与来源证明,并在全新 DSH 配置中安装验证,
99
+ 然后公开 GitHub 发布草稿。
100
+
101
+ 工作流不会因推送标签自动发布。npm 网站绑定、手动命令及首次发布的验证边界见
102
+ [npm 可信发布说明](./docs/NPM_PUBLISHING.md)。
110
103
 
111
104
 
112
105
  ## Connection and privacy
package/README.zh-CN.md CHANGED
@@ -30,13 +30,15 @@
30
30
 
31
31
  ## 从 npm 安装
32
32
 
33
- 当前源码面向 Node.js 22+ 与带 `web` profile 的 **DSH `0.2.x` 系列(`0.2.0-rc.2`
34
- 及以上,低于 `0.3.0`)**。npm 包内置 macOS、Linux、Windows
33
+ 当前源码面向 Node.js 22+ 与带 `web` profile 的**两个已审计 DSH 版本**:
34
+ `0.2.0-rc.2`(`latest`/`next` 通道)与 `0.2.1-alpha.3`(`alpha` 通道)。
35
+ DSH 在组合 profile 前会校验插件声明的 peer 范围,且**每次启动都会校验**(不只是
36
+ 安装时),因此其他宿主版本会拒绝激活本插件。npm 包内置 macOS、Linux、Windows
35
37
  的 amd64/arm64 Funnel helper;可信局域网模式不依赖 helper。
36
38
 
37
- 当前工作树接受 `>=0.2.0-rc.2 <0.3.0-0` 范围内的任意 DSH 版本,构建与类型检查针对
38
- `0.2.0-rc.2`。从此工作树发布新版本前,已发布的旧插件包仍可能带有各自旧的精确
39
- 版本兼容声明。
39
+ 当前工作树的 `peerDependencies` 精确列出这两个版本,构建与类型检查针对
40
+ `0.2.0-rc.2`。从此工作树发布新版本前,已发布的旧插件包仍可能带有各自旧的
41
+ 兼容声明。
40
42
 
41
43
  ```sh
42
44
  dsh plugin --profile web add dsh-deeppilot
@@ -71,27 +73,20 @@ dsh plugin --profile web remove dsh-deeppilot
71
73
 
72
74
  ## 发布说明(维护者)
73
75
 
74
- 此分支仅在 DSH `0.2.0-rc.2` 下完成验证后发布:
76
+ 采用可信发布(Trusted Publishing):GitHub Actions(GitHub 工作流)通过 OIDC
77
+ 身份认证发布,并生成来源证明(Provenance),无需长期 npm 发布令牌。
75
78
 
76
- 1. 同步修改 `package.json` 与 `package-lock.json` 根 `""` 条目中的
77
- `version`,然后运行 `npm test && npm run typecheck && npm run build`,
78
- 并检查 `npm pack --dry-run --json`。兼容性元数据测试会校验 peer 依赖是否为
79
- 审计过的版本范围、开发依赖是否固定为精确的审计基线版本。
80
- 2. 提交发布并推送。`npm publish` 会自动执行 `prepack`(构建)与
81
- `prepublishOnly`(测试 + 类型检查)。
82
- 3. 发布预发布版本,不要动 `latest`:
79
+ 1. 同步包版本与锁文件版本,按 `AGENTS.md` 完成发布验证;提交、推送并等待
80
+ 持续集成(CI)通过。
81
+ 2. 从已合入 `main` 的发布提交创建附注标签 `v<版本>`,准备英文与简体中文的
82
+ GitHub Release(发布页)草稿。
83
+ 3. 经用户授权后,在该版本标签上手动运行 `publish.yml`。稳定版使用
84
+ `latest`(稳定分发标签),预发布版使用 `alpha`(预发布分发标签)。
85
+ 4. 核对 npm 版本、分发标签、包完整性与来源证明,并在全新 DSH 配置中安装验证,
86
+ 然后公开 GitHub 发布草稿。
83
87
 
84
- ```sh
85
- npm publish --tag alpha
86
- ```
87
-
88
- 发布成功后检查 dist tag,并在 DSH `0.2.0-rc.2` profile 中安装验证发布的包。
89
- 4. 为发布提交打 `vX.Y.Z` tag,并准备包含英文与简体中文说明的
90
- GitHub Release,链接本 README 的发布说明。
91
- 5. 稳定版用 `npm publish --tag latest` 发布,使 `latest` 切换到新版本。
92
- 稳定版绝不使用 `--tag alpha` 发布。
93
-
94
- 绝不要从仍是旧版本的副本执行 `npm publish`。
88
+ 工作流不会因推送标签自动发布。npm 网站绑定、手动命令及首次发布的验证边界见
89
+ [npm 可信发布说明](./docs/NPM_PUBLISHING.md)。
95
90
 
96
91
 
97
92
  ## 连接与隐私
package/SECURITY.md CHANGED
@@ -12,11 +12,12 @@ Do not open a public issue containing pairing codes, APNs device tokens, provide
12
12
 
13
13
  - `/phone/pair` accepts only a short-lived, single-use pairing code and a P-256 public key. The code expires after five minutes and is invalidated after one successful registration.
14
14
  - `/phone` upgrades anonymously, then sends a fresh 30-second challenge bound to the stable Host audience. The client proves possession of its registered P-256 private key with ECDSA/SHA-256.
15
- - The iOS app uses a non-exportable Secure Enclave key on physical devices. The Mac stores only public keys and metadata.
15
+ - The iOS app uses a non-exportable Secure Enclave key on physical devices. The computer stores only public keys and metadata.
16
16
  - `GET /phone/health` is intentionally unauthenticated and returns only minimal readiness/version facts.
17
17
  - Protocol v1, shared Bearer tokens, query credentials, and `c2s.hello.auth` are not accepted. There is no remote downgrade switch.
18
18
  - The server enforces explicit protocol scopes, while the normal settings UI grants the default scope set and exposes only the global connection switch plus per-device deletion. Deleting a device disconnects it immediately.
19
19
  - The canonical `$DSH_HOME/deeppilot` directory is repaired to mode `0700`. `host-id` and `devices-v2.json` are owner-only.
20
+ - Losing `devices-v2.json`, `host-id` or `lan-tls/` makes every affected phone pair again; see [docs/DEVICE_REGISTRY_RECOVERY.md](./docs/DEVICE_REGISTRY_RECOVERY.md) for what to back up and how to recover.
20
21
  - The independent LAN listener (default port `3098`) is TLS-only: the plugin serves a self-signed EC P-256 certificate, and the app pins its SPKI fingerprint delivered inside the pairing QR. There is no trust-on-first-use; a regenerated key forces re-pairing. The private key lives owner-only under `$DSH_HOME/deeppilot/lan-tls/`. Never forward the LAN port to the public Internet; public access should use Funnel HTTPS/WSS.
21
22
  - The LAN listener and Funnel helper publish only `/phone`, `/phone/pair`, and `/phone/health`, never the complete DSH Web UI.
22
23
  - Logs must never include pairing codes, key material, APNs tokens, or message bodies.
package/cordis.patch.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  # dsh-deeppilot bundle layer.
2
- # Phone clients cannot interact with a native picker rendered on the Host Mac.
2
+ # Phone clients cannot interact with a native picker rendered on the host computer.
3
3
  # Replace dsh-web-app's adaptive picker with the official browse pair while
4
4
  # this bundle is composed, so host.listDirectory is available remotely.
5
5
  # Removing this bundle restores the lower web-app auto row unchanged.
@@ -0,0 +1,87 @@
1
+ # Recovering from a lost device registry
2
+
3
+ [简体中文](./DEVICE_REGISTRY_RECOVERY.zh-CN.md)
4
+
5
+ The computer keeps the list of paired phones in
6
+ `$DSH_HOME/deeppilot/devices-v2.json` (`~/.dsh/deeppilot/devices-v2.json` when
7
+ `DSH_HOME` is unset). It holds each phone's **public** key, scopes, display
8
+ name, notification preferences and push registrations. The matching private key
9
+ never leaves the phone (Secure Enclave on a physical iPhone), so this file is the
10
+ only thing that makes a paired phone recognisable. If it is gone, no phone can
11
+ prove who it is, and the only way back is to pair again.
12
+
13
+ ## What you will see
14
+
15
+ | Situation | Host | Phone |
16
+ | --- | --- | --- |
17
+ | File deleted, or the data directory is new | Starts with no devices and logs no error: a missing file is indistinguishable from a first run. The start-up line `device registry loaded from ...: 0 device(s), 0 push registration(s)` is the tell. | Every phone is refused with close code `4401` and shows "pairing is no longer valid". |
18
+ | File unreadable (truncated, disk error) | Logs `[deeppilot] device registry unreadable, starting empty: ...` at start-up, then reports `0 device(s)` and behaves as above. | Same. |
19
+ | A single device deleted in the settings page | That one device is refused; others are unaffected. | Same message on that phone only. |
20
+
21
+ The phone cannot tell these cases apart: `4401` is also what a revoked device
22
+ gets. If every phone fails at once, suspect the registry; if one fails, suspect
23
+ revocation.
24
+
25
+ ## Before you touch anything
26
+
27
+ 1. **Stop DeepSeek Harness first.** The plugin reads the registry once at start-up
28
+ and rewrites it from memory afterwards, so an edit made while it runs is
29
+ overwritten.
30
+ 2. **Copy the damaged file aside** (`cp devices-v2.json devices-v2.json.bad`) if
31
+ it exists. The first successful pairing writes a fresh registry over it, and
32
+ a truncated file may still contain recoverable entries.
33
+ 3. **Never delete `host-id`.** It is the stable Host identity (`audience`) that
34
+ every phone has stored. A new `host-id` invalidates every pairing even if the
35
+ registry is restored. If `host-id` itself is malformed the plugin refuses to
36
+ start; restore it from a backup rather than deleting it.
37
+
38
+ ## Recovery
39
+
40
+ ### A. You have a backup of `devices-v2.json`
41
+
42
+ The file is replaced atomically (written to a temporary file, then renamed), so
43
+ a copy taken at any moment is a consistent snapshot.
44
+
45
+ 1. Stop DeepSeek Harness.
46
+ 2. Put the backup at `$DSH_HOME/deeppilot/devices-v2.json` with mode `0600`.
47
+ 3. Start DeepSeek Harness. The start-up log reports how many devices and push
48
+ registrations were loaded.
49
+ 4. Bring the app to the foreground on each phone (or use its retry action). A phone that was refused with `4401` stops retrying by itself, so it reconnects when you reopen the app, not the moment the Host comes back. No re-pairing is needed.
50
+
51
+ Anything changed after the backup was taken (devices paired, renamed or deleted)
52
+ is rolled back with it. A device you deleted after the backup works again, so
53
+ delete it once more.
54
+
55
+ ### B. No usable backup
56
+
57
+ Every phone has to pair again.
58
+
59
+ 1. Start DeepSeek Harness and open the DeepPilot plugin settings page.
60
+ 2. Show the pairing QR code (or the copyable pairing text). A code is valid for five minutes and works once.
61
+ 3. On each phone, open the host in the app's settings, choose edit, and scan the
62
+ new QR code (or paste the pairing text). If that does not take, remove the host
63
+ in the app and add it again.
64
+ 4. Remove stale entries from the settings page if the old registry was partly
65
+ recovered.
66
+
67
+ What is reset: custom device names, per-device notification preferences and
68
+ scopes (back to the defaults). Push registrations and Live Activity tokens come
69
+ back by themselves, because the app registers them on every connection.
70
+
71
+ Conversations, projects and models are untouched; they belong to DeepSeek
72
+ Harness, not to this plugin.
73
+
74
+ ## Related files that also force re-pairing
75
+
76
+ | File or directory | If lost |
77
+ | --- | --- |
78
+ | `host-id` | New audience: every phone must pair again. |
79
+ | `lan-tls/` | New certificate key: phones paired over the LAN reject the host until re-paired (Funnel pairings are unaffected). |
80
+ | `tailscale/` | Funnel's Tailscale node state. Expect a new node and a fresh Funnel authorisation; LAN pairings are unaffected. |
81
+
82
+ ## Keeping a backup
83
+
84
+ Copy `devices-v2.json` and `host-id` together to somewhere outside `DSH_HOME`
85
+ whenever you pair or delete a device. Treat the copy like any file that grants
86
+ access to your sessions: it lists the keys allowed to connect, though it cannot
87
+ be used to impersonate a phone.
@@ -0,0 +1,73 @@
1
+ # 设备注册表丢失后的恢复
2
+
3
+ [English](./DEVICE_REGISTRY_RECOVERY.md)
4
+
5
+ 电脑上用 `$DSH_HOME/deeppilot/devices-v2.json`(未设置 `DSH_HOME` 时为
6
+ `~/.dsh/deeppilot/devices-v2.json`)记录已配对的手机:每台手机的**公钥**、权限范围、
7
+ 显示名称、通知偏好和推送注册。私钥永远不离开手机(真机在 Secure Enclave 中),
8
+ 所以这个文件是电脑认出已配对手机的唯一依据。文件丢了,手机就无法证明自己的身份,
9
+ 唯一的出路是重新配对。
10
+
11
+ ## 你会看到什么
12
+
13
+ | 情形 | 电脑端 | 手机端 |
14
+ | --- | --- | --- |
15
+ | 文件被删,或数据目录是新的 | 以空设备列表启动,不报错:缺文件与首次运行无法区分。启动日志里的 `device registry loaded from ...: 0 device(s), 0 push registration(s)` 就是线索 | 所有手机被关闭码 `4401` 拒绝,提示"配对已失效" |
16
+ | 文件不可读(被截断、磁盘错误) | 启动时记一条 `[deeppilot] device registry unreadable, starting empty: ...`,随后报告 `0 device(s)`,之后同上 | 同上 |
17
+ | 在设置页只删除了一台设备 | 仅该设备被拒绝,其余不受影响 | 仅该手机出现同样提示 |
18
+
19
+ 手机无法区分这几种情形:被吊销的设备拿到的同样是 `4401`。如果所有手机同时失败,先怀疑
20
+ 注册表;只有一台失败,先怀疑它被删除。
21
+
22
+ ## 动手之前
23
+
24
+ 1. **先停止 DeepSeek Harness。**插件只在启动时读一次注册表,之后按内存内容重写文件,
25
+ 运行中手工修改会被覆盖。
26
+ 2. **把损坏的文件另存一份**(`cp devices-v2.json devices-v2.json.bad`)。第一次成功配对
27
+ 会用新内容覆盖它,而被截断的文件里也许还有可挽回的条目。
28
+ 3. **不要删除 `host-id`。**它是所有手机都保存着的 Host 身份(`audience`);换了新的
29
+ `host-id`,即便注册表恢复了,所有配对也都作废。如果 `host-id` 本身格式损坏,插件会
30
+ 拒绝启动,请从备份恢复,而不是删除。
31
+
32
+ ## 恢复步骤
33
+
34
+ ### A. 有 `devices-v2.json` 的备份
35
+
36
+ 该文件是原子替换写入的(先写临时文件再改名),任何时刻拷贝出来的都是一致的快照。
37
+
38
+ 1. 停止 DeepSeek Harness。
39
+ 2. 把备份放回 `$DSH_HOME/deeppilot/devices-v2.json`,权限设为 `0600`。
40
+ 3. 启动 DeepSeek Harness。启动日志会报告载入了多少台设备和多少条推送注册。
41
+ 4. 在每台手机上把 App 切回前台(或使用重试操作)。被 `4401` 拒绝过的手机会停止自动重试,所以要等你重新打开 App 才会重连,而不是电脑恢复的那一刻。无需重新配对。
42
+
43
+ 备份之后的变更(新配对、改名、删除的设备)会一并回退。备份之后被你删除的设备会重新
44
+ 可用,请再删除一次。
45
+
46
+ ### B. 没有可用备份
47
+
48
+ 每台手机都需要重新配对。
49
+
50
+ 1. 启动 DeepSeek Harness,打开 DeepPilot 插件设置页。
51
+ 2. 显示配对二维码(或可复制的配对文本)。配对码 5 分钟有效,只能用一次。
52
+ 3. 在每台手机的 App 设置里编辑该主机,扫描新二维码(或粘贴配对文本)。如果不生效,
53
+ 就在 App 里删除该主机后重新添加。
54
+ 4. 如果旧注册表恢复了一部分,在设置页清理多余的条目。
55
+
56
+ 会被重置的内容:自定义设备名称、每台设备的通知偏好和权限范围(回到默认值)。推送注册和
57
+ 实时活动令牌会自动恢复,因为 App 每次连接都会重新注册。
58
+
59
+ 会话、项目和模型不受影响,它们属于 DeepSeek Harness,不属于本插件。
60
+
61
+ ## 同样会迫使重新配对的相关文件
62
+
63
+ | 文件或目录 | 丢失后果 |
64
+ | --- | --- |
65
+ | `host-id` | 新的 audience:所有手机都要重新配对 |
66
+ | `lan-tls/` | 新的证书密钥:经局域网配对的手机在重新配对前会拒绝该主机(Funnel 配对不受影响) |
67
+ | `tailscale/` | Funnel 的 Tailscale 节点状态。预期会出现新节点并需要重新授权 Funnel;局域网配对不受影响 |
68
+
69
+ ## 保留备份
70
+
71
+ 每次配对或删除设备后,把 `devices-v2.json` 和 `host-id` 一起拷贝到 `DSH_HOME` 之外。
72
+ 这份拷贝要像对待任何能访问你会话的文件一样保管:它列出了允许连接的公钥,但无法用来
73
+ 冒充手机。