@twinsearth/w2m-dsh-plugin 1.6.6 → 1.6.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 +15 -0
- package/codex/mcp-servers.toml +1 -1
- package/docs/acceptance/1.6.7/FAILOVER-CLEANUP-FIX.json +22 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/src/plugin/tools.mjs +23 -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.6.
|
|
8
|
+
"version": "1.6.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.6.
|
|
15
|
+
"version": "1.6.7",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -26,5 +26,5 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "1.6.
|
|
29
|
+
"version": "1.6.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.6.
|
|
4
|
+
"version": "1.6.7",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
|
|
8
8
|
All notable changes to this project are documented here.
|
|
9
9
|
|
|
10
|
+
## [1.6.7] — 2026-10-24
|
|
11
|
+
|
|
12
|
+
**修复 v1.6.6 failover 的阻塞 bug:远端 relay 模式下清理函数抛错,切换后无法重启 stack。**
|
|
13
|
+
|
|
14
|
+
现场演练(停主 relay 180s)暴露:机器掉线后没有切到备用,恢复主 relay 后才重新上线。根因:
|
|
15
|
+
|
|
16
|
+
- `startLocalStack` 的清理函数无条件执行 `await relay.close()`;而 `relay` 只在"本进程自建本地 relay"
|
|
17
|
+
时才非空,远端 relay 模式下是 `null` → 抛 TypeError;
|
|
18
|
+
- failover 的切换流程 `await stop()` 抛错后被 catch 吞掉,**后续的 restart 从未执行**(stack 停了没起来)。
|
|
19
|
+
|
|
20
|
+
修复:
|
|
21
|
+
- 清理函数 null-safe/idempotent:仅当 `relay` 是带 `close()` 的对象才调用;`agent.stop()` 失败也继续清理。
|
|
22
|
+
- 切换流程:`stop()` 失败不再跳过 restart;并写诊断日志 `xclient/relay-failover.log`(每轮探测/切换/重启错误)。
|
|
23
|
+
- 测试:relay-failover 5/5、relay 200/200;证据 `docs/acceptance/1.6.7/FAILOVER-CLEANUP-FIX.json`
|
|
24
|
+
(fleet task `01M5ANYMVMFSSP3`)。
|
|
10
25
|
## [1.6.6] — 2026-10-24
|
|
11
26
|
|
|
12
27
|
**relay 自动 failover:主 relay 故障时自动切到备用,恢复后自动切回。**
|
package/codex/mcp-servers.toml
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.6.7",
|
|
3
|
+
"case": "FAILOVER-CLEANUP-FIX",
|
|
4
|
+
"kind": "positive",
|
|
5
|
+
"machines": [
|
|
6
|
+
"b53a37f4-1464-4512-9259-33a7df091e3e"
|
|
7
|
+
],
|
|
8
|
+
"executed_at": "2026-10-24T13:31:02.803Z",
|
|
9
|
+
"executor": "lead",
|
|
10
|
+
"requirement": "a remote-relay in-process stack can be stopped and restarted for failover: the cleanup must not assume a local relay object, a failed stop must not skip the restart, and the switch must leave a diagnostics log",
|
|
11
|
+
"task_id": "01M5ANYMVMFSSP3",
|
|
12
|
+
"result": {
|
|
13
|
+
"outcome": "ok",
|
|
14
|
+
"assertions": {
|
|
15
|
+
"failover_suite": "5/5",
|
|
16
|
+
"relay_suite": "200/200",
|
|
17
|
+
"cleanup": "null-safe and idempotent",
|
|
18
|
+
"diagnostics": "xclient/relay-failover.log"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"note": "v1.6.6 field drill stopped the stack and never restarted it: relay.close() threw on remote-relay stacks"
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.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": {
|
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.6.
|
|
4
|
+
"version": "1.6.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": [
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -281,7 +281,7 @@ const MAX_STAGES = 16;
|
|
|
281
281
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
282
282
|
* overrides it at runtime for tests and packagers.
|
|
283
283
|
*/
|
|
284
|
-
const PLUGIN_VERSION = '1.6.
|
|
284
|
+
const PLUGIN_VERSION = '1.6.7';
|
|
285
285
|
|
|
286
286
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
287
287
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
@@ -7407,6 +7407,19 @@ async function probeRelayHealth(url, timeoutMs) {
|
|
|
7407
7407
|
* relay-failover.mjs and is unit-tested there; this function only supplies health and performs the
|
|
7408
7408
|
* restart.
|
|
7409
7409
|
*/
|
|
7410
|
+
/** Append one line to the failover diagnostics log; failures are swallowed (diagnostics must not break the switch). */
|
|
7411
|
+
async function appendFailoverLog(cfg, line) {
|
|
7412
|
+
try {
|
|
7413
|
+
const { appendFileSync, mkdirSync, statSync, writeFileSync } = await import('node:fs');
|
|
7414
|
+
const { join, dirname } = await import('node:path');
|
|
7415
|
+
const { homedir } = await import('node:os');
|
|
7416
|
+
const dir = cfg.stateDir ?? join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'xclient');
|
|
7417
|
+
const file = join(dir, 'relay-failover.log');
|
|
7418
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
7419
|
+
try { if (statSync(file).size > 256 * 1024) writeFileSync(file, ''); } catch { /* absent is fine */ }
|
|
7420
|
+
appendFileSync(file, new Date().toISOString() + ' ' + line + '\n');
|
|
7421
|
+
} catch { /* diagnostics only */ }
|
|
7422
|
+
}
|
|
7410
7423
|
async function startFailoverStack(ctx, cfg) {
|
|
7411
7424
|
let stop = await startLocalStack(ctx, cfg);
|
|
7412
7425
|
const pool = relayPool(cfg);
|
|
@@ -7422,6 +7435,7 @@ async function startFailoverStack(ctx, cfg) {
|
|
|
7422
7435
|
const health = {};
|
|
7423
7436
|
await Promise.all(pool.map(async (url) => { health[url] = await probeRelayHealth(url, FAILOVER_DEFAULTS.probeTimeoutMs); }));
|
|
7424
7437
|
const decision = failover.assess(health);
|
|
7438
|
+
await appendFailoverLog(cfg, 'probe ' + JSON.stringify(health) + ' -> ' + decision.action + (decision.target ? ' ' + decision.target : ''));
|
|
7425
7439
|
relayFailoverStatus = failover.snapshot();
|
|
7426
7440
|
if (decision.action !== 'switch') return;
|
|
7427
7441
|
switching = true;
|
|
@@ -8077,7 +8091,14 @@ async function startLocalStack(ctx, cfg) {
|
|
|
8077
8091
|
ensureFleetJoin = null;
|
|
8078
8092
|
inProcessAgentStatus = null;
|
|
8079
8093
|
await agent.stop();
|
|
8080
|
-
|
|
8094
|
+
// v1.6.7: `relay` is only a real relay object when THIS stack started a local relay; a remote-
|
|
8095
|
+
// relay stack has null here. The unconditional close() threw, which is what made the first
|
|
8096
|
+
// failover attempt stop the stack and never restart it.
|
|
8097
|
+
if (relay !== null && typeof relay.close === 'function') {
|
|
8098
|
+
try { await relay.close(); } catch (error) {
|
|
8099
|
+
ctx.logger?.warn?.('w2m: local relay close failed: ' + (error instanceof Error ? error.message : String(error)));
|
|
8100
|
+
}
|
|
8101
|
+
}
|
|
8081
8102
|
};
|
|
8082
8103
|
}
|
|
8083
8104
|
|