@twinsearth/w2m-dsh-plugin 1.9.12 → 1.9.14
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 +12 -0
- package/bin/w2m-joint.mjs +151 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/1.9.13/RELEASE-1913.json +20 -0
- package/docs/acceptance/1.9.14/RELEASE-1914.json +20 -0
- package/package.json +264 -265
- package/plugin.json +1 -1
- package/src/plugin/npm-update.mjs +62 -0
- package/src/plugin/tools.mjs +1 -1
- package/src/plugin/update-route.mjs +31 -0
- package/src/relay/repair-ledger.mjs +8 -2
|
@@ -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.9.
|
|
8
|
+
"version": "1.9.14"
|
|
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.9.
|
|
15
|
+
"version": "1.9.14",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "1.9.
|
|
29
|
+
"version": "1.9.14"
|
|
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.9.
|
|
4
|
+
"version": "1.9.14",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [1.9.14] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
- 修复「立即更新」失败(fetch failed):GitHub Release 下载被网络阻断时,自动回退到 npm registry 的同一版本 tarball;下载前校验 registry 的 sha512 integrity(缺失时 sha1 shasum),再由现有安装器做 sha256 校验、备份与回滚。
|
|
13
|
+
- 安装器仍走运行时 pnpm(Windows 不再尝试 dsh.cmd)。
|
|
14
|
+
- 更新相关测试 66/66 通过。
|
|
15
|
+
|
|
16
|
+
## [1.9.13] - 2026-10-28
|
|
17
|
+
|
|
18
|
+
- 新增操作员联合开发 CLI `w2m-joint`:plan create/list/get、approve(人签、authority≥70、TTL 默认 10 分钟)、status、dispatch --dry-run(打印控制信封,真实发送未接线并返回 DISPATCH_NOT_WIRED)。
|
|
19
|
+
- 该 CLI 是 SOP 的操作员入口:计划先落盘、人工审批、再允许派发;dispatch 真发仍要求 operator token 与 relay 能力广告(下一片)。
|
|
20
|
+
- 端到端本机冒烟:create → approve → status(dispatch_allowed=true) → dispatch --dry-run(2 envelopes)。
|
|
21
|
+
|
|
10
22
|
## [1.9.12] - 2026-10-28
|
|
11
23
|
|
|
12
24
|
- 联合开发安全门(S1/S2 预置):plan-store 保存与读取都重算 plan_sha256(篡改即 PLAN_TAMPERED)、planSha 强制 64 位十六进制(拒绝路径穿越)、审批绑定 plan_sha/approver/authority/TTL 并重算 approval_sha256(篡改/过期/低权限拒绝)、原子写。
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Operator CLI for joint-dev plans (v2 S2 slice).
|
|
4
|
+
*
|
|
5
|
+
* This is the operator side of the SOP, not an agent tool: a plan is created here, approved here by
|
|
6
|
+
* a human, and only then may a dispatch run. It never talks to a relay in this slice; `dispatch
|
|
7
|
+
* --dry-run` prints the control envelopes that would be sent. Real dispatch needs the operator token
|
|
8
|
+
* and the relay capability check, which land in the next slice.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node bin/w2m-joint.mjs plan create --state <dir> --steps <steps.json> [--base-commit <sha>] [--title <text>]
|
|
12
|
+
* node bin/w2m-joint.mjs plan list --state <dir>
|
|
13
|
+
* node bin/w2m-joint.mjs plan get --state <dir> --plan <sha>
|
|
14
|
+
* node bin/w2m-joint.mjs approve --state <dir> --plan <sha> --approver <name> --authority <n> [--ttl-min <n>] [--note <text>]
|
|
15
|
+
* node bin/w2m-joint.mjs status --state <dir> --plan <sha>
|
|
16
|
+
* node bin/w2m-joint.mjs dispatch --state <dir> --plan <sha> --dry-run
|
|
17
|
+
*
|
|
18
|
+
* Exit codes: 0 ok, 1 refusal (named code), 2 usage error.
|
|
19
|
+
*/
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
|
|
22
|
+
import { buildControlEnvelopes, buildPlan } from '../src/plugin/joint-dev/dispatch.mjs';
|
|
23
|
+
import {
|
|
24
|
+
approvePlan,
|
|
25
|
+
assertDispatchAllowed,
|
|
26
|
+
listPlans,
|
|
27
|
+
loadApproval,
|
|
28
|
+
loadPlan,
|
|
29
|
+
savePlan,
|
|
30
|
+
} from '../src/plugin/joint-dev/plan-store.mjs';
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const out = { _: [] };
|
|
34
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
35
|
+
const arg = argv[i];
|
|
36
|
+
if (arg.startsWith('--')) {
|
|
37
|
+
const key = arg.slice(2);
|
|
38
|
+
const next = argv[i + 1];
|
|
39
|
+
if (next !== undefined && !next.startsWith('--')) { out[key] = next; i += 1; }
|
|
40
|
+
else out[key] = true;
|
|
41
|
+
} else out._.push(arg);
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const print = (value) => process.stdout.write(JSON.stringify(value, null, 2) + '\n');
|
|
47
|
+
const fail = (code, detail) => { print({ ok: false, code, detail: detail ?? null }); process.exit(1); };
|
|
48
|
+
|
|
49
|
+
const args = parseArgs(process.argv.slice(2));
|
|
50
|
+
const [command, action] = args._;
|
|
51
|
+
const stateDir = typeof args.state === 'string' ? args.state : (process.env.W2M_JOINT_STATE ?? null);
|
|
52
|
+
if (stateDir === null) fail('STATE_DIR_REQUIRED', 'pass --state <dir> (the joint-dev state directory)');
|
|
53
|
+
|
|
54
|
+
if (command === 'plan' && action === 'create') {
|
|
55
|
+
if (typeof args.steps !== 'string') fail('STEPS_REQUIRED', 'pass --steps <steps.json>');
|
|
56
|
+
let steps;
|
|
57
|
+
try { steps = JSON.parse(readFileSync(args.steps, 'utf8').replace(/^\\uFEFF/, '')); } catch (error) {
|
|
58
|
+
fail('STEPS_UNREADABLE', error instanceof Error ? error.message : String(error));
|
|
59
|
+
}
|
|
60
|
+
if (!Array.isArray(steps)) fail('STEPS_INVALID', 'steps.json must be a JSON array');
|
|
61
|
+
const built = buildPlan({
|
|
62
|
+
base_commit: typeof args['base-commit'] === 'string' ? args['base-commit'] : undefined,
|
|
63
|
+
title: typeof args.title === 'string' ? args.title : undefined,
|
|
64
|
+
steps,
|
|
65
|
+
});
|
|
66
|
+
if (built.ok !== true) fail(built.code ?? 'PLAN_INVALID', built.detail ?? null);
|
|
67
|
+
const saved = savePlan({ stateDir, plan: built.plan });
|
|
68
|
+
if (saved.ok !== true) fail(saved.code ?? 'PLAN_SAVE_FAILED', saved.detail ?? null);
|
|
69
|
+
print({
|
|
70
|
+
ok: true,
|
|
71
|
+
plan_sha256: built.plan.plan_sha256,
|
|
72
|
+
steps: built.plan.steps.length,
|
|
73
|
+
machines: [...new Set(built.plan.steps.map((step) => step.machine_id))],
|
|
74
|
+
path: saved.path,
|
|
75
|
+
reused: saved.reused,
|
|
76
|
+
});
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (command === 'plan' && action === 'list') {
|
|
81
|
+
const listed = listPlans({ stateDir, limit: Number(args.limit) || 50 });
|
|
82
|
+
if (listed.ok !== true) fail(listed.code ?? 'PLAN_LIST_FAILED');
|
|
83
|
+
print({ ok: true, total: listed.total, plans: listed.plans });
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (command === 'plan' && action === 'get') {
|
|
88
|
+
if (typeof args.plan !== 'string') fail('PLAN_REQUIRED', 'pass --plan <sha256>');
|
|
89
|
+
const loaded = loadPlan({ stateDir, planSha: args.plan });
|
|
90
|
+
if (loaded.ok !== true) fail(loaded.code ?? 'PLAN_NOT_FOUND', loaded.detail ?? null);
|
|
91
|
+
print({ ok: true, plan: loaded.plan, saved_at: loaded.saved_at });
|
|
92
|
+
process.exit(0);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (command === 'approve') {
|
|
96
|
+
if (typeof args.plan !== 'string') fail('PLAN_REQUIRED', 'pass --plan <sha256>');
|
|
97
|
+
if (typeof args.approver !== 'string') fail('APPROVER_REQUIRED', 'pass --approver <name>');
|
|
98
|
+
const authority = Number.parseInt(String(args.authority ?? ''), 10);
|
|
99
|
+
if (!Number.isInteger(authority)) fail('AUTHORITY_REQUIRED', 'pass --authority <0..100>');
|
|
100
|
+
const ttlMin = Number.parseInt(String(args['ttl-min'] ?? '10'), 10);
|
|
101
|
+
const approved = approvePlan({
|
|
102
|
+
stateDir,
|
|
103
|
+
planSha: args.plan,
|
|
104
|
+
approver: args.approver,
|
|
105
|
+
authority,
|
|
106
|
+
note: typeof args.note === 'string' ? args.note : '',
|
|
107
|
+
ttlMs: (Number.isFinite(ttlMin) ? ttlMin : 10) * 60_000,
|
|
108
|
+
});
|
|
109
|
+
if (approved.ok !== true) fail(approved.code ?? 'APPROVAL_FAILED', approved.detail ?? null);
|
|
110
|
+
print({ ok: true, approval: approved.approval, path: approved.path });
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (command === 'status') {
|
|
115
|
+
if (typeof args.plan !== 'string') fail('PLAN_REQUIRED', 'pass --plan <sha256>');
|
|
116
|
+
const plan = loadPlan({ stateDir, planSha: args.plan });
|
|
117
|
+
if (plan.ok !== true) fail(plan.code ?? 'PLAN_NOT_FOUND');
|
|
118
|
+
const approval = loadApproval({ stateDir, planSha: args.plan });
|
|
119
|
+
const gate = assertDispatchAllowed({ stateDir, planSha: args.plan });
|
|
120
|
+
print({
|
|
121
|
+
ok: true,
|
|
122
|
+
plan_sha256: plan.plan.plan_sha256,
|
|
123
|
+
steps: plan.plan.steps.map((step) => ({ step_id: step.step_id, machine_id: step.machine_id, command_hash: step.command_hash })),
|
|
124
|
+
approval: approval.ok ? { approver: approval.approval.approver, authority: approval.approval.authority, expires_at: approval.approval.expires_at } : { state: approval.code },
|
|
125
|
+
dispatch_allowed: gate.ok === true,
|
|
126
|
+
blocked_by: gate.ok === true ? null : gate.code,
|
|
127
|
+
});
|
|
128
|
+
process.exit(0);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (command === 'dispatch') {
|
|
132
|
+
if (typeof args.plan !== 'string') fail('PLAN_REQUIRED', 'pass --plan <sha256>');
|
|
133
|
+
const gate = assertDispatchAllowed({ stateDir, planSha: args.plan });
|
|
134
|
+
if (gate.ok !== true) fail(gate.code ?? 'APPROVAL_REQUIRED', 'dispatch refused before anything was sent');
|
|
135
|
+
if (args['dry-run'] !== true) {
|
|
136
|
+
fail('DISPATCH_NOT_WIRED', 'real dispatch is the next slice: it needs the operator token and a relay with target_machines/capability advertisement; use --dry-run to inspect the envelopes');
|
|
137
|
+
}
|
|
138
|
+
const envelopes = buildControlEnvelopes(gate.plan);
|
|
139
|
+
print({
|
|
140
|
+
ok: true,
|
|
141
|
+
dry_run: true,
|
|
142
|
+
plan_sha256: gate.plan.plan_sha256,
|
|
143
|
+
approval_sha256: gate.approval.approval_sha256,
|
|
144
|
+
envelopes: envelopes.length,
|
|
145
|
+
control: envelopes.map((envelope) => ({ type: envelope.type, body: envelope.body })),
|
|
146
|
+
});
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
process.stderr.write('usage: node bin/w2m-joint.mjs <plan create|list|get|approve|status|dispatch> [options]\n');
|
|
151
|
+
process.exit(2);
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.9.13",
|
|
3
|
+
"case": "RELEASE-1913",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-28T00:31:27.437Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "pre-release-1913-1793147487438",
|
|
11
|
+
"requirement": "V1.9.13:操作员联合开发 CLI(计划/审批/状态/派发 dry-run)",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"cli": "w2m-joint plan create/list/get, approve, status, dispatch --dry-run",
|
|
16
|
+
"smoke": "create -> approve(authority 80, TTL 10m) -> status dispatch_allowed=true -> dry-run 2 envelopes",
|
|
17
|
+
"limits": "real dispatch returns DISPATCH_NOT_WIRED until operator token + relay capability slice"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.9.14",
|
|
3
|
+
"case": "RELEASE-1914",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-28T00:46:07.739Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"task_id": "pre-release-1914-1793148367740",
|
|
11
|
+
"requirement": "V1.9.14:立即更新 fetch failed 的 npm 回退",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"npm_fallback": "fetchNpmTarball verifies registry integrity then installs",
|
|
16
|
+
"route": "update-route falls back when GitHub install is not installed",
|
|
17
|
+
"tests": "npm-update/update-wiring/update-install 66/66"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
package/package.json
CHANGED
|
@@ -1,266 +1,265 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
"
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
"
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
+
"version": "1.9.14",
|
|
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
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./lib/index.js",
|
|
8
|
+
"./tools": "./lib/tools.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./rabbit": "./bin/w2m-rabbit.mjs",
|
|
11
|
+
"./localside": "./bin/w2m-localside.mjs",
|
|
12
|
+
"./stun": "./bin/w2m-stun.mjs",
|
|
13
|
+
"./package.json": "./package.json",
|
|
14
|
+
"./gpw": "./lib/gpw.js"
|
|
15
|
+
},
|
|
16
|
+
"dsh": {
|
|
17
|
+
"plugin": true,
|
|
18
|
+
"kind": "server",
|
|
19
|
+
"bundle": {
|
|
20
|
+
"patch": "cordis.patch.yml"
|
|
21
|
+
},
|
|
22
|
+
"client": {
|
|
23
|
+
"platform": "web"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"mcp.json",
|
|
28
|
+
"plugin.json",
|
|
29
|
+
"lib",
|
|
30
|
+
"src",
|
|
31
|
+
"bin",
|
|
32
|
+
"deploy",
|
|
33
|
+
"docs",
|
|
34
|
+
"skills",
|
|
35
|
+
"cordis.patch.yml",
|
|
36
|
+
"README.md",
|
|
37
|
+
"CHANGELOG.md",
|
|
38
|
+
"HANDOFF.md",
|
|
39
|
+
"ROADMAP.md",
|
|
40
|
+
"FLEET-RULES.md",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"PROTOCOL.md",
|
|
43
|
+
"PROTOCOL-v0.1.2.md",
|
|
44
|
+
"PROTOCOL-v0.3.0.md",
|
|
45
|
+
"PROTOCOL-v0.3.9.md",
|
|
46
|
+
"PROTOCOL-v0.4.0.md",
|
|
47
|
+
"TROUBLESHOOTING.md",
|
|
48
|
+
"SECURITY.md",
|
|
49
|
+
"CONTRIBUTING.md",
|
|
50
|
+
".claude-plugin",
|
|
51
|
+
"codex"
|
|
52
|
+
],
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=20.19.0"
|
|
55
|
+
},
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"scripts": {
|
|
58
|
+
"format": "prettier --write .",
|
|
59
|
+
"format:check": "prettier --check .",
|
|
60
|
+
"layer-check": "node scripts/layer-check.mjs",
|
|
61
|
+
"lint": "eslint .",
|
|
62
|
+
"lint:fix": "eslint . --fix",
|
|
63
|
+
"orphans": "node --test --test-force-exit test/accepted-orphans.test.mjs",
|
|
64
|
+
"pack": "node scripts/pack.mjs",
|
|
65
|
+
"reachability": "node scripts/reachability-check.mjs",
|
|
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/data-plane.test.mjs test/sealed-object.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",
|
|
68
|
+
"test:acceptance-doc": "node --test --test-force-exit test/acceptance-doc.test.mjs",
|
|
69
|
+
"test:account-identity": "node --test --test-force-exit test/account-identity.test.mjs",
|
|
70
|
+
"test:account-join": "node --test --test-force-exit test/account-join.test.mjs",
|
|
71
|
+
"test:admission": "node --test --test-force-exit test/admission.test.mjs",
|
|
72
|
+
"test:admission-routes": "node --test --test-force-exit test/admission-routes.test.mjs",
|
|
73
|
+
"test:agent": "node --test --test-force-exit test/agent.test.mjs test/memory.test.mjs",
|
|
74
|
+
"test:all": "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/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/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-agent.test.mjs test/stun-server.test.mjs test/loader-entry.test.mjs test/mdns-wire.test.mjs test/mdns-discover.test.mjs test/p2p-crypto.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",
|
|
75
|
+
"test:approval-gate": "node --test --test-force-exit test/approval-gate.test.mjs",
|
|
76
|
+
"test:approval-index": "node --test --test-force-exit test/approval-index.test.mjs",
|
|
77
|
+
"test:authority": "node --test --test-force-exit test/authority.test.mjs",
|
|
78
|
+
"test:authority-choice": "node --test --test-force-exit test/authority-choice.test.mjs",
|
|
79
|
+
"test:blake2s": "node --test --test-force-exit test/blake2s.test.mjs",
|
|
80
|
+
"test:blocks": "node --test --test-force-exit test/blocks.test.mjs",
|
|
81
|
+
"test:council": "node --test --test-force-exit test/council.test.mjs",
|
|
82
|
+
"test:council-session": "node --test --test-force-exit test/council-session.test.mjs",
|
|
83
|
+
"test:crossnetwork": "node --test --test-force-exit test/crossnetwork.test.mjs",
|
|
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 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",
|
|
86
|
+
"test:exec-env": "node --test --test-force-exit test/exec-env-injection.test.mjs",
|
|
87
|
+
"test:exit-channel": "node --test --test-force-exit test/exit-channel.test.mjs",
|
|
88
|
+
"test:field-authority": "node --test --test-force-exit test/field-authority.test.mjs",
|
|
89
|
+
"test:fleet-chat-identity": "node --test --test-force-exit test/fleet-chat-identity.test.mjs",
|
|
90
|
+
"test:fleet-concurrency": "node --test --test-force-exit test/fleet-concurrency.test.mjs",
|
|
91
|
+
"test:fleet-memory-e2e": "node --test --test-force-exit test/fleet-memory-e2e.test.mjs",
|
|
92
|
+
"test:fleet-merge": "node --test --test-force-exit test/fleet-merge.test.mjs",
|
|
93
|
+
"test:fleet-merge-race": "node --test --test-force-exit test/fleet-merge-race.test.mjs",
|
|
94
|
+
"test:fleet-messages": "node --test --test-force-exit test/fleet-messages.test.mjs",
|
|
95
|
+
"test:fleet-messages-http": "node --test --test-force-exit test/fleet-messages-http.test.mjs",
|
|
96
|
+
"test:fleet-scale": "node --test --test-force-exit test/fleet-scale.test.mjs",
|
|
97
|
+
"test:fleet-state": "node --test --test-force-exit test/fleet-state.test.mjs",
|
|
98
|
+
"test:fleet-sync": "node --test --test-force-exit test/fleet-sync.test.mjs",
|
|
99
|
+
"test:health": "node --test --test-force-exit test/health.test.mjs",
|
|
100
|
+
"test:identity-reader": "node --test --test-force-exit test/identity-reader.test.mjs",
|
|
101
|
+
"test:installed-version": "node --test --test-force-exit test/installed-version.test.mjs",
|
|
102
|
+
"test:join-challenge": "node --test --test-force-exit test/join-challenge.test.mjs",
|
|
103
|
+
"test:join-degradation": "node --test --test-force-exit test/join-degradation.test.mjs",
|
|
104
|
+
"test:layers": "node --test --test-force-exit test/layer-model.test.mjs",
|
|
105
|
+
"test:log-bounds": "node --test --test-force-exit test/log-bounds.test.mjs",
|
|
106
|
+
"test:memory": "node --test --test-force-exit test/memory.test.mjs",
|
|
107
|
+
"test:message-notify": "node --test --test-force-exit test/message-notify.test.mjs",
|
|
108
|
+
"test:message-shards": "node --test --test-force-exit test/message-shards.test.mjs",
|
|
109
|
+
"test:p2p": "node --test --test-force-exit test/p2p-stun.test.mjs test/p2p-transport.test.mjs test/p2p-signaling.test.mjs test/p2p-node.test.mjs test/stun-server.test.mjs test/p2p-transport-fields.test.mjs test/p2p-plugin.test.mjs test/p2p-agent.test.mjs",
|
|
110
|
+
"test:packet": "node --test --test-force-exit test/machine-test-packet.test.mjs",
|
|
111
|
+
"test:permission": "node --test --test-force-exit test/permission.test.mjs",
|
|
112
|
+
"test:portable": "node --test --test-force-exit test/portable-plugins.test.mjs",
|
|
113
|
+
"test:reachability": "node --test --test-force-exit test/reachability-check.test.mjs",
|
|
114
|
+
"test:relay": "node --test --test-force-exit test/relay.test.mjs",
|
|
115
|
+
"test:session-readonly": "node --test --test-force-exit test/session-readonly.test.mjs",
|
|
116
|
+
"test:session-sync": "node --test --test-force-exit test/session-sync.test.mjs",
|
|
117
|
+
"test:session-watch": "node --test --test-force-exit test/session-watch.test.mjs",
|
|
118
|
+
"test:storage": "node --test --test-force-exit test/storage.test.mjs",
|
|
119
|
+
"test:test-report": "node --test --test-force-exit test/test-report.test.mjs",
|
|
120
|
+
"test:tools": "node --test --test-force-exit test/tools.test.mjs",
|
|
121
|
+
"test:update-channels": "node --test --test-force-exit test/update-channels.test.mjs",
|
|
122
|
+
"test:update-prerelease": "node --test --test-force-exit test/update-prerelease.test.mjs",
|
|
123
|
+
"verify": "node scripts/check-entrypoints.mjs && node --test --test-force-exit test/relay.test.mjs test/broadcast.test.mjs test/agent.test.mjs test/tools.test.mjs test/e2e.test.mjs test/crossnetwork.test.mjs test/p2p-node.test.mjs test/p2p-agent.test.mjs test/client-bundle.test.mjs test/loader-entry.test.mjs",
|
|
124
|
+
"verify:join": "node scripts/verify-join.mjs",
|
|
125
|
+
"test:intents": "node --test --test-force-exit test/intents.test.mjs",
|
|
126
|
+
"test:dataflow": "node --test --test-force-exit test/dataflow.test.mjs",
|
|
127
|
+
"test:council-fleet": "node --test --test-force-exit test/council-fleet.test.mjs",
|
|
128
|
+
"test:fleet-memory": "node --test --test-force-exit test/fleet-memory.test.mjs",
|
|
129
|
+
"test:memory-view": "node --test --test-force-exit test/memory-view.test.mjs",
|
|
130
|
+
"test:schema-spec": "node --test --test-force-exit test/schema-spec.test.mjs",
|
|
131
|
+
"test:fleet-weight-parity": "node --test --test-force-exit test/fleet-weight-parity.test.mjs",
|
|
132
|
+
"test:task-deps": "node --test --test-force-exit test/task-deps.test.mjs",
|
|
133
|
+
"test:dataflow-wiring": "node --test --test-force-exit test/dataflow-wiring.test.mjs",
|
|
134
|
+
"test:task-graph": "node --test --test-force-exit test/task-graph.test.mjs",
|
|
135
|
+
"test:ask-store": "node --test --test-force-exit test/ask-store.test.mjs",
|
|
136
|
+
"test:turn": "node --test --test-force-exit test/turn.test.mjs",
|
|
137
|
+
"test:fleet-members": "node --test --test-force-exit test/fleet-members.test.mjs",
|
|
138
|
+
"test:redact": "node --test --test-force-exit test/redact.test.mjs",
|
|
139
|
+
"test:policy-matrix": "node --test --test-force-exit test/policy-matrix.test.mjs",
|
|
140
|
+
"test:envelope-signature": "node --test --test-force-exit test/envelope-signature.test.mjs",
|
|
141
|
+
"test:chat-crypto": "node --test --test-force-exit test/chat-crypto.test.mjs",
|
|
142
|
+
"test:reduce-suggestion": "node --test --test-force-exit test/reduce-suggestion.test.mjs",
|
|
143
|
+
"test:merge-hint": "node --test --test-force-exit test/merge-hint.test.mjs",
|
|
144
|
+
"test:fleet-chat-route": "node --test --test-force-exit test/fleet-chat-route.test.mjs",
|
|
145
|
+
"test:maintenance": "node --test --test-force-exit test/maintenance.test.mjs",
|
|
146
|
+
"test:maintenance-ferry": "node --test --test-force-exit test/maintenance-ferry.test.mjs",
|
|
147
|
+
"test:grant": "node --test --test-force-exit test/grant.test.mjs",
|
|
148
|
+
"test:grant-gate": "node --test --test-force-exit test/grant-gate.test.mjs",
|
|
149
|
+
"test:worktree": "node --test --test-force-exit test/worktree.test.mjs",
|
|
150
|
+
"test:patch-artifact": "node --test --test-force-exit test/patch-artifact.test.mjs",
|
|
151
|
+
"test:merge-queue": "node --test --test-force-exit test/merge-queue.test.mjs",
|
|
152
|
+
"test:m3-workflow": "node --test --test-force-exit test/m3-workflow.test.mjs",
|
|
153
|
+
"test:project-room": "node --test --test-force-exit test/project-room.test.mjs",
|
|
154
|
+
"test:project-attribution": "node --test --test-force-exit test/project-attribution.test.mjs",
|
|
155
|
+
"test:project-locks": "node --test --test-force-exit test/project-locks.test.mjs",
|
|
156
|
+
"test:project-reviews": "node --test --test-force-exit test/project-reviews.test.mjs",
|
|
157
|
+
"test:project-notifications": "node --test --test-force-exit test/project-notifications.test.mjs",
|
|
158
|
+
"test:blob-sessions": "node --test --test-force-exit test/blob-sessions.test.mjs",
|
|
159
|
+
"test:outbox": "node --test --test-force-exit test/outbox.test.mjs",
|
|
160
|
+
"test:metrics": "node --test --test-force-exit test/metrics.test.mjs",
|
|
161
|
+
"test:reliability-metrics": "node --test --test-force-exit test/metrics.test.mjs",
|
|
162
|
+
"test:evidence": "node --test --test-force-exit test/evidence.test.mjs",
|
|
163
|
+
"test:grant-issue": "node --test --test-force-exit test/grant-issue.test.mjs",
|
|
164
|
+
"test:project-refs": "node --test --test-force-exit test/project-refs.test.mjs",
|
|
165
|
+
"test:secondary-link": "node --test --test-force-exit test/secondary-link.test.mjs",
|
|
166
|
+
"verify:fleet-isolation": "node scripts/verify-fleet-isolation.mjs"
|
|
167
|
+
},
|
|
168
|
+
"devDependencies": {
|
|
169
|
+
"@eslint/js": "^10.0.1",
|
|
170
|
+
"eslint": "^10.12.0",
|
|
171
|
+
"globals": "^17.13.0",
|
|
172
|
+
"prettier": "^3.9.9"
|
|
173
|
+
},
|
|
174
|
+
"publishConfig": {
|
|
175
|
+
"access": "public"
|
|
176
|
+
},
|
|
177
|
+
"repository": {
|
|
178
|
+
"type": "git",
|
|
179
|
+
"url": "git+https://github.com/TwinsEarth/dsh-windows2macos.git"
|
|
180
|
+
},
|
|
181
|
+
"homepage": "https://github.com/TwinsEarth/dsh-windows2macos",
|
|
182
|
+
"bugs": {
|
|
183
|
+
"url": "https://github.com/TwinsEarth/dsh-windows2macos/issues"
|
|
184
|
+
},
|
|
185
|
+
"keywords": [
|
|
186
|
+
"dsh-bundle",
|
|
187
|
+
"dsh-plugin",
|
|
188
|
+
"deepseek-harness",
|
|
189
|
+
"deepseek",
|
|
190
|
+
"windows2macos",
|
|
191
|
+
"w2m",
|
|
192
|
+
"cross-platform",
|
|
193
|
+
"multi-machine",
|
|
194
|
+
"orchestration",
|
|
195
|
+
"replicate",
|
|
196
|
+
"sharding"
|
|
197
|
+
],
|
|
198
|
+
"main": "lib/index.js",
|
|
199
|
+
"bin": {
|
|
200
|
+
"w2m-localside": "bin/w2m-localside.mjs",
|
|
201
|
+
"w2m-mcp": "bin/w2m-mcp.mjs",
|
|
202
|
+
"w2m-rabbit": "bin/w2m-rabbit.mjs",
|
|
203
|
+
"w2m-stun": "bin/w2m-stun.mjs",
|
|
204
|
+
"w2m-grant": "./bin/w2m-grant.mjs",
|
|
205
|
+
"w2m-secondary-link": "bin/w2m-secondary-link.mjs",
|
|
206
|
+
"w2m-bootstrap": "bin/w2m-bootstrap.mjs",
|
|
207
|
+
"w2m-bootstrap-sign": "bin/w2m-bootstrap-sign.mjs",
|
|
208
|
+
"w2m-hidden-task": "bin/w2m-hidden-task.mjs",
|
|
209
|
+
"w2m-self-update": "bin/w2m-self-update.mjs",
|
|
210
|
+
"w2m-joint": "bin/w2m-joint.mjs"
|
|
211
|
+
},
|
|
212
|
+
"disclosure": {
|
|
213
|
+
"cloud": true,
|
|
214
|
+
"network": [
|
|
215
|
+
"http://202.182.123.154:8787",
|
|
216
|
+
"202.182.123.154:3478",
|
|
217
|
+
"stun.cloudflare.com:3478",
|
|
218
|
+
"stun.l.google.com:19302",
|
|
219
|
+
"stun.nextcloud.com:443",
|
|
220
|
+
"ilinkai.weixin.qq.com:443",
|
|
221
|
+
"weixin.qq.com:443",
|
|
222
|
+
"api.weixin.qq.com:443"
|
|
223
|
+
],
|
|
224
|
+
"offlineMode": false,
|
|
225
|
+
"apiKeys": [
|
|
226
|
+
{
|
|
227
|
+
"env": "W2M_OPERATOR_TOKEN",
|
|
228
|
+
"storage": "profile-config"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"env": null,
|
|
232
|
+
"storage": "file-0600"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"env": null,
|
|
236
|
+
"storage": "profile-config"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"env": null,
|
|
240
|
+
"storage": "im-bot-token"
|
|
241
|
+
}
|
|
242
|
+
],
|
|
243
|
+
"permissions": {
|
|
244
|
+
"network": "the configured relay (default: the shared server above), STUN servers, and UDP to the peer machines. The optional chatops channels reach one instant-messaging platform each, and only when that channel is named in `channels` and the host has supplied the transport: the WeChat channel reaches ilinkai.weixin.qq.com and sends the bot token as a bearer credential, and both that host and the base url in a login response are checked against an allow-list before a token is sent anywhere",
|
|
245
|
+
"filesystem": "read/write inside the project directory and DSH_HOME; the agent writes its spool and state under --state; the chatops file fence reads inside `projectRoot` only, after resolving symlinks",
|
|
246
|
+
"env": [
|
|
247
|
+
"W2M_*"
|
|
248
|
+
]
|
|
249
|
+
},
|
|
250
|
+
"jurisdiction": [
|
|
251
|
+
"CN",
|
|
252
|
+
"JP"
|
|
253
|
+
],
|
|
254
|
+
"retention": "server"
|
|
255
|
+
},
|
|
256
|
+
"dependencies": {},
|
|
257
|
+
"peerDependencies": {
|
|
258
|
+
"@deepseek-ai/dsh-tools": "*"
|
|
259
|
+
},
|
|
260
|
+
"peerDependenciesMeta": {
|
|
261
|
+
"@deepseek-ai/dsh-tools": {
|
|
262
|
+
"optional": true
|
|
263
|
+
}
|
|
264
|
+
}
|
|
266
265
|
}
|
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.9.
|
|
4
|
+
"version": "1.9.14",
|
|
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": [
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* package in the profile, and takes a backup. Keeping the check pure is what makes "NPM 对比" testable
|
|
9
9
|
* without a network or a profile.
|
|
10
10
|
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
11
15
|
import { compareVersions } from './update-source.mjs';
|
|
12
16
|
|
|
13
17
|
/** The package this plugin is. */
|
|
@@ -83,3 +87,61 @@ export async function checkNpmUpdate(input) {
|
|
|
83
87
|
return decideNpmUpdate({ latest: last, currentVersion: input?.currentVersion ?? null });
|
|
84
88
|
}
|
|
85
89
|
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
/** Registry package documents to try, in order (mirror first, then npmjs). */
|
|
93
|
+
export const NPM_PACKAGE_DOC_URLS = [
|
|
94
|
+
`https://registry.npmmirror.com/${encodeURIComponent(PLUGIN_PACKAGE_NAME)}`,
|
|
95
|
+
`https://registry.npmjs.org/${encodeURIComponent(PLUGIN_PACKAGE_NAME)}`,
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Download an exact npm version and verify it against the registry's own integrity field before it
|
|
100
|
+
* can be installed. This is the fallback for "immediately update failed: fetch failed": GitHub
|
|
101
|
+
* release downloads are blocked on some networks while the npm registry is reachable.
|
|
102
|
+
*/
|
|
103
|
+
export async function fetchNpmTarball(input) {
|
|
104
|
+
const fetchImpl = input?.fetchImpl ?? globalThis.fetch;
|
|
105
|
+
const version = typeof input?.version === 'string' ? input.version.trim() : '';
|
|
106
|
+
if (version === '') return { ok: false, error: 'version is required' };
|
|
107
|
+
const destDir = input?.destDir;
|
|
108
|
+
if (typeof destDir !== 'string' || destDir === '') return { ok: false, error: 'destDir is required' };
|
|
109
|
+
const timeoutMs = Number.isInteger(input?.timeoutMs) ? input.timeoutMs : NPM_CHECK_TIMEOUT_MS;
|
|
110
|
+
const urls = Array.isArray(input?.urls) ? input.urls : NPM_PACKAGE_DOC_URLS;
|
|
111
|
+
const signalOf = () => (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function' ? AbortSignal.timeout(timeoutMs) : undefined);
|
|
112
|
+
let meta = null;
|
|
113
|
+
let lastError = 'npm package document could not be read';
|
|
114
|
+
for (const url of urls) {
|
|
115
|
+
try {
|
|
116
|
+
const response = await fetchImpl(url, { headers: { accept: 'application/json' }, signal: signalOf() });
|
|
117
|
+
if (response?.ok !== true) { lastError = `npm registry answered HTTP ${response?.status ?? '?'}`; continue; }
|
|
118
|
+
const doc = await response.json();
|
|
119
|
+
const candidate = doc?.versions?.[version];
|
|
120
|
+
if (candidate?.dist?.tarball === undefined) { lastError = `npm has no version ${version} with a tarball`; continue; }
|
|
121
|
+
meta = candidate;
|
|
122
|
+
break;
|
|
123
|
+
} catch (error) { lastError = error instanceof Error ? error.message : String(error); }
|
|
124
|
+
}
|
|
125
|
+
if (meta === null) return { ok: false, error: lastError };
|
|
126
|
+
try {
|
|
127
|
+
const response = await fetchImpl(meta.dist.tarball, { signal: signalOf() });
|
|
128
|
+
if (response?.ok !== true) return { ok: false, error: `npm tarball answered HTTP ${response?.status ?? '?'}` };
|
|
129
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
130
|
+
const integrity = typeof meta.dist.integrity === 'string' ? meta.dist.integrity : null;
|
|
131
|
+
if (integrity !== null && /^sha512-/.test(integrity)) {
|
|
132
|
+
const actual = createHash('sha512').update(bytes).digest('base64');
|
|
133
|
+
if (actual !== integrity.slice('sha512-'.length)) return { ok: false, error: 'NPM_INTEGRITY_MISMATCH' };
|
|
134
|
+
} else if (typeof meta.dist.shasum === 'string') {
|
|
135
|
+
const actual = createHash('sha1').update(bytes).digest('hex');
|
|
136
|
+
if (actual !== meta.dist.shasum) return { ok: false, error: 'NPM_SHASUM_MISMATCH' };
|
|
137
|
+
} else {
|
|
138
|
+
return { ok: false, error: 'NPM_NO_INTEGRITY' };
|
|
139
|
+
}
|
|
140
|
+
mkdirSync(destDir, { recursive: true });
|
|
141
|
+
const path = join(destDir, `w2m-dsh-plugin-${version}.tgz`);
|
|
142
|
+
writeFileSync(path, bytes);
|
|
143
|
+
return { ok: true, path, bytes: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex'), version, tarball: meta.dist.tarball, integrityVerified: true };
|
|
144
|
+
} catch (error) {
|
|
145
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -283,7 +283,7 @@ const MAX_STAGES = 16;
|
|
|
283
283
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
284
284
|
* overrides it at runtime for tests and packagers.
|
|
285
285
|
*/
|
|
286
|
-
const PLUGIN_VERSION = '1.9.
|
|
286
|
+
const PLUGIN_VERSION = '1.9.14';
|
|
287
287
|
|
|
288
288
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
289
289
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
@@ -86,6 +86,13 @@ export function registerPluginUpdateRoutes(ctx, deps) {
|
|
|
86
86
|
const result = await updater.check('manual');
|
|
87
87
|
const installedTo = typeof result?.to === 'string' && result.to !== '' ? result.to : null;
|
|
88
88
|
const installed = result?.outcome === 'installed' || installedTo !== null && installedTo !== current;
|
|
89
|
+
// The GitHub release download is blocked on some networks ("fetch failed"), while the npm
|
|
90
|
+
// registry is reachable. Before reporting a failure, install the same version from the
|
|
91
|
+
// registry tarball, whose integrity field is verified before pnpm sees it.
|
|
92
|
+
if (installed !== true) {
|
|
93
|
+
const fallback = await installFromNpmFallback({ profileDir, current, version: checked.latest ?? current });
|
|
94
|
+
if (fallback !== null) return Response.json(fallback.body, { headers: NO_STORE });
|
|
95
|
+
}
|
|
89
96
|
return Response.json({
|
|
90
97
|
ok: result?.ok === true && installed === true,
|
|
91
98
|
outcome: result?.outcome ?? 'unknown',
|
|
@@ -116,3 +123,27 @@ async function defaultUpdater({ profileDir, currentVersion }) {
|
|
|
116
123
|
log: () => {},
|
|
117
124
|
});
|
|
118
125
|
}
|
|
126
|
+
|
|
127
|
+
/** Install the same version from the npm registry when the GitHub release download fails. */
|
|
128
|
+
async function installFromNpmFallback({ profileDir, current, version }) {
|
|
129
|
+
try {
|
|
130
|
+
const { fetchNpmTarball } = await import('./npm-update.mjs');
|
|
131
|
+
const { installUpdate } = await import('./update-install.mjs');
|
|
132
|
+
const { tmpdir } = await import('node:os');
|
|
133
|
+
const fetched = await fetchNpmTarball({ version, destDir: join(tmpdir(), 'w2m-npm-update-' + Date.now()) });
|
|
134
|
+
if (fetched.ok !== true) return null;
|
|
135
|
+
const result = await installUpdate({ profileDir, tarballPath: fetched.path, expectedSha256: fetched.sha256 });
|
|
136
|
+
if (result?.ok !== true) return null;
|
|
137
|
+
return {
|
|
138
|
+
body: {
|
|
139
|
+
ok: true,
|
|
140
|
+
outcome: 'installed',
|
|
141
|
+
installedVia: 'npm-tarball',
|
|
142
|
+
from: result.from ?? current,
|
|
143
|
+
to: result.to ?? version,
|
|
144
|
+
installed: true,
|
|
145
|
+
restart_required: true,
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
} catch { return null; }
|
|
149
|
+
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import {
|
|
18
18
|
chmodSync,
|
|
19
|
+
chownSync,
|
|
19
20
|
closeSync,
|
|
20
21
|
copyFileSync,
|
|
21
22
|
existsSync,
|
|
@@ -63,6 +64,7 @@ export function repairLedger(file, options = {}) {
|
|
|
63
64
|
const before = statSync(file);
|
|
64
65
|
if (!before.isFile()) return { ok: false, code: LEDGER_REPAIR_CODES.NOT_A_FILE, path: file };
|
|
65
66
|
|
|
67
|
+
const mode = before.mode & 0o777;
|
|
66
68
|
const raw = readFileSync(file);
|
|
67
69
|
const afterRead = statSync(file);
|
|
68
70
|
if (afterRead.mtimeMs !== before.mtimeMs || afterRead.size !== before.size) {
|
|
@@ -116,7 +118,8 @@ export function repairLedger(file, options = {}) {
|
|
|
116
118
|
const backup = `${file}.bak-${now}`;
|
|
117
119
|
try {
|
|
118
120
|
copyFileSync(file, backup);
|
|
119
|
-
chmodSync(backup,
|
|
121
|
+
chmodSync(backup, mode);
|
|
122
|
+
try { chownSync(backup, before.uid, before.gid); } catch { /* unprivileged run keeps the caller's owner */ }
|
|
120
123
|
} catch (error) {
|
|
121
124
|
return { ...base, dry_run: false, written: false, ok: false, code: LEDGER_REPAIR_CODES.WRITE_FAILED, error: String(error) };
|
|
122
125
|
}
|
|
@@ -132,7 +135,10 @@ export function repairLedger(file, options = {}) {
|
|
|
132
135
|
const next = valid.length === 0 ? '' : valid.join('\n') + (hasFinalNewline ? '\n' : '');
|
|
133
136
|
const tmp = `${file}.repair-${now}.tmp`;
|
|
134
137
|
try {
|
|
135
|
-
writeDurable(tmp, Buffer.from(next, 'utf8'),
|
|
138
|
+
writeDurable(tmp, Buffer.from(next, 'utf8'), mode);
|
|
139
|
+
// Keep the service account as the owner: a relay that runs as `w2m` must still be able to
|
|
140
|
+
// read the file after root repaired it (this exact break happened on Vultr).
|
|
141
|
+
try { chownSync(tmp, before.uid, before.gid); } catch { /* unprivileged */ }
|
|
136
142
|
renameSync(tmp, file);
|
|
137
143
|
} catch (error) {
|
|
138
144
|
try { if (existsSync(tmp)) unlinkSync(tmp); } catch { /* best effort */ }
|