@integrity-labs/agt-cli 0.28.411 → 0.28.412

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-JIT5ORMO.js";
43
+ } from "../chunk-5ES25OE6.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-D4MDIZ3T.js";
73
+ } from "../chunk-QZUKHEMO.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.411" : "dev";
4832
+ var cliVersion = true ? "0.28.412" : "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.411" : "dev";
6004
+ var cliVersion2 = true ? "0.28.412" : "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) => {
@@ -25,7 +25,7 @@ import {
25
25
  resolveConnectivityProbe,
26
26
  worseConnectivityOutcome,
27
27
  wrapScheduledTaskPrompt
28
- } from "./chunk-D4MDIZ3T.js";
28
+ } from "./chunk-QZUKHEMO.js";
29
29
  import {
30
30
  parsePsRows
31
31
  } from "./chunk-XWVM4KPK.js";
@@ -6247,7 +6247,7 @@ function requireHost() {
6247
6247
  }
6248
6248
 
6249
6249
  // src/lib/api-client.ts
6250
- var agtCliVersion = true ? "0.28.411" : "dev";
6250
+ var agtCliVersion = true ? "0.28.412" : "dev";
6251
6251
  var lastConfigHash = null;
6252
6252
  function setConfigHash(hash) {
6253
6253
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -8751,4 +8751,4 @@ export {
8751
8751
  managerInstallSystemUnitCommand,
8752
8752
  managerUninstallSystemUnitCommand
8753
8753
  };
8754
- //# sourceMappingURL=chunk-JIT5ORMO.js.map
8754
+ //# sourceMappingURL=chunk-5ES25OE6.js.map
@@ -10242,10 +10242,65 @@ async function execFilePromiseLong(cmd, args, opts) {
10242
10242
  });
10243
10243
  }
10244
10244
 
10245
+ // src/lib/turn-outcome-tracker.ts
10246
+ function isFailure(outcome) {
10247
+ return outcome === "no_reply" || outcome === "failed";
10248
+ }
10249
+ function emptyHealth() {
10250
+ return { lastOutcome: null, lastRepliedAt: null, lastAttemptAt: null, consecutiveFailures: 0 };
10251
+ }
10252
+ var DEFAULT_TURN_FAILURE_WARN_THRESHOLD = 3;
10253
+ var TurnOutcomeTracker = class {
10254
+ health = /* @__PURE__ */ new Map();
10255
+ warned = /* @__PURE__ */ new Set();
10256
+ threshold;
10257
+ constructor(threshold = DEFAULT_TURN_FAILURE_WARN_THRESHOLD) {
10258
+ if (!Number.isInteger(threshold) || threshold < 1) {
10259
+ throw new Error(`turn-outcome threshold must be an integer >= 1 (got ${threshold})`);
10260
+ }
10261
+ this.threshold = threshold;
10262
+ }
10263
+ /** Record one observed turn outcome for one agent. */
10264
+ record(codeName, outcome, now = Date.now()) {
10265
+ const current = this.health.get(codeName) ?? emptyHealth();
10266
+ const next = {
10267
+ lastOutcome: outcome,
10268
+ lastAttemptAt: now,
10269
+ lastRepliedAt: outcome === "replied" ? now : current.lastRepliedAt,
10270
+ // Only a real reply clears the streak. A `declined` or a fire-and-forget
10271
+ // `admitted` leaves it exactly where it was: neither proves the agent can
10272
+ // answer, so neither should be able to mask an ongoing wedge.
10273
+ consecutiveFailures: isFailure(outcome) ? current.consecutiveFailures + 1 : outcome === "replied" ? 0 : current.consecutiveFailures
10274
+ };
10275
+ this.health.set(codeName, next);
10276
+ if (outcome === "replied") {
10277
+ const wasWarned = this.warned.delete(codeName);
10278
+ return { health: next, shouldWarn: false, recovered: wasWarned };
10279
+ }
10280
+ const crossed = next.consecutiveFailures >= this.threshold && !this.warned.has(codeName);
10281
+ if (crossed) this.warned.add(codeName);
10282
+ return { health: next, shouldWarn: crossed, recovered: false };
10283
+ }
10284
+ /** Current turn health for an agent, or null if no turn has been observed. */
10285
+ get(codeName) {
10286
+ return this.health.get(codeName) ?? null;
10287
+ }
10288
+ /**
10289
+ * Drop all state for an agent. Called when its serve is torn down, so a fresh
10290
+ * serve is not born already carrying the dead one's failure streak.
10291
+ */
10292
+ reset(codeName) {
10293
+ this.health.delete(codeName);
10294
+ this.warned.delete(codeName);
10295
+ }
10296
+ };
10297
+
10245
10298
  // src/lib/opencode-session.ts
10246
10299
  var OPENCODE_BIN = process.env["AGT_OPENCODE_BIN"]?.trim() || "opencode";
10247
10300
  var OPENCODE_RUN_TIMEOUT_MS = Number(process.env["AGT_OPENCODE_RUN_TIMEOUT_MS"]) || 18e4;
10248
10301
  var sessions = /* @__PURE__ */ new Map();
10302
+ var loggers = /* @__PURE__ */ new Map();
10303
+ var turnOutcomeTracker = new TurnOutcomeTracker();
10249
10304
  var bridges = /* @__PURE__ */ new Map();
10250
10305
  function opencodeTmuxSession(codeName) {
10251
10306
  return `agt-oc-${codeName}`;
@@ -10425,6 +10480,7 @@ function findFreePort() {
10425
10480
  }
10426
10481
  async function startOpencodeSession(config) {
10427
10482
  const { codeName, log: log2 } = config;
10483
+ loggers.set(codeName, log2);
10428
10484
  const existing = sessions.get(codeName);
10429
10485
  if (existing && existing.status === "running" && !isOpencodeSessionHealthy(codeName)) {
10430
10486
  log2(
@@ -10585,7 +10641,29 @@ async function injectOpencodeMessage(codeName, msg, opts = {}) {
10585
10641
  return { status: "declined", reason: "server_not_running" };
10586
10642
  }
10587
10643
  const bridge = getBridge(codeName, session.port, session.password);
10588
- return bridge.handleInbound(msg, { gate: opts.gate, awaitReply: opts.awaitReply });
10644
+ try {
10645
+ const result = await bridge.handleInbound(msg, { gate: opts.gate, awaitReply: opts.awaitReply });
10646
+ const outcome = result.status === "declined" ? "declined" : result.status === "replied" && result.reply ? "replied" : opts.awaitReply === false ? "admitted" : "no_reply";
10647
+ noteOpencodeTurnOutcome(codeName, outcome, session);
10648
+ return result;
10649
+ } catch (err) {
10650
+ noteOpencodeTurnOutcome(codeName, "failed", session);
10651
+ throw err;
10652
+ }
10653
+ }
10654
+ function noteOpencodeTurnOutcome(codeName, outcome, startedOn) {
10655
+ if (sessions.get(codeName) !== startedOn || startedOn.status !== "running") return;
10656
+ const { health, shouldWarn, recovered } = turnOutcomeTracker.record(codeName, outcome);
10657
+ const log2 = loggers.get(codeName);
10658
+ if (!log2) return;
10659
+ if (shouldWarn) {
10660
+ const lastOk = health.lastRepliedAt ? `${Math.round((Date.now() - health.lastRepliedAt) / 1e3)}s ago` : "never";
10661
+ log2(
10662
+ `[turn-health] WARN: '${codeName}' has ${health.consecutiveFailures} consecutive turns with no reply (last successful turn: ${lastOk}; serve process alive=${isOpencodeSessionHealthy(codeName)}) \u2014 the serve is up but not completing turns (ENG-7996)`
10663
+ );
10664
+ } else if (recovered) {
10665
+ log2(`[turn-health] '${codeName}' completed a turn again after a no-reply streak (ENG-7996)`);
10666
+ }
10589
10667
  }
10590
10668
  function getBridge(codeName, port, password) {
10591
10669
  const cached = bridges.get(codeName);
@@ -10669,6 +10747,8 @@ function stopOpencodeSession(codeName, log2) {
10669
10747
  }
10670
10748
  stopTranscriptRefresher(codeName);
10671
10749
  bridges.delete(codeName);
10750
+ turnOutcomeTracker.reset(codeName);
10751
+ loggers.delete(codeName);
10672
10752
  const session = sessions.get(codeName);
10673
10753
  if (session) {
10674
10754
  session.status = "stopped";
@@ -10679,6 +10759,9 @@ function stopOpencodeSession(codeName, log2) {
10679
10759
  function getOpencodeSessionState(codeName) {
10680
10760
  return sessions.get(codeName) ?? null;
10681
10761
  }
10762
+ function getOpencodeTurnHealth(codeName) {
10763
+ return turnOutcomeTracker.get(codeName);
10764
+ }
10682
10765
  function stripUndefined(env2) {
10683
10766
  const out = {};
10684
10767
  for (const [k, v] of Object.entries(env2)) if (v !== void 0) out[k] = v;
@@ -12265,6 +12348,7 @@ function collectDiagnostics(codeNames, quarantineEntriesFor) {
12265
12348
  if (oc) {
12266
12349
  const serveAlive = isOpencodeSessionHealthy(codeName);
12267
12350
  const status = serveAlive ? oc.status : oc.status === "running" ? "crashed" : oc.status;
12351
+ const turn = getOpencodeTurnHealth(codeName);
12268
12352
  return {
12269
12353
  codeName,
12270
12354
  framework: "opencode",
@@ -12278,7 +12362,15 @@ function collectDiagnostics(codeNames, quarantineEntriesFor) {
12278
12362
  launchArgs: null,
12279
12363
  channelStatus: null,
12280
12364
  isolated: isolationMode(codeName) === "docker",
12281
- quarantinedChannels
12365
+ quarantinedChannels,
12366
+ ...turn ? {
12367
+ turnHealth: {
12368
+ lastOutcome: turn.lastOutcome,
12369
+ lastRepliedAt: turn.lastRepliedAt ? new Date(turn.lastRepliedAt).toISOString() : null,
12370
+ lastAttemptAt: turn.lastAttemptAt ? new Date(turn.lastAttemptAt).toISOString() : null,
12371
+ consecutiveFailures: turn.consecutiveFailures
12372
+ }
12373
+ } : {}
12282
12374
  };
12283
12375
  }
12284
12376
  const session = sessions2.get(codeName);
@@ -12514,4 +12606,4 @@ export {
12514
12606
  stopAllSessionsAndWait,
12515
12607
  getProjectDir
12516
12608
  };
12517
- //# sourceMappingURL=chunk-D4MDIZ3T.js.map
12609
+ //# sourceMappingURL=chunk-QZUKHEMO.js.map