@twinsearth/w2m-dsh-plugin 2.0.6 → 2.0.8

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.
@@ -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.6"
8
+ "version": "2.0.8"
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.6",
15
+ "version": "2.0.8",
16
16
  "author": {
17
17
  "name": "TwinsEarth"
18
18
  },
@@ -26,5 +26,5 @@
26
26
  ]
27
27
  }
28
28
  ],
29
- "version": "2.0.6"
29
+ "version": "2.0.8"
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.6",
4
+ "version": "2.0.8",
5
5
  "author": {
6
6
  "name": "TwinsEarth"
7
7
  },
package/CHANGELOG.md CHANGED
@@ -1,11 +1,29 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
1
5
  ## [Unreleased]
2
6
 
3
7
  - `w2m_intent`: structured intents mapped onto allow-listed commands; unknown actions, parameters and types
4
- are refused by name (the fleet does not invent commands).
5
-
6
- # Changelog
7
-
8
- All notable changes to this project are documented here.
8
+ are refused by name (the fleet does not invent commands).
9
+
10
+ ## [2.0.8] - 2026-10-29
11
+
12
+ - P2P 加密:新增 RFC 8439 ChaCha20-Poly1305 纯 JS 回退,Electron/BoringSSL 缺少 chacha 时自动启用,wire 字节与原生实现一致;保留 P2P_CIPHER_UNAVAILABLE 诊断。
13
+ - 编队绑定:w2m_status 新增 fleet 诊断块与 FLEET_UNBOUND;token 有效但 fleet_id=null 且已配置 fleetKey 时,启动路径自动重新准入,避免“只在全部机器、不在本编队”。
14
+ - 文件传输:want-block 协议(窗口、逐块校验、续传)与 sender/receiver 双角色 E2E 脚本。
15
+ - 测试:chacha 回退(Node + Electron)、fleet-binding、xfer 套件。
16
+
17
+
18
+
19
+ ## [2.0.7] - 2026-10-29
20
+
21
+ - A7 encrypted mirror:/healthz.features 新增 `chat-enc-v2` 门控(默认 off);支持时用
22
+ `encryptChatTextV2`(AAD=session/machine/message_id)投递会话镜像,否则保持 v1;
23
+ 读取端用 `decryptChatMessageV2` 的上下文校验并保持 v1 兼容。
24
+ - 聊天发送:relay 广告 v2 且本机有 machine id 时发 v2,否则回落 v1;同一 idempotency_key 作为
25
+ AAD message_id,重试不会产生第二行。
26
+ - 插件设置中开启 Node 代码执行能力后,任务派发无需 relay 改动;本机已切换为 permanent。
9
27
 
10
28
  ## [2.0.6] - 2026-10-29
11
29
 
@@ -11032,4 +11050,4 @@ First release. Scope: **one DeepSeek account, one instruction, several machines
11032
11050
  delete `state.json` in the relay's state directory and restart.
11033
11051
 
11034
11052
  [0.0.1]: https://github.com/TwinsEarth/dsh-windows2macos/releases/tag/v0.0.1
11035
- [0.1.2]: https://github.com/TwinsEarth/dsh-windows2macos/releases/tag/v0.1.2
11053
+ [0.1.2]: https://github.com/TwinsEarth/dsh-windows2macos/releases/tag/v0.1.2
@@ -12,6 +12,6 @@
12
12
 
13
13
  [mcp_servers.w2m-fleet]
14
14
  command = "npx"
15
- args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@2.0.6", "w2m-mcp"]
15
+ args = ["-y", "-p", "@twinsearth/w2m-dsh-plugin@2.0.8", "w2m-mcp"]
16
16
  startup_timeout_sec = 20
17
17
  tool_timeout_sec = 60
@@ -0,0 +1,21 @@
1
+ {
2
+ "version": "2.0.7",
3
+ "case": "RELEASE-2070",
4
+ "kind": "positive",
5
+ "machines": [
6
+ "b53a37f4-1464-4512-9259-33a7df091e3e"
7
+ ],
8
+ "executed_at": "2026-10-28T17:37:31.887Z",
9
+ "executor": "lead",
10
+ "task_id": "pre-release-2070-1793209051887",
11
+ "requirement": "v2.0.7: chat-enc-v2 门控 + encrypted mirror + v2 发送/读取;Node 执行能力开启",
12
+ "result": {
13
+ "outcome": "ok",
14
+ "assertions": {
15
+ "preflight": "GREEN",
16
+ "tests": "all declared suites",
17
+ "lint": "0 errors",
18
+ "scope": "local; relay already advertises chat-enc-v2"
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "version": "2.0.8",
3
+ "case": "RELEASE-2080",
4
+ "kind": "positive",
5
+ "machines": [
6
+ "b53a37f4-1464-4512-9259-33a7df091e3e"
7
+ ],
8
+ "executed_at": "2026-10-29T14:40:36.768Z",
9
+ "executor": "lead",
10
+ "task_id": "pre-release-2080-1793284836769",
11
+ "requirement": "v2.0.8: fleet-binding diagnostics + FLEET_UNBOUND auto re-admission; ChaCha20-Poly1305 JS fallback for Electron; xfer want-block + dual-role E2E",
12
+ "result": {
13
+ "outcome": "ok",
14
+ "assertions": {
15
+ "fleet_binding": "4/4 unit tests; live relay ToFuture fleet_id=004945… bound",
16
+ "chacha_fallback": "Node 46/46; Electron 44/44 (p2p-crypto + cipher diagnostics)",
17
+ "xfer": "xfer suites 19/19; loopback + sender/receiver split verified",
18
+ "lint": "0 errors"
19
+ }
20
+ }
21
+ }
@@ -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,7 +499,7 @@ 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
504
  '.w2mSearchToggle{height:26px;width:26px;border:0;border-radius:6px;background:transparent;color:inherit;font-size:14px;cursor:pointer}',
505
505
  '.w2mSearchToggle:hover{background:rgba(127,127,127,.14)}',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twinsearth/w2m-dsh-plugin",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
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": {
@@ -64,7 +64,7 @@
64
64
  "pack": "node scripts/pack.mjs",
65
65
  "reachability": "node scripts/reachability-check.mjs",
66
66
  "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",
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 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",
68
68
  "test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
69
69
  "test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
70
70
  "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.6",
4
+ "version": "2.0.8",
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": [
@@ -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
+ }