@integrity-labs/agt-cli 0.28.980 → 0.28.982

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 CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  success,
41
41
  table,
42
42
  warn
43
- } from "../chunk-Y7FKKMA5.js";
43
+ } from "../chunk-CD42ZALM.js";
44
44
  import {
45
45
  getProjectDir,
46
46
  isSessionResumeDisabled,
@@ -5472,7 +5472,7 @@ import { execFileSync, execSync } from "child_process";
5472
5472
  import { existsSync as existsSync11, realpathSync as realpathSync2 } from "fs";
5473
5473
  import chalk18 from "chalk";
5474
5474
  import ora16 from "ora";
5475
- var cliVersion = true ? "0.28.980" : "dev";
5475
+ var cliVersion = true ? "0.28.982" : "dev";
5476
5476
  async function fetchLatestVersion() {
5477
5477
  const host2 = getHost();
5478
5478
  if (!host2) return null;
@@ -6732,7 +6732,7 @@ function handleError(err) {
6732
6732
  }
6733
6733
 
6734
6734
  // src/bin/agt.ts
6735
- var cliVersion2 = true ? "0.28.980" : "dev";
6735
+ var cliVersion2 = true ? "0.28.982" : "dev";
6736
6736
  var program = new Command();
6737
6737
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
6738
6738
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -6967,7 +6967,7 @@ function exchangeFailureKind(err) {
6967
6967
  }
6968
6968
 
6969
6969
  // src/lib/api-client.ts
6970
- var agtCliVersion = true ? "0.28.980" : "dev";
6970
+ var agtCliVersion = true ? "0.28.982" : "dev";
6971
6971
  var lastConfigHash = null;
6972
6972
  function setConfigHash(hash) {
6973
6973
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -11709,4 +11709,4 @@ export {
11709
11709
  managerInstallSystemUnitCommand,
11710
11710
  managerUninstallSystemUnitCommand
11711
11711
  };
11712
- //# sourceMappingURL=chunk-Y7FKKMA5.js.map
11712
+ //# sourceMappingURL=chunk-CD42ZALM.js.map
@@ -63,7 +63,7 @@ import {
63
63
  safeWriteJsonAtomic,
64
64
  setConfigHash,
65
65
  tripClass
66
- } from "../chunk-Y7FKKMA5.js";
66
+ } from "../chunk-CD42ZALM.js";
67
67
  import {
68
68
  getProjectDir as getProjectDir2,
69
69
  getReadyTasks,
@@ -6461,6 +6461,16 @@ import { join as join30 } from "path";
6461
6461
  function claudeDeviceStatePath(env = process.env) {
6462
6462
  return join30(env.HOME?.trim() || homedir13(), ".claude.json");
6463
6463
  }
6464
+ function probeClaudeDeviceStatePresence(path = claudeDeviceStatePath()) {
6465
+ try {
6466
+ return statSync9(path).isFile() ? { present: true } : { present: false, reason: "not-a-file" };
6467
+ } catch (err) {
6468
+ return {
6469
+ present: false,
6470
+ reason: err?.code === "ENOENT" ? "missing" : "unreadable"
6471
+ };
6472
+ }
6473
+ }
6464
6474
  function checkClaudeDeviceState(path = claudeDeviceStatePath()) {
6465
6475
  try {
6466
6476
  if (!statSync9(path).isFile()) return { usable: false, reason: "not-a-file" };
@@ -6489,10 +6499,18 @@ function checkClaudeDeviceState(path = claudeDeviceStatePath()) {
6489
6499
  return { usable: true };
6490
6500
  }
6491
6501
  var DeviceStateRotationGate = class {
6492
- constructor(check = () => checkClaudeDeviceState()) {
6502
+ constructor(check = () => checkClaudeDeviceState(), probe = () => probeClaudeDeviceStatePresence()) {
6493
6503
  this.check = check;
6504
+ this.probe = probe;
6494
6505
  }
6495
6506
  held = /* @__PURE__ */ new Map();
6507
+ /**
6508
+ * ENG-10730: when the CHEAP probe first saw the file gone with nothing held.
6509
+ * Separate from `held`, which records when each agent's rotation was stopped -
6510
+ * in this state no rotation has been stopped at all. Cleared whenever the file
6511
+ * is seen present again, so a later episode gets its own clock.
6512
+ */
6513
+ unusableSinceMs = null;
6496
6514
  /**
6497
6515
  * Ask before tearing `codeName`'s session down for a planned rotation.
6498
6516
  * Returns `ready: true` when the respawn can start; otherwise the rotation must
@@ -6510,22 +6528,62 @@ var DeviceStateRotationGate = class {
6510
6528
  return { ready: false, reason: verdict.reason, firstHold };
6511
6529
  }
6512
6530
  /**
6513
- * The value for the heartbeat. Re-checks the file while anything is held, so
6514
- * the alert closes on the next heartbeat after the operator pairs the host,
6515
- * not only when some agent's rollover next asks. Reads nothing when nothing is
6516
- * held, so a healthy host pays no file read per heartbeat.
6531
+ * The value for the heartbeat.
6532
+ *
6533
+ * ENG-10730. This used to open with `if (this.held.size === 0) return null;` -
6534
+ * it returned the affirmative all-clear WITHOUT LOOKING, and the docblock
6535
+ * defended that as "a healthy host pays no file read per heartbeat". The
6536
+ * saving was real and so was the hole: a host whose `~/.claude.json` had been
6537
+ * unlinked, and which had not happened to attempt a rotation since, reported
6538
+ * `null` every 30 seconds while already doomed. Nothing asked until some
6539
+ * agent's rollover did, which on ENG-10724 was the maintenance window - i.e.
6540
+ * the moment the agents were lost. The host was discovered one whole host at a
6541
+ * time, at whatever hour its window fell.
6542
+ *
6543
+ * So the heartbeat now always looks, and pays for it with ONE `statSync` -
6544
+ * `probeClaudeDeviceStatePresence`, no read, no parse. The original perf
6545
+ * objection is answered rather than overridden: what was rejected in PR #6250
6546
+ * was a multi-MB synchronous `JSON.parse` on every healthy host's event loop,
6547
+ * and that is still not happening here.
6548
+ *
6549
+ * THE TWO PATHS ARE NOT SYMMETRIC, AND THAT IS DELIBERATE.
6550
+ * - Something held: the FULL check, exactly as before. A hold must only clear
6551
+ * on a genuinely usable file, so a present-but-corrupt one cannot read as a
6552
+ * recovery and release a rotation onto a host that still cannot respawn.
6553
+ * - Nothing held: the CHEAP probe. It can only report that the file has GONE,
6554
+ * never that one is well - so it can raise this warning and it can clear
6555
+ * this warning, but it is never allowed to clear a hold.
6556
+ *
6557
+ * `deferred_agents: []` with a reason is therefore a THIRD state, distinct
6558
+ * from both `null` and a hold: the file is unusable and nothing has been
6559
+ * stopped yet. The API must not describe that as a hold - see
6560
+ * `summariseDeviceStateBlock`, which separates the two so the alert does not
6561
+ * claim the manager is holding work it is not.
6517
6562
  */
6518
- heartbeatField() {
6519
- if (this.held.size === 0) return null;
6520
- const verdict = this.check();
6521
- if (verdict.usable) {
6522
- this.held.clear();
6563
+ heartbeatField(nowMs = Date.now()) {
6564
+ if (this.held.size > 0) {
6565
+ const verdict = this.check();
6566
+ if (verdict.usable) {
6567
+ this.held.clear();
6568
+ this.unusableSinceMs = null;
6569
+ return null;
6570
+ }
6571
+ return {
6572
+ reason: verdict.reason,
6573
+ deferred_agents: [...this.held.keys()].sort(),
6574
+ blocked_since: new Date(Math.min(...this.held.values())).toISOString()
6575
+ };
6576
+ }
6577
+ const presence = this.probe();
6578
+ if (presence.present) {
6579
+ this.unusableSinceMs = null;
6523
6580
  return null;
6524
6581
  }
6582
+ if (this.unusableSinceMs === null) this.unusableSinceMs = nowMs;
6525
6583
  return {
6526
- reason: verdict.reason,
6527
- deferred_agents: [...this.held.keys()].sort(),
6528
- blocked_since: new Date(Math.min(...this.held.values())).toISOString()
6584
+ reason: presence.reason,
6585
+ deferred_agents: [],
6586
+ blocked_since: new Date(this.unusableSinceMs).toISOString()
6529
6587
  };
6530
6588
  }
6531
6589
  /** For tests and logs. */
@@ -13955,7 +14013,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
13955
14013
  var lastVersionCheckAt = 0;
13956
14014
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
13957
14015
  var lastResponsivenessProbeAt = 0;
13958
- var agtCliVersion = true ? "0.28.980" : "dev";
14016
+ var agtCliVersion = true ? "0.28.982" : "dev";
13959
14017
  function resolveBrewPath(execFileSync2) {
13960
14018
  try {
13961
14019
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();