@twinsearth/w2m-dsh-plugin 2.0.8 → 2.0.10
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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +13 -0
- package/TEST-CASES-JOINT-FLEET-2.0.8.md +119 -0
- package/TEST-PLAN-JOINT-FLEET-2.0.8.md +99 -0
- package/bin/remote-s2-capture.mjs +116 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/DSH-UPGRADE-2.0.8.md +129 -0
- package/docs/TRANSFER-LAYER.md +162 -0
- package/docs/acceptance/2.0.10/RELEASE-2100.json +19 -0
- package/docs/acceptance/2.0.9/RELEASE-2090.json +24 -0
- package/docs/adr/0002-p2p-transport.md +67 -0
- package/docs/plans/REMOTE-DESKTOP-2.1.0.md +153 -0
- package/lib/client.js +29 -3
- package/package.json +5 -3
- package/plugin.json +1 -1
- package/src/agent/agent.mjs +7 -0
- package/src/agent/discovery.mjs +81 -0
- package/src/agent/iroh-sidecar.mjs +149 -0
- package/src/agent/p2p-node.mjs +11 -1
- package/src/agent/p2p-race.mjs +105 -0
- package/src/agent/p2p.mjs +105 -18
- package/src/agent/path-scheduler.mjs +106 -0
- package/src/agent/transfer/artifact-ref-v3.mjs +93 -0
- package/src/agent/transfer/ferry.mjs +12 -1
- package/src/agent/transfer/router.mjs +80 -0
- package/src/agent/transfer/want-block.mjs +46 -0
- package/src/agent/transport.mjs +146 -0
- package/src/plugin/tools.mjs +35 -25
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
|
|
8
|
-
"version": "2.0.
|
|
8
|
+
"version": "2.0.10"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "w2m-fleet",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Read fleet state and dispatch commands across the machines registered to one account. Read-only MCP surface: the five exposed tools cannot change anything.",
|
|
15
|
-
"version": "2.0.
|
|
15
|
+
"version": "2.0.10",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "2.0.
|
|
29
|
+
"version": "2.0.10"
|
|
30
30
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w2m-fleet",
|
|
3
3
|
"description": "Drive the same project on online Windows, macOS and Linux machines from one account: read fleet state, dispatch commands and read results. The MCP surface is deliberately read-only.",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.10",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ All notable changes to this project are documented here.
|
|
|
7
7
|
- `w2m_intent`: structured intents mapped onto allow-listed commands; unknown actions, parameters and types
|
|
8
8
|
are refused by name (the fleet does not invent commands).
|
|
9
9
|
|
|
10
|
+
## [2.0.10] - 2026-10-31
|
|
11
|
+
|
|
12
|
+
- remote-view S2: ship scripts/remote-s2-capture.mjs so macOS capture/TCC probes run as a normal script (no node -e, no reparse refusal); measures 1/2 fps capture timing, JPEG size, ffmpeg presence, writes w2m-s2-result.json and supports exit-code summary.
|
|
13
|
+
- tests: remote-s2-capture summary/platform contract.
|
|
14
|
+
|
|
15
|
+
## [2.0.9] - 2026-10-30
|
|
16
|
+
|
|
17
|
+
- P2P 可靠性:ACK 驱动窗口、自适应窗口、RFC 6298 RTT/自适应 RTO;明文通道复用改为强制新握手。
|
|
18
|
+
- 传输抽象:P2PTransport / udp-v1、能力协商 + 竞速、PathScheduler 评分与 make-before-break、iroh sidecar 契约(假二进制测试)。
|
|
19
|
+
- 传输层:ArtifactRef v3(SHA-256 Merkle + BLAKE3 回退标记)、xfer 帧路由、P0-a 通道适配器、verifyRefV2 自洽校验与冲突驱逐。
|
|
20
|
+
- 编队:w2m_status.fleet / FLEET_UNBOUND、fleetKey 自动重新准入、底部状态栏(本编队/全服/Relay 圆点)。
|
|
21
|
+
- 测试:新增 transport/race/scheduler/discovery/iroh-sidecar/xfer-channel/router/recovery/ref-v3 等套件。
|
|
22
|
+
|
|
10
23
|
## [2.0.8] - 2026-10-29
|
|
11
24
|
|
|
12
25
|
- P2P 加密:新增 RFC 8439 ChaCha20-Poly1305 纯 JS 回退,Electron/BoringSSL 缺少 chacha 时自动启用,wire 字节与原生实现一致;保留 P2P_CIPHER_UNAVAILABLE 诊断。
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# 联合开发 & 编队协作测试用例 v2.0.8
|
|
2
|
+
|
|
3
|
+
图例:✅ 已执行通过;⚠️ 部分通过/有限证据;🚫 被环境阻断;⬜ 计划未执行。
|
|
4
|
+
配套计划:[TEST-PLAN-JOINT-FLEET-2.0.8.md](TEST-PLAN-JOINT-FLEET-2.0.8.md)
|
|
5
|
+
|
|
6
|
+
## A. 联合开发(JD)
|
|
7
|
+
|
|
8
|
+
| ID | 级别 | 前置 | 步骤 | 预期 | 状态 / 证据 |
|
|
9
|
+
|---|---|---|---|---|---|
|
|
10
|
+
| JD-01 | P0 | CLI/stateDir | `w2m-joint plan` 创建 2 步计划 | 返回 plan_sha256;可 list/get;命令哈希稳定 | ✅ 既有 evidence(joint plan 92e299aa…) |
|
|
11
|
+
| JD-02 | P0 | 无签名审批 | `--allow-unsigned-approval` 派发 | 拒绝 `UNKNOWN_FLAG`,无 POST | ✅ joint-cli |
|
|
12
|
+
| JD-03 | P0 | operator key | 审批后派发一次 | relay 收到 1 次;审计 sending→sent;审批被消费 | ✅ 真实 relay 2.0.5 |
|
|
13
|
+
| JD-04 | P0 | 已消费审批 | 重放同一审批 | `APPROVAL_ALREADY_USED`,0 次 POST | ✅ joint-cli |
|
|
14
|
+
| JD-05 | P0 | 伪造/过期审批 | 篡改签名或超 TTL | `APPROVAL_SIGNATURE_INVALID` / `APPROVAL_EXPIRED` | ✅ plan-store/operator-key |
|
|
15
|
+
| JD-06 | 🛡 | 审批字段 | 改 plan_sha / base / target | 拒绝,绑定字段不符 | ✅ merge-approval;JOINT-R5 |
|
|
16
|
+
| JD-07 | P0 | DSH 在线 | 计划含 DSH/ToFuture 两步,派发 | 两步均 done;progress=complete | ⚠️ 曾 s1 done/s2 refused(allowlist);需复测 |
|
|
17
|
+
| JD-08 | P0 | 已合并 | rollback ref 恢复 | 目标回旧 head;审批不可复用 | ⬜ 现场演练 |
|
|
18
|
+
| JD-09 | P1 | 合并冲突 | 3-way/CAS 冲突 | 具名冲突,不静默覆盖 | ✅ merge-engine 套件 |
|
|
19
|
+
|
|
20
|
+
## B. 编队协作(FC)
|
|
21
|
+
|
|
22
|
+
| ID | 级别 | 前置 | 步骤 | 预期 | 状态 / 证据 |
|
|
23
|
+
|---|---|---|---|---|---|
|
|
24
|
+
| FC-01 | P0 | relay 在线 | `w2m_devices` | 只返回本编队设备,口径含 online/offline_reason | ✅ ToFuture/DSH/macmini 004945 |
|
|
25
|
+
| FC-02 | P0 | 两机 | 派发 `node --version`(assignee) | verdict=consistent;签名=signed | ✅ 任务 01M64TEK…(ToFuture) |
|
|
26
|
+
| FC-03 | P0 | 两机 | 不指定 assignee 派发 | **测试纪律禁止**;应显式目标 | ✅ TD-01 已固化 |
|
|
27
|
+
| FC-04 | P0 | 一机离线 | 派发到离线机 | lease expired → partial,不伪造 ok | ✅ 任务 01M64TR…(DSH expired) |
|
|
28
|
+
| FC-05 | P1 | 两机在线 | 编队聊天收发 | 双方可读,密文 v2 门控生效 | ✅ 既有 chess 会话 |
|
|
29
|
+
| FC-06 | P1 | composer | 选择目标机后派发 | 仅选中机收到任务 | ⚠️ 需 UI 现场复核 |
|
|
30
|
+
| FC-07 | P0 | 设备 token | `fleet_id` 为空 | 不显示“已加入”,返回 FLEET_UNBOUND | ✅ fleet-binding 单测 4/4 |
|
|
31
|
+
| FC-08 | P0 | 同机双 agent | 同时起两份 localside | 必须拒绝/二选一,避免版本跳变 | ⬜ 需负例演练 |
|
|
32
|
+
| FC-09 | P1 | 结果回传 | P2P 结果 ACK 超时 | 回退 relay 并显式记录 | ✅ P2P_ACK_TIMEOUT 可见 |
|
|
33
|
+
|
|
34
|
+
## C. 编队绑定与版本(FB)
|
|
35
|
+
|
|
36
|
+
| ID | 级别 | 前置 | 步骤 | 预期 | 状态 / 证据 |
|
|
37
|
+
|---|---|---|---|---|---|
|
|
38
|
+
| FB-01 | P0 | token 有效 | 读取自身设备记录 | `fleet_id` 非空 → state=bound | ✅ ToFuture fleet_id=004945 |
|
|
39
|
+
| FB-02 | P0 | token 有效 + fleet_id=null | 启动 agent | `FLEET_UNBOUND`;有 fleetKey 时自动 re-admission | ✅ 单测 + 真实修复 |
|
|
40
|
+
| FB-03 | P0 | 错 relay 的 token | 启动 | 401 → 重新配对,不静默 | ✅ 实测 202.182→43.142 |
|
|
41
|
+
| FB-04 | P0 | 无 fleetKey | 启动 | 返回可执行 remedy,不显示已加入 | ✅ fleet-binding 单测 |
|
|
42
|
+
| FB-05 | P0 | 配置 2.0.8 | `w2m_status.fleet` | 含 fleet_id/state/code/token_present/remedy | ✅ 宿主 2.0.8 输出 |
|
|
43
|
+
| FB-06 | P0 | 三机同名 | 读 relay caps | 版本稳定,无 2.0.7/2.0.8 跳变 | ⚠️ macmini 仍 2.0.7 |
|
|
44
|
+
| FB-07 | P1 | 备用链路 | 起 secondary-link | 不得覆盖主 device.json / 不得抢编队 | ✅ 已禁用 keeper |
|
|
45
|
+
| FB-08 | P1 | 计划任务 | 禁用 standby keeper 后重启 | 本机不再出现 ToFuture-standby | ✅ 已禁用 |
|
|
46
|
+
|
|
47
|
+
## D. P2P 加密握手(PH)
|
|
48
|
+
|
|
49
|
+
| ID | 级别 | 前置 | 步骤 | 预期 | 状态 / 证据 |
|
|
50
|
+
|---|---|---|---|---|---|
|
|
51
|
+
| PH-01 | P0 | Electron 宿主 | 探测 chacha | 原生缺失时 fallback 可用 | ✅ 宿主 native=false,caps 6/6 |
|
|
52
|
+
| PH-02 | P0 | JS/原生双端 | AEAD 交叉验证 | 密文逐字节一致、双向可解、篡改拒绝 | ✅ 52/52 |
|
|
53
|
+
| PH-03 | P0 | 非本机目标 | 派发一次任务 | 两端 handshakes≥1、encrypted≥1、refused=0 | ⚠️ 2026-10-30 复测 task 01M64WBW…:`Unknown cipher` 消失,但 `P2P_FRAGMENT_UNACKED` + `P2P_CHANNEL_CLOSED`;DSH 仍 `COMMAND_NOT_ALLOWED`;handshakes 仍 0 |
|
|
54
|
+
| PH-04 | P0 | 本机自派 | 向 ToFuture 派发 | 明文是设计行为,不作握手证据 | ✅ 已确认 NO_KEY/PLAINTEXT_PEER |
|
|
55
|
+
| PH-05 | P1 | 旧版对端 | 2.0.7 对 2.0.8 | 明确错误/拒绝,不静默降级 | ⬜ 需 macmini 升级前后对比 |
|
|
56
|
+
| PH-06 | P1 | stateDir 缺失 | 结果回传 | `P2P_NO_STATE_DIR` 可见,回退 relay | ✅ 任务 01M64TE… envelope 证据 |
|
|
57
|
+
|
|
58
|
+
## E. 文件 E2E(XF)
|
|
59
|
+
|
|
60
|
+
| ID | 级别 | 前置 | 步骤 | 预期 | 状态 / 证据 |
|
|
61
|
+
|---|---|---|---|---|---|
|
|
62
|
+
| XF-01 | P0 | 本地 | 分块 + bitmap + 逐块校验 | 乱序/续传/篡改/穿越均拒绝 | ✅ xfer-foundation/ferry 12/12 |
|
|
63
|
+
| XF-02 | P0 | relay 在线 | publishRef → listPublishedRefs | 同 rootCid 可读回,控制面无字节 | ✅ 真实 relay 2.0.5 |
|
|
64
|
+
| XF-03 | P0 | 单机 | sender/receiver 双进程 loopback | rootCid/落盘 SHA 一致 | ✅ 262144B verified=true |
|
|
65
|
+
| XF-04 | P0 | 单机 | `--peer` 候选地址探测 | 选中候选(192.168.56.1),verified=true | ✅ 131072B,artifact.ready=true |
|
|
66
|
+
| XF-05 | P0 | 两机 | 跨机 sender/receiver | 对端落盘一致,relay 零字节 | 🚫 DSH 离线;macmini allowlist |
|
|
67
|
+
| XF-06 | P0 | 传输中断 | kill 接收端再重启 | bitmap 续传,根哈希最终一致 | ⬜ 计划 |
|
|
68
|
+
| XF-07 | 🛡 | 路径穿越 | destination=`../x` | 拒绝(safeRelative) | ✅ ferry 单测 |
|
|
69
|
+
| XF-08 | P0 | 完成 | artifact.ready | relay 决策可见,仅元数据 | ✅ 实测 artifact_ready_published=true |
|
|
70
|
+
|
|
71
|
+
## F. 发布门禁(RG)
|
|
72
|
+
|
|
73
|
+
| ID | 级别 | 步骤 | 预期 | 状态 / 证据 |
|
|
74
|
+
|---|---|---|---|---|
|
|
75
|
+
| RG-01 | P0 | commit | 目标分支含 v2.0.8 | ✅ `0ecedb4c…b526d7` |
|
|
76
|
+
| RG-02 | P0 | tag | `v2.0.8` 指向同一 commit | ✅ |
|
|
77
|
+
| RG-03 | P0 | GitHub Release | 200,正文含变更/回滚 | ✅ |
|
|
78
|
+
| RG-04 | P0 | npm | 2.0.8=latest,shasum 一致 | ✅ `adf91572…` |
|
|
79
|
+
| RG-05 | P0 | CI | success | ✅ |
|
|
80
|
+
| RG-06 | P0 | Compat | success | ✅ 双 run |
|
|
81
|
+
| RG-07 | P0 | Release workflow | success | ✅ |
|
|
82
|
+
| RG-08 | P0 | healthz | relay 在线且 features 符合 | ✅ 2.0.5 + chat-enc-v2 |
|
|
83
|
+
| RG-09 | P0 | preflight | GREEN,0 fail | ✅ 3267 tests / 3246 pass / 21 skip |
|
|
84
|
+
|
|
85
|
+
## G. 安全负例(SEC)
|
|
86
|
+
|
|
87
|
+
| ID | 级别 | 步骤 | 预期 | 状态 |
|
|
88
|
+
|---|---|---|---|---|
|
|
89
|
+
| SEC-01 | 🛡 | 伪造审批签名 | 拒绝,无派发 | ✅ |
|
|
90
|
+
| SEC-02 | 🛡 | 重放审批 | 拒绝 | ✅ |
|
|
91
|
+
| SEC-03 | 🛡 | 空 allowlist 派发 | agent 拒绝,具名 COMMAND_NOT_ALLOWED | ✅ macmini/旧 ToFuture |
|
|
92
|
+
| SEC-04 | 🛡 | token 写入文档/技能 | 扫描拒绝 | ✅ skills/preflight |
|
|
93
|
+
| SEC-05 | 🛡 | relay 存文件字节 | 拒绝 DATA_PLANE_REFUSED | ✅ |
|
|
94
|
+
| SEC-06 | 🛡 | 明文聊天镜像 | 无 feature 时保持 v1 | ✅ chat-crypto-aad |
|
|
95
|
+
|
|
96
|
+
## H. 混合版本治理(MV)
|
|
97
|
+
|
|
98
|
+
| ID | 级别 | 步骤 | 预期 | 状态 |
|
|
99
|
+
|---|---|---|---|---|
|
|
100
|
+
| MV-01 | P0 | 统计 26 台名册 | 区分 bound/unknown/old,不用 state 字段 | ✅ 26 台 / 20 台 null / ~20 编队 |
|
|
101
|
+
| MV-02 | P0 | 三机统一 2.0.8 | 三机 caps=2.0.8 | ⚠️ ToFuture/DSH 2.0.8,macmini 2.0.7 |
|
|
102
|
+
| MV-03 | P0 | 版本门 | 混合期 warn,不 enforce | ✅ 默认 warn |
|
|
103
|
+
| MV-04 | P1 | 发布后 10 分钟 | devices_unbound=0 | ⬜ 需监控指标上线 |
|
|
104
|
+
|
|
105
|
+
## I. 测试纪律(TD)
|
|
106
|
+
|
|
107
|
+
| ID | 要求 | 状态 |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| TD-01 | 派发必须显式 assignee/target_machines | ✅ 已固化 |
|
|
110
|
+
| TD-02 | 禁止裸 replicate 全 fleet | ✅ 本轮 23 台泛洪已取消并记录 |
|
|
111
|
+
| TD-03 | 无 assignee 负例仅隔离环境且立即取消 | ✅ |
|
|
112
|
+
| TD-04 | plugin_version=null 只观测,不纳入验收 | ✅ |
|
|
113
|
+
| TD-05 | 未实跑项标 PLANNED/BLOCKED,不写成通过 | ✅ 本文档执行 |
|
|
114
|
+
|
|
115
|
+
## 当前汇总
|
|
116
|
+
|
|
117
|
+
- P0 已通过:JD 1–6、FC 1/2/4/7、FB 1–5、PH 1/2/4、XF 1–4/7/8、RG 1–9、SEC 1–6;
|
|
118
|
+
- 阻断:PH-03、XF-05(DSH 离线 / macmini allowlist);
|
|
119
|
+
- 待补:JD-07 复测、JD-08 回滚、FC-08 双实例、XF-06 续传、MV-02 三机统一。
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# 联合开发 & 编队协作测试计划 v2.0.8
|
|
2
|
+
|
|
3
|
+
状态:执行中(v2.0.8 已发布,三机版本尚未统一)
|
|
4
|
+
基线:commit `0ecedb4c658914bbf3dd901419f11c4920b526d7` / npm `2.0.8`
|
|
5
|
+
配套用例:[TEST-CASES-JOINT-FLEET-2.0.8.md](TEST-CASES-JOINT-FLEET-2.0.8.md)
|
|
6
|
+
|
|
7
|
+
## 1. 目标与范围
|
|
8
|
+
|
|
9
|
+
验证两条主能力:
|
|
10
|
+
|
|
11
|
+
1. **联合开发(Joint Dev)**:计划 → Ed25519 人工审批 → relay 派发 → 执行 → 进度投影 → 审计 → 合并/回滚;
|
|
12
|
+
2. **编队协作(Fleet)**:设备发现/编队绑定 → 聊天 → 目标机选择 → 跨机派发 → 结果回收 → 文件 E2E。
|
|
13
|
+
|
|
14
|
+
新增 v2.0.8 专项:
|
|
15
|
+
|
|
16
|
+
- 编队绑定诊断 `w2m_status.fleet` 与 `FLEET_UNBOUND` 自动纠正;
|
|
17
|
+
- P2P 加密握手(ChaCha20-Poly1305 纯 JS 回退);
|
|
18
|
+
- 文件传输 want-block / sender-receiver 双角色 / 候选地址探测;
|
|
19
|
+
- 八项发布门禁。
|
|
20
|
+
|
|
21
|
+
不在范围:relay 升级(保持 2.0.5)、CRDT(ADR only)、未发布客户端适配。
|
|
22
|
+
|
|
23
|
+
## 2. 环境与实例
|
|
24
|
+
|
|
25
|
+
| 实例 | 机器 | 角色 | 已知状态 |
|
|
26
|
+
|---|---|---|---|
|
|
27
|
+
| E1 | ToFuture `b53a37f4…` | 发射台 / 本机 | agent 2.0.8 在线,编队 004945,allowlist `["node"]` |
|
|
28
|
+
| E2 | DSH `1bef9935…` | 跨机执行 | 编队 004945;2026-10-30 曾离线,需先恢复 |
|
|
29
|
+
| E3 | macmini `b1b19d49…` | 跨机执行 | 2.0.7,allowlist 空,SSH 未开 |
|
|
30
|
+
| R1 | relay 43.142.74.71 | 主 relay | 2.0.5 冻结,features:target-machines/result-projection/chat-enc-v2 |
|
|
31
|
+
| R2 | relay 202.182.123.154 | 备用 relay | 仅备用链路,不参与主验收 |
|
|
32
|
+
|
|
33
|
+
名册口径(必须按此判定):
|
|
34
|
+
|
|
35
|
+
- 真正在线:`online===true && offline_reason===null`;
|
|
36
|
+
- 版本未知:`caps.plugin_version` 缺失,**不等于旧版**;
|
|
37
|
+
- 全服设备数来自 `/healthz`,逐台版本只来自 `/v1/devices`(按编队过滤)。
|
|
38
|
+
|
|
39
|
+
## 3. 测试分层
|
|
40
|
+
|
|
41
|
+
| 层 | 内容 | 入口 |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| L0 静态 | lint、schema、claims、Skills、Agent Plugins | `node scripts/preflight.mjs` |
|
|
44
|
+
| L1 单元 | fleet-binding、chacha、xfer、joint、chat、version gate | `node --test test/<name>.test.mjs` |
|
|
45
|
+
| L2 契约 | MCP 工具、relay 路由、acceptance records | preflight 内建 |
|
|
46
|
+
| L3 单机 E2E | plan→approve→dispatch→progress→audit(同机) | `bin/w2m-joint.mjs` |
|
|
47
|
+
| L4 双机 E2E | 跨机派发、聊天、文件 sender/receiver | relay + 两机 |
|
|
48
|
+
| L5 发布门禁 | commit/tag/Release/npm/CI/Compat/Release workflow/healthz | GitHub + npm |
|
|
49
|
+
|
|
50
|
+
## 4. 进入/退出准则
|
|
51
|
+
|
|
52
|
+
进入:
|
|
53
|
+
|
|
54
|
+
- 代码冻结点明确(v2.0.8 commit);
|
|
55
|
+
- preflight GREEN(允许 host 观测项 UNVERIFIABLE,但不允许 FAIL);
|
|
56
|
+
- 主 relay 在线,E1 的 `fleet_id=004945`;
|
|
57
|
+
- E2/E3 至少一台可执行(allowlist 非空且在线)。
|
|
58
|
+
|
|
59
|
+
退出(视为通过):
|
|
60
|
+
|
|
61
|
+
1. P0 用例 100%,P1 ≥95%,无 S1/S2 缺陷;
|
|
62
|
+
2. 联合开发:计划/审批/派发/审计/进度五段均有真实 relay 证据;
|
|
63
|
+
3. 编队:跨机任务 verdict=consistent 且签名有效;
|
|
64
|
+
4. P2P:跨机 `handshakes≥1`、`encrypted_channels≥1`、`refused=0`;
|
|
65
|
+
5. 文件:两机 rootCid 与落盘 SHA-256 一致,relay 无文件字节;
|
|
66
|
+
6. 八项发布门禁全绿,npm shasum 与 Release 资产一致;
|
|
67
|
+
7. 所有失败具名(错误码/机器/版本/relay)。
|
|
68
|
+
|
|
69
|
+
阻断判定:E2 离线或 E3 allowlist 为空时,L4 只能标 BLOCKED,不得用单机结果替代。
|
|
70
|
+
|
|
71
|
+
## 5. 执行顺序
|
|
72
|
+
|
|
73
|
+
1. 环境基线:三机版本、编队绑定、allowlist、relay 版本;
|
|
74
|
+
2. L0/L1:preflight + 新专项套件;
|
|
75
|
+
3. L3:联合开发单机闭环;
|
|
76
|
+
4. L4-A:编队聊天 + 目标选择 + 跨机派发;
|
|
77
|
+
5. L4-B:P2P 握手验证(只对非本机派发);
|
|
78
|
+
6. L4-C:文件 E2E(sender/receiver + 候选地址);
|
|
79
|
+
7. L5:发布门禁证据归档;
|
|
80
|
+
8. 失败项回归与报告。
|
|
81
|
+
|
|
82
|
+
## 6. 证据规则
|
|
83
|
+
|
|
84
|
+
- 每条用例记录:时间、机器、relay、版本、任务/ref ID、结果;
|
|
85
|
+
- 不把“代码已实现”当“现场通过”;
|
|
86
|
+
- 不接受自派发(ToFuture→ToFuture)作为 P2P 加密证据(设计为明文);
|
|
87
|
+
- 版本证据必须同一时间窗采集:package.json / 进程启动时间 / relay caps 三者相差 ≤5 分钟;
|
|
88
|
+
- 敏感信息(token/key/聊天明文)不得进入证据。
|
|
89
|
+
|
|
90
|
+
## 7. 风险
|
|
91
|
+
|
|
92
|
+
| 风险 | 影响 | 对策 |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| DSH 离线 | 跨机全阻断 | 到场恢复;或先修 macmini allowlist |
|
|
95
|
+
| macmini 2.0.7 + allowlist 空 | 版本不齐/拒绝执行 | 本机升级 2.0.8、写入 allowlist 后重启 |
|
|
96
|
+
| stateDir:null | FLEET_UNPAIRED、结果回退 relay | 配置 stateDir 并重启宿主 |
|
|
97
|
+
| 名册 26 台 / 20 台版本未知 | 统计口径混乱 | 用 §2 口径;只统计 004945 |
|
|
98
|
+
| 自派发明文 | 误判握手失败 | 禁止作为 PH 证据 |
|
|
99
|
+
| 手工 tarball 与 npm 字节差异 | 版本证据弱 | 比对 npm shasum 与本地包 |
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* S2: macOS screen capture probe for the remote-view P1 decision.
|
|
4
|
+
*
|
|
5
|
+
* Runs WITHOUT `node -e` (the agent refuses code-carrying argv under
|
|
6
|
+
* COMMAND_REPARSE), so it can be dispatched as:
|
|
7
|
+
* node <plugin>/scripts/remote-s2-capture.mjs --json
|
|
8
|
+
* node <plugin>/scripts/remote-s2-capture.mjs --exit-summary
|
|
9
|
+
*
|
|
10
|
+
* It measures two captures (spaced 1/fps), probes optional ffmpeg, guesses TCC
|
|
11
|
+
* denial/blank from exit status + JPEG size, writes ~/w2m-s2-result.json, and
|
|
12
|
+
* prints the same JSON. --exit-summary encodes the result into the exit code:
|
|
13
|
+
* 100 + bit0 ffmpeg | bit1 first ok | bit2 first >20KiB | bit3 first <500ms
|
|
14
|
+
* | bit4 second ok | bit5 second <500ms
|
|
15
|
+
*/
|
|
16
|
+
import { spawnSync } from 'node:child_process';
|
|
17
|
+
import { existsSync, statSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { pathToFileURL } from 'node:url';
|
|
21
|
+
|
|
22
|
+
export function parseArgs(argv) {
|
|
23
|
+
const out = { fps: 1, exitSummary: false, json: false, keep: false };
|
|
24
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
if (arg === '--fps') { out.fps = Number(argv[i + 1]) === 2 ? 2 : 1; i += 1; }
|
|
27
|
+
else if (arg === '--exit-summary') out.exitSummary = true;
|
|
28
|
+
else if (arg === '--json') out.json = true;
|
|
29
|
+
else if (arg === '--keep') out.keep = true;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function summaryBits(result) {
|
|
35
|
+
let bits = 0;
|
|
36
|
+
if (result?.ffmpeg?.present === true) bits |= 1;
|
|
37
|
+
const a = result?.captures?.[0];
|
|
38
|
+
const b = result?.captures?.[1];
|
|
39
|
+
if (a?.ok === true) bits |= 2;
|
|
40
|
+
if ((a?.bytes ?? 0) > 20 * 1024) bits |= 4;
|
|
41
|
+
if (Number.isFinite(a?.ms) && a.ms < 500) bits |= 8;
|
|
42
|
+
if (b?.ok === true) bits |= 16;
|
|
43
|
+
if (Number.isFinite(b?.ms) && b.ms < 500) bits |= 32;
|
|
44
|
+
return bits;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function capture(file, dimensions = true) {
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
const run = spawnSync('screencapture', ['-x', '-t', 'jpg', file], { timeout: 15000, encoding: 'utf8' });
|
|
50
|
+
const ms = Date.now() - started;
|
|
51
|
+
let bytes = 0;
|
|
52
|
+
if (existsSync(file)) { try { bytes = statSync(file).size; } catch { bytes = 0; } }
|
|
53
|
+
let width = null; let height = null;
|
|
54
|
+
if (dimensions && bytes > 0) {
|
|
55
|
+
const sips = spawnSync('sips', ['-g', 'pixelWidth', '-g', 'pixelHeight', file], { timeout: 5000, encoding: 'utf8' });
|
|
56
|
+
const w = /pixelWidth:\s*(\d+)/.exec(sips.stdout ?? '');
|
|
57
|
+
const h = /pixelHeight:\s*(\d+)/.exec(sips.stdout ?? '');
|
|
58
|
+
width = w === null ? null : Number(w[1]);
|
|
59
|
+
height = h === null ? null : Number(h[1]);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
ok: run.status === 0 && bytes > 0,
|
|
63
|
+
status: run.status,
|
|
64
|
+
ms,
|
|
65
|
+
bytes,
|
|
66
|
+
width,
|
|
67
|
+
height,
|
|
68
|
+
error: run.status === 0 ? null : (run.stderr || 'screencapture failed').toString().slice(0, 300),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function main() {
|
|
73
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
74
|
+
const result = {
|
|
75
|
+
ok: false,
|
|
76
|
+
at: new Date().toISOString(),
|
|
77
|
+
platform: process.platform,
|
|
78
|
+
fps: opts.fps,
|
|
79
|
+
ffmpeg: { present: false, version: null },
|
|
80
|
+
captures: [],
|
|
81
|
+
tcc_guess: 'unknown',
|
|
82
|
+
code: null,
|
|
83
|
+
};
|
|
84
|
+
if (process.platform !== 'darwin') {
|
|
85
|
+
result.code = 'S2_UNSUPPORTED_PLATFORM';
|
|
86
|
+
finish(result, opts, 2);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const ff = spawnSync('ffmpeg', ['-version'], { timeout: 1500, encoding: 'utf8' });
|
|
90
|
+
result.ffmpeg = { present: ff.status === 0, version: ff.status === 0 ? String(ff.stdout).split('\n')[0].slice(0, 120) : null };
|
|
91
|
+
|
|
92
|
+
const dir = join(homedir(), 'w2m-s2');
|
|
93
|
+
const fileA = join(dir, 'frame-1.jpg');
|
|
94
|
+
const fileB = join(dir, 'frame-2.jpg');
|
|
95
|
+
try { spawnSync('mkdir', ['-p', dir], { timeout: 5000 }); } catch { /* best effort */ }
|
|
96
|
+
result.captures.push(capture(fileA));
|
|
97
|
+
spawnSync('sleep', [String(opts.fps === 2 ? 0.5 : 1.0)], { timeout: 5000 });
|
|
98
|
+
result.captures.push(capture(fileB));
|
|
99
|
+
|
|
100
|
+
const a = result.captures[0];
|
|
101
|
+
result.tcc_guess = a.status === 0 && a.bytes > 5000 ? 'granted-or-desktop-visible' : 'denied-or-blank';
|
|
102
|
+
result.ok = a.ok === true && result.captures[1].ok === true;
|
|
103
|
+
result.code = result.ok ? 'S2_CAPTURE_OK' : 'S2_CAPTURE_FAILED';
|
|
104
|
+
finish(result, opts, result.ok ? 0 : 3);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function finish(result, opts, normalCode) {
|
|
108
|
+
const out = join(homedir(), 'w2m-s2-result.json');
|
|
109
|
+
try { writeFileSync(out, JSON.stringify(result, null, 2)); result.result_file = out; } catch { /* stdout only */ }
|
|
110
|
+
if (opts.json || !opts.exitSummary) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
111
|
+
if (opts.exitSummary) process.exit(100 + summaryBits(result));
|
|
112
|
+
process.exit(normalCode);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const invoked = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
116
|
+
if (invoked) main();
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# DSH 机器升级 2.0.8 + allowlist 修复清单
|
|
2
|
+
|
|
3
|
+
目标:DSH 侧具备 2.0.8 的 ChaCha20-Poly1305 纯 JS 回退与 peer key 逻辑,并让 allowlist 真正生效。
|
|
4
|
+
前置:DSH 机器本机操作;relay 保持 43.142.74.71:8787;编队 004945。
|
|
5
|
+
|
|
6
|
+
## 1. 升级前快照
|
|
7
|
+
|
|
8
|
+
```powershell
|
|
9
|
+
$profile = "$env:USERPROFILE\.dsh\profiles\desktop"
|
|
10
|
+
$plugin = "$profile\node_modules\@twinsearth\w2m-dsh-plugin"
|
|
11
|
+
Copy-Item "$profile\..\xclient\device.json" "$env:TEMP\device.json.dsh-bak" -Force
|
|
12
|
+
if (Test-Path $plugin) {
|
|
13
|
+
(Get-Content "$plugin\package.json" -Raw | ConvertFrom-Json).version
|
|
14
|
+
}
|
|
15
|
+
Get-CimInstance Win32_Process -Filter "name='node.exe'" |
|
|
16
|
+
Where-Object { $_.CommandLine -match 'w2m-localside' } |
|
|
17
|
+
Select-Object ProcessId, CommandLine
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
记录:当前版本、localside 启动参数、`device.json` 位置。
|
|
21
|
+
|
|
22
|
+
## 2. 下载并校验 2.0.8
|
|
23
|
+
|
|
24
|
+
```powershell
|
|
25
|
+
$tmp = Join-Path $env:TEMP 'w2m-2.0.8.tgz'
|
|
26
|
+
Invoke-WebRequest 'https://registry.npmjs.org/@twinsearth/w2m-dsh-plugin/-/w2m-dsh-plugin-2.0.8.tgz' -OutFile $tmp
|
|
27
|
+
(Get-FileHash $tmp -Algorithm SHA256).Hash.ToLower()
|
|
28
|
+
# 期望:700c06ba81195fc36c129bc2e9337c2cf2b678965decd456d3a73d05b9aa1251
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 3. 停旧进程(不要在这个窗口里启动升级脚本后再停自己)
|
|
32
|
+
|
|
33
|
+
```powershell
|
|
34
|
+
# 停独立 localside
|
|
35
|
+
Get-CimInstance Win32_Process -Filter "name='node.exe'" |
|
|
36
|
+
Where-Object { $_.CommandLine -match 'w2m-localside' } |
|
|
37
|
+
ForEach-Object { Stop-Process -Id $_.ProcessId -Force }
|
|
38
|
+
|
|
39
|
+
# 若由计划任务/WinSW 托管
|
|
40
|
+
Stop-ScheduledTask -TaskName 'W2M Localside agent' -ErrorAction SilentlyContinue
|
|
41
|
+
# 或:Restart-Service w2mlocalside
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 4. 替换插件树(脚本方式)
|
|
45
|
+
|
|
46
|
+
在 DSH 机器上执行:
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
powershell -ExecutionPolicy Bypass -File .\scripts\dsh-install-2.0.8.ps1 -ProfileName desktop
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
脚本会:校验 sha256 → 备份旧插件目录 → 解包 2.0.8 → 打印版本与新插件路径;**不会重启 DSH**。
|
|
53
|
+
|
|
54
|
+
手动等价步骤:
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
$backup = "$plugin.bak-$(Get-Date -Format yyyyMMddHHmmss)"
|
|
58
|
+
Move-Item $plugin $backup
|
|
59
|
+
New-Item -ItemType Directory -Force $plugin | Out-Null
|
|
60
|
+
tar -xzf $tmp -C $plugin --strip-components=1
|
|
61
|
+
node -e "console.log(require('$($plugin -replace '\\','/')/package.json').version)"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 5. 重启宿主 + 修复 allowlist
|
|
65
|
+
|
|
66
|
+
先重启 DSH(外部执行):
|
|
67
|
+
|
|
68
|
+
```powershell
|
|
69
|
+
Stop-Process -Name 'DeepSeek Harness' -Force
|
|
70
|
+
Start-Sleep 3
|
|
71
|
+
Start-Process 'E:\DS\DeepSeek Harness.exe'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
allowlist 必须落到**实际运行的 agent**:
|
|
75
|
+
|
|
76
|
+
### A. 独立 localside
|
|
77
|
+
|
|
78
|
+
```powershell
|
|
79
|
+
node "$plugin\bin\w2m-localside.mjs" `
|
|
80
|
+
--rabbit http://43.142.74.71:8787 `
|
|
81
|
+
--name DSH `
|
|
82
|
+
--state "$env:USERPROFILE\.dsh\xclient\localside" `
|
|
83
|
+
--fleet-key <004945 的 fleet key> `
|
|
84
|
+
--allowed-commands '["node"]'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### B. 插件 autoStartAgent(没有独立进程)
|
|
88
|
+
|
|
89
|
+
在 DSH 的项目根放 `.w2m.json`:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{ "allowedCommands": ["node"] }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
再重启 DSH。
|
|
96
|
+
|
|
97
|
+
## 6. 验收
|
|
98
|
+
|
|
99
|
+
```powershell
|
|
100
|
+
# 版本
|
|
101
|
+
node -e "console.log(require('$($plugin -replace '\\','/')/package.json').version)" # 2.0.8
|
|
102
|
+
|
|
103
|
+
# 执行(从 ToFuture 派发)
|
|
104
|
+
# w2m_run assignee=DSH command=["node","--version"]
|
|
105
|
+
# 期望:outcome=ok / exit_code=0;不再 COMMAND_NOT_ALLOWED
|
|
106
|
+
|
|
107
|
+
# P2P(ToFuture 侧)
|
|
108
|
+
# w2m_status.p2p.encryption:
|
|
109
|
+
# handshakes >= 1
|
|
110
|
+
# encrypted_channels >= 1
|
|
111
|
+
# refused = 0
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 7. 回滚
|
|
115
|
+
|
|
116
|
+
```powershell
|
|
117
|
+
Stop-Process -Name 'DeepSeek Harness' -Force -ErrorAction SilentlyContinue
|
|
118
|
+
Remove-Item $plugin -Recurse -Force
|
|
119
|
+
Move-Item $backup $plugin
|
|
120
|
+
Start-Process 'E:\DS\DeepSeek Harness.exe'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`device.json` 用第 1 步备份恢复;relay 不参与回滚。
|
|
124
|
+
|
|
125
|
+
## 已知边界
|
|
126
|
+
|
|
127
|
+
- 脚本只安装文件,不重启 DSH、不改 allowlist;
|
|
128
|
+
- 若 DSH 是 Intel Mac 或非 arm64/x64 平台,需单独确认 sidecar/平台能力;
|
|
129
|
+
- DSH 若仍使用旧宿主进程,版本显示 2.0.8 但握手仍会超时,必须真正重启宿主。
|