@twinsearth/w2m-dsh-plugin 1.1.2 → 1.1.3
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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +31 -0
- package/codex/mcp-servers.toml +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/src/plugin/fleet-target.mjs +23 -2
- package/src/plugin/tools.mjs +1 -1
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "The w2m fleet plugins: one account, many machines, any of Windows, macOS or Linux.",
|
|
8
|
-
"version": "1.1.
|
|
8
|
+
"version": "1.1.3"
|
|
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.1.
|
|
15
|
+
"version": "1.1.3",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "TwinsEarth"
|
|
18
18
|
},
|
|
@@ -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.1.
|
|
4
|
+
"version": "1.1.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TwinsEarth"
|
|
7
7
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented here.
|
|
4
4
|
|
|
5
|
+
## [1.1.3] — 2026-10-17
|
|
6
|
+
|
|
7
|
+
**A machine offline for more than 72 hours leaves the list. It is not deleted, and it comes back on its own.**
|
|
8
|
+
|
|
9
|
+
### The rule, and why hiding is not deleting
|
|
10
|
+
|
|
11
|
+
`readFleet` filters the roster at **the read path only**:
|
|
12
|
+
|
|
13
|
+
* a machine whose `last_seen` is at least **72 hours** old is **not shown** by `w2m_devices` or the composer;
|
|
14
|
+
* `records` still carries **every device the relay sent**, so nothing is lost and no bookkeeping is required;
|
|
15
|
+
* a **missing or unparseable** `last_seen` is **not** archived -- an unknown age must not be read as "gone for three
|
|
16
|
+
days", or a relay that omits the field would empty the list;
|
|
17
|
+
* `include_stale: true` shows the archived machines, which is the operator's audit route;
|
|
18
|
+
* the machine returns by reporting again: the same record with a fresh timestamp is listed, with no re-pairing.
|
|
19
|
+
|
|
20
|
+
Because the relay already stops leasing machines that are offline (v1.1.2), a hidden machine is also not a dispatch
|
|
21
|
+
target -- the list and the dispatch agree, from one notion of liveness plus this display rule.
|
|
22
|
+
|
|
23
|
+
### The fixtures were the reason this took two attempts
|
|
24
|
+
|
|
25
|
+
The first attempt changed nothing in the filter and failed 8 tests: `fleet-target`'s roster fixtures carried **fixed
|
|
26
|
+
dates** (`2026-10-09` … `2026-10-11`), which are more than 72 hours old *now*, so a correct filter archived all three
|
|
27
|
+
machines. Making the timestamps **relative** (`Date.now() - 60s` …) fixed all 8 without touching a single assertion,
|
|
28
|
+
which is the evidence that the filter was right and the fixture was the stale part.
|
|
29
|
+
|
|
30
|
+
### Verified
|
|
31
|
+
|
|
32
|
+
`test/fleet-target.test.mjs` gains four assertions -- hidden from the list, **records kept**, returns on its own and
|
|
33
|
+
`include_stale` shows it meanwhile, unknown age not archived -- for **45 pass, 0 fail**. `test/tools.test.mjs` 115 and
|
|
34
|
+
`test/relay.test.mjs` 160 remain green.
|
|
35
|
+
|
|
5
36
|
## [1.1.2] — 2026-10-17
|
|
6
37
|
|
|
7
38
|
**A dispatch that names no machines now goes to the machines that are online. The rule that decides "online" had been
|
package/codex/mcp-servers.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twinsearth/w2m-dsh-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
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
|
"main": "lib/index.js",
|
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.1.
|
|
4
|
+
"version": "1.1.3",
|
|
5
5
|
"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.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
@@ -156,6 +156,15 @@ export function isSelectableDevice(device) {
|
|
|
156
156
|
* @param {string|null} localMachineId This machine's id, for the "this machine" mark.
|
|
157
157
|
* @returns {object}
|
|
158
158
|
*/
|
|
159
|
+
/**
|
|
160
|
+
* v1.1.3: how long a machine may be unheard from before it leaves the list.
|
|
161
|
+
*
|
|
162
|
+
* The operator's rule: offline for 72 hours is no longer shown, **nothing is deleted**, and it comes back on its own
|
|
163
|
+
* once it reports again. Hiding and deleting are deliberately different -- deleting would throw away the history of a
|
|
164
|
+
* machine that is merely switched off, and re-pairing would be the only way back.
|
|
165
|
+
*/
|
|
166
|
+
const ARCHIVE_AFTER_MS = 72 * 60 * 60 * 1000;
|
|
167
|
+
|
|
159
168
|
export function describeTargetMachine(device, localMachineId = null) {
|
|
160
169
|
const machineId = typeof device?.machine_id === 'string' && device.machine_id !== '' ? device.machine_id : null;
|
|
161
170
|
return {
|
|
@@ -343,7 +352,7 @@ export function dispatchSelectionFields(selection, mode) {
|
|
|
343
352
|
* fields rather than this module's projection of them.
|
|
344
353
|
*/
|
|
345
354
|
export async function readFleet(deps) {
|
|
346
|
-
const { rabbitUrl, token = null, localMachineId = null, signal, request } = deps;
|
|
355
|
+
const { rabbitUrl, token = null, localMachineId = null, signal, request, include_stale = false } = deps;
|
|
347
356
|
if (typeof rabbitUrl !== 'string' || rabbitUrl === '') {
|
|
348
357
|
return {
|
|
349
358
|
ok: false,
|
|
@@ -397,7 +406,18 @@ export async function readFleet(deps) {
|
|
|
397
406
|
const records = Array.isArray(result.json?.devices)
|
|
398
407
|
? result.json.devices
|
|
399
408
|
: (Array.isArray(result.json?.machines) ? result.json.machines : []);
|
|
400
|
-
const
|
|
409
|
+
const allMachines = records.map((record) => describeTargetMachine(record, localMachineId));
|
|
410
|
+
/**
|
|
411
|
+
* Archived means "not shown", never "removed": `records` still carries every device, so a caller that asks for the
|
|
412
|
+
* archived ones receives them. A missing or unparseable timestamp is NOT archived -- "unknown" must not be read as
|
|
413
|
+
* "gone for three days", or a relay that omits the field would empty the list.
|
|
414
|
+
*/
|
|
415
|
+
const isArchived = (machine) => {
|
|
416
|
+
const raw = machine.last_seen;
|
|
417
|
+
const at = typeof raw === 'number' ? raw : Date.parse(String(raw ?? ''));
|
|
418
|
+
return Number.isFinite(at) && (Date.now() - at) >= ARCHIVE_AFTER_MS;
|
|
419
|
+
};
|
|
420
|
+
const machines = include_stale === true ? allMachines : allMachines.filter((machine) => !isArchived(machine));
|
|
401
421
|
return {
|
|
402
422
|
ok: true,
|
|
403
423
|
error: null,
|
|
@@ -410,6 +430,7 @@ export async function readFleet(deps) {
|
|
|
410
430
|
// Machines the relay still lists but whose lease is not live. Reported so "3 machines" is never
|
|
411
431
|
// mistaken for "3 machines you can use".
|
|
412
432
|
excluded: machines.filter((machine) => !machine.online).length,
|
|
433
|
+
archived: allMachines.length - machines.length,
|
|
413
434
|
};
|
|
414
435
|
}
|
|
415
436
|
|
package/src/plugin/tools.mjs
CHANGED
|
@@ -211,7 +211,7 @@ const MAX_STAGES = 16;
|
|
|
211
211
|
* `package.json` during `scripts/pack.mjs` rather than hand-maintained, and `W2M_PLUGIN_VERSION`
|
|
212
212
|
* overrides it at runtime for tests and packagers.
|
|
213
213
|
*/
|
|
214
|
-
const PLUGIN_VERSION = '1.1.
|
|
214
|
+
const PLUGIN_VERSION = '1.1.3';
|
|
215
215
|
|
|
216
216
|
/** How long any single HTTP call may take before it is aborted, in milliseconds. */
|
|
217
217
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|