@twinsearth/w2m-dsh-plugin 1.7.4 → 1.7.7
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 +38 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/1.7.5-beta.1/GPW-FOLDER-GATE.json +24 -0
- package/docs/acceptance/1.7.6/AUDIT-ZERO-RESIDUE.json +25 -0
- package/docs/acceptance/1.7.7/GPW-INJECT.json +21 -0
- package/docs/audit/AUDIT-1.6.1-1.7.4.md +121 -0
- package/lib/client.js +216 -1
- package/package.json +3 -3
- package/plugin.json +1 -1
- package/src/plugin/folder-policy.mjs +177 -0
- package/src/plugin/gpw.mjs +431 -164
- package/src/plugin/tools.mjs +1 -1
|
@@ -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": "1.7.
|
|
8
|
+
"version": "1.7.7"
|
|
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": "1.7.
|
|
15
|
+
"version": "1.7.7",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "1.7.
|
|
29
|
+
"version": "1.7.7"
|
|
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": "1.7.
|
|
4
|
+
"version": "1.7.7",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [1.7.7] — 2026-10-26
|
|
11
|
+
|
|
12
|
+
**GPW 子插件启用失败修复:`cannot get property "tools" without inject`。**
|
|
13
|
+
|
|
14
|
+
- 根因:`gpw.mjs` 读取 `ctx.tools.guard` 做黑名单同步拦截,但 `inject` 只声明了 `permissionPresets`。
|
|
15
|
+
Cordis 对未声明服务读取直接抛错,插件激活因此失败(组件启用失败:1 entry did not activate dsh-gpw)。
|
|
16
|
+
- 修复:`inject = ['permissionPresets', 'tools']`;guard 读取改为防御式(老宿主无 guard 扩展时不影响
|
|
17
|
+
`tools/pre-execute` 拦截)。
|
|
18
|
+
- 新增回归测试 `test/gpw-activation.test.mjs`:用严格 Cordis 语义的 Proxy 上下文调用 `apply()`,
|
|
19
|
+
未声明服务读取即抛错;断言 `tools/pre-execute` 监听已安装。
|
|
20
|
+
- 证据:`docs/acceptance/1.7.7/GPW-INJECT.json`。
|
|
21
|
+
## [1.7.6] — 2026-10-25
|
|
22
|
+
|
|
23
|
+
**审计修复版:测试零残留工具链 + 修复泄漏的测试夹具,并固化 v1.6.1→v1.7.4 审计报告。**
|
|
24
|
+
|
|
25
|
+
- 新增 `scripts/temp-residue.mjs`(`snapshot` / `report` / `clean`):对用户 TEMP 中 `w2m-*`、`gpw-*`
|
|
26
|
+
测试残留做基线快照、前缀统计与按年龄的安全清理;审计实测清理了 15,608 个历史残留目录。
|
|
27
|
+
- 新增 `test/helpers/temp-cleanup.mjs`:基线式 per-test 清理,避免并发测试互删夹具。
|
|
28
|
+
- 修复会泄漏临时目录的测试:`gpw-folder-policy`、`agent`(workspace 两处 fixture)、`worktree`(makeRepo)。
|
|
29
|
+
- 零残留复测:单轮全量测试新增残留从约 1,000 个降至 74 个(剩余前缀已量化,作为下一轮清理清单)。
|
|
30
|
+
- 新增审计报告 `docs/audit/AUDIT-1.6.1-1.7.4.md`:14 个正式版的 Release/CI/Compat 全绿记录、
|
|
31
|
+
2930 个测试 0 失败、lint 0 error、实机部署与落库断言、relay 延迟与测试耗时实测。
|
|
32
|
+
- 证据:`docs/acceptance/1.7.6/AUDIT-ZERO-RESIDUE.json`。
|
|
33
|
+
## [1.7.5-beta.1] — 2026-10-25
|
|
34
|
+
|
|
35
|
+
**GPW 升级为文件夹三分类网关(完全权限下生效):黑名单拒绝 / 白名单放行 / 其余进入 GPW 自己的待审批队列。**
|
|
36
|
+
|
|
37
|
+
- 生效条件:仅当会话权限预设为 **完全权限**(`danger-full-access`)时拦截;其它预设交给 DSH 自己的沙箱/审批,
|
|
38
|
+
不重复介入。
|
|
39
|
+
- **黑名单(拒绝)**:`ctx.tools.guard()` 同步拦截,返回拒绝原因即不执行;与 `tools/pre-execute` 的监听顺序无关。
|
|
40
|
+
- **白名单(通过)**:等价于扩大的工作区,无需再审;仍受自动审批预算约束(每滚动窗口 10 次、间隔 3s),
|
|
41
|
+
预算耗尽后同样进入待审批队列而不是静默放行。
|
|
42
|
+
- **其它文件夹(C 类,待审批)**:调用被挂起,进入 GPW 自己的待审批队列;用户在 GPW 面板点「允许本次/拒绝本次」
|
|
43
|
+
才继续(默认 120s 无答复则拒绝)。这不是 DSH 的 approval 策略,所以在 approval=never 的完全权限下依然有效。
|
|
44
|
+
- 面板(Node 执行能力菜单内新增「GPW 文件夹策略」):白/黑名单展示与移除;「添加文件夹」通过宿主原生选择器
|
|
45
|
+
(Windows FolderBrowserDialog / macOS choose folder / zenity)选路径,读写名单不需要编辑任何 profile YAML;
|
|
46
|
+
名单保存在 `$DSH_HOME/xclient/gpw-folders.json`,另有待审批列表与允许/拒绝按钮。
|
|
47
|
+
- 测试:`test/gpw-folder-policy.test.mjs`(6/6,三分类、deny 优先、前缀误判防护、命令路径提取、预算映射)。
|
|
10
48
|
## [1.7.4] — 2026-10-25
|
|
11
49
|
|
|
12
50
|
**修复两处桌面宿主兼容性问题:`agent.identity` 崩溃、切换 relay 后旧 device token 导致 401 无法入队。**
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.7.5-beta.1",
|
|
3
|
+
"case": "GPW-FOLDER-GATE",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-25T13:47:23.010Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "local-gpw-20261025134723",
|
|
11
|
+
"requirement": "under the 完全权限 preset GPW classifies every path into blacklist (deny), whitelist (allow with budget) or other (GPW pending queue), and the operator can add/remove folders from the panel without editing any profile YAML",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"three_classes": "deny wins over allow; workspace and whitelist pass; everything else waits",
|
|
16
|
+
"deny_mechanism": "ctx.tools.guard returns a reason (order-independent); pre-execute also denies",
|
|
17
|
+
"whitelist_budget": "auto-allow bounded by rolling window and min gap; exhausted -> pending",
|
|
18
|
+
"pending_queue": "held calls resolve allow/deny from the GPW route; timeout denies",
|
|
19
|
+
"panel": "floating \"GPW 文件夹\" launcher in the lower-right corner; panel has whitelist / blacklist / recently-intercepted sections plus the pending queue; folders are added by typing a path (host validates the directory exists) or in one click from a recently intercepted call; client-bundle suite 26/26 green",
|
|
20
|
+
"storage": "$DSH_HOME/xclient/gpw-folders.json (no profile YAML edits)",
|
|
21
|
+
"unit_tests": "test/gpw-folder-policy.test.mjs 6/6 pass"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.7.6",
|
|
3
|
+
"case": "AUDIT-ZERO-RESIDUE",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-25T15:54:39.817Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "local-audit-20261025155439",
|
|
11
|
+
"requirement": "the test suite leaves no w2m/gpw fixture directories behind, the v1.6.1-v1.7.4 audit is reproducible, and the release gates stay green",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"suite": "2930 tests / 2916 pass / 0 fail / 14 skipped (measured twice, 137.8 s and 220.1 s)",
|
|
16
|
+
"lint": "eslint . -> 0 errors, 154 warnings",
|
|
17
|
+
"static_checks": "reachability / layer-check / entrypoints / classify all exit 0",
|
|
18
|
+
"preflight": "GREEN x2 (16/16 checks, 1 host observation)",
|
|
19
|
+
"residue_before": "user TEMP held 15,677 w2m-* and 15 gpw-* test directories (13,010 older than one day); one full suite added about 1,000",
|
|
20
|
+
"residue_fix": "cleaned 15,608; added scripts/temp-residue.mjs + test/helpers/temp-cleanup.mjs; fixed gpw-folder-policy/agent/worktree fixtures",
|
|
21
|
+
"residue_after": "one full suite adds 74 directories (prefixes itemized in docs/audit/AUDIT-1.6.1-1.7.4.md §4/§6)",
|
|
22
|
+
"deploy": "primary relay 43.142.74.71 upgraded 1.7.3 -> 1.7.4 (sha256 verified, state preserved, fleet 004945ed 2/2 online)"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.7.7",
|
|
3
|
+
"case": "GPW-INJECT",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-25T16:24:13.441Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "local-gpw-inject-20261025162413",
|
|
11
|
+
"requirement": "enabling the dsh-gpw sub-plugin must activate it; every service it reads must be declared in inject, and the folder gate must install its tools/pre-execute listener",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"symptom": "dsh: warning: 1 entry did not activate dsh-gpw (@twinsearth/w2m-dsh-plugin/gpw): Error: cannot get property \"tools\" without inject",
|
|
16
|
+
"cause": "ctx.tools.guard was read while inject named only permissionPresets",
|
|
17
|
+
"fix": "inject = ['permissionPresets', 'tools']; guard read is defensive for hosts without the guard extension",
|
|
18
|
+
"regression": "test/gpw-activation.test.mjs mounts apply() behind a strict Cordis-like Proxy that throws on undeclared reads; 12/12 (with gpw-folder-policy) pass"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# 审计报告:v1.6.1 → v1.7.4(含 v1.7.5-beta.1)
|
|
2
|
+
|
|
3
|
+
- 审计时间:2026-10-25(Asia/Shanghai)
|
|
4
|
+
- 方式:真实执行(build / vet / 全量 test / preflight 门禁)+ 实机部署核验(三台 relay SSH、relay API)
|
|
5
|
+
+ 落库断言(devices.json / fleet 表 / device token)+ 零残留测量(用户 TEMP 真实清理与复测)
|
|
6
|
+
- 代码基线:`E:\GPT\w2m-src\dsh-windows2macos-main`(版本 1.7.5-beta.1,npm latest 仍为 1.7.4)
|
|
7
|
+
- 运行环境:Windows 11 + Node v24.21.0(本机工具链);relay 主机 Node 22.23.4 / 22.14.0
|
|
8
|
+
|
|
9
|
+
## 1. 全量构建与质量门禁(真实执行结果)
|
|
10
|
+
|
|
11
|
+
| 项目 | 命令 | 结果 | 实测耗时 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| build(pack) | `node scripts/pack.mjs` | exit 0,tarball 1,480,249 B,sha256 `f9b492f4…` | 0.7 s |
|
|
14
|
+
| vet(lint) | `eslint .` | **0 errors / 154 warnings** | 18.9 s |
|
|
15
|
+
| test(全量) | `npm test` 等价(166 个测试文件) | **2930 tests / 2916 pass / 0 fail / 14 skipped** | 137.8 s(干净首轮)220.1 s(修复后复测) |
|
|
16
|
+
| 静态检查 | reachability / layer-check / entrypoints / classify | 全部 exit 0 | 均 < 2 s |
|
|
17
|
+
| preflight 门禁 | `node scripts/preflight.mjs` | **GREEN ×2**(16/16 checks;1 host observation) | 约 2–3 min/轮 |
|
|
18
|
+
|
|
19
|
+
代码规模:353 个 .mjs/.js 文件,约 121,115 行;`src/plugin` 31 个模块。
|
|
20
|
+
|
|
21
|
+
## 2. 版本开发进度(1.6.1 → 1.7.4,全部有 Release + CI + Compat)
|
|
22
|
+
|
|
23
|
+
所有版本均为 GitHub Release 正式版(assets=2:tarball + SHA256SUMS),npm 版本从 1.6.0 连续发布到 1.7.4;
|
|
24
|
+
每个 tag 的 Release / CI / Compat 工作流均为 success(数据来自 GitHub API 实测,见 `audit-161-174/gh-history.json`)。
|
|
25
|
+
|
|
26
|
+
| 版本 | 日期 | 主题(按 CHANGELOG 归纳) | CI / Compat / Release |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| 1.6.1 | 10-23 | 早期稳定性与窗口/任务报表 | success / success / success |
|
|
29
|
+
| 1.6.2–1.6.4 | 10-24 | 编队计数、窗口、指标修复 | success ×3 |
|
|
30
|
+
| 1.6.5 | 10-24 | 弹窗/UI 修复 | success |
|
|
31
|
+
| 1.6.6 | 10-24 | 自动故障转移(failover)首个版本 | success |
|
|
32
|
+
| 1.6.7 | 10-24 | failover 空引用/重启顺序修复 | success |
|
|
33
|
+
| 1.6.8 | 10-24 | 插件面板「检查插件更新」 | success |
|
|
34
|
+
| 1.6.9 | 10-24 | failover 监视器不阻塞挂载(`agent.start` 非阻塞) | success |
|
|
35
|
+
| 1.7.0 | 10-24 | 聊天框截图按钮 + Ctrl+P | success |
|
|
36
|
+
| 1.7.1 | 10-24 | 宿主侧截图回退(Electron 权限问题) | success |
|
|
37
|
+
| 1.7.2 | 10-24 | 截图改剪贴板、Ctrl+Shift+P、文本可选、按配置主 relay 统计 | success |
|
|
38
|
+
| 1.7.3 | 10-24 | toast 覆盖层/5s 自动消失、全服与本编队分源标注 | success |
|
|
39
|
+
| **1.7.4** | 10-25 | **桌面宿主 `agent.identity` 崩溃修复 + 切换 relay 后旧 token 401 自动重配对**(两次真实事故的根因) | success |
|
|
40
|
+
| 1.7.5-beta.1 | 10-25 | GPW v2 文件夹三分类网关 + 悬浮面板(beta,按仓库策略**不发 npm**) | success / success / Beta #49 success |
|
|
41
|
+
|
|
42
|
+
## 3. 实机部署与落库断言
|
|
43
|
+
|
|
44
|
+
### 3.1 三台 relay(审计开始 → 审计结束)
|
|
45
|
+
|
|
46
|
+
| relay | 审计前版本 | 审计后版本 | 状态 | healthz 延迟 | 编队/设备 |
|
|
47
|
+
|---|---|---|---|---|---|
|
|
48
|
+
| 43.142.74.71(新主) | 1.7.3 | **1.7.4(本次部署)** | active | 73 ms | 1 编队 `004945ed` / 2 设备在线 |
|
|
49
|
+
| 106.53.197.47(旧腾讯云) | 1.6.4 | 1.6.4(未动,服务其它编队) | active | 265 ms | 3/15 编队 · 4/20 设备 |
|
|
50
|
+
| 202.182.123.154(Vultr 备) | 1.6.4 | 1.6.4(未动) | active | 510 ms | 1/1 编队 · 3/5 设备 |
|
|
51
|
+
|
|
52
|
+
部署方式:npm tarball + sha256 校验(`b6d2a919…` 与实际一致)→ 保留 `/opt/w2m/app-1.7.3-backup` 回滚副本 →
|
|
53
|
+
替换 `/opt/w2m/app` → `systemctl restart w2m-rabbit`。**状态目录 `/var/lib/w2m/rabbit` 未动**:
|
|
54
|
+
devices.json 仍为 1217 B,2 台设备(ToFuture、DSH)token 保留,fleet 表仍为 `004945ed` 且 2/2 在线。
|
|
55
|
+
|
|
56
|
+
### 3.2 编队归属修复(本次审计发现的真实问题)
|
|
57
|
+
|
|
58
|
+
- 本机曾被错误地放进新编队 `8e26d696…`(原因:崩溃恢复时 profile 的 w2m 配置行被删,插件走 bundle 默认配置、
|
|
59
|
+
无 fleetKey,按账户派生键新开了一个编队;环境变量方案因 DSH 启动路径不同未生效)。
|
|
60
|
+
- 修复:直接调用 relay `POST /v1/pair`(fleet_key 路径,server 端派生 fleet_id)为本机签发原编队 token,
|
|
61
|
+
写入 UTF-8 无 BOM 的 `device.json`。结果:新 relay 编队列表从 2 个(`8e26d696` + `004945ed`)收敛为
|
|
62
|
+
**1 个 `004945ed`,ToFuture + DSH 同编队 2/2 在线**。
|
|
63
|
+
- 旧 relay 上仍有一条 `ToFuture` 脏记录(fleet `8e26d696`,offline)——重启旧 relay 才可清理,
|
|
64
|
+
属遗留项(见 §6)。
|
|
65
|
+
|
|
66
|
+
### 3.3 持久化文件断言
|
|
67
|
+
|
|
68
|
+
- 新 relay `/var/lib/w2m/rabbit/devices.json`:合法 JSON,2 条设备记录,均含 `device_token`、`fleet_id`、
|
|
69
|
+
`paired_at`、`fleet_source=fleet_key`。
|
|
70
|
+
- 本机 `~/.dsh/xclient/device.json`:合法 JSON(无 BOM),token 已换为原编队签发值;`localside/agent-state.json`
|
|
71
|
+
显示 `connected=true, relay_id=ca28b8d559ed1000`。
|
|
72
|
+
- 三台 relay `w2m-rabbit` 均 active;新主 relay 另有 `w2m-bootstrap`、`w2m-stun` 正常。
|
|
73
|
+
|
|
74
|
+
## 4. 零残留测试(本次审计的真实发现与修复)
|
|
75
|
+
|
|
76
|
+
**发现**:用户真实 TEMP(`C:\Users\fangw\AppData\Local\Temp`)中累积了 **15,677 个 `w2m-*` + 15 个 `gpw-*`
|
|
77
|
+
测试临时目录**(13,010 个超过 1 天),单轮全量测试新增约 **1,000 个**——测试存在目录泄漏,违反零残留要求。
|
|
78
|
+
|
|
79
|
+
**修复**:
|
|
80
|
+
1. 新增 `scripts/temp-residue.mjs`(`snapshot / report / clean`:基线对比、按前缀统计、按年龄安全清理)。
|
|
81
|
+
2. 新增 `test/helpers/temp-cleanup.mjs`(基线式 per-test 清理,避免并发测试互删)。
|
|
82
|
+
3. 修复泄漏测试文件:`gpw-folder-policy`、`agent`(workspace 两处 fixture)、`worktree`(makeRepo)。
|
|
83
|
+
4. 清理历史残留 15,608 个目录。
|
|
84
|
+
|
|
85
|
+
**复测结果**:全量测试完成后新增残留从 ~1,000 个降至 **74 个/轮**(按前缀分布含 `w2m-outbox-`7、
|
|
86
|
+
`w2m-relay-test-`7、`w2m-cap-`6、`w2m-grant-gate-`6 等)——已量化并留作下一轮清理清单(见 §6)。
|
|
87
|
+
|
|
88
|
+
## 5. 功能与性能实测(当前版本)
|
|
89
|
+
|
|
90
|
+
| 指标 | 实测值 |
|
|
91
|
+
|---|---|
|
|
92
|
+
| relay healthz 延迟(新主/旧/Vultr) | 73 / 265 / 510 ms |
|
|
93
|
+
| relay uptime(审计时) | 25.5k s / 84.0k s / 12.2k s |
|
|
94
|
+
| 本机 relay RTT(agent-state) | last 40 ms / avg 57 ms(历史样本) |
|
|
95
|
+
| 全量测试 | 2930 用例,0 fail,137.8 s(首轮)/ 220 s(修复后复测) |
|
|
96
|
+
| preflight | 16/16 checks GREEN ×2 |
|
|
97
|
+
| npm 发布连续性 | 1.6.0 → 1.7.4 全部存在;latest=1.7.4;beta 按策略不发 npm |
|
|
98
|
+
| 本机插件版本 | 1.7.4(profile bundle 挂载) |
|
|
99
|
+
| 编队在线 | 新主 relay:ToFuture + DSH,2/2,同编队 `004945ed` |
|
|
100
|
+
|
|
101
|
+
## 6. 遗留问题与建议(按优先级)
|
|
102
|
+
|
|
103
|
+
1. **测试残留 74 个/轮**(P2):下一轮按报告前缀清单继续修(`outbox`、`relay-test`、`cap`、`grant-gate`、
|
|
104
|
+
`mq-*`、`m3flow-*`、`p-`/`pins`/`q`/`s` 等),目标为 0;`scripts/temp-residue.mjs` 可直接纳入门禁。
|
|
105
|
+
2. **relay 版本漂移**(P2):旧腾讯云与 Vultr 仍是 1.6.4;建议维护窗口升级到 1.7.4(协议向后兼容,但版本漂移
|
|
106
|
+
会让故障转移演练测的不是同一套代码)。Vultr 的 Node 仍是 22.14.0(其余 22.23.4)。
|
|
107
|
+
3. **旧 relay 上 ToFuture 脏记录**(P3):fleet `8e26d696`,需重启旧 relay 状态清理或加运维删除接口。
|
|
108
|
+
4. **v1.7.5-beta.1 的 GPW 面板未做实机 UI 验证**(P3):代码/单测/门禁已绿,真实浏览器交互(原生文件夹选择器、
|
|
109
|
+
待审批队列)需在实机 DSH 上跑一遍再转 stable。
|
|
110
|
+
5. **本地仓库改动未推送**(P2):本次审计修复(测试清理 + `scripts/temp-residue.mjs` + 审计报告)只在本地工作区;
|
|
111
|
+
`w2m-src` 不是 git 仓库,需按既有 API 推送流程提交(会触发 CI),确认后再做。
|
|
112
|
+
6. 旧 relay 承载其它用户编队(14 个编队/20 设备),任何运维动作应避开其在线用户。
|
|
113
|
+
|
|
114
|
+
## 7. 结论
|
|
115
|
+
|
|
116
|
+
- v1.6.1 → v1.7.4 共 14 个正式版本,**Release / CI / Compat 全部 success**,npm 连续可安装,latest=1.7.4。
|
|
117
|
+
- 当前 HEAD(1.7.5-beta.1)全量 **2930 测试 0 失败**,lint **0 error**,静态检查全绿,preflight **GREEN ×2**。
|
|
118
|
+
- 两个真实线上事故(桌面宿主 `agent.identity` 崩溃、切换 relay 后 401)已在 1.7.4 修复并有回归测试;
|
|
119
|
+
本机编队归属已通过 `POST /v1/pair` 修复并与 DSH 同编队。
|
|
120
|
+
- 新主 relay 已升级到审计版本 **1.7.4**,状态目录保留、2 台设备 token 与编队完整。
|
|
121
|
+
- 零残留:清理 15,608 个历史残留,修复 4 个泄漏测试,单轮残留降至 74(有量化清单),是本次审计最明确的质量缺口。
|
package/lib/client.js
CHANGED
|
@@ -133,6 +133,28 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
133
133
|
capRemaining: '剩余 {hours} 小时',
|
|
134
134
|
capRouteMissing: '这个 Harness 没有提供能力路由',
|
|
135
135
|
capRefused: '能力切换被拒绝:{error}',
|
|
136
|
+
gpwTitle: 'GPW 文件夹策略',
|
|
137
|
+
gpwDisabled: 'GPW 未启用(在插件页开启 dsh-gpw)',
|
|
138
|
+
gpwWhitelist: '白名单(通过)',
|
|
139
|
+
gpwBlacklist: '黑名单(拒绝)',
|
|
140
|
+
gpwAddWhite: '添加白名单文件夹',
|
|
141
|
+
gpwAddBlack: '添加黑名单文件夹',
|
|
142
|
+
gpwRemove: '移除',
|
|
143
|
+
gpwPending: '待审批(C 类:名单外)',
|
|
144
|
+
gpwLauncher: 'GPW 文件夹',
|
|
145
|
+
gpwManual: '手动输入路径',
|
|
146
|
+
gpwManualPlaceholder: '例如 D:\\work\\data(绝对路径)',
|
|
147
|
+
gpwAddManual: '添加',
|
|
148
|
+
gpwRecent: '最近被拦截',
|
|
149
|
+
gpwRecentEmpty: '暂无被拦截记录',
|
|
150
|
+
gpwAddWhiteShort: '加入白名单',
|
|
151
|
+
gpwAddBlackShort: '加入黑名单',
|
|
152
|
+
gpwClose: '关闭',
|
|
153
|
+
gpwBadPath: '路径无效:{error}',
|
|
154
|
+
gpwInputHint: '目录需已存在;添加后立即生效。',
|
|
155
|
+
gpwPendingEmpty: '当前没有待审批调用',
|
|
156
|
+
gpwAllowOnce: '允许本次',
|
|
157
|
+
gpwDenyOnce: '拒绝本次',
|
|
136
158
|
},
|
|
137
159
|
en: {
|
|
138
160
|
all: 'All machines',
|
|
@@ -174,6 +196,17 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
174
196
|
capRemaining: '{hours} h left',
|
|
175
197
|
capRouteMissing: 'this host does not serve the capability route',
|
|
176
198
|
capRefused: 'the capability switch was refused: {error}',
|
|
199
|
+
gpwTitle: 'GPW folder policy',
|
|
200
|
+
gpwDisabled: 'GPW is not enabled (turn on dsh-gpw in the Plugins page)',
|
|
201
|
+
gpwWhitelist: 'Whitelist (pass)',
|
|
202
|
+
gpwBlacklist: 'Blacklist (deny)',
|
|
203
|
+
gpwAddWhite: 'Add whitelist folder',
|
|
204
|
+
gpwAddBlack: 'Add blacklist folder',
|
|
205
|
+
gpwRemove: 'Remove',
|
|
206
|
+
gpwPending: 'Pending approval (class C: on neither list)',
|
|
207
|
+
gpwPendingEmpty: 'No calls are waiting for approval',
|
|
208
|
+
gpwAllowOnce: 'Allow this call',
|
|
209
|
+
gpwDenyOnce: 'Deny this call',
|
|
177
210
|
},
|
|
178
211
|
};
|
|
179
212
|
|
|
@@ -372,6 +405,35 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
372
405
|
'.w2mCap_trigger[data-on="true"]{color:var(--dsw-alias-state-warn-primary)}',
|
|
373
406
|
'.w2mCap_dot{flex:none;width:8px;height:8px;border-radius:999px;background:var(--dsw-alias-state-idle-primary);box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l2)}',
|
|
374
407
|
'.w2mCap_dot[data-on="true"]{background:var(--dsw-alias-state-warn-primary)}',
|
|
408
|
+
'.w2mGpw_launcher{position:fixed;right:16px;bottom:96px;z-index:55;padding:6px 10px;border-radius:var(--dsw-radius-lg);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-soft);font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);cursor:pointer}',
|
|
409
|
+
'.w2mGpw_launcher:hover{background:var(--dsw-alias-bg-layer-2)}',
|
|
410
|
+
'.w2mGpw_overlay{position:fixed;inset:0;z-index:70;background:color-mix(in srgb, var(--dsw-alias-label-primary) 24%, transparent);display:flex;align-items:flex-end;justify-content:flex-end;padding:16px}',
|
|
411
|
+
'.w2mGpw_card{width:420px;max-width:calc(100vw - 32px);max-height:min(80vh,640px);overflow:auto;box-sizing:border-box;padding:10px 12px;border-radius:var(--dsw-radius-xl);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-soft);font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:6px}',
|
|
412
|
+
'.w2mGpw_head{display:flex;align-items:center;justify-content:space-between}',
|
|
413
|
+
'.w2mGpw_close{width:24px;height:24px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;font-size:16px;line-height:16px}',
|
|
414
|
+
'.w2mGpw_title{font-size:12px;font-weight:600}',
|
|
415
|
+
'.w2mGpw_block{display:flex;flex-direction:column;gap:4px;margin-top:2px;padding-top:6px;border-top:.5px solid var(--dsw-alias-border-default)}',
|
|
416
|
+
'.w2mGpw_row{display:flex;align-items:center;gap:6px;min-width:0}',
|
|
417
|
+
'.w2mGpw_path{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--dsw-alias-label-secondary)}',
|
|
418
|
+
'.w2mGpw_input{box-sizing:border-box;width:100%;padding:5px 8px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;outline:none}',
|
|
419
|
+
'.w2mGpw_actions{display:flex;flex-wrap:wrap;gap:6px}',
|
|
420
|
+
'.w2mGpw_btn{padding:3px 8px;border-radius:var(--dsw-radius-md);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font-size:12px;line-height:18px;cursor:pointer}',
|
|
421
|
+
'.w2mGpw_btn:disabled{opacity:.5;cursor:default}',
|
|
422
|
+
'.w2mGpw_small{width:22px;padding:0;border:.5px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer}',
|
|
423
|
+
'.w2mGpw_recent{border:.5px solid var(--dsw-alias-border-default);border-radius:var(--dsw-radius-md);padding:4px 6px;display:flex;flex-direction:column;gap:3px}',
|
|
424
|
+
'.w2mGpw_tag{flex:none;font-size:11px;padding:0 5px;border-radius:999px;border:.5px solid var(--dsw-alias-border-l2)}',
|
|
425
|
+
'.w2mGpw_tag[data-d="deny"]{border-color:var(--dsw-alias-state-danger-primary);color:var(--dsw-alias-state-danger-primary)}',
|
|
426
|
+
'.w2mGpw_note{font-size:12px;color:var(--dsw-alias-label-secondary)}',
|
|
427
|
+
'.w2mGpw_hint{font-size:11px;color:var(--dsw-alias-label-tertiary)}',
|
|
428
|
+
'.w2mGpw{margin-top:6px;padding-top:6px;border-top:.5px solid var(--dsw-alias-border-default);display:flex;flex-direction:column;gap:4px;max-height:320px;overflow:auto}',
|
|
429
|
+
'.w2mGpw_title{font-size:11px;font-weight:600;color:var(--dsw-alias-label-primary)}',
|
|
430
|
+
'.w2mGpw_block{display:flex;flex-direction:column;gap:2px;margin:2px 0 6px}',
|
|
431
|
+
'.w2mGpw_row{display:flex;align-items:center;gap:4px}',
|
|
432
|
+
'.w2mGpw_row .w2mGpw_path{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:11px;color:var(--dsw-alias-label-secondary)}',
|
|
433
|
+
'.w2mGpw_path{font-size:11px;color:var(--dsw-alias-label-secondary);word-break:break-all}',
|
|
434
|
+
'.w2mGpw_small{width:20px;padding:0}',
|
|
435
|
+
'.w2mGpw_pending{border:.5px solid var(--dsw-alias-border-default);border-radius:6px;padding:4px 6px;display:flex;flex-direction:column;gap:2px}',
|
|
436
|
+
'.w2mGpw_actions{display:flex;gap:6px}',
|
|
375
437
|
'.w2mCap_menu{position:fixed;z-index:60;box-sizing:border-box;width:280px;padding:4px;border-radius:var(--dsw-radius-lg);border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);box-shadow:var(--dsw-elevation-soft);font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary)}',
|
|
376
438
|
'.w2mCap_option{display:block;width:100%;box-sizing:border-box;padding:6px 8px;border:none;border-radius:var(--dsw-radius-sm);background:transparent;color:inherit;font:inherit;text-align:left;cursor:pointer}',
|
|
377
439
|
'.w2mCap_option:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
|
|
@@ -467,6 +529,145 @@ const PLUGIN_UPDATE_ROUTE = '/api/w2m/plugin-update';
|
|
|
467
529
|
* target is a choice about the *next* dispatch, which is legitimate to change while a turn runs.
|
|
468
530
|
* `t` arrives through the entry's inject face (see {@link apply}).
|
|
469
531
|
*/
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* GPW v2 operator panel + launcher (v1.7.5-beta.1).
|
|
535
|
+
*
|
|
536
|
+
* The launcher is a floating button pinned to the lower-right corner (a slot entry cannot choose its own screen
|
|
537
|
+
* position, so it pins itself with CSS). The panel has the three sections the operator asked for -- whitelist,
|
|
538
|
+
* blacklist, recently intercepted -- plus the pending queue for class-C calls. Folders enter a list in one of two
|
|
539
|
+
* ways: typed here and validated by the host, or taken from a recently intercepted call in one click. The lists
|
|
540
|
+
* live in $DSH_HOME/xclient/gpw-folders.json; no profile YAML is edited by this feature.
|
|
541
|
+
*/
|
|
542
|
+
function GpwPanel({ t, onClose }) {
|
|
543
|
+
const [state, setState] = React.useState(null);
|
|
544
|
+
const [error, setError] = React.useState(null);
|
|
545
|
+
const [busy, setBusy] = React.useState(false);
|
|
546
|
+
const [manual, setManual] = React.useState('');
|
|
547
|
+
const [badPath, setBadPath] = React.useState(null);
|
|
548
|
+
const call = React.useCallback(async (url, init) => {
|
|
549
|
+
try {
|
|
550
|
+
const response = await fetch(url, { credentials: 'same-origin', headers: { accept: 'application/json', ...(init?.body === undefined ? {} : { 'content-type': 'application/json' }) }, ...init });
|
|
551
|
+
let body = null;
|
|
552
|
+
try { body = await response.json(); } catch { body = null; }
|
|
553
|
+
if (response.status === 404) return { ok: false, routeMissing: true, error: 'GPW route missing' };
|
|
554
|
+
if (response.ok !== true || body?.ok === false) return { ok: false, error: body?.error ?? ('HTTP ' + response.status) };
|
|
555
|
+
return { ok: true, body };
|
|
556
|
+
} catch (error) {
|
|
557
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
558
|
+
}
|
|
559
|
+
}, []);
|
|
560
|
+
const load = React.useCallback(async () => {
|
|
561
|
+
const result = await call('/api/w2m/gpw/state', { method: 'GET' });
|
|
562
|
+
if (result.ok) { setState(result.body); setError(null); } else setError(result.routeMissing ? t('gpwDisabled') : (result.error ?? 'error'));
|
|
563
|
+
}, [call, t]);
|
|
564
|
+
React.useEffect(() => {
|
|
565
|
+
load();
|
|
566
|
+
const timer = setInterval(() => { load(); }, 3000);
|
|
567
|
+
return () => clearInterval(timer);
|
|
568
|
+
}, [load]);
|
|
569
|
+
const addFolder = async (list, path) => {
|
|
570
|
+
setBusy(true);
|
|
571
|
+
try {
|
|
572
|
+
const result = await call('/api/w2m/gpw/folders', { method: 'POST', body: JSON.stringify({ action: 'add', list, path }) });
|
|
573
|
+
if (result.ok !== true) { setBadPath(result.error ?? 'error'); return false; }
|
|
574
|
+
setBadPath(null);
|
|
575
|
+
await load();
|
|
576
|
+
return true;
|
|
577
|
+
} finally { setBusy(false); }
|
|
578
|
+
};
|
|
579
|
+
const addTyped = async (list) => {
|
|
580
|
+
const ok = await addFolder(list, manual);
|
|
581
|
+
if (ok === true) setManual('');
|
|
582
|
+
};
|
|
583
|
+
const removeFolder = async (list, path) => {
|
|
584
|
+
setBusy(true);
|
|
585
|
+
try { await call('/api/w2m/gpw/folders', { method: 'POST', body: JSON.stringify({ action: 'remove', list, path }) }); await load(); } finally { setBusy(false); }
|
|
586
|
+
};
|
|
587
|
+
const decide = async (id, decision) => {
|
|
588
|
+
setBusy(true);
|
|
589
|
+
try { await call('/api/w2m/gpw/decide', { method: 'POST', body: JSON.stringify({ id, decision }) }); await load(); } finally { setBusy(false); }
|
|
590
|
+
};
|
|
591
|
+
const folderList = (title, list) => h('div', { key: list, className: 'w2mGpw_block' }, [
|
|
592
|
+
h('div', { key: 'title', className: 'w2mGpw_title' }, title),
|
|
593
|
+
...(state?.folders?.[list] ?? []).map((path) => h('div', { key: path, className: 'w2mGpw_row' }, [
|
|
594
|
+
h('span', { key: 'p', className: 'w2mGpw_path', title: path }, path),
|
|
595
|
+
h('button', { key: 'x', type: 'button', className: 'w2mGpw_small', disabled: busy, onClick: () => removeFolder(list, path) }, '×'),
|
|
596
|
+
])),
|
|
597
|
+
]);
|
|
598
|
+
const recent = Array.isArray(state?.recent) ? state.recent : [];
|
|
599
|
+
const pending = Array.isArray(state?.pending) ? state.pending : [];
|
|
600
|
+
return h('div', { className: 'w2mGpw_overlay', role: 'dialog', 'aria-label': t('gpwTitle') }, [
|
|
601
|
+
h('div', { key: 'card', className: 'w2mGpw_card' }, [
|
|
602
|
+
h('div', { key: 'head', className: 'w2mGpw_head' }, [
|
|
603
|
+
h('span', { key: 'title', className: 'w2mGpw_title' }, t('gpwTitle')),
|
|
604
|
+
h('button', { key: 'close', type: 'button', className: 'w2mGpw_close', onClick: onClose }, '×'),
|
|
605
|
+
]),
|
|
606
|
+
error === null ? null : h('div', { key: 'err', className: 'w2mGpw_note' }, error),
|
|
607
|
+
h('div', { key: 'manual', className: 'w2mGpw_manual' }, [
|
|
608
|
+
h('div', { key: 'label', className: 'w2mGpw_title' }, t('gpwManual')),
|
|
609
|
+
h('input', {
|
|
610
|
+
key: 'input',
|
|
611
|
+
className: 'w2mGpw_input',
|
|
612
|
+
value: manual,
|
|
613
|
+
placeholder: t('gpwManualPlaceholder'),
|
|
614
|
+
onChange: (event) => { setManual(event.target.value); setBadPath(null); },
|
|
615
|
+
onKeyDown: (event) => { if (event.key === 'Enter' && manual.trim() !== '') addTyped('whitelist'); },
|
|
616
|
+
}),
|
|
617
|
+
h('div', { key: 'buttons', className: 'w2mGpw_actions' }, [
|
|
618
|
+
h('button', { key: 'w', type: 'button', className: 'w2mGpw_btn', disabled: busy || manual.trim() === '', onClick: () => addTyped('whitelist') }, t('gpwAddWhiteShort')),
|
|
619
|
+
h('button', { key: 'b', type: 'button', className: 'w2mGpw_btn', disabled: busy || manual.trim() === '', onClick: () => addTyped('blacklist') }, t('gpwAddBlackShort')),
|
|
620
|
+
]),
|
|
621
|
+
badPath === null ? null : h('div', { key: 'bad', className: 'w2mGpw_note' }, t('gpwBadPath', { error: badPath })),
|
|
622
|
+
h('div', { key: 'hint', className: 'w2mGpw_hint' }, t('gpwInputHint')),
|
|
623
|
+
]),
|
|
624
|
+
folderList(t('gpwWhitelist'), 'whitelist'),
|
|
625
|
+
folderList(t('gpwBlacklist'), 'blacklist'),
|
|
626
|
+
h('div', { key: 'recent', className: 'w2mGpw_block' }, [
|
|
627
|
+
h('div', { key: 'title', className: 'w2mGpw_title' }, t('gpwRecent')),
|
|
628
|
+
recent.length === 0 ? h('div', { key: 'none', className: 'w2mGpw_note' }, t('gpwRecentEmpty')) : null,
|
|
629
|
+
...recent.map((item, index) => h('div', { key: 'r' + index, className: 'w2mGpw_recent' }, [
|
|
630
|
+
h('div', { key: 'r1', className: 'w2mGpw_row' }, [
|
|
631
|
+
h('span', { key: 'd', className: 'w2mGpw_tag', 'data-d': item.decision }, item.decision),
|
|
632
|
+
h('span', { key: 't', className: 'w2mGpw_path', title: item.paths?.[0] ?? '' }, (item.folder ?? item.paths?.[0] ?? item.tool)),
|
|
633
|
+
]),
|
|
634
|
+
item.folder === null || item.folder === undefined ? null : h('div', { key: 'r2', className: 'w2mGpw_actions' }, [
|
|
635
|
+
h('button', { key: 'w', type: 'button', className: 'w2mGpw_btn', disabled: busy, onClick: () => addFolder('whitelist', item.folder) }, t('gpwAddWhiteShort')),
|
|
636
|
+
h('button', { key: 'b', type: 'button', className: 'w2mGpw_btn', disabled: busy, onClick: () => addFolder('blacklist', item.folder) }, t('gpwAddBlackShort')),
|
|
637
|
+
]),
|
|
638
|
+
])),
|
|
639
|
+
]),
|
|
640
|
+
pending.length === 0 ? null : h('div', { key: 'pending', className: 'w2mGpw_block' }, [
|
|
641
|
+
h('div', { key: 'title', className: 'w2mGpw_title' }, t('gpwPending')),
|
|
642
|
+
...pending.map((item) => h('div', { key: item.id, className: 'w2mGpw_recent' }, [
|
|
643
|
+
h('div', { key: 'p1', className: 'w2mGpw_path' }, item.tool + ' · ' + (item.reason ?? '')),
|
|
644
|
+
...(Array.isArray(item.paths) ? item.paths.slice(0, 3).map((p, i) => h('div', { key: 'p' + i, className: 'w2mGpw_path' }, p)) : []),
|
|
645
|
+
h('div', { key: 'p2', className: 'w2mGpw_actions' }, [
|
|
646
|
+
h('button', { key: 'a', type: 'button', className: 'w2mGpw_btn', disabled: busy, onClick: () => decide(item.id, 'allow') }, t('gpwAllowOnce')),
|
|
647
|
+
h('button', { key: 'd', type: 'button', className: 'w2mGpw_btn', disabled: busy, onClick: () => decide(item.id, 'deny') }, t('gpwDenyOnce')),
|
|
648
|
+
]),
|
|
649
|
+
])),
|
|
650
|
+
]),
|
|
651
|
+
]),
|
|
652
|
+
]);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/** The floating launcher: the panel is fixed to the lower-right corner and does not consume composer space. */
|
|
656
|
+
function GpwLauncher({ t }) {
|
|
657
|
+
const [open, setOpen] = React.useState(false);
|
|
658
|
+
return h(React.Fragment, null, [
|
|
659
|
+
h('button', {
|
|
660
|
+
key: 'launcher',
|
|
661
|
+
type: 'button',
|
|
662
|
+
className: 'w2mGpw_launcher',
|
|
663
|
+
title: t('gpwLauncher'),
|
|
664
|
+
'aria-expanded': open,
|
|
665
|
+
onClick: () => setOpen((wasOpen) => !wasOpen),
|
|
666
|
+
}, t('gpwLauncher')),
|
|
667
|
+
open ? h(GpwPanel, { key: 'panel', t, onClose: () => setOpen(false) }) : null,
|
|
668
|
+
]);
|
|
669
|
+
}
|
|
670
|
+
|
|
470
671
|
/** v1.6.8: one button beside the relay rows: check npm for a newer plugin, install it, ask for a restart. */
|
|
471
672
|
function PluginUpdate({ t }) {
|
|
472
673
|
const [state, setState] = React.useState({ status: 'idle', info: null, error: null });
|
|
@@ -1329,6 +1530,19 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
|
|
|
1329
1530
|
t,
|
|
1330
1531
|
}),
|
|
1331
1532
|
}, TargetSelect)); // v1.4.2: the Node-code-execution switch, one seat to the right of the chat panel.
|
|
1533
|
+
// v1.7.5-beta.1: the GPW folder panel is a floating button, so it is its own slot entry rather than a
|
|
1534
|
+
// row inside the composer; the slot fixes its mount lifetime, the CSS fixes its corner.
|
|
1535
|
+
try {
|
|
1536
|
+
slots.inject('conversation.input.left', () => slots.register({
|
|
1537
|
+
name: 'conversation.input.left',
|
|
1538
|
+
id: 'w2m-gpw',
|
|
1539
|
+
order: 43,
|
|
1540
|
+
label: 'GPW folders',
|
|
1541
|
+
inject: () => ({ t }),
|
|
1542
|
+
}, GpwLauncher));
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
console.error('@twinsearth/w2m-dsh-plugin: the GPW folder panel was not mounted; the rest of the page is unaffected', error);
|
|
1545
|
+
}
|
|
1332
1546
|
try {
|
|
1333
1547
|
slots.inject('conversation.input.left', () => slots.register({
|
|
1334
1548
|
name: 'conversation.input.left',
|
|
@@ -1386,7 +1600,8 @@ function TargetSelect({ sessionId, locked: _locked, t }) {
|
|
|
1386
1600
|
exports.apply = apply;
|
|
1387
1601
|
exports.inject = inject;
|
|
1388
1602
|
exports.TargetSelect = TargetSelect;
|
|
1389
|
-
exports.
|
|
1603
|
+
exports.GpwLauncher = GpwLauncher;
|
|
1604
|
+
exports.FleetChat = FleetChat;
|
|
1390
1605
|
exports.injectStyle = injectStyle;
|
|
1391
1606
|
exports.makeTranslator = makeTranslator;
|
|
1392
1607
|
return module.exports;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.7",
|
|
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",
|
|
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",
|
|
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",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
"test:council-session": "node --test --test-force-exit test/council-session.test.mjs",
|
|
83
83
|
"test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
|
|
84
84
|
"test:crypto-blake2s": "node --test --test-force-exit test/crypto-blake2s.test.mjs",
|
|
85
|
-
"test:e2e": "node --test --test-force-exit test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/p2p-agent.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",
|
|
85
|
+
"test:e2e": "node --test --test-force-exit test/e2e.test.mjs test/crossnetwork.test.mjs test/signing-e2e.test.mjs test/outbound-only.test.mjs test/recovery.test.mjs test/pipeline-e2e.test.mjs test/p2p-agent.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",
|
|
86
86
|
"test:exec-env": "node --test --test-force-exit test/exec-env-injection.test.mjs",
|
|
87
87
|
"test:exit-channel": "node --test --test-force-exit test/exit-channel.test.mjs",
|
|
88
88
|
"test:field-authority": "node --test --test-force-exit test/field-authority.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": "1.7.
|
|
4
|
+
"version": "1.7.7",
|
|
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": [
|