@integrity-labs/agt-cli 0.28.1138 → 0.28.1140

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.
Files changed (23) hide show
  1. package/dist/bin/agt.js +5 -5
  2. package/dist/{chunk-J6ORWGUB.js → chunk-7U4VSBOE.js} +2 -2
  3. package/dist/{chunk-3INFJV37.js → chunk-DRAZDG3U.js} +4 -4
  4. package/dist/{chunk-EIXOI2ZB.js → chunk-DYFE224A.js} +14 -1
  5. package/dist/{chunk-EIXOI2ZB.js.map → chunk-DYFE224A.js.map} +1 -1
  6. package/dist/{claude-pair-runtime-VQGEM5AY.js → claude-pair-runtime-BOTE3DRW.js} +2 -2
  7. package/dist/lib/manager-worker.js +324 -16
  8. package/dist/lib/manager-worker.js.map +1 -1
  9. package/dist/mcp/direct-chat-channel.js +13 -0
  10. package/dist/mcp/index.js +13 -0
  11. package/dist/mcp/origami.js +13 -0
  12. package/dist/mcp/slack-channel.js +13 -0
  13. package/dist/mcp/telegram-channel.js +13 -0
  14. package/dist/{persistent-session-ZBKLEYUQ.js → persistent-session-6O66QEI4.js} +3 -3
  15. package/dist/{responsiveness-probe-FXDO35IT.js → responsiveness-probe-WGD3NDWG.js} +3 -3
  16. package/dist/{session-auth-dead-OGNOSJIB.js → session-auth-dead-6N2AEFKX.js} +2 -2
  17. package/package.json +1 -1
  18. /package/dist/{chunk-J6ORWGUB.js.map → chunk-7U4VSBOE.js.map} +0 -0
  19. /package/dist/{chunk-3INFJV37.js.map → chunk-DRAZDG3U.js.map} +0 -0
  20. /package/dist/{claude-pair-runtime-VQGEM5AY.js.map → claude-pair-runtime-BOTE3DRW.js.map} +0 -0
  21. /package/dist/{persistent-session-ZBKLEYUQ.js.map → persistent-session-6O66QEI4.js.map} +0 -0
  22. /package/dist/{responsiveness-probe-FXDO35IT.js.map → responsiveness-probe-WGD3NDWG.js.map} +0 -0
  23. /package/dist/{session-auth-dead-OGNOSJIB.js.map → session-auth-dead-6N2AEFKX.js.map} +0 -0
@@ -269,7 +269,7 @@ async function spawnPairSession(session) {
269
269
  return { ok: true };
270
270
  } catch {
271
271
  }
272
- const { resolveClaudeBinary } = await import("./persistent-session-ZBKLEYUQ.js");
272
+ const { resolveClaudeBinary } = await import("./persistent-session-6O66QEI4.js");
273
273
  const claudeBin = resolveClaudeBinary();
274
274
  const pairEnv = {
275
275
  ...process.env,
@@ -739,4 +739,4 @@ export {
739
739
  startClaudePair,
740
740
  submitClaudePairCode
741
741
  };
742
- //# sourceMappingURL=claude-pair-runtime-VQGEM5AY.js.map
742
+ //# sourceMappingURL=claude-pair-runtime-BOTE3DRW.js.map
@@ -76,7 +76,7 @@ import {
76
76
  setConfigHash,
77
77
  shellGuard,
78
78
  tripClass
79
- } from "../chunk-3INFJV37.js";
79
+ } from "../chunk-DRAZDG3U.js";
80
80
  import {
81
81
  getProjectDir as getProjectDir2,
82
82
  getReadyTasks,
@@ -163,7 +163,7 @@ import {
163
163
  webFetchBaselineValue,
164
164
  webFetchRestartNeeded,
165
165
  writeEgressAllowlist
166
- } from "../chunk-J6ORWGUB.js";
166
+ } from "../chunk-7U4VSBOE.js";
167
167
  import {
168
168
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
169
169
  AnchorSessionClient,
@@ -274,7 +274,7 @@ import {
274
274
  subagentActivityAgeSeconds,
275
275
  sumTranscriptUsageInWindow,
276
276
  transcriptActivityAgeSeconds
277
- } from "../chunk-EIXOI2ZB.js";
277
+ } from "../chunk-DYFE224A.js";
278
278
  import {
279
279
  classifyUnanswerablePane,
280
280
  focusedConfirmDialogBlock,
@@ -321,6 +321,205 @@ function defaultUnique() {
321
321
  return `${process.pid}-${seq}-${Date.now().toString(36)}`;
322
322
  }
323
323
 
324
+ // src/lib/host-node-pin.ts
325
+ var HOST_NODE_PIN_VERSION_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
326
+ var HOST_NODE_PIN_MIN_MAJOR = 22;
327
+ function normalizeHostNodePinVersion(raw) {
328
+ if (raw == null) return null;
329
+ const v = raw.trim().replace(/^v/, "");
330
+ if (!HOST_NODE_PIN_VERSION_RE.test(v)) return null;
331
+ const major = Number(v.split(".")[0]);
332
+ if (!Number.isInteger(major) || major < HOST_NODE_PIN_MIN_MAJOR) return null;
333
+ return v;
334
+ }
335
+
336
+ // src/lib/host-node-convergence.ts
337
+ var HOST_NODE_RETRY_MS = 15 * 6e4;
338
+ var HOST_NODE_MAX_FAILURES = 3;
339
+ var HostNodeConvergence = class {
340
+ constructor(maxFailures = HOST_NODE_MAX_FAILURES, retryMs = HOST_NODE_RETRY_MS) {
341
+ this.maxFailures = maxFailures;
342
+ this.retryMs = retryMs;
343
+ }
344
+ converged = /* @__PURE__ */ new Set();
345
+ abandoned = /* @__PURE__ */ new Set();
346
+ failures = /* @__PURE__ */ new Map();
347
+ retryAfter = /* @__PURE__ */ new Map();
348
+ /**
349
+ * The ONE pin currently installing, or null. Deliberately a single slot
350
+ * rather than a per-pin set: see {@link shouldAttempt}.
351
+ */
352
+ inFlightPin = null;
353
+ lastDesired = null;
354
+ /**
355
+ * Record what the control plane currently wants, and discard stale
356
+ * conclusions when that changes.
357
+ *
358
+ * Keying per pin is necessary but NOT sufficient, which is the hole CodeRabbit
359
+ * found on #7088. Every state here is a claim about THIS HOST'S DISK at some
360
+ * past moment, and a pin change invalidates all of them: if the fleet moves
361
+ * 22.23.4 -> 22.23.5 and later back to 22.23.4, the disk now holds 22.23.5,
362
+ * while `converged` still asserts 22.23.4 from before. Without this the
363
+ * manager would skip the re-install forever on the strength of a measurement
364
+ * that a later install had already falsified - and 'converged' would once again
365
+ * mean something other than "this host is on the pin", which is the one thing
366
+ * this class exists to keep true.
367
+ *
368
+ * Clearing is cheap to be wrong about in the safe direction: a re-attempt
369
+ * reads `node --version` once and, if the disk already matches, records
370
+ * success immediately.
371
+ *
372
+ * `null` means NO pin is in force — the control plane sent none, or sent one
373
+ * this host refused as malformed. That must be recorded rather than ignored,
374
+ * which was the second hole on #7088: skipping the call left `lastDesired`
375
+ * pointing at the last pin seen, so clearing A and later restoring it looked
376
+ * like no change at all. Any drift on disk during the cleared window —
377
+ * an OS patch, an AMI replacement, a manual install — then survived
378
+ * indefinitely, because the restored pin's stale 'converged' suppressed the
379
+ * one disk read that would have caught it.
380
+ */
381
+ noteDesiredPin(pin) {
382
+ if (this.lastDesired === pin) return;
383
+ this.lastDesired = pin;
384
+ this.converged.clear();
385
+ this.abandoned.clear();
386
+ this.failures.clear();
387
+ this.retryAfter.clear();
388
+ }
389
+ /** The pin last passed to {@link noteDesiredPin}, for reporting. */
390
+ desiredPin() {
391
+ return this.lastDesired;
392
+ }
393
+ stateOf(pin, now = Date.now()) {
394
+ if (this.converged.has(pin)) return "converged";
395
+ if (this.abandoned.has(pin)) return "abandoned";
396
+ if ((this.retryAfter.get(pin) ?? 0) > now) return "cooling";
397
+ return "ready";
398
+ }
399
+ /**
400
+ * Whether to start an attempt now. False while ANY attempt is running —
401
+ * across pins, not just this one.
402
+ *
403
+ * Per-pin gating was the hole CodeRabbit found on #7088, and it is reachable
404
+ * because the caller is fire-and-forget while a `dnf install` may run for ten
405
+ * minutes. Sequence: A is converged; the pin moves to B and an install
406
+ * starts; the pin reverts to A. {@link noteDesiredPin} clears A's stale
407
+ * conclusion (correctly), a per-pin gate does not see B's attempt, so an A
408
+ * attempt starts WHILE B is installing. It reads the disk, which still says
409
+ * A, and records A as converged. B's install then lands, leaving B on disk
410
+ * with `converged` asserting A — and because that is terminal, every later
411
+ * poll skips the check. The host sits off-pin permanently while this class
412
+ * claims otherwise, which is the exact failure it was written to prevent.
413
+ *
414
+ * Serializing costs nothing in the steady state: convergence is a once-per
415
+ * -pin event, and a skipped poll retries ~10s later.
416
+ */
417
+ shouldAttempt(pin, now = Date.now()) {
418
+ if (this.inFlightPin !== null) return false;
419
+ return this.stateOf(pin, now) === "ready";
420
+ }
421
+ /** The pin currently installing, or null. For reporting and for tests. */
422
+ inFlight() {
423
+ return this.inFlightPin;
424
+ }
425
+ beginAttempt(pin) {
426
+ this.inFlightPin = pin;
427
+ }
428
+ endAttempt(pin) {
429
+ if (this.inFlightPin === pin) this.inFlightPin = null;
430
+ }
431
+ /**
432
+ * The on-disk version now equals the pin. Clears any failure history.
433
+ *
434
+ * A success for a pin that is no longer the desired one is DISCARDED. An
435
+ * attempt can outlive the pin that started it, and 'converged' is terminal —
436
+ * so recording it would suppress all future attempts on the strength of a
437
+ * measurement taken against a pin nobody wants any more. Dropping it costs
438
+ * one extra disk read if that pin is ever requested again.
439
+ *
440
+ * The guard is inactive until {@link noteDesiredPin} has been called at least
441
+ * once, so a caller that does not track a desired pin is unaffected.
442
+ */
443
+ recordSuccess(pin) {
444
+ if (this.lastDesired !== null && pin !== this.lastDesired) return;
445
+ this.converged.add(pin);
446
+ this.abandoned.delete(pin);
447
+ this.failures.delete(pin);
448
+ this.retryAfter.delete(pin);
449
+ }
450
+ /**
451
+ * An attempt failed. Returns the resulting state so the caller can log the
452
+ * right thing: 'cooling' means it will retry, 'abandoned' means it will not.
453
+ */
454
+ recordFailure(pin, now = Date.now()) {
455
+ const count = (this.failures.get(pin) ?? 0) + 1;
456
+ this.failures.set(pin, count);
457
+ if (count >= this.maxFailures) {
458
+ this.abandoned.add(pin);
459
+ this.retryAfter.delete(pin);
460
+ return "abandoned";
461
+ }
462
+ this.retryAfter.set(pin, now + this.retryMs);
463
+ return "cooling";
464
+ }
465
+ attemptCount(pin) {
466
+ return this.failures.get(pin) ?? 0;
467
+ }
468
+ /**
469
+ * Every pin this host stopped trying to reach. A non-empty result means "a
470
+ * patch the control plane asked for is NOT applied and nothing is retrying".
471
+ *
472
+ * BE PRECISE ABOUT WHO CAN SEE THAT, because this module's header argues for
473
+ * telling abandonment apart from success "from outside" and Obi Wan correctly
474
+ * pointed out (#7088) that the claim outruns the code. Nothing in production
475
+ * reads this method, `stateOf`, `desiredPin` or `inFlight`: the state is not
476
+ * reported to the API, the heartbeat, or any console surface. The only signal
477
+ * that leaves the host is one `[host-node] … GIVING UP` line in manager.log.
478
+ *
479
+ * So what this class actually buys today is that the DISTINCTION EXISTS and
480
+ * cannot collapse — `converged` can never come to mean "gave up", which is
481
+ * the `ensureIsolationImage` defect the header cites. Surfacing it is a
482
+ * separate, unbuilt piece of work (ENG-11617 acceptance criterion 6), and it
483
+ * matters most for a BYO host in a customer account, which is the one
484
+ * population we cannot inspect any other way.
485
+ */
486
+ abandonedPins() {
487
+ return [...this.abandoned];
488
+ }
489
+ };
490
+
491
+ // src/lib/host-node-report.ts
492
+ var HOST_NODE_PROBE_INTERVAL_MS = 15 * 60 * 1e3;
493
+ function shouldReprobeHostNode(lastObservedAt, now) {
494
+ if (lastObservedAt === 0) return true;
495
+ return now - lastObservedAt >= HOST_NODE_PROBE_INTERVAL_MS;
496
+ }
497
+ function normalizeReportedNodeVersion(raw) {
498
+ if (raw == null) return null;
499
+ const v = raw.trim().replace(/^v/, "");
500
+ return HOST_NODE_PIN_VERSION_RE.test(v) ? v : null;
501
+ }
502
+ function parsePathNodeVersion(stdout) {
503
+ const m = /^v?(\d+\.\d+\.\d+)/.exec(stdout.trim());
504
+ return m ? normalizeReportedNodeVersion(m[1]) : null;
505
+ }
506
+ function parseRpmNodeVersion(stdout) {
507
+ return normalizeReportedNodeVersion(stdout.trim());
508
+ }
509
+ function heartbeatHostNodeFields(observation, pin, pinState) {
510
+ if (!observation) return {};
511
+ return {
512
+ host_node_version: normalizeReportedNodeVersion(observation.pathVersion),
513
+ host_node_rpm_version: normalizeReportedNodeVersion(observation.rpmVersion),
514
+ // A state without a pin would be meaningless, so the two are resolved
515
+ // together: no pin in force means no state, whatever the converger's
516
+ // internal maps happen to still hold for an older pin.
517
+ host_node_pin: pin,
518
+ host_node_pin_state: pin ? pinState : null,
519
+ host_node_observed: true
520
+ };
521
+ }
522
+
324
523
  // src/lib/manager-worker.ts
325
524
  import { execFileSync as syncExecFile } from "child_process";
326
525
  import { join as join43, dirname as dirname11, delimiter as pathDelimiter } from "path";
@@ -14491,7 +14690,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
14491
14690
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
14492
14691
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
14493
14692
  async function channelInboundActivityAgeSecondsFor(codeName) {
14494
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-FXDO35IT.js");
14693
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-WGD3NDWG.js");
14495
14694
  const newest = newestPendingInboundActivityMtimeMs(dirname11(paneLogPath(codeName)));
14496
14695
  if (newest === null) return null;
14497
14696
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -15249,7 +15448,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
15249
15448
  var lastVersionCheckAt = 0;
15250
15449
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
15251
15450
  var lastResponsivenessProbeAt = 0;
15252
- var agtCliVersion = true ? "0.28.1138" : "dev";
15451
+ var agtCliVersion = true ? "0.28.1140" : "dev";
15253
15452
  function resolveBrewPath(execFileSync3) {
15254
15453
  try {
15255
15454
  const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -15413,6 +15612,99 @@ function recordIsolationImageFailure(imageUri, reason) {
15413
15612
  isolationImageRetryAfter.set(imageUri, Date.now() + ISOLATION_IMAGE_RETRY_MS);
15414
15613
  }
15415
15614
  }
15615
+ var hostNodeConvergence = new HostNodeConvergence();
15616
+ var hostNodeInvalidLogged = /* @__PURE__ */ new Set();
15617
+ var hostNodeNoDnfLogged = false;
15618
+ function recordHostNodeFailure(pin, reason) {
15619
+ const outcome = hostNodeConvergence.recordFailure(pin);
15620
+ const attempts = hostNodeConvergence.attemptCount(pin);
15621
+ if (outcome === "abandoned") {
15622
+ log(
15623
+ `[host-node] ${reason} \u2014 GIVING UP after ${attempts} attempts. Host Node is NOT on ${pin} and nothing will retry until the manager restarts. This host remains unpatched (ENG-11617).`
15624
+ );
15625
+ } else {
15626
+ log(`[host-node] ${reason} (attempt ${attempts}/${HOST_NODE_MAX_FAILURES}, retrying in ${HOST_NODE_RETRY_MS / 6e4}m)`);
15627
+ }
15628
+ }
15629
+ async function diskNodeVersion() {
15630
+ const probe = await runAsync("node", ["--version"], { timeout: 15e3 });
15631
+ if (probe.code !== 0) return null;
15632
+ return parsePathNodeVersion(probe.stdout);
15633
+ }
15634
+ var hostNodeObservation = null;
15635
+ var hostNodeObservedAt = 0;
15636
+ async function probeOrNull(probe) {
15637
+ try {
15638
+ return await probe();
15639
+ } catch {
15640
+ return null;
15641
+ }
15642
+ }
15643
+ async function refreshHostNodeObservation(now = Date.now()) {
15644
+ if (!shouldReprobeHostNode(hostNodeObservedAt, now)) return;
15645
+ hostNodeObservedAt = now;
15646
+ const pathVersion = await probeOrNull(() => diskNodeVersion());
15647
+ const rpmVersion = await probeOrNull(async () => {
15648
+ const rpm = await runAsync("rpm", ["-q", "--qf", "%{VERSION}", "nodejs"], { timeout: 15e3 });
15649
+ return rpm.code === 0 ? parseRpmNodeVersion(rpm.stdout) : null;
15650
+ });
15651
+ hostNodeObservation = { pathVersion, rpmVersion };
15652
+ }
15653
+ async function ensureHostNodeVersion(rawPin) {
15654
+ const pin = normalizeHostNodePinVersion(rawPin);
15655
+ if (!pin) {
15656
+ const raw = rawPin == null ? "" : String(rawPin).trim();
15657
+ if (raw !== "" && !hostNodeInvalidLogged.has(raw)) {
15658
+ hostNodeInvalidLogged.add(raw);
15659
+ log(`[host-node] ignoring malformed desired_node_version '${raw.slice(0, 40)}' - not an exact MAJOR.MINOR.PATCH at or above major ${HOST_NODE_PIN_MIN_MAJOR}`);
15660
+ }
15661
+ hostNodeConvergence.noteDesiredPin(null);
15662
+ return;
15663
+ }
15664
+ hostNodeConvergence.noteDesiredPin(pin);
15665
+ if (!hostNodeConvergence.shouldAttempt(pin)) return;
15666
+ hostNodeConvergence.beginAttempt(pin);
15667
+ try {
15668
+ const current = await diskNodeVersion();
15669
+ if (current === pin) {
15670
+ hostNodeConvergence.recordSuccess(pin);
15671
+ return;
15672
+ }
15673
+ const { execFileSync: execFileSync3 } = await import("child_process");
15674
+ try {
15675
+ execFileSync3("which", ["dnf"], { timeout: 5e3, stdio: "pipe" });
15676
+ } catch {
15677
+ if (!hostNodeNoDnfLogged) {
15678
+ hostNodeNoDnfLogged = true;
15679
+ log(`[host-node] 'dnf' not found - cannot converge host Node onto ${pin} on this platform (leaving ${current ?? "unknown"} in place)`);
15680
+ }
15681
+ hostNodeConvergence.recordSuccess(pin);
15682
+ return;
15683
+ }
15684
+ const asRoot = typeof process.getuid === "function" && process.getuid() === 0;
15685
+ const pkg = `nodejs-${pin}`;
15686
+ const dnfArgs = ["-y", "--refresh", "install", pkg];
15687
+ const bin = asRoot ? "dnf" : "sudo";
15688
+ const argv = asRoot ? dnfArgs : ["-n", "dnf", ...dnfArgs];
15689
+ log(`[host-node] converging host Node ${current ?? "unknown"} -> ${pin}\u2026`);
15690
+ const res = await runAsync(bin, argv, { timeout: 6e5 });
15691
+ if (res.code !== 0) {
15692
+ recordHostNodeFailure(pin, `install of ${pkg} failed (exit ${res.code}): ${(res.stderr || res.stdout).slice(0, 300)}`);
15693
+ return;
15694
+ }
15695
+ const after = await diskNodeVersion();
15696
+ if (after !== pin) {
15697
+ recordHostNodeFailure(pin, `installer reported success but on-disk Node is ${after ?? "unreadable"}, not ${pin}`);
15698
+ return;
15699
+ }
15700
+ log(`[host-node] host Node is now ${pin} on disk. Running processes keep the previous binary until they restart - this does NOT restart the manager (ENG-11617).`);
15701
+ hostNodeConvergence.recordSuccess(pin);
15702
+ } catch (err) {
15703
+ recordHostNodeFailure(pin, `install attempt threw: ${isolationImageErrDetail(err)}`);
15704
+ } finally {
15705
+ hostNodeConvergence.endAttempt(pin);
15706
+ }
15707
+ }
15416
15708
  async function ensureIsolationImage(imageUri) {
15417
15709
  if (process.env.AGT_ISOLATION_IMAGE) return;
15418
15710
  if (!imageUri || isolationImageEnsured.has(imageUri)) return;
@@ -16879,7 +17171,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
16879
17171
  if (codeNames.length === 0) return;
16880
17172
  void (async () => {
16881
17173
  try {
16882
- const { collectDiagnostics } = await import("../persistent-session-ZBKLEYUQ.js");
17174
+ const { collectDiagnostics } = await import("../persistent-session-6O66QEI4.js");
16883
17175
  await api.post("/host/heartbeat", {
16884
17176
  host_id: hostId,
16885
17177
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -17021,7 +17313,7 @@ async function pollCycleInner() {
17021
17313
  }
17022
17314
  try {
17023
17315
  const { detectHostSecurity } = await import("../host-security-KQYYZZVW.js");
17024
- const { collectDiagnostics } = await import("../persistent-session-ZBKLEYUQ.js");
17316
+ const { collectDiagnostics } = await import("../persistent-session-6O66QEI4.js");
17025
17317
  const diagCodeNames = [...agentState.persistentSessionAgents];
17026
17318
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor) : void 0;
17027
17319
  let tailscaleHostname;
@@ -17075,6 +17367,8 @@ async function pollCycleInner() {
17075
17367
  } else {
17076
17368
  lastManagedSettingsStatus = null;
17077
17369
  }
17370
+ await refreshHostNodeObservation();
17371
+ const hostNodePin = hostNodeConvergence.desiredPin();
17078
17372
  const hbResp = await api.post("/host/heartbeat", {
17079
17373
  host_id: hostId,
17080
17374
  framework_version: cachedFrameworkVersion ?? void 0,
@@ -17088,6 +17382,17 @@ async function pollCycleInner() {
17088
17382
  // rather than as two fields a future edit could separate. See
17089
17383
  // `heartbeatRuntimeAuthFields`.
17090
17384
  ...heartbeatRuntimeAuthFields(runtimeAuthProbeVerdict),
17385
+ // ENG-11639: host Node, and how the pin stands with this host. Spread as
17386
+ // ONE unit for the same reason as the line above — the API stamps a
17387
+ // freshness timestamp on receipt, so the values and their
17388
+ // `host_node_observed` marker must never be editable apart. Omitted
17389
+ // entirely until the first measurement, because ABSENCE MEANS NO SIGNAL
17390
+ // here and must not be readable as either patched or unpatched.
17391
+ ...heartbeatHostNodeFields(
17392
+ hostNodeObservation,
17393
+ hostNodePin,
17394
+ hostNodePin ? hostNodeConvergence.stateOf(hostNodePin) : null
17395
+ ),
17091
17396
  agent_diagnostics: agentDiagnostics,
17092
17397
  hostname: tailscaleHostname,
17093
17398
  os_username: osUsername,
@@ -17229,7 +17534,7 @@ async function pollCycleInner() {
17229
17534
  collectPanelessActivityProbes,
17230
17535
  getResponsivenessIntervalMs,
17231
17536
  occupancyQualificationClassifications
17232
- } = await import("../responsiveness-probe-FXDO35IT.js");
17537
+ } = await import("../responsiveness-probe-WGD3NDWG.js");
17233
17538
  const probeIntervalMs = getResponsivenessIntervalMs();
17234
17539
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
17235
17540
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -17346,7 +17651,7 @@ async function pollCycleInner() {
17346
17651
  collectResponsivenessProbes,
17347
17652
  livePendingInboundOldestAgeSeconds,
17348
17653
  parkPendingInbound
17349
- } = await import("../responsiveness-probe-FXDO35IT.js");
17654
+ } = await import("../responsiveness-probe-WGD3NDWG.js");
17350
17655
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-M6AFZWNU.js");
17351
17656
  const wedgeNow = /* @__PURE__ */ new Date();
17352
17657
  const liveAgents = agentState.persistentSessionAgents;
@@ -17632,7 +17937,7 @@ async function pollCycleInner() {
17632
17937
  }
17633
17938
  }
17634
17939
  );
17635
- const { parkPendingInbound } = await import("../responsiveness-probe-FXDO35IT.js");
17940
+ const { parkPendingInbound } = await import("../responsiveness-probe-WGD3NDWG.js");
17636
17941
  const { parked, deadLettered } = parkPendingInbound(codeName, new Date(fatalNowMs));
17637
17942
  paneFatalStreak.delete(codeName);
17638
17943
  paneFatalRestarts.set(codeName, [...history, fatalNowMs]);
@@ -17663,7 +17968,7 @@ async function pollCycleInner() {
17663
17968
  TRANSCRIPT_MOUNT_ESCALATION_REFRESH_MS,
17664
17969
  bindSourceEscalations
17665
17970
  } = await import("../transcript-mount-health-OHE6IVJV.js");
17666
- const { transcriptBindSourcesFor, ensureTranscriptBindSources } = await import("../persistent-session-ZBKLEYUQ.js");
17971
+ const { transcriptBindSourcesFor, ensureTranscriptBindSources } = await import("../persistent-session-6O66QEI4.js");
17667
17972
  const mountConfig = resolveTranscriptMountConfig();
17668
17973
  const liveAgents = agentState.persistentSessionAgents;
17669
17974
  for (const tracked of [
@@ -17828,7 +18133,7 @@ async function pollCycleInner() {
17828
18133
  }
17829
18134
  try {
17830
18135
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-VR5QGP3J.js");
17831
- const { probeSessionAuth } = await import("../session-auth-dead-OGNOSJIB.js");
18136
+ const { probeSessionAuth } = await import("../session-auth-dead-6N2AEFKX.js");
17832
18137
  const observations = [];
17833
18138
  const pendingCacheCommits = [];
17834
18139
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -20633,6 +20938,9 @@ async function processAgent(agent, agentStates, managedToolkits) {
20633
20938
  log(`[isolation-image] unexpected managed-pull failure: ${isolationImageErrDetail(err)}`);
20634
20939
  });
20635
20940
  }
20941
+ void ensureHostNodeVersion(refreshAny.desired_node_version).catch((err) => {
20942
+ log(`[host-node] unexpected convergence failure: ${isolationImageErrDetail(err)}`);
20943
+ });
20636
20944
  const agentFw2 = agentFrameworkCache.get(agent.code_name) ?? DEFAULT_FRAMEWORK;
20637
20945
  if (agentFw2 === "claude-code" && installedIntegrationSkills.length > 0 && isSessionHealthy(agent.code_name)) {
20638
20946
  const names = installedIntegrationSkills.join(", ");
@@ -22423,7 +22731,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
22423
22731
  try {
22424
22732
  const freshId = rotateSessionForWedge(codeName);
22425
22733
  consecutiveWedgeCycles.delete(codeName);
22426
- const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-FXDO35IT.js");
22734
+ const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-WGD3NDWG.js");
22427
22735
  const { parked, deadLettered } = parkForRestart(codeName, /* @__PURE__ */ new Date());
22428
22736
  const parkNote = parked > 0 ? `, ${parked} inbound parked` : "";
22429
22737
  const deadNote = deadLettered > 0 ? `, ${deadLettered} undeliverable dead-lettered` : "";
@@ -22466,7 +22774,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
22466
22774
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
22467
22775
  void (async () => {
22468
22776
  try {
22469
- const { collectDiagnostics } = await import("../persistent-session-ZBKLEYUQ.js");
22777
+ const { collectDiagnostics } = await import("../persistent-session-6O66QEI4.js");
22470
22778
  await api.post("/host/heartbeat", {
22471
22779
  host_id: hostId,
22472
22780
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -22517,7 +22825,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
22517
22825
  }
22518
22826
  try {
22519
22827
  const hostId = await getHostId();
22520
- const { collectDiagnostics } = await import("../persistent-session-ZBKLEYUQ.js");
22828
+ const { collectDiagnostics } = await import("../persistent-session-6O66QEI4.js");
22521
22829
  await api.post("/host/heartbeat", {
22522
22830
  host_id: hostId,
22523
22831
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor, runtimeVersionFor)
@@ -23190,7 +23498,7 @@ async function processClaudePairSessions(agents) {
23190
23498
  // cannot take it for itself — by the time it runs, the write it is looking
23191
23499
  // for may already have happened.
23192
23500
  snapshotPairConfig
23193
- } = await import("../claude-pair-runtime-VQGEM5AY.js");
23501
+ } = await import("../claude-pair-runtime-BOTE3DRW.js");
23194
23502
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
23195
23503
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
23196
23504
  const killed = await killPairSession(pairTmuxSession(pairId));