@twinsearth/w2m-dsh-plugin 2.0.7 → 2.0.9
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 +22 -5
- package/TEST-CASES-JOINT-FLEET-2.0.8.md +119 -0
- package/TEST-PLAN-JOINT-FLEET-2.0.8.md +99 -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.8/RELEASE-2080.json +21 -0
- package/docs/acceptance/2.0.9/RELEASE-2090.json +24 -0
- package/docs/adr/0002-p2p-transport.md +67 -0
- package/docs/plans/IM2A-2.2.2-PLAN.md +293 -0
- package/lib/client.js +30 -4
- package/package.json +5 -3
- package/plugin.json +1 -1
- package/src/agent/agent.mjs +7 -0
- package/src/agent/chacha.mjs +93 -0
- package/src/agent/crypto.mjs +64 -29
- 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 +206 -0
- package/src/agent/transfer/index.mjs +4 -0
- package/src/agent/transfer/manifest.mjs +92 -0
- package/src/agent/transfer/object-store.mjs +144 -0
- package/src/agent/transfer/publish.mjs +76 -0
- package/src/agent/transfer/reference.mjs +97 -0
- package/src/agent/transfer/router.mjs +80 -0
- package/src/agent/transfer/want-block.mjs +135 -0
- package/src/agent/transport.mjs +146 -0
- package/src/game/xiangqi.mjs +217 -0
- package/src/plugin/fleet-binding.mjs +105 -0
- package/src/plugin/tools.mjs +34 -2
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# ADR 0002: P2P 传输路线(iroh 优先 + 多方案并行)
|
|
2
|
+
|
|
3
|
+
状态:Accepted(2026-10-30)
|
|
4
|
+
关联:ADR 0001(CRDT)、TRANSFER-LAYER.md、TEST-PLAN-JOINT-FLEET-2.0.8.md
|
|
5
|
+
|
|
6
|
+
## 决策
|
|
7
|
+
|
|
8
|
+
1. **传输抽象先行**:定义 `P2PTransport`(start/stop/send/onMessage/paths/diagnostics),
|
|
9
|
+
现有 UDP 实现为 `udp-v1`,iroh 为 `iroh-v1`,libp2p 仅作候选发现。
|
|
10
|
+
2. **iroh 优先,但不阻塞 UDP**:双方都声明 `p2p.transports` 时并行起跑,首个完成
|
|
11
|
+
加密握手 + 首帧 ACK 的路径胜出;总预算 ≤3s;失败具名,不静默回退。
|
|
12
|
+
3. **iroh 以 sidecar 集成**:不把原生 addon 加载进 Electron 主进程;子进程 IPC,
|
|
13
|
+
失败/超时可单独回滚;默认分发仍可只带 UDP。
|
|
14
|
+
4. **自建 iroh-relay(TLS 域名 + 80/443 + ACME)**:作为可选基础设施;
|
|
15
|
+
不复用 8787 relay,不修改 relay 2.0.5。
|
|
16
|
+
5. **中继语义边界**:允许 DERP 式**密文转发**;relay 不得解密、不得落盘、
|
|
17
|
+
不得记录内容;仅保留必要的连接元数据与速率限制。
|
|
18
|
+
6. **平台范围**:Apple Silicon macOS 为唯一 macOS 目标;不支持 Intel Mac;
|
|
19
|
+
不因 Intel 预编译缺失而阻塞 iroh 路线。
|
|
20
|
+
7. **身份**:iroh `EndpointId`(Ed25519)与现有 `device.key`(X25519)独立派生:
|
|
21
|
+
`hkdf(seed, 'iroh-ed25519')`;通过 offer 的 `p2p.iroh_ids` 交换,relay 不改协议。
|
|
22
|
+
8. **数据面与文件层解耦**:iroh/UDP 只替换传输;`xfer.want/chunk`、Merkle、
|
|
23
|
+
ArtifactRef 语义保持不变。
|
|
24
|
+
|
|
25
|
+
## 立即优先级(在 iroh 之前)
|
|
26
|
+
|
|
27
|
+
- P0-a:chunk 帧接入 `P2PChannel`,不再裸 `dgram.send`;
|
|
28
|
+
- P0-b:`onAck` 触发 `pump()`,窗口随 ACK/BDP 推进(当前 0.12 MiB/s 的结构性上限);
|
|
29
|
+
- P0-c:`verifyRefV2` 自洽校验 + 统一两套 v2 ref;
|
|
30
|
+
- P1:SHA-256 Merkle 树(逐块证明,作为多源准入条件);
|
|
31
|
+
- 之后才是 iroh sidecar 时间盒。
|
|
32
|
+
|
|
33
|
+
## 验收
|
|
34
|
+
|
|
35
|
+
| 指标 | 目标 |
|
|
36
|
+
|---|---|
|
|
37
|
+
| 加密覆盖率 | 同版本、双方有 key:100% |
|
|
38
|
+
| 跨机直连率 | 同 LAN ≥90%,跨网 ≥70% |
|
|
39
|
+
| 首帧时延 p95 | 直连 <300ms |
|
|
40
|
+
| 中继边界 | relay 无明文、无落盘、无内容日志 |
|
|
41
|
+
| 回滚 | `p2p.transports=['udp']` 即回退,无需重装 |
|
|
42
|
+
| 平台 | macOS arm64 + Windows x64 通过;Intel Mac 明确不支持 |
|
|
43
|
+
|
|
44
|
+
## 方案定位(冻结)
|
|
45
|
+
|
|
46
|
+
| 方案 | 定位 | 优势 | 短板 | 是否进入数据面 |
|
|
47
|
+
|---|---|---|---|---|
|
|
48
|
+
| **iroh** | 首选、高打洞成功率 | QUIC 原生打洞、DERP 回落、路径选择、加密/重传内建 | 需 sidecar + 自建 TLS relay;JS 绑定/原生二进制 | ✅ 是 |
|
|
49
|
+
| **现有 UDP** | 默认兜底、零新增依赖 | 已有加密栈、wire 兼容、无新进程 | 可靠性仍缺 RTT 采样/拥塞控制/PMTU | ✅ 是 |
|
|
50
|
+
| **libp2p** | 可选“全家桶”参照 | 模块化、DHT 发现、多传输 | 打洞率约 70%、依赖重、复杂度高 | ❌ 否,仅发现层 |
|
|
51
|
+
|
|
52
|
+
规则:
|
|
53
|
+
|
|
54
|
+
1. 数据面只允许 `iroh` 与 `udp`;`libp2p` 不得接管 send/ACK/加密;
|
|
55
|
+
2. libp2p 只作为 `discovery` provider(DHT/mDNS)产出候选,候选仍需 relay/offer 身份校验;
|
|
56
|
+
3. 未声明 iroh 的对端只走 UDP,行为与 v2.0.8 一致;
|
|
57
|
+
4. 竞速失败返回 `ALL_TRANSPORTS_FAILED`,数据面不回落 relay;
|
|
58
|
+
5. 发现层依赖放 sidecar,服务端 `dependencies` 保持为空;
|
|
59
|
+
6. DHT 记录必须签名,失败丢弃并记 `DHT_RECORD_INVALID`。
|
|
60
|
+
|
|
61
|
+
## 风险
|
|
62
|
+
|
|
63
|
+
- iroh JS 绑定滞后(npm 1.2.0 vs 上游 1.4.1):sidecar 锁版本;
|
|
64
|
+
- 自建 relay 的域名/证书/CN→JP RTT:上线前实测;
|
|
65
|
+
- sidecar 崩溃/升级:独立进程 + 健康检查 + 自动回退 UDP;
|
|
66
|
+
- 多源投毒:Merkle 未验收前禁止开多源;
|
|
67
|
+
- 中继流量合规:只转发密文、限速、不记内容,写入运维文档。
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# IM2A v2.2.2 策划与技术方案(先策划,后开发)
|
|
2
|
+
|
|
3
|
+
状态:DRAFT — 等待产品/技术决策确认,未开始改代码、未发布。
|
|
4
|
+
基线:`@twinsearth/w2m-dsh-plugin` v2.0.7(含未发布的 P2P cipher 回退、xfer want-block、cipher 诊断)。
|
|
5
|
+
目标:升级到 **v2.2.2**,完成八项发布门禁:commit / tag / GitHub Release / npm / CI / Compat / Release workflow / healthz。
|
|
6
|
+
|
|
7
|
+
## 1. 范围与假设
|
|
8
|
+
|
|
9
|
+
- v2.2.2 面向 IM2A 网络:服务器端已具备 relay、fleet、joint-dev、project-room、data-plane refs;本版本补齐**前端统一载体、SDK/API、Agent Plugins 1.0、Skills 标准**。
|
|
10
|
+
- 服务端继续零运行时依赖、Node ≥20.19、ESM;新增前端与 SDK 单独成包,不污染 relay 依赖面。
|
|
11
|
+
- 发布目标仓库默认 `TwinsEarth/IM2A`(若 v2.2.2 实际指 `@twinsearth/w2m-dsh-plugin`,需在决策点 D1 确认)。
|
|
12
|
+
- 现有未发布改动(chacha fallback、cipher 诊断、xfer want-block)作为 v2.2.2 的一部分冻结并验收,不单独发 2.1.x。
|
|
13
|
+
|
|
14
|
+
## 2. 前端主语言与通用框架(决策 D2)
|
|
15
|
+
|
|
16
|
+
| 方案 | 多端 | 与 SDK/TS 生态 | 包体/维护 | 结论 |
|
|
17
|
+
|---|---|---|---|---|
|
|
18
|
+
| TypeScript + React + Vite + Tauri 2 | Web/PWA、Windows/macOS/Linux 桌面;移动端 Capacitor 后续 | 最佳(SDK 同为 TS,类型共享) | 中 | **推荐** |
|
|
19
|
+
| Flutter (Dart) | 六端最整齐 | 需维护 Dart SDK 双实现 | 大 | 备选(团队熟悉 Dart 时) |
|
|
20
|
+
| 纯 Web + PWA | 浏览器/桌面壳 | 好 | 小 | 不满足桌面能力(托盘/本地文件) |
|
|
21
|
+
|
|
22
|
+
推荐架构:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
packages/
|
|
26
|
+
sdk/ @twinsearth/im2a-sdk TypeScript,协议/类型/客户端
|
|
27
|
+
ui/ @twinsearth/im2a-ui React 组件与设计令牌
|
|
28
|
+
app-web/ Vite + React Router 浏览器/PWA
|
|
29
|
+
app-desktop/ Tauri 2 壳,复用 app-web WebView + 原生能力
|
|
30
|
+
adapters/ harness 适配器(见 §4)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
原则:一次开发、多端复用 UI 与 SDK;桌面仅做原生薄壳(托盘、文件、自动更新);移动端二期用 Capacitor 复用同一 Web 代码。
|
|
34
|
+
|
|
35
|
+
## 3. SDK / API 交付物(决策 D3)
|
|
36
|
+
|
|
37
|
+
- `@twinsearth/im2a-sdk`:TypeScript,导出 `Im2aClient`、`FleetClient`、`ProjectRoomClient`、`SkillRuntime`、类型定义、错误码;同时产出 ESM + d.ts。
|
|
38
|
+
- Agent Plugins 1.0:`plugin.json` 清单(name/version/description/skills/mcpServers/commands),校验 schema;兼容 Claude Code Mods 布局(`.claude-plugin/plugin.json`)。
|
|
39
|
+
- MCP:复用现有 `bin/w2m-mcp.mjs`,新增 IM2A 工具命名空间;`mcp.json` 可被 Claude/Codex/Copilot/VS Code 读取。
|
|
40
|
+
- ACA:新增 `src/aca/` 适配层(能力声明、调用、错误映射),与 MCP 工具共用底层服务;先做协议一致性测试,不承诺未验证的厂商实现。
|
|
41
|
+
- Agent Skills:见 §4,技能目录随 npm 包发布,提供 `skills validate` CI 门禁。
|
|
42
|
+
|
|
43
|
+
兼容策略:SDK 1.x 对 v2.0 API 只增不改;v2.2.2 引入的字段走 capability/feature 探测,旧 relay(2.0.5)缺失时明确降级码,不伪造成功。
|
|
44
|
+
|
|
45
|
+
## 4. Skills 框架与标准(决策 D4)
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
skills/
|
|
49
|
+
├── fleet-control/SKILL.md # 编队协同控制
|
|
50
|
+
├── memory-sync/SKILL.md # 跨平台记忆同步(MMP)
|
|
51
|
+
├── session-ref/SKILL.md # 跨会话引用协议
|
|
52
|
+
└── resource-schedule/SKILL.md # 资源调度
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`SKILL.md` 约定:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
---
|
|
59
|
+
name: fleet-control
|
|
60
|
+
description: 何时使用、解决什么问题(≤ 512 字符)
|
|
61
|
+
version: 2.2.2
|
|
62
|
+
license: MIT
|
|
63
|
+
metadata:
|
|
64
|
+
requires: [mcp:w2m-fleet]
|
|
65
|
+
capabilities: [fleet.run, fleet.wait, fleet.chat]
|
|
66
|
+
---
|
|
67
|
+
# 正文:可执行指令、输入输出、失败处理、安全边界
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- Skill = Agent 应知道什么/怎么工作;MCP = 连接外部能力的端点;两者同插件共存。
|
|
71
|
+
- `mcp.json` 只声明端点与传输,不放密钥;token 走 host config/env。
|
|
72
|
+
- CI 新增 `skills-validate`:frontmatter schema、目录名=name、正文非空、mcp 引用存在、无明文密钥。
|
|
73
|
+
|
|
74
|
+
## 5. v2.2.2 工作分解(WBS)
|
|
75
|
+
|
|
76
|
+
| 编号 | 工作流 | 交付物 | 依赖 | 验收 |
|
|
77
|
+
|---|---|---|---|---|
|
|
78
|
+
| W0 | 规格冻结 | 本计划 + 3 份 ADR(前端、SDK、Skills) | D1–D4 | 评审通过 |
|
|
79
|
+
| W1 | 服务端兼容 | capability/feature 探测、错误码、healthz 增补 | W0 | 旧 relay 降级用例 |
|
|
80
|
+
| W2 | SDK | `packages/sdk` 首发、类型、单测、consumer 样例 | W1 | `npm pack` + 三客户端冒烟 |
|
|
81
|
+
| W3 | 适配器 | DeepSeekHarness/Codex/Claude/Gemini/OpenClaw/Muse/Copilot/VS Code/WorkBuddy/豆包/Qwen/MiMo 清单 | W2 | 每个客户端一条安装/调用契约测试 |
|
|
82
|
+
| W4 | 前端 | app-web + app-desktop(Tauri) | W2 | 登录/编队/会话/技能四页 E2E |
|
|
83
|
+
| W5 | Skills | 四技能 + 校验器 + 文档 | W2 | skills-validate 全绿 |
|
|
84
|
+
| W6 | 记忆层 | `src/memory-hub/`:SQLite schema、MCP read/write、设备视角过滤、审批队列 | W2 | §10.6 五类用例全绿 |
|
|
85
|
+
| W7 | 发布工程 | 版本号、CHANGELOG、acceptance、release notes、scripts | W1–W6 | 八门禁脚本本地演练 |
|
|
86
|
+
| W8 | 验收 | 单测/集成/契约/安全/回滚 | W7 | 见 §7 |
|
|
87
|
+
|
|
88
|
+
关键路径:W0 → W1 → W2 →(W3/W4/W5 并行)→ W6 → W7 → W8。
|
|
89
|
+
|
|
90
|
+
## 6. 八项发布门禁(缺一不可)
|
|
91
|
+
|
|
92
|
+
| # | 门禁 | 判定 | 证据 |
|
|
93
|
+
|---|---|---|---|
|
|
94
|
+
| 1 | commit | 目标分支含 v2.2.2 变更,作者/签名可审计 | commit SHA |
|
|
95
|
+
| 2 | tag | `v2.2.2` annotated tag 指向同一 SHA | tag SHA 校验 |
|
|
96
|
+
| 3 | GitHub Release | Release 正文含变更/升级/回滚 | Release URL |
|
|
97
|
+
| 4 | npm | `npm view @twinsearth/w2m-dsh-plugin@2.2.2` 与 tarball shasum 一致 | shasum |
|
|
98
|
+
| 5 | CI | 全平台矩阵绿 | workflow run |
|
|
99
|
+
| 6 | Compat | 近三个 relay/plugin 版本兼容矩阵绿 | compat run |
|
|
100
|
+
| 7 | Release workflow | 发布工作流成功且产物可下载 | run URL |
|
|
101
|
+
| 8 | healthz | 生产 relay `/healthz` 版本/features 与发布一致 | healthz JSON |
|
|
102
|
+
|
|
103
|
+
补充:当前工作树**没有本地 `.git`**;正式发版前必须恢复可提交仓库(或继续使用受审计的 GitHub API 提交脚本),并把该差异写入发布记录。此项列为 W6 阻断风险。
|
|
104
|
+
|
|
105
|
+
## 7. 验收与测试
|
|
106
|
+
|
|
107
|
+
- 单元/集成:现有 preflight 全绿(当前基线 3233 tests / 225 files;新增 SDK/前端另计)。
|
|
108
|
+
- 契约测试:12 个 Harness 客户端各一条“安装→发现→调用→错误”用例;未实际执行的客户端只能标 PLANNED。
|
|
109
|
+
- Skills:schema/目录/mcp 引用/密钥扫描四类用例。
|
|
110
|
+
- 端到端:两机 joint dev + 文件 E2E(依赖 P2P handshake 修复部署)。
|
|
111
|
+
- 安全:插件清单/技能正文无 token;MCP 端点的鉴权与超时;npm 包内容审计。
|
|
112
|
+
- 回滚:npm dist-tag 回退 + GitHub Release 标记 + relay 不改 schema 的兼容回退。
|
|
113
|
+
|
|
114
|
+
## 8. 风险与对策
|
|
115
|
+
|
|
116
|
+
| 风险 | 影响 | 对策 |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| v2.2.2 目标仓库/版本归属未确认 | 发错仓库 | 决策点 D1 先确认 |
|
|
119
|
+
| 本地无 `.git` | 无法真实 commit/tag | 恢复 clone 或继续 API 提交并审计 |
|
|
120
|
+
| 三机混合版本(26 台在线,多台 plugin_version=null) | P2P/兼容验收假绿 | 验收样本锁定三机同版本,其余只观测 |
|
|
121
|
+
| macmini allowlist 空、SSH 未开 | 无法部署与测试 | 本机恢复 allowlist 或开 SSH |
|
|
122
|
+
| Electron 宿主缺 chacha(已修) | P2P 握手失败 | v2.2.2 必须包含 chacha fallback 并重启宿主 |
|
|
123
|
+
| 前端技术栈引入构建链 | 违反零依赖/供应链审计 | 前端独立包、锁版本、SBOM、许可证扫描 |
|
|
124
|
+
|
|
125
|
+
## 9. 时间表(建议,按 1 人 + 专家评审)
|
|
126
|
+
|
|
127
|
+
- D0–D1:决策点确认、ADR 评审、规格冻结。
|
|
128
|
+
- D2–D4:W1 + W2(服务端兼容 + SDK)。
|
|
129
|
+
- D5–D7:W3/W4/W5 并行(适配器、前端、Skills)。
|
|
130
|
+
- D8–D9:W6 发布工程 + 本地八门禁演练。
|
|
131
|
+
- D10:W7 验收、发布窗口、八项证据归档。
|
|
132
|
+
|
|
133
|
+
## 10. 记忆层:W2M Memory Hub(新增,v2.2.2 纳入 MVP)
|
|
134
|
+
|
|
135
|
+
### 10.1 目标与边界
|
|
136
|
+
|
|
137
|
+
- 在 Rabbit relay 之外增加**编队共享记忆层**,不把记忆塞进 relay 状态;
|
|
138
|
+
- 通过 MCP 暴露 `memory_read` / `memory_write`(可选 `memory_query` / `memory_approve`),任何 MCP 客户端可读写;
|
|
139
|
+
- 设备视角隔离:每条记忆携带 `origin_machine` / `origin_platform` / `scope`;读取按请求方平台与 scope 过滤,平台特定执行细节不污染跨平台记忆;
|
|
140
|
+
- MMP(Mesh Memory Protocol)兼容:保留导入/导出适配器,不把 MMP 作为唯一内部格式。
|
|
141
|
+
|
|
142
|
+
### 10.2 组件与存储
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
Memory Hub(独立进程/模块,不依赖 relay 在线)
|
|
146
|
+
· MCP 工具面:read / write / query / approve
|
|
147
|
+
· 设备视角过滤器 + scope 校验
|
|
148
|
+
· 审批队列(跨 Agent 共享默认需人工批准)
|
|
149
|
+
· 审计日志(谁在何时读了哪条记忆)
|
|
150
|
+
· 后端:SQLite(默认,WAL,单机/小舰队) / Postgres(可选,多副本)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
最小 schema:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
memories(
|
|
157
|
+
id, fleet_id, scope, key, value_json,
|
|
158
|
+
origin_machine, origin_platform, session_ref,
|
|
159
|
+
tags, sensitivity, approval_state,
|
|
160
|
+
version, created_at, updated_at, expires_at
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
并发:写入用 version/CAS,冲突返回 `MEMORY_VERSION_CONFLICT`;删除用 tombstone,保留审计。
|
|
165
|
+
|
|
166
|
+
### 10.3 与现有记忆工具的关系
|
|
167
|
+
|
|
168
|
+
- 现有 `w2m_memory`(agent 本地 jsonl)保留为**本地热缓存**;
|
|
169
|
+
- Hub 是**共享事实源**;跨机共享必须显式 `memory_write`,禁止隐式双写;
|
|
170
|
+
- `memory_read` 先查 Hub,miss 时可选回落本地缓存,并标记 `source=local-cache`;
|
|
171
|
+
- 任何 token/密钥/聊天明文不得写入 Hub;写入前做敏感字段扫描并拒绝。
|
|
172
|
+
|
|
173
|
+
### 10.4 外部基础设施策略
|
|
174
|
+
|
|
175
|
+
- Priostack ACN、ContextGO、imcodes、dsh-ai-memory、dsh-mnemos 作为**可选连接器**,放在 `connectors/`,通过同一 MCP 表面接入;
|
|
176
|
+
- v2.2.2 不把它们设为硬依赖;未安装时 Hub 自身可用(SQLite);
|
|
177
|
+
- 连接器必须通过同一权限/审批/脱敏管线,不能绕过设备视角过滤。
|
|
178
|
+
|
|
179
|
+
### 10.5 分层适配架构落位
|
|
180
|
+
|
|
181
|
+
| 层 | 本仓库落位 | v2.2.2 交付 |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| 分发层 Agent Plugins 1.0 | `plugin.json`、`skills/`、`mcp.json`、`extensions/` | 清单 + schema 校验 |
|
|
184
|
+
| 描述层 Agent Skills | `skills/*/SKILL.md` | 四技能 + skills-validate |
|
|
185
|
+
| 协议层 MCP | `bin/w2m-mcp.mjs`、`src/mcp/` | IM2A 工具命名空间 + Memory Hub 工具 |
|
|
186
|
+
| 记忆层 | `src/memory-hub/` | SQLite MVP + MCP read/write + 隔离/审批 |
|
|
187
|
+
| 平台适配层 | `extensions/{vscode,cursor,codex,claude,...}` | 12 客户端安装/调用清单 |
|
|
188
|
+
|
|
189
|
+
### 10.6 记忆层验收
|
|
190
|
+
|
|
191
|
+
- 读写隔离:Mac 写入的平台细节,Windows 读取不可见(除显式 `scope=shared`);
|
|
192
|
+
- 重启持久化:Hub 重启后记忆与审批状态不丢;
|
|
193
|
+
- 并发:两设备同时写同一 key,后者收到版本冲突而非静默覆盖;
|
|
194
|
+
- 安全:写入含 token/密钥样式的值被拒绝;审计可查;
|
|
195
|
+
- MCP 一致性:Claude/Codex/VS Code 三类客户端各一条 read/write 契约测试(未实跑标 PLANNED)。
|
|
196
|
+
|
|
197
|
+
## 11. 平台适配层策略(12+ 客户端)
|
|
198
|
+
|
|
199
|
+
原则:**一个 MCP 端点,多种安装形态**。所有客户端最终连到同一个 `bin/w2m-mcp.mjs`(stdio 或 HTTP),差异只在“如何把配置放进去”。
|
|
200
|
+
|
|
201
|
+
| 客户端 | 接入方式 | 交付物 | 验证级别 |
|
|
202
|
+
|---|---|---|---|
|
|
203
|
+
| Claude Code | `mcp.json` / `.mcp.json`,项目或用户级 | `extensions/claude/mcp.json` + 安装说明 | 契约测试(已具备 .claude-plugin 基础) |
|
|
204
|
+
| Codex | `~/.codex/config.toml` 的 MCP 段 | `extensions/codex/mcp-servers.toml` | 契约测试 |
|
|
205
|
+
| Cursor | `.cursor/mcp.json` | `extensions/cursor/mcp.json` | 契约测试 |
|
|
206
|
+
| VS Code | `.vscode/mcp.json`(或用户设置) | `extensions/vscode/mcp.json` + 扩展清单 | 契约测试 |
|
|
207
|
+
| OpenClaw | Bundle 安装 Agent Plugins 包,自动映射 skills + MCP | `extensions/openclaw/` bundle 清单 | 安装冒烟 |
|
|
208
|
+
| Qwen Code | 原生加载 Agent Plugins v1 包,stdio MCP 自动展开 | `plugin.json` + `skills/` + `mcp.json` | 安装冒烟 |
|
|
209
|
+
| MiMo Code | 同 Qwen Code(Agent Plugins v1 + stdio MCP) | 同上 | 安装冒烟 |
|
|
210
|
+
| Grok | `grok mcp add` 或复用 `.cursor/mcp.json` | 命令片段 + 配置 | 文档 + 契约(工具存在性需实测) |
|
|
211
|
+
| WorkBuddy | 插件面板或 `mcp.json` | 面板步骤 + 配置文件 | 文档 + 冒烟 |
|
|
212
|
+
| Kimi | 插件面板或 `mcp.json` | 面板步骤 + 配置文件 | 文档 + 冒烟 |
|
|
213
|
+
| Muse | `muse mcp` 命令或自建 MCP 客户端 | 命令片段 + 最小客户端样例 | 文档 + 冒烟 |
|
|
214
|
+
| 豆包 | 火山引擎方舟 API 的 MCP 支持 | API 配置模板(endpoint/token 走环境变量) | 文档 + 待实测 |
|
|
215
|
+
| GLM | 智谱一键安装助手管理 MCP | 安装助手配置模板 | 文档 + 待实测 |
|
|
216
|
+
|
|
217
|
+
### 11.1 统一交付结构
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
extensions/
|
|
221
|
+
claude/ mcp.json + README
|
|
222
|
+
codex/ mcp-servers.toml + README
|
|
223
|
+
cursor/ mcp.json + README
|
|
224
|
+
vscode/ mcp.json + README
|
|
225
|
+
openclaw/ bundle.json + README
|
|
226
|
+
qwen/ README(复用根 plugin.json)
|
|
227
|
+
mimo/ README(复用根 plugin.json)
|
|
228
|
+
grok/ README + 命令片段
|
|
229
|
+
workbuddy/ README + mcp.json
|
|
230
|
+
kimi/ README + mcp.json
|
|
231
|
+
muse/ README + 最小客户端
|
|
232
|
+
doubao/ README + ark-mcp 模板
|
|
233
|
+
glm/ README + 安装助手模板
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
- 根 `mcp.json` 是唯一事实源;各客户端文件只做路径/格式转换,不复制工具定义;
|
|
237
|
+
- 密钥只放 host config/环境变量,模板中一律 `${ENV}` 占位;
|
|
238
|
+
- 每个客户端 README 必须包含:安装、启动、一条只读工具调用、一个具名错误码、卸载/回滚。
|
|
239
|
+
|
|
240
|
+
### 11.2 验证边界
|
|
241
|
+
|
|
242
|
+
- **已具备基础**:Claude Code、Codex(仓库已有 `.claude-plugin/`、`codex/`、`mcp.json`)。
|
|
243
|
+
- **配置模板可交付**:Cursor、VS Code、OpenClaw、Qwen、MiMo、Grok、WorkBuddy、Kimi、Muse。
|
|
244
|
+
- **需实测后才可以宣称适配**:豆包(方舟 MCP)、GLM(智谱安装助手)、Grok(`grok mcp` 子命令是否存在)、Muse(`muse mcp` 子命令)。
|
|
245
|
+
- 未实跑的客户端在发布说明中标记 **PLANNED**,不得写成“已支持”。
|
|
246
|
+
|
|
247
|
+
## 12. 架构不变量(结语固化)
|
|
248
|
+
|
|
249
|
+
以下三条作为 v2.2.2 的硬约束,任何实现不得绕过:
|
|
250
|
+
|
|
251
|
+
### 12.1 一个源,多平台输出
|
|
252
|
+
|
|
253
|
+
- 唯一源包:根 `plugin.json` + `skills/` + `mcp.json`;
|
|
254
|
+
- 新增生成器 `scripts/build-platforms.mjs`:从单一源生成 `extensions/{claude,codex,cursor,vscode,openclaw,...}/`;
|
|
255
|
+
- CI 门禁 `--check`:生成结果与仓库内文件逐字节一致,否则失败;禁止手改生成文件;
|
|
256
|
+
- 发布产物只从源包构建一次,各平台安装包/配置由生成器产出。
|
|
257
|
+
|
|
258
|
+
### 12.2 MCP 作为统一调用协议
|
|
259
|
+
|
|
260
|
+
- 能力调用统一走 MCP 工具面(`w2m_run/w2m_wait/memory_*` 等),不为任何平台写定制集成代码;
|
|
261
|
+
- 平台适配层只负责**配置落点/传输方式**(stdio/http、config 文件位置),不复制工具逻辑;
|
|
262
|
+
- ACA 作为可选协议适配器,位于 MCP 之下,不形成第二套工具语义;
|
|
263
|
+
- 任何“平台专用分支”必须被拒绝,除非以数据/配置形式存在。
|
|
264
|
+
|
|
265
|
+
### 12.3 记忆层与调用层分离
|
|
266
|
+
|
|
267
|
+
- 两个独立 MCP 服务器:
|
|
268
|
+
- `w2m-fleet`:任务/编队/项目/文件能力;
|
|
269
|
+
- `w2m-memory-hub`:共享记忆读写/审批/审计;
|
|
270
|
+
- 进程、配置、存储、权限各自独立;记忆层不可调用 fleet 执行工具;
|
|
271
|
+
- 记忆共享通过独立 MCP 端点,与能力调用解耦;任一侧故障不拖垮另一侧;
|
|
272
|
+
- 统一鉴权约定(token 从 host config/env 读取),但权限模型各自实现。
|
|
273
|
+
|
|
274
|
+
### 12.4 对发布门禁的影响
|
|
275
|
+
|
|
276
|
+
八项门禁之外新增两个**前置检查**(不改变门禁数量):
|
|
277
|
+
|
|
278
|
+
1. `build-platforms --check` 无漂移;
|
|
279
|
+
2. 两个 MCP 服务器各自通过 `doctor`/schema 校验,且互不 import 对方执行模块。
|
|
280
|
+
|
|
281
|
+
## 13. 决策点(需要你确认后才能开发)
|
|
282
|
+
|
|
283
|
+
- **D1**:v2.2.2 发布目标是 `TwinsEarth/IM2A` 还是 `@twinsearth/w2m-dsh-plugin`?是否允许把当前未发布改动并入?
|
|
284
|
+
- **D2**:前端采用 **TypeScript + React + Vite + Tauri**(推荐)还是 Flutter?
|
|
285
|
+
- **D3**:SDK 首发是否只做 TypeScript/Node,Python SDK 延后?
|
|
286
|
+
- **D4**:Skills 四个目录是否就按 `fleet-control / memory-sync / session-ref / resource-schedule` 冻结?
|
|
287
|
+
- **D5**:发布审批人/窗口,以及是否允许我在正式发版前先跑一次“预演发布”(不 tag、不 npm publish)。
|
|
288
|
+
|
|
289
|
+
- **D6**:记忆层默认后端用 SQLite(推荐)还是直接 Postgres?Priostack ACN / ContextGO 等外部记忆设施是否作为 v2.2.2 的可选连接器(推荐)而不是硬依赖?
|
|
290
|
+
|
|
291
|
+
- **D7**:平台适配的验收边界:13 个客户端是否全部要求“实机契约通过”,还是 Claude/Codex/Cursor/VS Code 实机通过、其余以配置模板 + PLANNED 标记交付?
|
|
292
|
+
|
|
293
|
+
在 D1–D7 确认前,本方案不改代码、不发版。
|
package/lib/client.js
CHANGED
|
@@ -499,8 +499,13 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
499
499
|
'.w2mChat_stickerBig{font-size:28px;line-height:1.1}',
|
|
500
500
|
'.w2mChat_emojiRow{display:flex;gap:4px;padding:4px 8px;flex-wrap:wrap}',
|
|
501
501
|
'.w2mChat_emoji{padding:2px 4px;border:0;background:transparent;font-size:16px;line-height:1;cursor:pointer}',
|
|
502
|
-
'.w2mFleetStatus{display:flex;gap:16px;align-items:center;padding:2px 12px;font-size:11px;opacity:.8;white-space:nowrap}',
|
|
502
|
+
'.w2mFleetStatus{display:flex;gap:16px;align-items:center;justify-content:center;width:100%;padding:2px 12px;font-size:11px;opacity:.8;white-space:nowrap}',
|
|
503
503
|
'.w2mFleetStatusItem{white-space:nowrap}',
|
|
504
|
+
'.w2mRelayItem{display:inline-flex;align-items:center;gap:4px}',
|
|
505
|
+
'.w2mRelayDot{width:8px;height:8px;border-radius:50%;display:inline-block;background:var(--dsw-alias-state-idle-primary)}',
|
|
506
|
+
'.w2mRelayDot[data-state="green"]{background:var(--dsw-alias-state-success-primary)}',
|
|
507
|
+
'.w2mRelayDot[data-state="yellow"]{background:var(--dsw-alias-state-warn-primary)}',
|
|
508
|
+
'.w2mRelayDot[data-state="red"]{background:var(--dsw-alias-state-danger-primary)}',
|
|
504
509
|
'.w2mSearchToggle{height:26px;width:26px;border:0;border-radius:6px;background:transparent;color:inherit;font-size:14px;cursor:pointer}',
|
|
505
510
|
'.w2mSearchToggle:hover{background:rgba(127,127,127,.14)}',
|
|
506
511
|
'.w2mSearchBox{display:flex;align-items:center;gap:4px;padding:2px 4px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-background-secondary)}',
|
|
@@ -2080,12 +2085,33 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
|
|
|
2080
2085
|
return () => { alive = false; clearInterval(timer); };
|
|
2081
2086
|
}, []);
|
|
2082
2087
|
const fleet = snapshot?.fleet ?? null;
|
|
2083
|
-
const
|
|
2088
|
+
const relays = snapshot?.relays ?? null;
|
|
2089
|
+
const server = relays?.server ?? null;
|
|
2084
2090
|
const fleetText = fleet && Number.isFinite(fleet.online) && Number.isFinite(fleet.total) ? fleet.online + '/' + fleet.total : '—';
|
|
2085
2091
|
const serverText = server && Number.isFinite(server.devices_online) && Number.isFinite(server.devices_total) ? server.devices_online + '/' + server.devices_total : '—';
|
|
2086
|
-
|
|
2092
|
+
const relayItems = Array.isArray(relays?.shown) ? relays.shown.slice(0, 3) : [];
|
|
2093
|
+
const relayLabel = (index) => (['主', '备1', '备2'][index] ?? ('备' + index));
|
|
2094
|
+
const relayDot = (relay) => {
|
|
2095
|
+
if (relay?.ok !== true) return 'red';
|
|
2096
|
+
if (Number.isFinite(relay.rtt_ms) && relay.rtt_ms > 500) return 'yellow';
|
|
2097
|
+
return 'green';
|
|
2098
|
+
};
|
|
2099
|
+
const relayTitle = (relay) => {
|
|
2100
|
+
if (relay?.ok !== true) return (relay?.url ?? 'relay') + ':离线' + (relay?.error ? '(' + relay.error + ')' : '');
|
|
2101
|
+
return (relay?.url ?? 'relay') + ':在线,' + (Number.isFinite(relay?.rtt_ms) ? relay.rtt_ms + 'ms' : '延迟未知');
|
|
2102
|
+
};
|
|
2103
|
+
return h('div', { className: 'w2mFleetStatus', title: '编队、全服与 Relay 在线状态' }, [
|
|
2087
2104
|
h('span', { key: 'f', className: 'w2mFleetStatusItem' }, '本编队在线 ' + fleetText),
|
|
2088
|
-
h('span', { key: 's', className: 'w2mFleetStatusItem' }, '
|
|
2105
|
+
h('span', { key: 's', className: 'w2mFleetStatusItem' }, '全服机器在线 ' + serverText),
|
|
2106
|
+
h('span', { key: 'r', className: 'w2mFleetStatusItem' }, [
|
|
2107
|
+
h('span', { key: 'label' }, '在线Relay '),
|
|
2108
|
+
...(relayItems.length === 0
|
|
2109
|
+
? [h('span', { key: 'none' }, '—')]
|
|
2110
|
+
: relayItems.map((relay, index) => h('span', { key: 'relay-' + index, className: 'w2mRelayItem', title: relayTitle(relay) }, [
|
|
2111
|
+
h('span', { key: 'dot', className: 'w2mRelayDot', 'data-state': relayDot(relay) }),
|
|
2112
|
+
h('span', { key: 'name' }, relayLabel(index)),
|
|
2113
|
+
]))),
|
|
2114
|
+
]),
|
|
2089
2115
|
]);
|
|
2090
2116
|
}
|
|
2091
2117
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.9",
|
|
4
4
|
"description": "Run one project across your own Windows and macOS machines from a single DeepSeek account, through 11 w2m_* tools. Four coordination modes (replicate, split, broadcast, pipeline) and six aggregate verdicts (consistent, divergent, divergent-platform, failed, partial, unverifiable). P2P hole punching by default, with a bundled relay, STUN responder and ledger fallback, so the fleet needs no third-party service. Joining needs a pairing code plus autoStartAgent: true; installing alone does not join.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -48,7 +48,9 @@
|
|
|
48
48
|
"SECURITY.md",
|
|
49
49
|
"CONTRIBUTING.md",
|
|
50
50
|
".claude-plugin",
|
|
51
|
-
"codex"
|
|
51
|
+
"codex",
|
|
52
|
+
"TEST-PLAN-JOINT-FLEET-2.0.8.md",
|
|
53
|
+
"TEST-CASES-JOINT-FLEET-2.0.8.md"
|
|
52
54
|
],
|
|
53
55
|
"engines": {
|
|
54
56
|
"node": ">=20.19.0"
|
|
@@ -64,7 +66,7 @@
|
|
|
64
66
|
"pack": "node scripts/pack.mjs",
|
|
65
67
|
"reachability": "node scripts/reachability-check.mjs",
|
|
66
68
|
"reachability:classify": "node scripts/reachability-classify.mjs",
|
|
67
|
-
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/blake2s.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.test.mjs test/fleet-memory-e2e.test.mjs test/fleet-concurrency.test.mjs test/fleet-merge-race.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/portable-plugins.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs test/admission.test.mjs test/admission-routes.test.mjs test/join-degradation.test.mjs test/account-identity.test.mjs test/join-challenge.test.mjs test/exec-env-injection.test.mjs test/account-join.test.mjs test/fleet-messages.test.mjs test/message-shards.test.mjs test/fleet-messages-http.test.mjs test/message-notify.test.mjs test/update-channels.test.mjs test/update-prerelease.test.mjs test/session-watch.test.mjs test/session-readonly.test.mjs test/session-sync.test.mjs test/fleet-chat-identity.test.mjs test/identity-reader.test.mjs test/intents.test.mjs test/dataflow.test.mjs test/council-fleet.test.mjs test/fleet-memory.test.mjs test/memory-view.test.mjs test/schema-spec.test.mjs test/fleet-weight-parity.test.mjs test/task-deps.test.mjs test/dataflow-wiring.test.mjs test/task-graph.test.mjs test/ask-store.test.mjs test/turn.test.mjs test/fleet-members.test.mjs test/redact.test.mjs test/policy-matrix.test.mjs test/envelope-signature.test.mjs test/chat-crypto.test.mjs test/reduce-suggestion.test.mjs test/merge-hint.test.mjs test/fleet-chat-route.test.mjs test/maintenance.test.mjs test/maintenance-ferry.test.mjs test/grant.test.mjs test/grant-gate.test.mjs test/worktree.test.mjs test/patch-artifact.test.mjs test/merge-queue.test.mjs test/m3-workflow.test.mjs test/project-room.test.mjs test/project-attribution.test.mjs test/project-locks.test.mjs test/project-reviews.test.mjs test/project-notifications.test.mjs test/blob-sessions.test.mjs test/outbox.test.mjs test/reliability-metrics.test.mjs test/evidence.test.mjs test/grant-issue.test.mjs test/project-refs.test.mjs test/exec-capability.test.mjs test/secondary-link.test.mjs test/bootstrap-cli.test.mjs test/bootstrap.test.mjs test/windows-hidden-task.test.mjs test/relay-failover.test.mjs test/npm-update.test.mjs test/agent-start-nonblocking.test.mjs test/fleet-chat-capture.test.mjs test/capture-route.test.mjs test/update-resolver.test.mjs test/agent-identity-safe.test.mjs test/re-pair-stale-token.test.mjs test/gpw-folder-policy.test.mjs test/gpw-activation.test.mjs test/gpw-hardening.test.mjs test/gpw-tamper-gate.test.mjs test/gpw-persistence.test.mjs test/gpw-colors.test.mjs test/gpw-recent-fold.test.mjs test/gpw-auto-approve.test.mjs test/joint-dev-dispatch.test.mjs test/joint-dev-loop.test.mjs test/joint-dev-audit.test.mjs test/joint-dev-redline.test.mjs test/relay-redline-guard.test.mjs test/relay-p0-projection.test.mjs test/guarded-dispatch.test.mjs test/p0-single-matcher.test.mjs test/path-policy.test.mjs test/path-policy-fs.test.mjs test/gpw-inheritance-table.test.mjs test/gpw-policy-adapter.test.mjs test/target-policy.test.mjs test/relay-redline-audit.test.mjs test/relay-repair-ledger.test.mjs test/relay-purge-blobs.test.mjs test/relay-scrub.test.mjs test/joint-dev-plan-store.test.mjs test/relay-basepath-blobs.test.mjs test/operator-token.test.mjs test/relay-health-capabilities.test.mjs test/joint-relay-dispatch.test.mjs test/operator-token-route.test.mjs test/relay-scrub-task-projection.test.mjs test/operator-key-approval.test.mjs test/relay-offer-journal.test.mjs test/joint-r5-wiring.test.mjs test/p1-hardening.test.mjs test/joint-merge-engine.test.mjs test/artifact-ref-dir-sink.test.mjs test/chat-sticker-guard.test.mjs test/relay-task-progress.test.mjs test/gpw-modes.test.mjs test/joint-cli.test.mjs test/joint-command-hash.test.mjs test/joint-dispatch-audit.test.mjs test/tools-cancel-auth.test.mjs test/fleet-mirror.test.mjs test/sink-git-ssh.test.mjs test/agent-sse-watchdog.test.mjs test/version-gate.test.mjs test/joint-merge-cli.test.mjs test/joint-progress.test.mjs test/project-room-truncation.test.mjs test/chat-crypto-aad.test.mjs test/agent-artifact-delivery.test.mjs test/dataplane-sink-route.test.mjs test/artifact-sink-e2e.test.mjs test/project-room-persistence.test.mjs test/project-room-disk-persistence.test.mjs test/project-stream-route.test.mjs test/chat-v2-gate.test.mjs",
|
|
69
|
+
"test": "node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/blake2s.test.mjs test/agent.test.mjs test/plan-sec-cmd.test.mjs test/authority.test.mjs test/blocks.test.mjs test/approval-index.test.mjs test/fleet-sync.test.mjs test/fleet-merge.test.mjs test/fleet-memory-e2e.test.mjs test/fleet-concurrency.test.mjs test/fleet-merge-race.test.mjs test/health.test.mjs test/storage.test.mjs test/exit-channel.test.mjs test/council.test.mjs test/council-session.test.mjs test/log-bounds.test.mjs test/test-report.test.mjs test/fleet-scale.test.mjs test/fleet-state.test.mjs test/crypto-blake2s.test.mjs test/installed-version.test.mjs test/permission.test.mjs test/machine-test-packet.test.mjs test/field-authority.test.mjs test/approval-gate.test.mjs test/authority-choice.test.mjs test/reachability-check.test.mjs test/reachability-classify.test.mjs test/accepted-orphans.test.mjs test/acceptance-doc.test.mjs test/layer-model.test.mjs test/cwd-containment.test.mjs test/cwd-outside-project.test.mjs test/memory.test.mjs test/tools.test.mjs test/claims.test.mjs test/fleet-target.test.mjs test/client-bundle.test.mjs test/schedule.test.mjs test/auto-update.test.mjs test/update-source.test.mjs test/update-install.test.mjs test/update-wiring.test.mjs test/signing.test.mjs test/stress.test.mjs test/shared-config.test.mjs test/metrics.test.mjs test/metrics-route.test.mjs test/tool-cards.test.mjs test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-crypto.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/chatops-approval.test.mjs test/chatops-policy.test.mjs test/chatops-file-safety.test.mjs test/chatops-outbound-queue.test.mjs test/chatops-commands.test.mjs test/chatops-bridge.test.mjs test/chatops-config-route.test.mjs test/chatops-apply.test.mjs test/chatops-ilink-contract.test.mjs test/chatops-fleet.test.mjs test/chatops-account.test.mjs test/chatops-auth.test.mjs test/chatops-fleet-scope.test.mjs test/chatops-handoff.test.mjs test/chatops-fleet-wiring.test.mjs test/chatops-fleet-live.test.mjs test/chatops-takeover.test.mjs test/chatops-device-signing.test.mjs test/chatops-relay-verify.test.mjs test/chatops-durability.test.mjs test/chatops-run-command.test.mjs test/secrets-guard.test.mjs test/chatops-ilink-fleet-bridge.test.mjs test/agent-plugins.test.mjs test/memory-layer.test.mjs test/mcp-server.test.mjs test/portable-plugins.test.mjs test/mcp-http.test.mjs test/mcp-http-live.test.mjs test/vision.test.mjs test/ilink-shape.test.mjs test/chatops-fleet-join.test.mjs test/admission.test.mjs test/admission-routes.test.mjs test/join-degradation.test.mjs test/account-identity.test.mjs test/join-challenge.test.mjs test/exec-env-injection.test.mjs test/account-join.test.mjs test/fleet-messages.test.mjs test/message-shards.test.mjs test/fleet-messages-http.test.mjs test/message-notify.test.mjs test/update-channels.test.mjs test/update-prerelease.test.mjs test/session-watch.test.mjs test/session-readonly.test.mjs test/session-sync.test.mjs test/fleet-chat-identity.test.mjs test/identity-reader.test.mjs test/intents.test.mjs test/dataflow.test.mjs test/council-fleet.test.mjs test/fleet-memory.test.mjs test/memory-view.test.mjs test/schema-spec.test.mjs test/fleet-weight-parity.test.mjs test/task-deps.test.mjs test/dataflow-wiring.test.mjs test/task-graph.test.mjs test/ask-store.test.mjs test/turn.test.mjs test/fleet-members.test.mjs test/redact.test.mjs test/policy-matrix.test.mjs test/envelope-signature.test.mjs test/chat-crypto.test.mjs test/reduce-suggestion.test.mjs test/merge-hint.test.mjs test/fleet-chat-route.test.mjs test/maintenance.test.mjs test/maintenance-ferry.test.mjs test/grant.test.mjs test/grant-gate.test.mjs test/worktree.test.mjs test/patch-artifact.test.mjs test/merge-queue.test.mjs test/m3-workflow.test.mjs test/project-room.test.mjs test/project-attribution.test.mjs test/project-locks.test.mjs test/project-reviews.test.mjs test/project-notifications.test.mjs test/blob-sessions.test.mjs test/outbox.test.mjs test/reliability-metrics.test.mjs test/evidence.test.mjs test/grant-issue.test.mjs test/project-refs.test.mjs test/exec-capability.test.mjs test/secondary-link.test.mjs test/bootstrap-cli.test.mjs test/bootstrap.test.mjs test/windows-hidden-task.test.mjs test/relay-failover.test.mjs test/npm-update.test.mjs test/agent-start-nonblocking.test.mjs test/fleet-chat-capture.test.mjs test/capture-route.test.mjs test/update-resolver.test.mjs test/agent-identity-safe.test.mjs test/re-pair-stale-token.test.mjs test/gpw-folder-policy.test.mjs test/gpw-activation.test.mjs test/gpw-hardening.test.mjs test/gpw-tamper-gate.test.mjs test/gpw-persistence.test.mjs test/gpw-colors.test.mjs test/gpw-recent-fold.test.mjs test/gpw-auto-approve.test.mjs test/joint-dev-dispatch.test.mjs test/joint-dev-loop.test.mjs test/joint-dev-audit.test.mjs test/joint-dev-redline.test.mjs test/relay-redline-guard.test.mjs test/relay-p0-projection.test.mjs test/guarded-dispatch.test.mjs test/p0-single-matcher.test.mjs test/path-policy.test.mjs test/path-policy-fs.test.mjs test/gpw-inheritance-table.test.mjs test/gpw-policy-adapter.test.mjs test/target-policy.test.mjs test/relay-redline-audit.test.mjs test/relay-repair-ledger.test.mjs test/relay-purge-blobs.test.mjs test/relay-scrub.test.mjs test/joint-dev-plan-store.test.mjs test/relay-basepath-blobs.test.mjs test/operator-token.test.mjs test/relay-health-capabilities.test.mjs test/joint-relay-dispatch.test.mjs test/operator-token-route.test.mjs test/relay-scrub-task-projection.test.mjs test/operator-key-approval.test.mjs test/relay-offer-journal.test.mjs test/joint-r5-wiring.test.mjs test/p1-hardening.test.mjs test/joint-merge-engine.test.mjs test/artifact-ref-dir-sink.test.mjs test/chat-sticker-guard.test.mjs test/relay-task-progress.test.mjs test/gpw-modes.test.mjs test/joint-cli.test.mjs test/joint-command-hash.test.mjs test/joint-dispatch-audit.test.mjs test/tools-cancel-auth.test.mjs test/fleet-mirror.test.mjs test/sink-git-ssh.test.mjs test/agent-sse-watchdog.test.mjs test/version-gate.test.mjs test/joint-merge-cli.test.mjs test/joint-progress.test.mjs test/project-room-truncation.test.mjs test/chat-crypto-aad.test.mjs test/agent-artifact-delivery.test.mjs test/dataplane-sink-route.test.mjs test/artifact-sink-e2e.test.mjs test/project-room-persistence.test.mjs test/project-room-disk-persistence.test.mjs test/project-stream-route.test.mjs test/chat-v2-gate.test.mjs test/xiangqi.test.mjs test/xfer-foundation.test.mjs test/xfer-ferry.test.mjs test/xfer-publish.test.mjs test/xfer-want-block.test.mjs test/p2p-cipher-diag.test.mjs test/chacha-fallback.test.mjs test/fleet-binding.test.mjs test/xfer-channel.test.mjs test/p2p-race.test.mjs test/path-scheduler.test.mjs test/transport.test.mjs test/iroh-sidecar.test.mjs test/discovery.test.mjs test/p2p-rtt.test.mjs test/artifact-ref-v3.test.mjs test/xfer-recovery.test.mjs test/xfer-router.test.mjs",
|
|
68
70
|
"test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
|
|
69
71
|
"test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
|
|
70
72
|
"test:account-join": "node --test --test-force-exit test/account-join.test.mjs",
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "twinsearth-w2m-dsh",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.9",
|
|
5
5
|
"description": "Run one project across your own Windows and macOS machines from a single DeepSeek account, through 12 w2m_* tools. Four coordination modes (replicate, split, broadcast, pipeline) and six aggregate verdicts (consistent, divergent, divergent-platform, failed, partial, unverifiable). P2P hole punching by default, with a bundled relay, STUN responder and ledger fallback, so the fleet needs no third-party service. Joining needs a pairing code plus autoStartAgent: true; installing alone does not join.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
package/src/agent/agent.mjs
CHANGED
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
readExecCapability,
|
|
70
70
|
} from '../util/exec-capability.mjs';
|
|
71
71
|
import { CRYPTO_REASON, DEVICE_KEY_FILE, DeviceKey, PeerKeyStore, deriveSigningKey } from './crypto.mjs';
|
|
72
|
+
import { createXferFrameRouter } from './transfer/router.mjs';
|
|
72
73
|
import { DEFAULT_P2P_MODE, P2P_DEFAULTS, P2PNode, normalizeP2PMode, normalizeP2PPort } from './p2p-node.mjs';
|
|
73
74
|
import { punchSession, reverseDialSession } from './p2p.mjs';
|
|
74
75
|
import { createSigner } from '../signing.mjs';
|
|
@@ -1142,6 +1143,8 @@ export function createAgent(options) {
|
|
|
1142
1143
|
* @type {PeerKeyStore}
|
|
1143
1144
|
*/
|
|
1144
1145
|
const peerKeys = new PeerKeyStore();
|
|
1146
|
+
/** P0-a: xfer.* frames arriving on a P2P channel are routed here, not to task dispatch. */
|
|
1147
|
+
const xferRouter = createXferFrameRouter();
|
|
1145
1148
|
/**
|
|
1146
1149
|
* What each task's direct channel actually was, for the result envelope.
|
|
1147
1150
|
*
|
|
@@ -3959,6 +3962,10 @@ let attemptAttached = false;
|
|
|
3959
3962
|
*/
|
|
3960
3963
|
function handleP2PMessage(event) {
|
|
3961
3964
|
const payload = parseP2PFrame(event.payload);
|
|
3965
|
+
if (payload !== null && typeof payload.type === 'string' && payload.type.startsWith('xfer.')) {
|
|
3966
|
+
xferRouter.handle(payload, { channel: event.channel });
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3962
3969
|
if (payload === null) {
|
|
3963
3970
|
log('warn', 'ignoring non-JSON frame on a P2P channel', {
|
|
3964
3971
|
peer: event.peer ?? null,
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChaCha20-Poly1305 (RFC 8439) in plain JS — the fallback for runtimes whose OpenSSL/BoringSSL
|
|
3
|
+
* has no `chacha20-poly1305` (notably the DSH Electron host). Wire bytes are identical to the
|
|
4
|
+
* native AEAD, so peers cannot tell which implementation produced them.
|
|
5
|
+
*/
|
|
6
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
const P = (1n << 130n) - 5n;
|
|
9
|
+
const MASK128 = (1n << 128n) - 1n;
|
|
10
|
+
const rotl = (x, n) => ((x << n) | (x >>> (32 - n))) >>> 0;
|
|
11
|
+
const le32 = (buf, off) => buf.readUInt32LE(off);
|
|
12
|
+
const CONSTANTS = Buffer.from('expand 32-byte k', 'ascii');
|
|
13
|
+
|
|
14
|
+
function quarterRound(s, a, b, c, d) {
|
|
15
|
+
s[a] = (s[a] + s[b]) >>> 0; s[d] = rotl(s[d] ^ s[a], 16);
|
|
16
|
+
s[c] = (s[c] + s[d]) >>> 0; s[b] = rotl(s[b] ^ s[c], 12);
|
|
17
|
+
s[a] = (s[a] + s[b]) >>> 0; s[d] = rotl(s[d] ^ s[a], 8);
|
|
18
|
+
s[c] = (s[c] + s[d]) >>> 0; s[b] = rotl(s[b] ^ s[c], 7);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function chachaBlock(key, nonce, counter) {
|
|
22
|
+
const state = new Uint32Array(16);
|
|
23
|
+
for (let i = 0; i < 4; i += 1) state[i] = le32(CONSTANTS, i * 4);
|
|
24
|
+
for (let i = 0; i < 8; i += 1) state[4 + i] = le32(key, i * 4);
|
|
25
|
+
state[12] = counter >>> 0;
|
|
26
|
+
for (let i = 0; i < 3; i += 1) state[13 + i] = le32(nonce, i * 4);
|
|
27
|
+
const working = state.slice();
|
|
28
|
+
for (let i = 0; i < 10; i += 1) {
|
|
29
|
+
quarterRound(working, 0, 4, 8, 12); quarterRound(working, 1, 5, 9, 13);
|
|
30
|
+
quarterRound(working, 2, 6, 10, 14); quarterRound(working, 3, 7, 11, 15);
|
|
31
|
+
quarterRound(working, 0, 5, 10, 15); quarterRound(working, 1, 6, 11, 12);
|
|
32
|
+
quarterRound(working, 2, 7, 8, 13); quarterRound(working, 3, 4, 9, 14);
|
|
33
|
+
}
|
|
34
|
+
const out = Buffer.alloc(64);
|
|
35
|
+
for (let i = 0; i < 16; i += 1) out.writeUInt32LE((working[i] + state[i]) >>> 0, i * 4);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function keystreamXor(key, nonce, input, startCounter) {
|
|
40
|
+
const out = Buffer.alloc(input.length);
|
|
41
|
+
let counter = startCounter;
|
|
42
|
+
for (let offset = 0; offset < input.length; offset += 64, counter += 1) {
|
|
43
|
+
const block = chachaBlock(key, nonce, counter);
|
|
44
|
+
const end = Math.min(offset + 64, input.length);
|
|
45
|
+
for (let i = offset; i < end; i += 1) out[i] = input[i] ^ block[i - offset];
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function poly1305(message, key) {
|
|
51
|
+
const rBytes = Buffer.from(key.subarray(0, 16));
|
|
52
|
+
rBytes[3] &= 15; rBytes[7] &= 15; rBytes[11] &= 15; rBytes[15] &= 15;
|
|
53
|
+
rBytes[4] &= 252; rBytes[8] &= 252; rBytes[12] &= 252;
|
|
54
|
+
const r = BigInt('0x' + Buffer.from(rBytes).reverse().toString('hex'));
|
|
55
|
+
const s = BigInt('0x' + Buffer.from(key.subarray(16, 32)).reverse().toString('hex'));
|
|
56
|
+
let acc = 0n;
|
|
57
|
+
for (let offset = 0; offset < message.length; offset += 16) {
|
|
58
|
+
const chunk = message.subarray(offset, Math.min(offset + 16, message.length));
|
|
59
|
+
let n = BigInt('0x' + Buffer.from(chunk).reverse().toString('hex'));
|
|
60
|
+
n += 1n << BigInt(8 * chunk.length);
|
|
61
|
+
acc = ((acc + n) * r) % P;
|
|
62
|
+
}
|
|
63
|
+
acc = (acc + s) & MASK128;
|
|
64
|
+
const out = Buffer.alloc(16);
|
|
65
|
+
let value = acc;
|
|
66
|
+
for (let i = 0; i < 16; i += 1) { out[i] = Number(value & 0xffn); value >>= 8n; }
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const pad16 = (length) => (length % 16 === 0 ? Buffer.alloc(0) : Buffer.alloc(16 - (length % 16)));
|
|
71
|
+
const le64 = (value) => { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(value)); return b; };
|
|
72
|
+
|
|
73
|
+
function macData(aad, ciphertext) {
|
|
74
|
+
return Buffer.concat([aad, pad16(aad.length), ciphertext, pad16(ciphertext.length), le64(aad.length), le64(ciphertext.length)]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @returns {Buffer} ciphertext followed by the 16-byte tag. */
|
|
78
|
+
export function jsSeal(key, nonce, plaintext, aad = Buffer.alloc(0)) {
|
|
79
|
+
const polyKey = chachaBlock(key, nonce, 0).subarray(0, 32);
|
|
80
|
+
const ciphertext = keystreamXor(key, nonce, plaintext, 1);
|
|
81
|
+
return Buffer.concat([ciphertext, poly1305(macData(aad, ciphertext), polyKey)]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** @returns {Buffer|null} plaintext, or null when the tag does not verify. */
|
|
85
|
+
export function jsOpen(key, nonce, sealed, aad = Buffer.alloc(0)) {
|
|
86
|
+
if (!Buffer.isBuffer(sealed) || sealed.length < 16) return null;
|
|
87
|
+
const ciphertext = sealed.subarray(0, sealed.length - 16);
|
|
88
|
+
const tag = sealed.subarray(sealed.length - 16);
|
|
89
|
+
const polyKey = chachaBlock(key, nonce, 0).subarray(0, 32);
|
|
90
|
+
const expected = poly1305(macData(aad, ciphertext), polyKey);
|
|
91
|
+
if (!timingSafeEqual(tag, expected)) return null;
|
|
92
|
+
return keystreamXor(key, nonce, ciphertext, 1);
|
|
93
|
+
}
|