@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.
- package/dist/bin/agt.js +5 -5
- package/dist/{chunk-J6ORWGUB.js → chunk-7U4VSBOE.js} +2 -2
- package/dist/{chunk-3INFJV37.js → chunk-DRAZDG3U.js} +4 -4
- package/dist/{chunk-EIXOI2ZB.js → chunk-DYFE224A.js} +14 -1
- package/dist/{chunk-EIXOI2ZB.js.map → chunk-DYFE224A.js.map} +1 -1
- package/dist/{claude-pair-runtime-VQGEM5AY.js → claude-pair-runtime-BOTE3DRW.js} +2 -2
- package/dist/lib/manager-worker.js +324 -16
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/direct-chat-channel.js +13 -0
- package/dist/mcp/index.js +13 -0
- package/dist/mcp/origami.js +13 -0
- package/dist/mcp/slack-channel.js +13 -0
- package/dist/mcp/telegram-channel.js +13 -0
- package/dist/{persistent-session-ZBKLEYUQ.js → persistent-session-6O66QEI4.js} +3 -3
- package/dist/{responsiveness-probe-FXDO35IT.js → responsiveness-probe-WGD3NDWG.js} +3 -3
- package/dist/{session-auth-dead-OGNOSJIB.js → session-auth-dead-6N2AEFKX.js} +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-J6ORWGUB.js.map → chunk-7U4VSBOE.js.map} +0 -0
- /package/dist/{chunk-3INFJV37.js.map → chunk-DRAZDG3U.js.map} +0 -0
- /package/dist/{claude-pair-runtime-VQGEM5AY.js.map → claude-pair-runtime-BOTE3DRW.js.map} +0 -0
- /package/dist/{persistent-session-ZBKLEYUQ.js.map → persistent-session-6O66QEI4.js.map} +0 -0
- /package/dist/{responsiveness-probe-FXDO35IT.js.map → responsiveness-probe-WGD3NDWG.js.map} +0 -0
- /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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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.
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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));
|