@twinsearth/w2m-dsh-plugin 2.0.9 → 2.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +5 -0
- package/bin/remote-s2-capture.mjs +116 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/2.0.10/RELEASE-2100.json +19 -0
- package/docs/plans/REMOTE-DESKTOP-2.1.0.md +153 -0
- package/package.json +2 -2
- package/plugin.json +1 -1
- 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": "2.0.
|
|
8
|
+
"version": "2.0.10"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "w2m-fleet",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Read fleet state and dispatch commands across the machines registered to one account. Read-only MCP surface: the five exposed tools cannot change anything.",
|
|
15
|
-
"version": "2.0.
|
|
15
|
+
"version": "2.0.10",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "2.0.
|
|
29
|
+
"version": "2.0.10"
|
|
30
30
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w2m-fleet",
|
|
3
3
|
"description": "Drive the same project on online Windows, macOS and Linux machines from one account: read fleet state, dispatch commands and read results. The MCP surface is deliberately read-only.",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.10",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ All notable changes to this project are documented here.
|
|
|
7
7
|
- `w2m_intent`: structured intents mapped onto allow-listed commands; unknown actions, parameters and types
|
|
8
8
|
are refused by name (the fleet does not invent commands).
|
|
9
9
|
|
|
10
|
+
## [2.0.10] - 2026-10-31
|
|
11
|
+
|
|
12
|
+
- remote-view S2: ship scripts/remote-s2-capture.mjs so macOS capture/TCC probes run as a normal script (no node -e, no reparse refusal); measures 1/2 fps capture timing, JPEG size, ffmpeg presence, writes w2m-s2-result.json and supports exit-code summary.
|
|
13
|
+
- tests: remote-s2-capture summary/platform contract.
|
|
14
|
+
|
|
10
15
|
## [2.0.9] - 2026-10-30
|
|
11
16
|
|
|
12
17
|
- P2P 可靠性:ACK 驱动窗口、自适应窗口、RFC 6298 RTT/自适应 RTO;明文通道复用改为强制新握手。
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* S2: macOS screen capture probe for the remote-view P1 decision.
|
|
4
|
+
*
|
|
5
|
+
* Runs WITHOUT `node -e` (the agent refuses code-carrying argv under
|
|
6
|
+
* COMMAND_REPARSE), so it can be dispatched as:
|
|
7
|
+
* node <plugin>/scripts/remote-s2-capture.mjs --json
|
|
8
|
+
* node <plugin>/scripts/remote-s2-capture.mjs --exit-summary
|
|
9
|
+
*
|
|
10
|
+
* It measures two captures (spaced 1/fps), probes optional ffmpeg, guesses TCC
|
|
11
|
+
* denial/blank from exit status + JPEG size, writes ~/w2m-s2-result.json, and
|
|
12
|
+
* prints the same JSON. --exit-summary encodes the result into the exit code:
|
|
13
|
+
* 100 + bit0 ffmpeg | bit1 first ok | bit2 first >20KiB | bit3 first <500ms
|
|
14
|
+
* | bit4 second ok | bit5 second <500ms
|
|
15
|
+
*/
|
|
16
|
+
import { spawnSync } from 'node:child_process';
|
|
17
|
+
import { existsSync, statSync, writeFileSync } from 'node:fs';
|
|
18
|
+
import { homedir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { pathToFileURL } from 'node:url';
|
|
21
|
+
|
|
22
|
+
export function parseArgs(argv) {
|
|
23
|
+
const out = { fps: 1, exitSummary: false, json: false, keep: false };
|
|
24
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
25
|
+
const arg = argv[i];
|
|
26
|
+
if (arg === '--fps') { out.fps = Number(argv[i + 1]) === 2 ? 2 : 1; i += 1; }
|
|
27
|
+
else if (arg === '--exit-summary') out.exitSummary = true;
|
|
28
|
+
else if (arg === '--json') out.json = true;
|
|
29
|
+
else if (arg === '--keep') out.keep = true;
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function summaryBits(result) {
|
|
35
|
+
let bits = 0;
|
|
36
|
+
if (result?.ffmpeg?.present === true) bits |= 1;
|
|
37
|
+
const a = result?.captures?.[0];
|
|
38
|
+
const b = result?.captures?.[1];
|
|
39
|
+
if (a?.ok === true) bits |= 2;
|
|
40
|
+
if ((a?.bytes ?? 0) > 20 * 1024) bits |= 4;
|
|
41
|
+
if (Number.isFinite(a?.ms) && a.ms < 500) bits |= 8;
|
|
42
|
+
if (b?.ok === true) bits |= 16;
|
|
43
|
+
if (Number.isFinite(b?.ms) && b.ms < 500) bits |= 32;
|
|
44
|
+
return bits;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function capture(file, dimensions = true) {
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
const run = spawnSync('screencapture', ['-x', '-t', 'jpg', file], { timeout: 15000, encoding: 'utf8' });
|
|
50
|
+
const ms = Date.now() - started;
|
|
51
|
+
let bytes = 0;
|
|
52
|
+
if (existsSync(file)) { try { bytes = statSync(file).size; } catch { bytes = 0; } }
|
|
53
|
+
let width = null; let height = null;
|
|
54
|
+
if (dimensions && bytes > 0) {
|
|
55
|
+
const sips = spawnSync('sips', ['-g', 'pixelWidth', '-g', 'pixelHeight', file], { timeout: 5000, encoding: 'utf8' });
|
|
56
|
+
const w = /pixelWidth:\s*(\d+)/.exec(sips.stdout ?? '');
|
|
57
|
+
const h = /pixelHeight:\s*(\d+)/.exec(sips.stdout ?? '');
|
|
58
|
+
width = w === null ? null : Number(w[1]);
|
|
59
|
+
height = h === null ? null : Number(h[1]);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
ok: run.status === 0 && bytes > 0,
|
|
63
|
+
status: run.status,
|
|
64
|
+
ms,
|
|
65
|
+
bytes,
|
|
66
|
+
width,
|
|
67
|
+
height,
|
|
68
|
+
error: run.status === 0 ? null : (run.stderr || 'screencapture failed').toString().slice(0, 300),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function main() {
|
|
73
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
74
|
+
const result = {
|
|
75
|
+
ok: false,
|
|
76
|
+
at: new Date().toISOString(),
|
|
77
|
+
platform: process.platform,
|
|
78
|
+
fps: opts.fps,
|
|
79
|
+
ffmpeg: { present: false, version: null },
|
|
80
|
+
captures: [],
|
|
81
|
+
tcc_guess: 'unknown',
|
|
82
|
+
code: null,
|
|
83
|
+
};
|
|
84
|
+
if (process.platform !== 'darwin') {
|
|
85
|
+
result.code = 'S2_UNSUPPORTED_PLATFORM';
|
|
86
|
+
finish(result, opts, 2);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const ff = spawnSync('ffmpeg', ['-version'], { timeout: 1500, encoding: 'utf8' });
|
|
90
|
+
result.ffmpeg = { present: ff.status === 0, version: ff.status === 0 ? String(ff.stdout).split('\n')[0].slice(0, 120) : null };
|
|
91
|
+
|
|
92
|
+
const dir = join(homedir(), 'w2m-s2');
|
|
93
|
+
const fileA = join(dir, 'frame-1.jpg');
|
|
94
|
+
const fileB = join(dir, 'frame-2.jpg');
|
|
95
|
+
try { spawnSync('mkdir', ['-p', dir], { timeout: 5000 }); } catch { /* best effort */ }
|
|
96
|
+
result.captures.push(capture(fileA));
|
|
97
|
+
spawnSync('sleep', [String(opts.fps === 2 ? 0.5 : 1.0)], { timeout: 5000 });
|
|
98
|
+
result.captures.push(capture(fileB));
|
|
99
|
+
|
|
100
|
+
const a = result.captures[0];
|
|
101
|
+
result.tcc_guess = a.status === 0 && a.bytes > 5000 ? 'granted-or-desktop-visible' : 'denied-or-blank';
|
|
102
|
+
result.ok = a.ok === true && result.captures[1].ok === true;
|
|
103
|
+
result.code = result.ok ? 'S2_CAPTURE_OK' : 'S2_CAPTURE_FAILED';
|
|
104
|
+
finish(result, opts, result.ok ? 0 : 3);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function finish(result, opts, normalCode) {
|
|
108
|
+
const out = join(homedir(), 'w2m-s2-result.json');
|
|
109
|
+
try { writeFileSync(out, JSON.stringify(result, null, 2)); result.result_file = out; } catch { /* stdout only */ }
|
|
110
|
+
if (opts.json || !opts.exitSummary) process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
111
|
+
if (opts.exitSummary) process.exit(100 + summaryBits(result));
|
|
112
|
+
process.exit(normalCode);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const invoked = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
116
|
+
if (invoked) main();
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.10",
|
|
3
|
+
"case": "RELEASE-2100",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-30T21:19:15.294Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "pre-release-2100-1793394641623",
|
|
11
|
+
"requirement": "v2.0.10: ship remote-view S2 capture probe as a script (no node -e)",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"s2_script": "bin/remote-s2-capture.mjs shipped (scripts/ is forbidden in the release tarball); summaryBits/platform test 2/2",
|
|
16
|
+
"preflight": "GREEN: 16 pass, 0 fail, 1 host-only UNVERIFIABLE; 3299 tests / 244 files"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# v2.1.0 远程桌面子插件(仅查看)落地方案
|
|
2
|
+
|
|
3
|
+
状态:DRAFT,待 P0 spike 与决策确认;未改代码、未发布。
|
|
4
|
+
专家评审:国内网络架构工程师 + 视频流工程师(只读)。
|
|
5
|
+
目标:设置页开关;开启后侧边栏出现 iMac 图标标签;仅查看、不可控制;后续版本再开放鼠标操控。
|
|
6
|
+
|
|
7
|
+
## 1. 结论
|
|
8
|
+
|
|
9
|
+
1. v2.1.0 的现实目标是 **加密、可用的 1–2 fps 查看**,不是 RustDesk 级 30fps 远控;10fps+ 需要 sidecar/不可靠传输,排 P4。
|
|
10
|
+
2. **不 vendor RustDesk/hbb_common**(AGPL 边界 + 与现有 P2P 重复);只借鉴分层与自适应思路,sidecar 必须可选、独立进程、外部二进制。
|
|
11
|
+
3. 先做 P0 三个 spike(浏览器流、macOS TCC/采集、VK 握手),再写 UI;UI 是最不会失败的部分。
|
|
12
|
+
4. 媒体 E2E 加密,relay 只做信令/授权/密文回退;目标端默认拒绝,每次会话显式同意。
|
|
13
|
+
5. v2.1.0 不做:输入控制、剪贴板、文件、音频、录制、无人值守、多查看者、Windows 被查看端。
|
|
14
|
+
|
|
15
|
+
## 2. 分层
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
L5 浏览器:sidebar iMac 标签 + canvas 查看页 + 设置页开关
|
|
19
|
+
L4 DSH 宿主:remote-view route/service/session(同源 /api/w2m/remote/*)
|
|
20
|
+
L3 agent:view 会话、采集、同意、帧加密(view.* P2P 路由)
|
|
21
|
+
L2 传输:P2PChannel(优先)| relay 密文帧(回退);VK 会话加密
|
|
22
|
+
L1 relay:/v1/remote/signal、/v1/remote/stream、/v1/remote/frame(只密文)
|
|
23
|
+
L0 平台:macOS screencapture/sips(P0)→ ScreenCaptureKit(P3/P4)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
同包内子模块 `src/plugin/remote-view/*` + `src/agent/view/*`,不拆 npm 包;单版本、单 patch row。
|
|
27
|
+
|
|
28
|
+
## 3. UI 接口
|
|
29
|
+
|
|
30
|
+
- `settings.section`:远程桌面开关(默认关闭),写 `<stateDir>/remote-view.json`(0600)。
|
|
31
|
+
- `sidebar.panellist`:iMac 图标,仅开关开启时渲染;关闭时返回 null。
|
|
32
|
+
- `main`:查看页(目标选择、同意状态、canvas、直连/中继徽标、停止、“仅查看”常驻说明)。
|
|
33
|
+
- 每个 slot 独立 try/catch,不新增 inject;动态显隐需 P0 验证外壳是否折叠空行,失败则提示刷新。
|
|
34
|
+
|
|
35
|
+
## 4. 信令与安全
|
|
36
|
+
|
|
37
|
+
- 身份:fleet 准入(relay)+ X25519 device key(通道认证)+ 临时 X25519 三重 DH 派生 VK(媒体 E2E)。
|
|
38
|
+
- invite/accept:`k_mac = HKDF(ECDH(static keys))` 密封;VK 派生后媒体/控制分方向密钥。
|
|
39
|
+
- 目标端授权:默认拒绝;每次 `osascript display dialog`,30s 超时;可选内存态“允许 30 分钟”,不落盘。
|
|
40
|
+
- relay:同 fleet 校验;只接受 `{session_id, seq, ct}`;拒绝 JPEG/PNG magic、明文 JSON、输入类型;TLS 强制(否则 `VIEW_RELAY_INSECURE`)。
|
|
41
|
+
- 错误码:VIEW_DISABLED/TARGET_BUSY/NO_ANSWER/CONSENT_DENIED/P2P_TIMEOUT/CAPTURE_DENIED/PLAINTEXT_MEDIA_REFUSED 等。
|
|
42
|
+
|
|
43
|
+
## 5. 视频链路(专家修订版)
|
|
44
|
+
|
|
45
|
+
### 5.1 安全上下文:不是部署要求
|
|
46
|
+
|
|
47
|
+
页面从本机 DSH GUI(`127.0.0.1`)加载,属于 potentially trustworthy origin;媒体帧由目标机经 P2P/relay 到本机宿主,再由同源 route 给浏览器。因此:
|
|
48
|
+
|
|
49
|
+
| 拓扑 | 页面 origin | isSecureContext | WebCodecs | P1 |
|
|
50
|
+
|---|---|---|---|---|
|
|
51
|
+
| A 本机 DSH 面板 | 127.0.0.1 | true | 可用 | 主路径 |
|
|
52
|
+
| B 本机 DSH + HTTPS | https | true | 可用 | 可选 |
|
|
53
|
+
| C 局域网直开 DSH | http://192.168.x.x | false | 不可用 | 仅 JPEG 档 |
|
|
54
|
+
| D 局域网 HTTPS 反代 | https | true | 可用 | 需证书运维 |
|
|
55
|
+
|
|
56
|
+
P1 不要求 HTTPS;运行时探针 `isSecureContext / VideoDecoder / createImageBitmap`,C 拓扑只走 JPEG。不要把整个 DSH GUI 绑到 0.0.0.0;局域网只读查看应使用独立端口 + 一次性 token。
|
|
57
|
+
|
|
58
|
+
### 5.2 渲染:SSE + createImageBitmap(P1 默认)
|
|
59
|
+
|
|
60
|
+
每帧独立 JPEG/WebP/AVIF,经同源 SSE 下发:
|
|
61
|
+
`{ session_id, seq, ts_ms, w, h, mime, bytes, data_b64 }`;无变化发 `view.unchanged`。
|
|
62
|
+
前端 `createImageBitmap(new Blob([bytes]))` → canvas `drawImage`;不需要 WebCodecs,不需要 HTTPS。
|
|
63
|
+
“最新帧优先”:上一帧未投递完就丢新帧(dropped++),不排队。
|
|
64
|
+
|
|
65
|
+
### 5.3 编解码:帧内优先,视频档可选
|
|
66
|
+
|
|
67
|
+
| 层 | 编码 | 1–2fps 收益 | 结论 |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| 帧内 | JPEG | 地板,永可用 | P1 默认 |
|
|
70
|
+
| 帧内 | WebP | 体积 0.65–0.75× | P1.5 升级 |
|
|
71
|
+
| 帧内 | AVIF/HEIC | 0.5–0.7×,编码 100–300ms | P2(需 ffmpeg/Safari) |
|
|
72
|
+
| 帧间 | H.264/HEVC/AV1 | 1–2fps 下 IDR/GOP 成本抵消收益 | 可选加速档,非地板 |
|
|
73
|
+
| 帧间 | VP9 | 无 Apple 硬编 | 不建议 |
|
|
74
|
+
|
|
75
|
+
“不接受仅 H.264”的落法:**P1 的媒体地板不是 H.264,而是 JPEG**;HEVC/AV1 作为探测后的可选加速档,缺失不影响可用性。
|
|
76
|
+
1–2fps 最有效的省流不是换编码器,而是“画面未变”抑制(可砍 50–80% 帧)。
|
|
77
|
+
|
|
78
|
+
### 5.4 macOS 采集
|
|
79
|
+
|
|
80
|
+
- P1:`screencapture -x -t jpg -D 1`(1fps 稳,2fps 边际);relay 档再加 `sips -Z 1024 -s formatOptions 45`。
|
|
81
|
+
- 2fps 可选:`ffmpeg -f avfoundation -framerate 2 -c:v mjpeg` 常驻,外部二进制、不打包、缺失即回落。
|
|
82
|
+
- TCC:Screen Recording 按责任进程归属;必须区分 `CAPTURE_DENIED / CAPTURE_BLANK / CAPTURE_FAILED`,目标端 GUI 给系统设置跳转。
|
|
83
|
+
- 采集 argv 固定,不走 allow-list。
|
|
84
|
+
|
|
85
|
+
### 5.5 同意 UI(目标端 DSH GUI)
|
|
86
|
+
|
|
87
|
+
目标端也跑 DSH GUI:同意弹窗显示 viewer/屏幕/分辨率/fps/时长;允许后常驻“正在被查看”横幅 + 停止按钮;默认拒绝、30s 超时、内存态授权 ≤30min;锁屏/关闭面板/停止 → 停采 ≤1s。headless 才用 osascript 兜底。
|
|
88
|
+
|
|
89
|
+
### 5.6 帧预算与传输
|
|
90
|
+
|
|
91
|
+
| 通道 | 单帧 raw 上限 | 目标 | 默认 fps | 超限行为 |
|
|
92
|
+
|---|---|---|---|---|
|
|
93
|
+
| P2P | 96 KiB | 64 KiB | 1(P1 只承诺 1fps) | 降分辨率,不分块 |
|
|
94
|
+
| relay | 47 KiB 硬顶 | 40 KiB | 1 | 降分辨率/1fps |
|
|
95
|
+
|
|
96
|
+
- relay 只接受 `{session_id, seq, ct}`;拒绝 JPEG/PNG magic、明文 JSON、输入类型;媒体必须 E2E 加密(TLS 不能替代)。
|
|
97
|
+
- 不把帧塞进 `/v1/fleet/messages`(4 KiB + 配额 + 通知不带正文)。
|
|
98
|
+
- 新 feature:`remote-frame-v1`;老 relay → `VIEW_RELAY_UNSUPPORTED`,不发第一帧。
|
|
99
|
+
- P1 建议:P2P 媒体 + relay 信令;relay 密文帧放 P1.5(40 KiB 清晰度有限,先做逃生门)。
|
|
100
|
+
|
|
101
|
+
### 5.7 P0 spike(3–4 人日)
|
|
102
|
+
|
|
103
|
+
1. S1 JPEG→canvas 1–2fps 渲染(≤30ms/帧);
|
|
104
|
+
2. S2 macOS 采集 1/2fps + TCC 三形态(先跑,唯一能推翻 fps 目标);
|
|
105
|
+
3. S3 安全上下文矩阵(127.0.0.1 vs 局域网 IP);
|
|
106
|
+
4. S4 P2P 40/64/96/128 KiB 单帧预算;
|
|
107
|
+
5. S5 relay 47 KiB 边界/413/magic 拒绝;
|
|
108
|
+
6. S6 同意/撤销六态。
|
|
109
|
+
|
|
110
|
+
## 6. 阶段
|
|
111
|
+
|
|
112
|
+
| 阶段 | 目标 | 退出条件 | 人日 |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| P0 | 证伪浏览器流/TCC/VK 三假设 | 首帧延迟、采集耗时、权限拒绝行为、VK 单测 | 3–5 |
|
|
115
|
+
| P1 | 最小可用:P2P JPEG + relay 信令 + UI 开关/标签/同意 | Windows 看 Mac 1fps;错误码齐全;relay 无媒体明文 | 8–12 |
|
|
116
|
+
| P2 | 隐私/生命周期硬化 | 30min soak、锁屏暂停、限流、默认拒绝 | 5–8 |
|
|
117
|
+
| P3 | 密文中继回退 + TLS + 384KiB 帧上限 | 阻断 UDP 后仍 1fps;relay 无 JPEG magic | 8–12 |
|
|
118
|
+
| P4 | 可选 sidecar(非 RustDesk)10–15fps + 窗口选择 | sidecar 缺失时 P1 仍可用 | 10–15 |
|
|
119
|
+
|
|
120
|
+
## 7. 验收(节选)
|
|
121
|
+
|
|
122
|
+
- 开关关闭:无标签、`start` 返回 VIEW_DISABLED、无 UDP bind/relay stream。
|
|
123
|
+
- 开关开启:iMac 标签出现;roster 只列 `caps.view` 的 macOS 机器。
|
|
124
|
+
- 同意:拒绝/超时/忙/离线均有具名 code;默认拒绝。
|
|
125
|
+
- 性能:LAN 首帧 ≤2.5s;1fps 持续 10 分钟;停止 ≤1s;中继 ≥0.8fps。
|
|
126
|
+
- 安全:relay 请求体无 `FFD8FF`/`image/jpeg`;发真 JPEG 到 frame 端点返回 400;输入类型返回 VIEW_INPUT_FORBIDDEN。
|
|
127
|
+
- 零依赖:`dependencies` 不变;client bundle 测试通过;旧 relay/旧 agent 有明确降级。
|
|
128
|
+
|
|
129
|
+
## 8. 决策与待确认
|
|
130
|
+
|
|
131
|
+
已确认:
|
|
132
|
+
|
|
133
|
+
1. 接受 1–2 fps 查看;
|
|
134
|
+
2. relay 有 TLS;
|
|
135
|
+
3. macOS 目标端也跑 DSH GUI(同意 UI 放目标端 GUI);
|
|
136
|
+
4. 不接受仅 H.264 → 媒体地板改为 JPEG,HEVC/AV1 作为可选加速档;
|
|
137
|
+
5. 允许局域网 HTTP → C 拓扑只走 JPEG,不要求 WebCodecs;
|
|
138
|
+
6. **relay 密文帧路径进 P1.5**;P1 为 P2P 媒体 + relay 信令,P2P 失败明确 `P2P_UNAVAILABLE`;
|
|
139
|
+
7. **P1 不包含 2fps 档**:只有 1fps 的对外承诺;2fps 在 S2 实测达标后仅作为隐藏实验开关,不进 UI/验收/文档承诺。
|
|
140
|
+
8. **允许 2fps 依赖外部 ffmpeg**(面向 P1.5):可选、不打包、不自动下载、缺失回落;`dependencies` 保持为空。
|
|
141
|
+
|
|
142
|
+
P1 基线(无 ffmpeg):`screencapture` 1fps + JPEG;有 ffmpeg:2fps 档可选。
|
|
143
|
+
|
|
144
|
+
## 9. 2fps 决策(2026-10-30)
|
|
145
|
+
|
|
146
|
+
结论:P1 不设 2fps 交付档位。
|
|
147
|
+
|
|
148
|
+
- 唯一保证的采集后端 screencapture 单帧 p50 215–360ms / p95 590ms,2fps 的 500ms 预算越界;
|
|
149
|
+
- 2fps 的收益仅约 250–450ms 新鲜度,1fps 与 2fps 同属幻灯片(8–15fps 才是感知门槛);
|
|
150
|
+
- 1% 丢包下 57 分片帧受损概率 43%,一次 RTO 300ms 吃掉 2fps 预算 60%;
|
|
151
|
+
- P1 的坑应在同意/TCC/撤销,不在帧率;2fps 留 P1.5 视频档或 S2 后隐藏开关。
|
|
152
|
+
|
|
153
|
+
替代方案(P1 推荐):1fps + 变化追帧 burst(检测到变化时最多 2.5fps 持续 0.75s,令牌桶)+ 首帧双发 + 帧龄 HUD + unchanged 抑制读数。
|
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.10",
|
|
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": {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"pack": "node scripts/pack.mjs",
|
|
67
67
|
"reachability": "node scripts/reachability-check.mjs",
|
|
68
68
|
"reachability:classify": "node scripts/reachability-classify.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",
|
|
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 test/remote-s2-capture.test.mjs",
|
|
70
70
|
"test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
|
|
71
71
|
"test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
|
|
72
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.10",
|
|
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/plugin/tools.mjs
CHANGED
|
@@ -291,7 +291,7 @@ const MAX_STAGES = 16;
|
|
|
291
291
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
292
292
|
* overrides it at runtime for tests and packagers.
|
|
293
293
|
*/
|
|
294
|
-
const PLUGIN_VERSION = '2.0.
|
|
294
|
+
const PLUGIN_VERSION = '2.0.10';
|
|
295
295
|
|
|
296
296
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
297
297
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|