@integrity-labs/agt-cli 0.28.425 → 0.28.426

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-M3L6HOYG.js";
43
+ } from "../chunk-XDODHW3I.js";
44
44
  import {
45
45
  AnchorSessionClient,
46
46
  CHANNEL_REGISTRY,
@@ -70,7 +70,7 @@ import {
70
70
  renderTemplate,
71
71
  resolveChannels,
72
72
  serializeManifestForSlackCli
73
- } from "../chunk-PR3IJAMV.js";
73
+ } from "../chunk-HXP4I5QZ.js";
74
74
  import "../chunk-XWVM4KPK.js";
75
75
 
76
76
  // src/bin/agt.ts
@@ -4829,7 +4829,7 @@ import { execFileSync, execSync } from "child_process";
4829
4829
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4830
4830
  import chalk18 from "chalk";
4831
4831
  import ora16 from "ora";
4832
- var cliVersion = true ? "0.28.425" : "dev";
4832
+ var cliVersion = true ? "0.28.426" : "dev";
4833
4833
  async function fetchLatestVersion() {
4834
4834
  const host2 = getHost();
4835
4835
  if (!host2) return null;
@@ -6001,7 +6001,7 @@ function handleError(err) {
6001
6001
  }
6002
6002
 
6003
6003
  // src/bin/agt.ts
6004
- var cliVersion2 = true ? "0.28.425" : "dev";
6004
+ var cliVersion2 = true ? "0.28.426" : "dev";
6005
6005
  var program = new Command();
6006
6006
  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");
6007
6007
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -10444,8 +10444,150 @@ var TurnOutcomeTracker = class {
10444
10444
  }
10445
10445
  };
10446
10446
 
10447
+ // src/lib/busy-bucket-ledger.ts
10448
+ var BUCKET_MS = 6e4;
10449
+ var MAX_ACCRUAL_SPAN_MS = 10 * 6e4;
10450
+ var MAX_RETAINED_BUCKETS = 240;
10451
+ var BILL_WHOLE_MINUTES = true;
10452
+ var MIN_BILLABLE_MS = 100;
10453
+ function bucketStartMs(atMs) {
10454
+ return Math.floor(atMs / BUCKET_MS) * BUCKET_MS;
10455
+ }
10456
+ var BusyBucketLedger = class {
10457
+ state = /* @__PURE__ */ new Map();
10458
+ entry(codeName) {
10459
+ let cur = this.state.get(codeName);
10460
+ if (!cur) {
10461
+ cur = { buckets: /* @__PURE__ */ new Map(), openSince: null };
10462
+ this.state.set(codeName, cur);
10463
+ }
10464
+ return cur;
10465
+ }
10466
+ /**
10467
+ * Credit occupancy for the span [fromMs, toMs), splitting it across every
10468
+ * minute bucket it covers. Safe to call with any ordering — a reversed or
10469
+ * zero-length span is a no-op rather than a negative credit.
10470
+ */
10471
+ accrueSpan(codeName, fromMs, toMs) {
10472
+ if (!Number.isFinite(fromMs) || !Number.isFinite(toMs)) return;
10473
+ if (toMs <= fromMs) return;
10474
+ const start = Math.max(fromMs, toMs - MAX_ACCRUAL_SPAN_MS);
10475
+ const led = this.entry(codeName);
10476
+ for (let b = bucketStartMs(start); b < toMs; b += BUCKET_MS) {
10477
+ const overlap = Math.min(toMs, b + BUCKET_MS) - Math.max(start, b);
10478
+ if (overlap <= 0) continue;
10479
+ const prev = led.buckets.get(b) ?? 0;
10480
+ led.buckets.set(b, Math.min(BUCKET_MS, prev + overlap));
10481
+ }
10482
+ this.evict(led);
10483
+ }
10484
+ /** The agent started doing work. Idempotent while already occupied. */
10485
+ open(codeName, atMs) {
10486
+ const led = this.entry(codeName);
10487
+ if (led.openSince == null) led.openSince = atMs;
10488
+ }
10489
+ /**
10490
+ * Accrue everything owed up to `nowMs` WITHOUT ending the occupancy. This is
10491
+ * what makes a long turn fill every bucket it spans: each probe pass ticks,
10492
+ * banking the elapsed slice and moving the watermark forward.
10493
+ */
10494
+ tick(codeName, nowMs) {
10495
+ const led = this.state.get(codeName);
10496
+ if (!led || led.openSince == null) return;
10497
+ if (nowMs <= led.openSince) return;
10498
+ this.accrueSpan(codeName, led.openSince, nowMs);
10499
+ led.openSince = nowMs;
10500
+ }
10501
+ /** The agent stopped doing work. Banks the final slice. */
10502
+ close(codeName, atMs) {
10503
+ const led = this.state.get(codeName);
10504
+ if (!led || led.openSince == null) return;
10505
+ this.accrueSpan(codeName, led.openSince, atMs);
10506
+ led.openSince = null;
10507
+ }
10508
+ /**
10509
+ * Take every CLOSED bucket for reporting, removing it from the ledger.
10510
+ *
10511
+ * Only closed buckets — a bucket whose minute has not yet elapsed is still
10512
+ * accruing, and reporting it early would send a partial value that the next
10513
+ * drain would have to correct. The open bucket stays and is drained once it
10514
+ * closes.
10515
+ *
10516
+ * DESTRUCTIVE, deliberately: this is the same read-and-reset contract as the
10517
+ * watchdog give-up counters, and it carries the same obligation — the caller
10518
+ * MUST `credit()` the result back if the POST fails, or a transient 5xx
10519
+ * permanently deletes billable occupancy that cannot be reconstructed.
10520
+ */
10521
+ drainClosed(codeName, nowMs) {
10522
+ const led = this.state.get(codeName);
10523
+ if (!led) return [];
10524
+ this.tick(codeName, nowMs);
10525
+ const openBucket = bucketStartMs(nowMs);
10526
+ const out = [];
10527
+ for (const [b, ms] of [...led.buckets].sort((x, y) => x[0] - y[0])) {
10528
+ if (b >= openBucket) continue;
10529
+ led.buckets.delete(b);
10530
+ if (ms < MIN_BILLABLE_MS) continue;
10531
+ const seconds = BILL_WHOLE_MINUTES ? 60 : Math.min(60, Math.ceil(ms / 1e3));
10532
+ out.push({ bucket: new Date(b).toISOString(), seconds });
10533
+ }
10534
+ return out;
10535
+ }
10536
+ /**
10537
+ * Put drained buckets back after a failed POST. Merges rather than replaces,
10538
+ * so occupancy accrued since the drain is preserved.
10539
+ */
10540
+ credit(codeName, buckets) {
10541
+ if (buckets.length === 0) return;
10542
+ const led = this.entry(codeName);
10543
+ for (const b of buckets) {
10544
+ const at = Date.parse(b.bucket);
10545
+ if (!Number.isFinite(at)) continue;
10546
+ const key = bucketStartMs(at);
10547
+ const prev = led.buckets.get(key) ?? 0;
10548
+ led.buckets.set(key, Math.min(BUCKET_MS, prev + b.seconds * 1e3));
10549
+ }
10550
+ this.evict(led);
10551
+ }
10552
+ /** Agents currently holding reportable state. */
10553
+ trackedAgents() {
10554
+ return [...this.state.keys()];
10555
+ }
10556
+ /**
10557
+ * Drop everything for an agent. Called from the same teardown that resets
10558
+ * turn health, so a fresh session is not born holding the dead one's
10559
+ * occupancy.
10560
+ */
10561
+ reset(codeName) {
10562
+ this.state.delete(codeName);
10563
+ }
10564
+ evict(led) {
10565
+ if (led.buckets.size <= MAX_RETAINED_BUCKETS) return;
10566
+ const ordered = [...led.buckets.keys()].sort((a, b) => a - b);
10567
+ for (const k of ordered.slice(0, led.buckets.size - MAX_RETAINED_BUCKETS)) {
10568
+ led.buckets.delete(k);
10569
+ }
10570
+ }
10571
+ };
10572
+ var sharedBusyBuckets = new BusyBucketLedger();
10573
+
10447
10574
  // src/lib/opencode-activity-tracker.ts
10448
10575
  var OpencodeActivityTracker = class {
10576
+ /**
10577
+ * ENG-8116: the same lifecycle also drives the duration ledger. opencode is
10578
+ * the runtime that can measure occupancy EXACTLY — beginTurn/endTurn bracket
10579
+ * real work — so the spans go straight in rather than being inferred from a
10580
+ * file mtime the way Claude Code's have to be.
10581
+ *
10582
+ * Kept inside this class deliberately, rather than having callers poke both:
10583
+ * the lifecycle discipline that makes occupancy correct (the `finally`, the
10584
+ * live-session guard) already lives at one call site, and a second one to
10585
+ * keep in sync is exactly how a leaked in-flight count would bill an idle
10586
+ * agent around the clock.
10587
+ */
10588
+ constructor(ledger = sharedBusyBuckets) {
10589
+ this.ledger = ledger;
10590
+ }
10449
10591
  state = /* @__PURE__ */ new Map();
10450
10592
  entry(codeName) {
10451
10593
  let cur = this.state.get(codeName);
@@ -10456,8 +10598,9 @@ var OpencodeActivityTracker = class {
10456
10598
  return cur;
10457
10599
  }
10458
10600
  /** A turn has been dispatched to the serve. Call BEFORE awaiting it. */
10459
- beginTurn(codeName) {
10601
+ beginTurn(codeName, now = Date.now()) {
10460
10602
  this.entry(codeName).inFlight += 1;
10603
+ this.ledger.open(codeName, now);
10461
10604
  }
10462
10605
  /**
10463
10606
  * A dispatched turn has resolved. Call in a `finally`, so a throw cannot
@@ -10471,6 +10614,7 @@ var OpencodeActivityTracker = class {
10471
10614
  const cur = this.entry(codeName);
10472
10615
  cur.inFlight = Math.max(0, cur.inFlight - 1);
10473
10616
  if (counted) cur.lastActiveAt = now;
10617
+ if (cur.inFlight === 0) this.ledger.close(codeName, now);
10474
10618
  }
10475
10619
  /**
10476
10620
  * Seconds since this agent was last doing work, or null if it never has been
@@ -10494,6 +10638,7 @@ var OpencodeActivityTracker = class {
10494
10638
  */
10495
10639
  reset(codeName) {
10496
10640
  this.state.delete(codeName);
10641
+ this.ledger.reset(codeName);
10497
10642
  }
10498
10643
  };
10499
10644
 
@@ -12768,6 +12913,7 @@ export {
12768
12913
  sha256,
12769
12914
  hashFile,
12770
12915
  execFilePromiseLong,
12916
+ sharedBusyBuckets,
12771
12917
  readProvisionedOpencodeModel,
12772
12918
  startOpencodeSession,
12773
12919
  readOpencodePaneLogTail,
@@ -12825,4 +12971,4 @@ export {
12825
12971
  stopAllSessionsAndWait,
12826
12972
  getProjectDir
12827
12973
  };
12828
- //# sourceMappingURL=chunk-PR3IJAMV.js.map
12974
+ //# sourceMappingURL=chunk-HXP4I5QZ.js.map