@twinsearth/w2m-dsh-plugin 1.1.1 → 1.1.2

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.
@@ -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.1';
214
+ const PLUGIN_VERSION = '1.1.2';
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;
@@ -3589,6 +3589,16 @@ export async function apply(ctx, config = {}) {
3589
3589
  description: 'Number of slices for mode=split. Defaults to 1, which is the only value replicate accepts.',
3590
3590
  default: 1,
3591
3591
  },
3592
+ include_offline: {
3593
+ type: 'boolean',
3594
+ // v1.1.2. The relay now leases only devices that are online and not silent by default; this asks it to include
3595
+ // the machines that are down as well, which is what an operator wants when queueing work for a machine that
3596
+ // is currently switched off. Default false, because a stale lease is what kept every aggregate at `partial`.
3597
+ description:
3598
+ 'Default false: the relay leases only the machines that are online and have not gone silent. Set true to ' +
3599
+ 'include machines that are currently down (their lease will simply expire if they do not come back).',
3600
+ default: false,
3601
+ },
3592
3602
  cwd_rel: {
3593
3603
  type: 'string',
3594
3604
  description: 'Working directory relative to the project root, with forward slashes. Defaults to ".".',
@@ -4023,6 +4033,8 @@ export async function apply(ctx, config = {}) {
4023
4033
  // in play. Spread after the caller's own field above so the caller always wins even if a
4024
4034
  // future edit makes both possible.
4025
4035
  ...selectionFields,
4036
+ // Named on the wire only when asked, so a default dispatch sends the same bytes as it always did.
4037
+ ...(args?.include_offline === true ? { include_offline: true } : {}),
4026
4038
  // Only for pipeline. The relay ignores `stages` under the other modes and the validation
4027
4039
  // above refuses the combination, so this can never silently mean nothing.
4028
4040
  ...(mode === 'pipeline' && stagesPayload !== null ? { stages: stagesPayload } : {}),
@@ -1257,6 +1257,22 @@ export class RabbitState {
1257
1257
  * (a test, or a caller that never runs the sweep) cannot report a departed machine as present.
1258
1258
  * The sweep is still what fixes the flag and broadcasts; this only refuses to repeat a stale `true`.
1259
1259
  */
1260
+ /**
1261
+ * Whether this device is usable right now: its flag is set AND it is not silent.
1262
+ *
1263
+ * \`device.online === true\` alone is not the answer -- the flag stays true until the sweep runs, so a machine whose
1264
+ * agent stopped heartbeating was still a dispatch target. The same expression appeared in \`deviceStatus\`,
1265
+ * \`publicDevice\` and (v1.1.2) the default target list; one definition keeps the list and the dispatch from
1266
+ * disagreeing about which machines exist.
1267
+ *
1268
+ * \`silentMs === null\` means no contact has ever been observed, and that is NOT silent.
1269
+ */
1270
+ isOnlineDevice(device, nowMs = this.nowMs()) {
1271
+ if (!device || device.online !== true) return false;
1272
+ const silentMs = this.silenceAgeMs(device, nowMs);
1273
+ return !(silentMs !== null && silentMs >= this.silenceMs);
1274
+ }
1275
+
1260
1276
  deviceStatus(machineId, nowMs = this.nowMs()) {
1261
1277
  const device = this.devices.get(machineId);
1262
1278
  if (!device) return null;
@@ -1270,7 +1286,7 @@ export class RabbitState {
1270
1286
  relay_id: this.relayId,
1271
1287
  connected: (device.streams ?? 0) > 0,
1272
1288
  streams: device.streams ?? 0,
1273
- online: device.online === true && !silent,
1289
+ online: this.isOnlineDevice(device),
1274
1290
  /**
1275
1291
  * Why this machine is not usable, when it is not. `null` for a healthy one.
1276
1292
  *
@@ -1340,7 +1356,7 @@ export class RabbitState {
1340
1356
  * the stored flag and broadcasts `peer.bye`; this expression makes the read agree with it even
1341
1357
  * between sweeps, so no caller can observe the stale value at all.
1342
1358
  */
1343
- online: device.online === true && !silent,
1359
+ online: this.isOnlineDevice(device),
1344
1360
  streams: device.streams,
1345
1361
  /**
1346
1362
  * v0.9.1: why this machine is not usable, when it is not.
@@ -1560,9 +1576,27 @@ export class RabbitState {
1560
1576
  //
1561
1577
  // Resolved before the task literal because the literal records it. (`targets` is computed below,
1562
1578
  // so the executor is re-validated against it after that.)
1579
+ /**
1580
+ * v1.1.2: a dispatch that names no machines goes to the machines that are **online**.
1581
+ *
1582
+ * WHY THIS IS THE DEFAULT: this line used to return every paired device, including one whose agent stopped
1583
+ * heartbeating days ago. The relay still built it a lease, that lease expired, and \`partial\` became the only
1584
+ * verdict the task could reach -- so a fleet whose live machines agreed reported "incomplete" forever
1585
+ * (observed 2026-10-17: \`ok: 3, expired: 4\`). Liveness is the relay's own knowledge, which is why the fix
1586
+ * belongs here and not in a client-side pre-read that would add a round trip to every dispatch.
1587
+ *
1588
+ * \`include_offline: true\` restores the old behaviour for the operator who really does want to queue work for a
1589
+ * machine that is down. A device that comes back is online again and rejoins the default set with no bookkeeping:
1590
+ * nothing is deleted, and the only state consulted is \`online\`.
1591
+ */
1563
1592
  const targetIds = Array.isArray(input.target_machines) && input.target_machines.length > 0
1564
1593
  ? input.target_machines
1565
- : [...this.devices.keys()];
1594
+ : input.include_offline === true || mode === 'broadcast'
1595
+ // broadcast is NOT a fan-out: it names one executor and tells the rest the outcome. Narrowing the default set
1596
+ // here would change WHICH machine is elected (or make the election impossible when the fixture's devices are
1597
+ // not marked online), so the default set stays every paired device for this mode only.
1598
+ ? [...this.devices.keys()]
1599
+ : [...this.devices.entries()].filter(([, d]) => this.isOnlineDevice(d)).map(([id]) => id);
1566
1600
  const executorMachineId = mode === 'broadcast'
1567
1601
  ? (typeof input.executor_machine_id === 'string' && input.executor_machine_id !== ''
1568
1602
  ? input.executor_machine_id
@@ -1587,6 +1621,7 @@ export class RabbitState {
1587
1621
  base_tree: baseTree,
1588
1622
  requirements: input.requirements ?? {},
1589
1623
  compare_policy: input.compare_policy ?? {},
1624
+ include_offline: input.include_offline === true,
1590
1625
  halt,
1591
1626
  created_by: input.created_by ?? null,
1592
1627
  created_at_ms: now,