@twinsearth/w2m-dsh-plugin 1.2.2 → 1.2.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/README.md CHANGED
@@ -1,462 +1,462 @@
1
- # DSH: Windows2MacOS
2
-
3
- **One DeepSeek account. One instruction. Every machine you own runs the same project.**
4
-
5
- [![CI](https://github.com/TwinsEarth/dsh-windows2macos/actions/workflows/ci.yml/badge.svg)](https://github.com/TwinsEarth/dsh-windows2macos/actions/workflows/ci.yml)
6
- [![npm](https://img.shields.io/npm/v/@twinsearth/w2m-dsh-plugin?label=npm)](https://www.npmjs.com/package/@twinsearth/w2m-dsh-plugin)
7
- [![DSH plugin](https://img.shields.io/badge/DSH-plugin-4c6ef5)](#1-dsh-plugin-recommended--zero-configuration)
8
- [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
9
- [![platforms](https://img.shields.io/badge/CI-windows%20%7C%20macos%20%7C%20linux-4c6ef5)](#verified-and-not-verified)
10
-
11
- English | [中文](#中文说明)
12
-
13
- ---
14
-
15
- ## Install
16
-
17
- Five routes in. They all end with **13 tools registered** in your agent. Pick one.
18
-
19
- ### 1. DSH plugin (recommended — zero configuration)
20
-
21
- ```bash
22
- # 1. install the package into your DSH profile
23
- dsh plugin add @twinsearth/w2m-dsh-plugin
24
-
25
- # 2. name it in the profile's bundle list — a package that is not bundled never loads
26
- # ~/.dsh/profiles/<profile>/dsh.profile.yml
27
- plugins:
28
- - '@twinsearth/w2m-dsh-plugin'
29
-
30
- # 3. restart DSH; joining happens at mount time, so a hot reload is not enough
31
- ```
32
-
33
- Since **1.1.15** the agent starts by itself (`autoStartAgent` defaults to `true`): a freshly installed machine
34
- derives its account identity, joins the account's fleet, and shows up in `w2m_devices` with no pairing step.
35
- To turn that off, patch the **existing** `w2m` row — do not add a second one:
36
-
37
- ```yaml
38
- # cordis.patch.yml
39
- - id: w2m
40
- config:
41
- autoStartAgent: false
42
- ```
43
-
44
- ### 2. npm (MCP server, any client)
45
-
46
- ```bash
47
- npm install -g @twinsearth/w2m-dsh-plugin
48
- w2m-mcp --help
49
- ```
50
-
51
- Point any MCP client at `w2m-mcp`. **Zero runtime dependencies**; the package ships the relay, the agent and the
52
- tools.
53
-
54
- ### 3. Claude Code
55
-
56
- ```bash
57
- /plugin marketplace add TwinsEarth/dsh-windows2macos
58
- /plugin install w2m@twinsearth
59
- ```
60
-
61
- The manifests live in [`.claude-plugin/`](.claude-plugin/) and are version-locked to the release.
62
-
63
- ### 4. Codex and other MCP-capable CLIs
64
-
65
- ```toml
66
- # copy this block into your own codex/mcp-servers.toml
67
- [mcp_servers.w2m]
68
- command = "npx"
69
- args = ["-y", "@twinsearth/w2m-dsh-plugin@1.1.19", "w2m-mcp"]
70
- ```
71
-
72
- ### 5. Verified release tarball (offline)
73
-
74
- From the `v1.1.19` release page download **both** files — `twinsearth-w2m-dsh-plugin-1.1.19.tgz` and `SHA256SUMS` —
75
- then:
76
-
77
- ```bash
78
- curl -LO https://github.com/TwinsEarth/dsh-windows2macos/releases/download/v1.1.19/twinsearth-w2m-dsh-plugin-1.1.19.tgz
79
- sha256sum -c SHA256SUMS # verify before installing, always
80
- npm install -g ./twinsearth-w2m-dsh-plugin-1.1.19.tgz
81
- ```
82
-
83
- Every release carries `SHA256SUMS` for exactly this reason. If a release is missing it, do not install that release.
84
-
85
- ### 6. Beta (prerelease — never on npm)
86
-
87
- A beta is a **GitHub prerelease**, not an npm version. Nothing about it is resolved by `npm install`, and `latest` never
88
- points at one. It exists for a developer who has been granted `beta-access` and accepts that it can break.
89
-
90
- ```bash
91
- # 1. find the newest prerelease (a beta is marked "Pre-release" on the releases page)
92
- # https://github.com/TwinsEarth/dsh-windows2macos/releases -> latest entry labelled Pre-release
93
- # 2. download BOTH files attached to it: twinsearth-w2m-dsh-plugin-<version>.tgz and SHA256SUMS
94
- sha256sum -c SHA256SUMS # verify before installing, always
95
- npm install -g ./twinsearth-w2m-dsh-plugin-<version>.tgz
96
- ```
97
-
98
- - **Access**: the beta workflow waits on the protected `beta-access` environment, so a reviewer must approve each run
99
- before the artifact exists at all.
100
- - **Risk acknowledgment**: install it only if you have read [SECURITY.md](SECURITY.md) and accept that a beta may fail
101
- where a stable release does not.
102
- - **Going back**: `npm install -g @twinsearth/w2m-dsh-plugin@latest` returns you to the stable channel.
103
- - **Why not npm**: there is no `npm publish` line in the beta workflow. A channel cannot leak into the default install if
104
- the publication cannot happen at all.
105
-
106
-
107
- ---
108
-
109
- ## Quick start
110
-
111
- ```text
112
- w2m_status # am I paired? which fleet?
113
- w2m_devices # what is in the fleet?
114
- w2m_run mode="replicate" command_argv=["git","rev-parse","HEAD"]
115
- w2m_wait task_id="..."
116
- ```
117
-
118
- `replicate` runs on every online machine; the verdict is `consistent` only when every machine returned the same
119
- result **and** the same commit anchor. `split` gives each machine a slice, `pipeline` chains stages, `broadcast`
120
- names one executor and reports observers, `compose` merges results.
121
-
122
- ---
123
-
124
- ## What it is
125
-
126
- An installable plugin that turns a set of machines into **one fleet**, keyed by **your account**, not by a
127
- hand-maintained host list.
128
-
129
- | Piece | Responsibility |
130
- |---|---|
131
- | **Relay** | a small HTTP server: task routing, leases, SSE, blob storage. Run it anywhere your machines can reach. |
132
- | **Agent** | runs on each machine: pairs once, executes allow-listed commands, heartbeats. |
133
- | **Plugin** | the tools your agent calls. |
134
- | **Admission** (1.1.14+) | a new machine asks to join; an online member approves, **10 s auto-approves**, and a machine admitted in the last 24 h can be removed immediately. |
135
-
136
- ### Tools
137
-
138
- `w2m_status` · `w2m_devices` · `w2m_run` · `w2m_wait` · `w2m_history` · `w2m_stats` · `w2m_report` ·
139
- `w2m_memory` · `w2m_approvals` · `w2m_council` · `w2m_chat` · `w2m_update`
140
- - `w2m_intent` — map a structured intent (run-tests, node-version, git-status, git-head, fleet-facts, shard-probe) onto an allow-listed command; unknown intents are refused by name, never guessed.
141
-
142
- ### Environment a dispatched command receives (1.1.15+)
143
-
144
- Values come from the offer, never from argv, so one machine cannot reshape a fleet-wide split.
145
-
146
- | Variable | Meaning |
147
- |---|---|
148
- | `W2M_INDEX` / `W2M_INDEX_TOTAL` | this machine's slice number and how many slices exist |
149
- | `W2M_STAGE_INDEX` | the pipeline stage; deliberately separate from `W2M_INDEX` |
150
- | `W2M_TASK_ID` / `W2M_MODE` | the task and its mode |
151
- | `W2M_MACHINE_ID` / `W2M_MACHINE_NAME` | this machine |
152
-
153
- So `command_argv=["node","-e","process.exit(Number(process.env.W2M_INDEX)+1)"]` over four machines exits 1/2/3/4.
154
- Single-machine behaviour is tested; the four-machine run is listed as **not verified** below.
155
-
156
- ---
157
-
158
- ## Fleet model
159
-
160
- Identity is **the account**, not a list of hosts. A machine derives an Ed25519 identity from its account
161
- material; the relay registers the matching public key when the account's first machine pairs; every later machine
162
- proves the same account with a one-time signed challenge.
163
-
164
- If the old pairing path fails for a **named** reason only — invalid pairing code, unreachable relay, timeout — the
165
- machine falls back to asking for admission. Any other failure needs a human and does **not** change mechanisms. A
166
- rejection is final; the machine does not re-ask.
167
-
168
- The relay is authoritative for every time window and every quorum, because client clocks disagree (measured:
169
- tens of seconds between machines in one fleet):
170
-
171
- - **10 s** without a decision auto-approves, but only while at least one member is **online** — an empty fleet
172
- cannot consent, and that refusal is named (`NO_ONLINE_MEMBER`);
173
- - within **24 h** of admission, any online member may remove the machine immediately;
174
- - after 24 h a single machine may only **open a vote**, whose quorum set is **frozen at open time** so a voter
175
- going offline mid-vote cannot move the bar;
176
- - the quorum is 3+ old members need **≥ 50 %** (rounded up); a two-member fleet needs **one** vote;
177
- - a machine offline for more than **72 hours** leaves the **list** — it is never deleted, its records stay, and it
178
- returns by reporting again.
179
-
180
- ---
181
-
182
- ## Security posture
183
-
184
- - The command allow-list is **argv-prefix** based and rejects argv-reparsing flags (`--import`, appended `-e`) with
185
- `COMMAND_REPARSE_ARGUMENT`. It is a boundary, not a sandbox.
186
- - Dispatching tasks requires an operator token. A device token cannot dispatch.
187
- - Admission approval requires at least one member online.
188
- - The account signing key is derived from the fleet key, **never** from the account code — the account code is
189
- written to logs, and deriving a key from a readable value would hand out the account identity.
190
- - Every release is checksummed.
191
-
192
- ---
193
-
194
- ## Verified and not verified
195
-
196
- A green badge that hides this is worse than no badge.
197
-
198
- **Verified:** 2458 tests in 102 files (2444 pass, 14 skip), 15 pre-flight checks, CI green on Windows, macOS and
199
- Linux, npm + GitHub release + checksums consistent, end-to-end fleet runs on a real Linux/Mac/Windows triple.
200
-
201
- **Not verified / known gaps:**
202
-
203
- | Gap | State |
204
- |---|---|
205
- | Approval **popup** control | the relay enforces the rules and `w2m_approvals` lists and settles requests, but the 10 s countdown is relay data shown in a list, not a dedicated popup |
206
- | Four-machine `W2M_INDEX` run | single-machine behaviour tested; the fleet-wide 1/2/3/4 run has not been executed |
207
- | Fleet chat / broadcast channel | message store + quota implemented and tested; encryption, sharding, endpoints and the panel are **not built** |
208
- | P2P on some macOS hosts | `P2P_PUNCH_FAILED: Unknown cipher` observed; the task falls back to the relay path |
209
-
210
- ---
211
-
212
- ## Troubleshooting
213
-
214
- | Symptom | Cause | Fix |
215
- |---|---|---|
216
- | installed, but `w2m_devices` answers 401 | the agent never started, so no device token | check `autoStartAgent` is not `false`; restart DSH (not a hot reload) |
217
- | machine missing from the fleet | the plugin is not in `dsh.profile.bundles` | add it — a package that is not bundled never loads |
218
- | `COMMAND_NOT_ALLOWED` | command not in the allow-list | extend the allow-list; argv is matched by prefix |
219
- | verdict always `partial` | a stale lease from a machine that is down | fixed in 1.1.2: a dispatch leases only online, non-silent machines |
220
- | `P2P_PUNCH_FAILED` | NAT, or a host crypto gap | it downgrades to the relay path; results are unaffected |
221
-
222
- ---
223
-
224
- ## Release channels
225
-
226
- Three channels, and only one of them is what \
227
- pm install\ gives you.
228
-
229
- | Channel | Where it comes from | Who should use it |
230
- |---|---|---|
231
- | **stable** | npm \latest\ — a version the developer declared stable | everyone; this is the default install |
232
- | **beta** | a GitHub **prerelease** (tarball + \SHA256SUMS\), **never on npm** | developers granted \eta-access\ who accept that it can fail |
233
- | **latest build** | the newest of the two, whatever its channel | a developer who wants the newest code and knows the above |
234
-
235
- A bump is a **beta by default**. npm receives a version only when the developer says, in so many words, that this one is a
236
- stable release: a beta is published as a prerelease (so neither GitHub nor npm ever resolves it as \latest\), it is
237
- downloadable only after the \eta-access\ environment approves the run, and installing it requires reading
238
- [SECURITY.md](SECURITY.md) and accepting the risk.
239
-
240
- There is no \
241
- pm publish\ line in the beta workflow. That is deliberate — a channel cannot leak to the default install if
242
- the publication cannot happen at all.
243
- ---
244
-
245
- ## Links
246
-
247
- - [CHANGELOG](CHANGELOG.md) — every version, including what it deliberately did **not** ship
248
- - [PLATFORMS](docs/PLATFORMS.md) — Claude Code, Codex and MCP wiring details
249
- - [Releases](https://github.com/TwinsEarth/dsh-windows2macos/releases) — per-release evidence and checksums
250
- - [Issues](https://github.com/TwinsEarth/dsh-windows2macos/issues)
251
-
252
- MIT.
253
-
254
- ---
255
-
256
- ## 中文说明
257
-
258
- **一个 DeepSeek 账号。一句话。你自己的每一台机器跑同一个项目。**
259
-
260
- 以下内容与上方英文**逐节对应**,不是摘要。
261
-
262
- ### 安装
263
-
264
- 五种方式,最终都会在你的 agent 里注册 **13 tools registered**。任选其一。
265
-
266
- #### 1. DSH 插件(推荐,零配置)
267
-
268
- ```bash
269
- # 1. 把包装进你的 DSH profile
270
- dsh plugin add @twinsearth/w2m-dsh-plugin
271
-
272
- # 2. 在 profile 的 bundle 列表里写上包名 —— 没进 bundle 的包永远不会被加载
273
- # ~/.dsh/profiles/<profile>/dsh.profile.yml
274
- plugins:
275
- - '@twinsearth/w2m-dsh-plugin'
276
-
277
- # 3. 重启 DSH。加入编队发生在 mount 时,热重载不够
278
- ```
279
-
280
- 从 **1.1.15** 起 agent 会自动启动(`autoStartAgent` 默认 `true`):新装的机器自行派生账号身份、加入该账号的
281
- 编队,无需配对步骤就会出现在 `w2m_devices` 里。若要关闭,请 patch **已存在的** `w2m` 行,**不要新增第二行**:
282
-
283
- ```yaml
284
- # cordis.patch.yml
285
- - id: w2m
286
- config:
287
- autoStartAgent: false
288
- ```
289
-
290
- #### 2. npm(MCP 服务器,任意客户端)
291
-
292
- ```bash
293
- npm install -g @twinsearth/w2m-dsh-plugin
294
- w2m-mcp --help
295
- ```
296
-
297
- 把任意 MCP 客户端指向 `w2m-mcp`。**零运行时依赖**;包里同时带有 relay、agent 与工具。
298
-
299
- #### 3. Claude Code
300
-
301
- ```bash
302
- /plugin marketplace add TwinsEarth/dsh-windows2macos
303
- /plugin install w2m@twinsearth
304
- ```
305
-
306
- 清单文件位于 [`.claude-plugin/`](.claude-plugin/),版本与发行版锁定。
307
-
308
- #### 4. Codex 及其它支持 MCP 的 CLI
309
-
310
- ```toml
311
- # 把这段复制进你自己的 codex/mcp-servers.toml
312
- [mcp_servers.w2m]
313
- command = "npx"
314
- args = ["-y", "@twinsearth/w2m-dsh-plugin@1.1.19", "w2m-mcp"]
315
- ```
316
-
317
- #### 5. 校验过的发布包(离线)
318
-
319
- 从 `v1.1.19` 的 release 页面下载**两个**文件 —— `twinsearth-w2m-dsh-plugin-1.1.19.tgz` 与 `SHA256SUMS` —— 然后:
320
-
321
- ```bash
322
- curl -LO https://github.com/TwinsEarth/dsh-windows2macos/releases/download/v1.1.19/twinsearth-w2m-dsh-plugin-1.1.19.tgz
323
- sha256sum -c SHA256SUMS # 安装前务必校验
324
- npm install -g ./twinsearth-w2m-dsh-plugin-1.1.19.tgz
325
- ```
326
-
327
- 每个 release 都带 `SHA256SUMS` 正是为了这一步。若某个 release 缺少它,**不要安装该版本**。
328
-
329
- #### 6. Beta(预发布版 —— 不进 npm)
330
-
331
- Beta 是 **GitHub prerelease**,不是 npm 版本:`npm install` 永远拿不到它,`latest` 也不会指向它。它是给**已获
332
- `beta-access` 授权、且接受它可能出问题**的开发者用的。
333
-
334
- ```bash
335
- # 1. 在 releases 页面找标着 Pre-release 的最新条目
336
- # https://github.com/TwinsEarth/dsh-windows2macos/releases
337
- # 2. 下载该条目的两个文件:twinsearth-w2m-dsh-plugin-<版本>.tgz 与 SHA256SUMS
338
- sha256sum -c SHA256SUMS # 安装前务必校验
339
- npm install -g ./twinsearth-w2m-dsh-plugin-<版本>.tgz
340
- ```
341
-
342
- - **权限**:beta 流水线挂在受保护的 `beta-access` 环境上,**每次运行都需要审批者批准**,产物才会存在;
343
- - **风险确认**:安装前请阅读 [SECURITY.md](SECURITY.md),并接受 beta 可能比稳定版更容易失败;
344
- - **回退**:`npm install -g @twinsearth/w2m-dsh-plugin@latest` 即回到稳定版通道;
345
- - **为什么不走 npm**:beta 工作流里**没有 `npm publish`** —— 只要发布动作不可能发生,这个通道就漏不进默认安装。
346
-
347
-
348
- ### 快速开始
349
-
350
- ```text
351
- w2m_status # 我配对了吗?在哪个编队?
352
- w2m_devices # 编队里有什么?
353
- w2m_run mode="replicate" command_argv=["git","rev-parse","HEAD"]
354
- w2m_wait task_id="..."
355
- ```
356
-
357
- `replicate` 在每一台在线机器上执行;只有当所有机器返回**相同结果**且**相同 commit 锚点**时,裁决才是
358
- `consistent`。`split` 给每台机器一个分片,`pipeline` 串联多个阶段,`broadcast` 指定一个执行者并汇报观察者,
359
- `compose` 合并结果。
360
-
361
- ### 它是什么
362
-
363
- 一个可安装插件,把一组机器变成**一个编队**,以**你的账号**为键,而不是靠手工维护的主机清单。
364
-
365
- | 组件 | 职责 |
366
- |---|---|
367
- | **Relay** | 一个小型 HTTP 服务:任务路由、租约、SSE、blob 存储。放在任何机器可达的位置。 |
368
- | **Agent** | 每台机器上运行:配对一次、执行白名单命令、心跳。 |
369
- | **Plugin** | 你的 agent 调用的那些工具。 |
370
- | **Admission**(1.1.14+) | 新机器申请加入;在线成员批准,**10 秒自动同意**;入队 24 小时内的机器可被立即移除。 |
371
-
372
- ### 工具
373
-
374
- `w2m_status` · `w2m_devices` · `w2m_run` · `w2m_wait` · `w2m_history` · `w2m_stats` · `w2m_report` ·
375
- `w2m_memory` · `w2m_approvals` · `w2m_council` · `w2m_chat` · `w2m_update`
376
-
377
- ### 被派发命令会收到的环境变量(1.1.15+)
378
-
379
- 取值来自 offer,**不来自 argv**,因此单台机器无法改变整个编队的分片:`W2M_INDEX` / `W2M_INDEX_TOTAL` /
380
- `W2M_STAGE_INDEX` / `W2M_TASK_ID` / `W2M_MODE` / `W2M_MACHINE_ID` / `W2M_MACHINE_NAME`。
381
-
382
- 因此 `command_argv=["node","-e","process.exit(Number(process.env.W2M_INDEX)+1)"]` 在四台机器上应分别退出
383
- 1/2/3/4。**单机行为已测;四台机器的联跑尚未验证**(见下方"未验证")。
384
-
385
- ### 编队模型
386
-
387
- 身份是**账号**,不是主机清单。机器从账号材料派生 Ed25519 身份;账号的第一台机器配对时,relay 登记对应公钥;
388
- 此后每台机器用一次性签名挑战自证同一个账号。
389
-
390
- 只有当旧配对路径因**明确原因**失败时 —— 配对码无效、relay 不可达、超时 —— 机器才降级去申请入队。其它失败需要
391
- 人工介入,**不会**切换机制。**拒绝即终态**,机器不会再次申请。
392
-
393
- 所有时间窗与法定人数由 relay 权威判定,因为客户端时钟互不一致(实测:同一编队内相差数十秒):
394
-
395
- - 无决策 **10 秒**后自动同意,但**仅当至少一名成员在线** —— 空编队无法同意,该拒绝有明确错误码
396
- (`NO_ONLINE_MEMBER`);
397
- - 入队后 **24 小时内**,任一在线成员可立即移除该机器;
398
- - 超过 24 小时,单台机器只能**发起表决**,且法定人数集合在**发起时冻结**,投票者中途离线无法改变门槛;
399
- - 法定人数:旧成员 ≥3 人需 **≥50%(向上取整)**;2 人编队的移除只要 **1 票**;
400
- - 离线超过 **72 小时**的机器会离开**列表** —— 记录**永不删除**,它重新上报后会自动回来。
401
-
402
- ### 安全姿态
403
-
404
- - 命令白名单按 **argv 前缀**匹配,并拒绝 argv 重解析标志(`--import`、追加 `-e`),错误码
405
- `COMMAND_REPARSE_ARGUMENT`。这是边界,不是沙箱。
406
- - 派发任务需要 operator token;device token 不能派发。
407
- - 审批入队要求至少一名成员在线。
408
- - 账户签名密钥从 **fleet key** 派生,**绝不**用 account code —— account code 会写进日志,用可读值派生密钥等于
409
- 交出账号身份。
410
- - 每个发行版都有校验和。
411
-
412
- ### 已验证与未验证
413
-
414
- **一句真话胜过一块掩盖问题的绿牌。**
415
-
416
- **已验证:** 102 个文件中的 2458 项测试(2444 通过、14 跳过)、15 项门禁检查、Windows / macOS / Linux 三平台
417
- CI 全绿、npm 与 GitHub 发布及校验和一致、在真实的 Linux/Mac/Windows 三机编队上跑过端到端。
418
-
419
- **未验证 / 已知缺口:**
420
-
421
- | 缺口 | 状态 |
422
- |---|---|
423
- | 审批**弹窗**控件 | relay 强制规则、`w2m_approvals` 可列出并裁决,但 10 秒倒计时是列表里的 relay 数据,不是独立弹窗 |
424
- | 四台机器的 `W2M_INDEX` 联跑 | 单机行为已测;编队级 1/2/3/4 尚未执行 |
425
- | 编队聊天 / 广播通道 | 消息存储与配额已实现并测试;加密、分片、端点与面板**尚未构建** |
426
- | 部分 macOS 主机的 P2P | 观察到 `P2P_PUNCH_FAILED: Unknown cipher`;任务会降级走 relay 路径 |
427
-
428
- ### 故障排查
429
-
430
- | 症状 | 原因 | 处理 |
431
- |---|---|---|
432
- | 装好了,但 `w2m_devices` 返回 401 | agent 从未启动,因此没有 device token | 确认 `autoStartAgent` 不是 `false`;**重启 DSH**(热重载无效) |
433
- | 编队里看不到这台机器 | 插件没写进 `dsh.profile.bundles` | 加进去 —— 不在 bundle 里的包永远不会加载 |
434
- | `COMMAND_NOT_ALLOWED` | 命令不在白名单 | 扩充白名单;argv 按前缀匹配 |
435
- | 裁决总是 `partial` | 来自已下线机器的陈旧租约 | 1.1.2 已修:派发只对在线且未沉默的机器建租约 |
436
- | `P2P_PUNCH_FAILED` | NAT,或宿主密码学能力缺口 | 会自动降级到 relay 路径,结果不受影响 |
437
-
438
- ### 发布通道(中文)
439
-
440
- 三个通道,其中只有一个是你 \
441
- pm install\ 默认拿到的。
442
-
443
- | 通道 | 来源 | 适合谁 |
444
- |---|---|---|
445
- | **stable** | npm \latest\,开发者明确声明为稳定版 | 所有人;默认安装就是它 |
446
- | **beta** | GitHub **prerelease**(tarball + \SHA256SUMS\),**绝不进 npm** | 被授予 \eta-access\、且接受它可能失败的开发者 |
447
- | **最新构建** | 两者中版本较新的那个,不论通道 | 想要最新代码、并理解上述含义的开发者 |
448
-
449
- **默认的 bump 是 beta**。只有开发者明确说明"这次是稳定版",npm 才会收到该版本:beta 以 prerelease 形式发布(因此
450
- GitHub 与 npm 都不会把它解析成 \latest\),只有在 \eta-access\ 环境审批通过后才能下载,安装前需阅读
451
- [SECURITY.md](SECURITY.md) 并自担风险。
452
-
453
- beta 工作流里**没有 \
454
- pm publish\ 这一行**,这是刻意的 —— 只要发布动作不可能发生,这个通道就不可能泄漏到默认安装。
455
- ### 链接
456
-
457
- - [CHANGELOG](CHANGELOG.md) —— 每个版本,包括它**故意没有**交付的内容
458
- - [PLATFORMS](docs/PLATFORMS.md) —— Claude Code、Codex 与 MCP 的接线细节
459
- - [Releases](https://github.com/TwinsEarth/dsh-windows2macos/releases) —— 每个版本的证据与校验和
460
- - [Issues](https://github.com/TwinsEarth/dsh-windows2macos/issues)
461
-
462
- MIT。
1
+ # DSH: Windows2MacOS
2
+
3
+ **One DeepSeek account. One instruction. Every machine you own runs the same project.**
4
+
5
+ [![CI](https://github.com/TwinsEarth/dsh-windows2macos/actions/workflows/ci.yml/badge.svg)](https://github.com/TwinsEarth/dsh-windows2macos/actions/workflows/ci.yml)
6
+ [![npm](https://img.shields.io/npm/v/@twinsearth/w2m-dsh-plugin?label=npm)](https://www.npmjs.com/package/@twinsearth/w2m-dsh-plugin)
7
+ [![DSH plugin](https://img.shields.io/badge/DSH-plugin-4c6ef5)](#1-dsh-plugin-recommended--zero-configuration)
8
+ [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
9
+ [![platforms](https://img.shields.io/badge/CI-windows%20%7C%20macos%20%7C%20linux-4c6ef5)](#verified-and-not-verified)
10
+
11
+ English | [中文](#中文说明)
12
+
13
+ ---
14
+
15
+ ## Install
16
+
17
+ Five routes in. They all end with **15 tools registered** in your agent. Pick one.
18
+
19
+ ### 1. DSH plugin (recommended — zero configuration)
20
+
21
+ ```bash
22
+ # 1. install the package into your DSH profile
23
+ dsh plugin add @twinsearth/w2m-dsh-plugin
24
+
25
+ # 2. name it in the profile's bundle list — a package that is not bundled never loads
26
+ # ~/.dsh/profiles/<profile>/dsh.profile.yml
27
+ plugins:
28
+ - '@twinsearth/w2m-dsh-plugin'
29
+
30
+ # 3. restart DSH; joining happens at mount time, so a hot reload is not enough
31
+ ```
32
+
33
+ Since **1.1.15** the agent starts by itself (`autoStartAgent` defaults to `true`): a freshly installed machine
34
+ derives its account identity, joins the account's fleet, and shows up in `w2m_devices` with no pairing step.
35
+ To turn that off, patch the **existing** `w2m` row — do not add a second one:
36
+
37
+ ```yaml
38
+ # cordis.patch.yml
39
+ - id: w2m
40
+ config:
41
+ autoStartAgent: false
42
+ ```
43
+
44
+ ### 2. npm (MCP server, any client)
45
+
46
+ ```bash
47
+ npm install -g @twinsearth/w2m-dsh-plugin
48
+ w2m-mcp --help
49
+ ```
50
+
51
+ Point any MCP client at `w2m-mcp`. **Zero runtime dependencies**; the package ships the relay, the agent and the
52
+ tools.
53
+
54
+ ### 3. Claude Code
55
+
56
+ ```bash
57
+ /plugin marketplace add TwinsEarth/dsh-windows2macos
58
+ /plugin install w2m@twinsearth
59
+ ```
60
+
61
+ The manifests live in [`.claude-plugin/`](.claude-plugin/) and are version-locked to the release.
62
+
63
+ ### 4. Codex and other MCP-capable CLIs
64
+
65
+ ```toml
66
+ # copy this block into your own codex/mcp-servers.toml
67
+ [mcp_servers.w2m]
68
+ command = "npx"
69
+ args = ["-y", "@twinsearth/w2m-dsh-plugin@1.1.19", "w2m-mcp"]
70
+ ```
71
+
72
+ ### 5. Verified release tarball (offline)
73
+
74
+ From the `v1.1.19` release page download **both** files — `twinsearth-w2m-dsh-plugin-1.1.19.tgz` and `SHA256SUMS` —
75
+ then:
76
+
77
+ ```bash
78
+ curl -LO https://github.com/TwinsEarth/dsh-windows2macos/releases/download/v1.1.19/twinsearth-w2m-dsh-plugin-1.1.19.tgz
79
+ sha256sum -c SHA256SUMS # verify before installing, always
80
+ npm install -g ./twinsearth-w2m-dsh-plugin-1.1.19.tgz
81
+ ```
82
+
83
+ Every release carries `SHA256SUMS` for exactly this reason. If a release is missing it, do not install that release.
84
+
85
+ ### 6. Beta (prerelease — never on npm)
86
+
87
+ A beta is a **GitHub prerelease**, not an npm version. Nothing about it is resolved by `npm install`, and `latest` never
88
+ points at one. It exists for a developer who has been granted `beta-access` and accepts that it can break.
89
+
90
+ ```bash
91
+ # 1. find the newest prerelease (a beta is marked "Pre-release" on the releases page)
92
+ # https://github.com/TwinsEarth/dsh-windows2macos/releases -> latest entry labelled Pre-release
93
+ # 2. download BOTH files attached to it: twinsearth-w2m-dsh-plugin-<version>.tgz and SHA256SUMS
94
+ sha256sum -c SHA256SUMS # verify before installing, always
95
+ npm install -g ./twinsearth-w2m-dsh-plugin-<version>.tgz
96
+ ```
97
+
98
+ - **Access**: the beta workflow waits on the protected `beta-access` environment, so a reviewer must approve each run
99
+ before the artifact exists at all.
100
+ - **Risk acknowledgment**: install it only if you have read [SECURITY.md](SECURITY.md) and accept that a beta may fail
101
+ where a stable release does not.
102
+ - **Going back**: `npm install -g @twinsearth/w2m-dsh-plugin@latest` returns you to the stable channel.
103
+ - **Why not npm**: there is no `npm publish` line in the beta workflow. A channel cannot leak into the default install if
104
+ the publication cannot happen at all.
105
+
106
+
107
+ ---
108
+
109
+ ## Quick start
110
+
111
+ ```text
112
+ w2m_status # am I paired? which fleet?
113
+ w2m_devices # what is in the fleet?
114
+ w2m_run mode="replicate" command_argv=["git","rev-parse","HEAD"]
115
+ w2m_wait task_id="..."
116
+ ```
117
+
118
+ `replicate` runs on every online machine; the verdict is `consistent` only when every machine returned the same
119
+ result **and** the same commit anchor. `split` gives each machine a slice, `pipeline` chains stages, `broadcast`
120
+ names one executor and reports observers, `compose` merges results.
121
+
122
+ ---
123
+
124
+ ## What it is
125
+
126
+ An installable plugin that turns a set of machines into **one fleet**, keyed by **your account**, not by a
127
+ hand-maintained host list.
128
+
129
+ | Piece | Responsibility |
130
+ |---|---|
131
+ | **Relay** | a small HTTP server: task routing, leases, SSE, blob storage. Run it anywhere your machines can reach. |
132
+ | **Agent** | runs on each machine: pairs once, executes allow-listed commands, heartbeats. |
133
+ | **Plugin** | the tools your agent calls. |
134
+ | **Admission** (1.1.14+) | a new machine asks to join; an online member approves, **10 s auto-approves**, and a machine admitted in the last 24 h can be removed immediately. |
135
+
136
+ ### Tools
137
+
138
+ `w2m_status` · `w2m_devices` · `w2m_run` · `w2m_wait` · `w2m_history` · `w2m_stats` · `w2m_report` ·
139
+ `w2m_memory` · `w2m_approvals` · `w2m_cancel` · `w2m_council` · `w2m_chat` · `w2m_update` `w2m_tasks`
140
+ - `w2m_intent` — map a structured intent (run-tests, node-version, git-status, git-head, fleet-facts, shard-probe) onto an allow-listed command; unknown intents are refused by name, never guessed.
141
+
142
+ ### Environment a dispatched command receives (1.1.15+)
143
+
144
+ Values come from the offer, never from argv, so one machine cannot reshape a fleet-wide split.
145
+
146
+ | Variable | Meaning |
147
+ |---|---|
148
+ | `W2M_INDEX` / `W2M_INDEX_TOTAL` | this machine's slice number and how many slices exist |
149
+ | `W2M_STAGE_INDEX` | the pipeline stage; deliberately separate from `W2M_INDEX` |
150
+ | `W2M_TASK_ID` / `W2M_MODE` | the task and its mode |
151
+ | `W2M_MACHINE_ID` / `W2M_MACHINE_NAME` | this machine |
152
+
153
+ So `command_argv=["node","-e","process.exit(Number(process.env.W2M_INDEX)+1)"]` over four machines exits 1/2/3/4.
154
+ Single-machine behaviour is tested; the four-machine run is listed as **not verified** below.
155
+
156
+ ---
157
+
158
+ ## Fleet model
159
+
160
+ Identity is **the account**, not a list of hosts. A machine derives an Ed25519 identity from its account
161
+ material; the relay registers the matching public key when the account's first machine pairs; every later machine
162
+ proves the same account with a one-time signed challenge.
163
+
164
+ If the old pairing path fails for a **named** reason only — invalid pairing code, unreachable relay, timeout — the
165
+ machine falls back to asking for admission. Any other failure needs a human and does **not** change mechanisms. A
166
+ rejection is final; the machine does not re-ask.
167
+
168
+ The relay is authoritative for every time window and every quorum, because client clocks disagree (measured:
169
+ tens of seconds between machines in one fleet):
170
+
171
+ - **10 s** without a decision auto-approves, but only while at least one member is **online** — an empty fleet
172
+ cannot consent, and that refusal is named (`NO_ONLINE_MEMBER`);
173
+ - within **24 h** of admission, any online member may remove the machine immediately;
174
+ - after 24 h a single machine may only **open a vote**, whose quorum set is **frozen at open time** so a voter
175
+ going offline mid-vote cannot move the bar;
176
+ - the quorum is 3+ old members need **≥ 50 %** (rounded up); a two-member fleet needs **one** vote;
177
+ - a machine offline for more than **72 hours** leaves the **list** — it is never deleted, its records stay, and it
178
+ returns by reporting again.
179
+
180
+ ---
181
+
182
+ ## Security posture
183
+
184
+ - The command allow-list is **argv-prefix** based and rejects argv-reparsing flags (`--import`, appended `-e`) with
185
+ `COMMAND_REPARSE_ARGUMENT`. It is a boundary, not a sandbox.
186
+ - Dispatching tasks requires an operator token. A device token cannot dispatch.
187
+ - Admission approval requires at least one member online.
188
+ - The account signing key is derived from the fleet key, **never** from the account code — the account code is
189
+ written to logs, and deriving a key from a readable value would hand out the account identity.
190
+ - Every release is checksummed.
191
+
192
+ ---
193
+
194
+ ## Verified and not verified
195
+
196
+ A green badge that hides this is worse than no badge.
197
+
198
+ **Verified:** 2458 tests in 102 files (2444 pass, 14 skip), 15 pre-flight checks, CI green on Windows, macOS and
199
+ Linux, npm + GitHub release + checksums consistent, end-to-end fleet runs on a real Linux/Mac/Windows triple.
200
+
201
+ **Not verified / known gaps:**
202
+
203
+ | Gap | State |
204
+ |---|---|
205
+ | Approval **popup** control | the relay enforces the rules and `w2m_approvals` · `w2m_cancel` lists and settles requests, but the 10 s countdown is relay data shown in a list, not a dedicated popup |
206
+ | Four-machine `W2M_INDEX` run | single-machine behaviour tested; the fleet-wide 1/2/3/4 run has not been executed |
207
+ | Fleet chat / broadcast channel | message store + quota implemented and tested; encryption, sharding, endpoints and the panel are **not built** |
208
+ | P2P on some macOS hosts | `P2P_PUNCH_FAILED: Unknown cipher` observed; the task falls back to the relay path |
209
+
210
+ ---
211
+
212
+ ## Troubleshooting
213
+
214
+ | Symptom | Cause | Fix |
215
+ |---|---|---|
216
+ | installed, but `w2m_devices` answers 401 | the agent never started, so no device token | check `autoStartAgent` is not `false`; restart DSH (not a hot reload) |
217
+ | machine missing from the fleet | the plugin is not in `dsh.profile.bundles` | add it — a package that is not bundled never loads |
218
+ | `COMMAND_NOT_ALLOWED` | command not in the allow-list | extend the allow-list; argv is matched by prefix |
219
+ | verdict always `partial` | a stale lease from a machine that is down | fixed in 1.1.2: a dispatch leases only online, non-silent machines |
220
+ | `P2P_PUNCH_FAILED` | NAT, or a host crypto gap | it downgrades to the relay path; results are unaffected |
221
+
222
+ ---
223
+
224
+ ## Release channels
225
+
226
+ Three channels, and only one of them is what \
227
+ pm install\ gives you.
228
+
229
+ | Channel | Where it comes from | Who should use it |
230
+ |---|---|---|
231
+ | **stable** | npm \latest\ — a version the developer declared stable | everyone; this is the default install |
232
+ | **beta** | a GitHub **prerelease** (tarball + \SHA256SUMS\), **never on npm** | developers granted \eta-access\ who accept that it can fail |
233
+ | **latest build** | the newest of the two, whatever its channel | a developer who wants the newest code and knows the above |
234
+
235
+ A bump is a **beta by default**. npm receives a version only when the developer says, in so many words, that this one is a
236
+ stable release: a beta is published as a prerelease (so neither GitHub nor npm ever resolves it as \latest\), it is
237
+ downloadable only after the \eta-access\ environment approves the run, and installing it requires reading
238
+ [SECURITY.md](SECURITY.md) and accepting the risk.
239
+
240
+ There is no \
241
+ pm publish\ line in the beta workflow. That is deliberate — a channel cannot leak to the default install if
242
+ the publication cannot happen at all.
243
+ ---
244
+
245
+ ## Links
246
+
247
+ - [CHANGELOG](CHANGELOG.md) — every version, including what it deliberately did **not** ship
248
+ - [PLATFORMS](docs/PLATFORMS.md) — Claude Code, Codex and MCP wiring details
249
+ - [Releases](https://github.com/TwinsEarth/dsh-windows2macos/releases) — per-release evidence and checksums
250
+ - [Issues](https://github.com/TwinsEarth/dsh-windows2macos/issues)
251
+
252
+ MIT.
253
+
254
+ ---
255
+
256
+ ## 中文说明
257
+
258
+ **一个 DeepSeek 账号。一句话。你自己的每一台机器跑同一个项目。**
259
+
260
+ 以下内容与上方英文**逐节对应**,不是摘要。
261
+
262
+ ### 安装
263
+
264
+ 五种方式,最终都会在你的 agent 里注册 **15 tools registered**。任选其一。
265
+
266
+ #### 1. DSH 插件(推荐,零配置)
267
+
268
+ ```bash
269
+ # 1. 把包装进你的 DSH profile
270
+ dsh plugin add @twinsearth/w2m-dsh-plugin
271
+
272
+ # 2. 在 profile 的 bundle 列表里写上包名 —— 没进 bundle 的包永远不会被加载
273
+ # ~/.dsh/profiles/<profile>/dsh.profile.yml
274
+ plugins:
275
+ - '@twinsearth/w2m-dsh-plugin'
276
+
277
+ # 3. 重启 DSH。加入编队发生在 mount 时,热重载不够
278
+ ```
279
+
280
+ 从 **1.1.15** 起 agent 会自动启动(`autoStartAgent` 默认 `true`):新装的机器自行派生账号身份、加入该账号的
281
+ 编队,无需配对步骤就会出现在 `w2m_devices` 里。若要关闭,请 patch **已存在的** `w2m` 行,**不要新增第二行**:
282
+
283
+ ```yaml
284
+ # cordis.patch.yml
285
+ - id: w2m
286
+ config:
287
+ autoStartAgent: false
288
+ ```
289
+
290
+ #### 2. npm(MCP 服务器,任意客户端)
291
+
292
+ ```bash
293
+ npm install -g @twinsearth/w2m-dsh-plugin
294
+ w2m-mcp --help
295
+ ```
296
+
297
+ 把任意 MCP 客户端指向 `w2m-mcp`。**零运行时依赖**;包里同时带有 relay、agent 与工具。
298
+
299
+ #### 3. Claude Code
300
+
301
+ ```bash
302
+ /plugin marketplace add TwinsEarth/dsh-windows2macos
303
+ /plugin install w2m@twinsearth
304
+ ```
305
+
306
+ 清单文件位于 [`.claude-plugin/`](.claude-plugin/),版本与发行版锁定。
307
+
308
+ #### 4. Codex 及其它支持 MCP 的 CLI
309
+
310
+ ```toml
311
+ # 把这段复制进你自己的 codex/mcp-servers.toml
312
+ [mcp_servers.w2m]
313
+ command = "npx"
314
+ args = ["-y", "@twinsearth/w2m-dsh-plugin@1.1.19", "w2m-mcp"]
315
+ ```
316
+
317
+ #### 5. 校验过的发布包(离线)
318
+
319
+ 从 `v1.1.19` 的 release 页面下载**两个**文件 —— `twinsearth-w2m-dsh-plugin-1.1.19.tgz` 与 `SHA256SUMS` —— 然后:
320
+
321
+ ```bash
322
+ curl -LO https://github.com/TwinsEarth/dsh-windows2macos/releases/download/v1.1.19/twinsearth-w2m-dsh-plugin-1.1.19.tgz
323
+ sha256sum -c SHA256SUMS # 安装前务必校验
324
+ npm install -g ./twinsearth-w2m-dsh-plugin-1.1.19.tgz
325
+ ```
326
+
327
+ 每个 release 都带 `SHA256SUMS` 正是为了这一步。若某个 release 缺少它,**不要安装该版本**。
328
+
329
+ #### 6. Beta(预发布版 —— 不进 npm)
330
+
331
+ Beta 是 **GitHub prerelease**,不是 npm 版本:`npm install` 永远拿不到它,`latest` 也不会指向它。它是给**已获
332
+ `beta-access` 授权、且接受它可能出问题**的开发者用的。
333
+
334
+ ```bash
335
+ # 1. 在 releases 页面找标着 Pre-release 的最新条目
336
+ # https://github.com/TwinsEarth/dsh-windows2macos/releases
337
+ # 2. 下载该条目的两个文件:twinsearth-w2m-dsh-plugin-<版本>.tgz 与 SHA256SUMS
338
+ sha256sum -c SHA256SUMS # 安装前务必校验
339
+ npm install -g ./twinsearth-w2m-dsh-plugin-<版本>.tgz
340
+ ```
341
+
342
+ - **权限**:beta 流水线挂在受保护的 `beta-access` 环境上,**每次运行都需要审批者批准**,产物才会存在;
343
+ - **风险确认**:安装前请阅读 [SECURITY.md](SECURITY.md),并接受 beta 可能比稳定版更容易失败;
344
+ - **回退**:`npm install -g @twinsearth/w2m-dsh-plugin@latest` 即回到稳定版通道;
345
+ - **为什么不走 npm**:beta 工作流里**没有 `npm publish`** —— 只要发布动作不可能发生,这个通道就漏不进默认安装。
346
+
347
+
348
+ ### 快速开始
349
+
350
+ ```text
351
+ w2m_status # 我配对了吗?在哪个编队?
352
+ w2m_devices # 编队里有什么?
353
+ w2m_run mode="replicate" command_argv=["git","rev-parse","HEAD"]
354
+ w2m_wait task_id="..."
355
+ ```
356
+
357
+ `replicate` 在每一台在线机器上执行;只有当所有机器返回**相同结果**且**相同 commit 锚点**时,裁决才是
358
+ `consistent`。`split` 给每台机器一个分片,`pipeline` 串联多个阶段,`broadcast` 指定一个执行者并汇报观察者,
359
+ `compose` 合并结果。
360
+
361
+ ### 它是什么
362
+
363
+ 一个可安装插件,把一组机器变成**一个编队**,以**你的账号**为键,而不是靠手工维护的主机清单。
364
+
365
+ | 组件 | 职责 |
366
+ |---|---|
367
+ | **Relay** | 一个小型 HTTP 服务:任务路由、租约、SSE、blob 存储。放在任何机器可达的位置。 |
368
+ | **Agent** | 每台机器上运行:配对一次、执行白名单命令、心跳。 |
369
+ | **Plugin** | 你的 agent 调用的那些工具。 |
370
+ | **Admission**(1.1.14+) | 新机器申请加入;在线成员批准,**10 秒自动同意**;入队 24 小时内的机器可被立即移除。 |
371
+
372
+ ### 工具
373
+
374
+ `w2m_status` · `w2m_devices` · `w2m_run` · `w2m_wait` · `w2m_history` · `w2m_stats` · `w2m_report` ·
375
+ `w2m_memory` · `w2m_approvals` · `w2m_cancel` · `w2m_council` · `w2m_chat` · `w2m_update` `w2m_tasks`
376
+
377
+ ### 被派发命令会收到的环境变量(1.1.15+)
378
+
379
+ 取值来自 offer,**不来自 argv**,因此单台机器无法改变整个编队的分片:`W2M_INDEX` / `W2M_INDEX_TOTAL` /
380
+ `W2M_STAGE_INDEX` / `W2M_TASK_ID` / `W2M_MODE` / `W2M_MACHINE_ID` / `W2M_MACHINE_NAME`。
381
+
382
+ 因此 `command_argv=["node","-e","process.exit(Number(process.env.W2M_INDEX)+1)"]` 在四台机器上应分别退出
383
+ 1/2/3/4。**单机行为已测;四台机器的联跑尚未验证**(见下方"未验证")。
384
+
385
+ ### 编队模型
386
+
387
+ 身份是**账号**,不是主机清单。机器从账号材料派生 Ed25519 身份;账号的第一台机器配对时,relay 登记对应公钥;
388
+ 此后每台机器用一次性签名挑战自证同一个账号。
389
+
390
+ 只有当旧配对路径因**明确原因**失败时 —— 配对码无效、relay 不可达、超时 —— 机器才降级去申请入队。其它失败需要
391
+ 人工介入,**不会**切换机制。**拒绝即终态**,机器不会再次申请。
392
+
393
+ 所有时间窗与法定人数由 relay 权威判定,因为客户端时钟互不一致(实测:同一编队内相差数十秒):
394
+
395
+ - 无决策 **10 秒**后自动同意,但**仅当至少一名成员在线** —— 空编队无法同意,该拒绝有明确错误码
396
+ (`NO_ONLINE_MEMBER`);
397
+ - 入队后 **24 小时内**,任一在线成员可立即移除该机器;
398
+ - 超过 24 小时,单台机器只能**发起表决**,且法定人数集合在**发起时冻结**,投票者中途离线无法改变门槛;
399
+ - 法定人数:旧成员 ≥3 人需 **≥50%(向上取整)**;2 人编队的移除只要 **1 票**;
400
+ - 离线超过 **72 小时**的机器会离开**列表** —— 记录**永不删除**,它重新上报后会自动回来。
401
+
402
+ ### 安全姿态
403
+
404
+ - 命令白名单按 **argv 前缀**匹配,并拒绝 argv 重解析标志(`--import`、追加 `-e`),错误码
405
+ `COMMAND_REPARSE_ARGUMENT`。这是边界,不是沙箱。
406
+ - 派发任务需要 operator token;device token 不能派发。
407
+ - 审批入队要求至少一名成员在线。
408
+ - 账户签名密钥从 **fleet key** 派生,**绝不**用 account code —— account code 会写进日志,用可读值派生密钥等于
409
+ 交出账号身份。
410
+ - 每个发行版都有校验和。
411
+
412
+ ### 已验证与未验证
413
+
414
+ **一句真话胜过一块掩盖问题的绿牌。**
415
+
416
+ **已验证:** 102 个文件中的 2458 项测试(2444 通过、14 跳过)、15 项门禁检查、Windows / macOS / Linux 三平台
417
+ CI 全绿、npm 与 GitHub 发布及校验和一致、在真实的 Linux/Mac/Windows 三机编队上跑过端到端。
418
+
419
+ **未验证 / 已知缺口:**
420
+
421
+ | 缺口 | 状态 |
422
+ |---|---|
423
+ | 审批**弹窗**控件 | relay 强制规则、`w2m_approvals` · `w2m_cancel` 可列出并裁决,但 10 秒倒计时是列表里的 relay 数据,不是独立弹窗 |
424
+ | 四台机器的 `W2M_INDEX` 联跑 | 单机行为已测;编队级 1/2/3/4 尚未执行 |
425
+ | 编队聊天 / 广播通道 | 消息存储与配额已实现并测试;加密、分片、端点与面板**尚未构建** |
426
+ | 部分 macOS 主机的 P2P | 观察到 `P2P_PUNCH_FAILED: Unknown cipher`;任务会降级走 relay 路径 |
427
+
428
+ ### 故障排查
429
+
430
+ | 症状 | 原因 | 处理 |
431
+ |---|---|---|
432
+ | 装好了,但 `w2m_devices` 返回 401 | agent 从未启动,因此没有 device token | 确认 `autoStartAgent` 不是 `false`;**重启 DSH**(热重载无效) |
433
+ | 编队里看不到这台机器 | 插件没写进 `dsh.profile.bundles` | 加进去 —— 不在 bundle 里的包永远不会加载 |
434
+ | `COMMAND_NOT_ALLOWED` | 命令不在白名单 | 扩充白名单;argv 按前缀匹配 |
435
+ | 裁决总是 `partial` | 来自已下线机器的陈旧租约 | 1.1.2 已修:派发只对在线且未沉默的机器建租约 |
436
+ | `P2P_PUNCH_FAILED` | NAT,或宿主密码学能力缺口 | 会自动降级到 relay 路径,结果不受影响 |
437
+
438
+ ### 发布通道(中文)
439
+
440
+ 三个通道,其中只有一个是你 \
441
+ pm install\ 默认拿到的。
442
+
443
+ | 通道 | 来源 | 适合谁 |
444
+ |---|---|---|
445
+ | **stable** | npm \latest\,开发者明确声明为稳定版 | 所有人;默认安装就是它 |
446
+ | **beta** | GitHub **prerelease**(tarball + \SHA256SUMS\),**绝不进 npm** | 被授予 \eta-access\、且接受它可能失败的开发者 |
447
+ | **最新构建** | 两者中版本较新的那个,不论通道 | 想要最新代码、并理解上述含义的开发者 |
448
+
449
+ **默认的 bump 是 beta**。只有开发者明确说明"这次是稳定版",npm 才会收到该版本:beta 以 prerelease 形式发布(因此
450
+ GitHub 与 npm 都不会把它解析成 \latest\),只有在 \eta-access\ 环境审批通过后才能下载,安装前需阅读
451
+ [SECURITY.md](SECURITY.md) 并自担风险。
452
+
453
+ beta 工作流里**没有 \
454
+ pm publish\ 这一行**,这是刻意的 —— 只要发布动作不可能发生,这个通道就不可能泄漏到默认安装。
455
+ ### 链接
456
+
457
+ - [CHANGELOG](CHANGELOG.md) —— 每个版本,包括它**故意没有**交付的内容
458
+ - [PLATFORMS](docs/PLATFORMS.md) —— Claude Code、Codex 与 MCP 的接线细节
459
+ - [Releases](https://github.com/TwinsEarth/dsh-windows2macos/releases) —— 每个版本的证据与校验和
460
+ - [Issues](https://github.com/TwinsEarth/dsh-windows2macos/issues)
461
+
462
+ MIT。