@integrity-labs/agt-cli 0.28.632 → 0.28.633

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,10 +40,10 @@ import {
40
40
  success,
41
41
  table,
42
42
  warn
43
- } from "../chunk-QDIF33SF.js";
43
+ } from "../chunk-IDERROFJ.js";
44
44
  import {
45
45
  readDirectChatSessionState
46
- } from "../chunk-IT2WYXBM.js";
46
+ } from "../chunk-GNTCOXZG.js";
47
47
  import {
48
48
  AnchorSessionClient,
49
49
  CHANNEL_REGISTRY,
@@ -4909,7 +4909,7 @@ import { execFileSync, execSync } from "child_process";
4909
4909
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4910
4910
  import chalk18 from "chalk";
4911
4911
  import ora16 from "ora";
4912
- var cliVersion = true ? "0.28.632" : "dev";
4912
+ var cliVersion = true ? "0.28.633" : "dev";
4913
4913
  async function fetchLatestVersion() {
4914
4914
  const host2 = getHost();
4915
4915
  if (!host2) return null;
@@ -6089,7 +6089,7 @@ function handleError(err) {
6089
6089
  }
6090
6090
 
6091
6091
  // src/bin/agt.ts
6092
- var cliVersion2 = true ? "0.28.632" : "dev";
6092
+ var cliVersion2 = true ? "0.28.633" : "dev";
6093
6093
  var program = new Command();
6094
6094
  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");
6095
6095
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -2400,8 +2400,22 @@ function isSessionFeedbackDialogVisible(screen) {
2400
2400
  return screen.includes("How is Claude doing this session") && screen.includes("0: Dismiss");
2401
2401
  }
2402
2402
  var USAGE_LIMIT_SAFE_OPTION = "Stop and wait for limit to reset";
2403
+ var USAGE_LIMIT_CONTINUE_OPTION = "Wait here, then continue automatically";
2404
+ var USAGE_LIMIT_SAFE_OPTIONS = [
2405
+ USAGE_LIMIT_CONTINUE_OPTION,
2406
+ USAGE_LIMIT_SAFE_OPTION
2407
+ ];
2408
+ var USAGE_LIMIT_KNOWN_OPTIONS = [
2409
+ ...USAGE_LIMIT_SAFE_OPTIONS,
2410
+ "Switch to usage credits",
2411
+ "Switch to Team plan",
2412
+ // ENG-9006: the two-option variant Claude Code shipped ~2026-08-17. Filed
2413
+ // with a wedged pane and four unanswered customer Telegram messages queued
2414
+ // behind it.
2415
+ "Upgrade your plan",
2416
+ "Ask your admin for more usage"
2417
+ ];
2403
2418
  var USAGE_LIMIT_BLOCK_LINES = 6;
2404
- var BILLING_OPTIONS = ["Switch to usage credits", "Switch to Team plan"];
2405
2419
  function optionRowDigit(line2, label) {
2406
2420
  const m = line2.match(
2407
2421
  new RegExp(
@@ -2410,6 +2424,27 @@ function optionRowDigit(line2, label) {
2410
2424
  );
2411
2425
  return m?.[1] ?? null;
2412
2426
  }
2427
+ function focusedModalBlock(screen, blockLines) {
2428
+ const lines = screen.split("\n");
2429
+ let footer = -1;
2430
+ for (let i = lines.length - 1; i >= 0; i--) {
2431
+ if (lines[i].includes("Enter to confirm")) {
2432
+ footer = i;
2433
+ break;
2434
+ }
2435
+ }
2436
+ if (footer < 0) return null;
2437
+ return lines.slice(Math.max(0, footer - blockLines), footer);
2438
+ }
2439
+ var MCP_DIALOG_OPTIONS = [
2440
+ "Use this and all future MCP servers in this project",
2441
+ "Continue without using these MCP servers"
2442
+ ];
2443
+ function isMcpServersDialogVisible(screen) {
2444
+ const block = focusedModalBlock(screen, USAGE_LIMIT_BLOCK_LINES);
2445
+ if (!block) return false;
2446
+ return block.some((l) => MCP_DIALOG_OPTIONS.some((label) => optionRowDigit(l, label) !== null));
2447
+ }
2413
2448
  function usageLimitModalBlock(screen) {
2414
2449
  const lines = screen.split("\n");
2415
2450
  let footer = -1;
@@ -2421,20 +2456,38 @@ function usageLimitModalBlock(screen) {
2421
2456
  }
2422
2457
  if (footer < 0) return null;
2423
2458
  const block = lines.slice(Math.max(0, footer - USAGE_LIMIT_BLOCK_LINES), footer);
2424
- const hasBillingRow = block.some(
2425
- (l) => BILLING_OPTIONS.some((label) => optionRowDigit(l, label) !== null)
2459
+ const hasKnownOptionRow = block.some(
2460
+ (l) => USAGE_LIMIT_KNOWN_OPTIONS.some((label) => optionRowDigit(l, label) !== null)
2426
2461
  );
2427
- return hasBillingRow ? block : null;
2462
+ return hasKnownOptionRow ? block : null;
2428
2463
  }
2429
2464
  function isUsageLimitChoiceDialogVisible(screen) {
2430
2465
  return usageLimitModalBlock(screen) !== null;
2431
2466
  }
2467
+ function findUsageLimitSafeOption(screen) {
2468
+ const block = usageLimitModalBlock(screen);
2469
+ if (!block) return null;
2470
+ for (const label of USAGE_LIMIT_SAFE_OPTIONS) {
2471
+ for (const line2 of block) {
2472
+ const digit = optionRowDigit(line2, label);
2473
+ if (digit !== null) return { key: digit, label };
2474
+ }
2475
+ }
2476
+ return null;
2477
+ }
2432
2478
  function findUsageLimitSafeOptionKey(screen) {
2479
+ return findUsageLimitSafeOption(screen)?.key ?? null;
2480
+ }
2481
+ function findUsageLimitResetHint(screen) {
2433
2482
  const block = usageLimitModalBlock(screen);
2434
2483
  if (!block) return null;
2435
2484
  for (const line2 of block) {
2436
- const digit = optionRowDigit(line2, USAGE_LIMIT_SAFE_OPTION);
2437
- if (digit !== null) return digit;
2485
+ if (optionRowDigit(line2, USAGE_LIMIT_CONTINUE_OPTION) === null) continue;
2486
+ const idx = line2.indexOf(USAGE_LIMIT_CONTINUE_OPTION);
2487
+ const tail = line2.slice(idx + USAGE_LIMIT_CONTINUE_OPTION.length).trim();
2488
+ const when = tail.replace(/^at\s+/i, "").trim();
2489
+ if (!when) return null;
2490
+ return when.slice(0, 40);
2438
2491
  }
2439
2492
  return null;
2440
2493
  }
@@ -2443,13 +2496,17 @@ function isUnanswerableUsageLimitDialog(screen) {
2443
2496
  }
2444
2497
  function sweepDialogs(screen) {
2445
2498
  if (isUsageLimitChoiceDialogVisible(screen)) {
2446
- const safeKey = findUsageLimitSafeOptionKey(screen);
2447
- if (!safeKey) return null;
2499
+ const safe = findUsageLimitSafeOption(screen);
2500
+ if (!safe) return null;
2448
2501
  return {
2449
2502
  kind: "usage-limit-choice",
2450
- keys: [safeKey, "Enter"],
2503
+ // The log names the option ACTUALLY picked, not a fixed literal. With two
2504
+ // selectable rows now, a constant string would make a fleet sweep unable
2505
+ // to tell an agent that will resume by itself from one parked until a
2506
+ // human touches it.
2507
+ keys: [safe.key, "Enter"],
2451
2508
  interKeyDelayMs: 300,
2452
- logMessage: `Auto-answered usage-limit choice dialog (picked '${USAGE_LIMIT_SAFE_OPTION}')`
2509
+ logMessage: `Auto-answered usage-limit choice dialog (picked '${safe.label}')`
2453
2510
  };
2454
2511
  }
2455
2512
  if (screen.includes("Choose the text style") || screen.includes("Dark mode") && screen.includes("Light mode")) {
@@ -2484,7 +2541,7 @@ function sweepDialogs(screen) {
2484
2541
  logMessage: "Auto-accepted dev channels"
2485
2542
  };
2486
2543
  }
2487
- if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
2544
+ if (isMcpServersDialogVisible(screen)) {
2488
2545
  return {
2489
2546
  kind: "mcp-servers",
2490
2547
  keys: ["Enter"],
@@ -2520,6 +2577,24 @@ async function sendDialogKeys(tmuxSession, action) {
2520
2577
  });
2521
2578
  }
2522
2579
  }
2580
+ function focusedConfirmDialogBlock(screen) {
2581
+ const lines = screen.split("\n");
2582
+ let footer = -1;
2583
+ for (let i = lines.length - 1; i >= 0; i--) {
2584
+ if (lines[i].includes("Enter to confirm")) {
2585
+ footer = i;
2586
+ break;
2587
+ }
2588
+ }
2589
+ if (footer < 0) return null;
2590
+ return lines.slice(Math.max(0, footer - USAGE_LIMIT_BLOCK_LINES), footer + 1);
2591
+ }
2592
+ function isUnclaimedConfirmDialog(screen) {
2593
+ if (focusedConfirmDialogBlock(screen) === null) return false;
2594
+ if (sweepDialogs(screen) !== null) return false;
2595
+ if (isUnanswerableUsageLimitDialog(screen)) return false;
2596
+ return true;
2597
+ }
2523
2598
  function simpleTextHash(s) {
2524
2599
  let h = 0;
2525
2600
  for (let i = 0; i < s.length; i++) {
@@ -2529,6 +2604,7 @@ function simpleTextHash(s) {
2529
2604
  }
2530
2605
 
2531
2606
  // src/lib/channel-input-watchdog.ts
2607
+ var UNCLAIMED_DIALOG_ALERT_MS = 12e4;
2532
2608
  var STUCK_THRESHOLD_MS = 5e3;
2533
2609
  var MAX_ENTER_FIRES = 3;
2534
2610
  var DISTURB_HEAL_KEYS = ["x", "BSpace", "Enter"];
@@ -2554,30 +2630,39 @@ function decide(pane, prev, now, config = {}) {
2554
2630
  next: prev
2555
2631
  };
2556
2632
  }
2633
+ const unclaimedBlock = isUnclaimedConfirmDialog(pane) ? focusedConfirmDialogBlock(pane) : null;
2634
+ const unclaimedDialogHash = unclaimedBlock ? simpleTextHash(unclaimedBlock.join("\n")) : void 0;
2557
2635
  const inputText = extractInputBoxText(pane);
2558
2636
  if (!inputText) {
2559
- return { fire: false, next: void 0 };
2637
+ return { fire: false, unclaimedDialogHash, next: void 0 };
2560
2638
  }
2561
2639
  if (inputText === "\u2026") {
2562
- return { fire: false, next: void 0 };
2640
+ return { fire: false, unclaimedDialogHash, next: void 0 };
2563
2641
  }
2564
2642
  const hash = simpleTextHash(inputText);
2565
2643
  if (!prev || prev.lastInputHash !== hash) {
2566
2644
  return {
2567
2645
  fire: false,
2646
+ unclaimedDialogHash,
2568
2647
  next: { lastInputHash: hash, firstSeenAt: now, fires: 0, lastFireAt: 0, gaveUpLogged: false }
2569
2648
  };
2570
2649
  }
2571
2650
  if (prev.fires >= maxFires) {
2572
2651
  if (!prev.gaveUpLogged) {
2573
- return { fire: false, gaveUp: true, next: { ...prev, gaveUpLogged: true } };
2652
+ return {
2653
+ fire: false,
2654
+ gaveUp: true,
2655
+ unclaimedDialogHash,
2656
+ next: { ...prev, gaveUpLogged: true }
2657
+ };
2574
2658
  }
2575
- return { fire: false, next: prev };
2659
+ return { fire: false, unclaimedDialogHash, next: prev };
2576
2660
  }
2577
2661
  const sinceLastAttempt = prev.fires === 0 ? now - prev.firstSeenAt : now - prev.lastFireAt;
2578
- if (sinceLastAttempt < threshold) return { fire: false, next: prev };
2662
+ if (sinceLastAttempt < threshold) return { fire: false, unclaimedDialogHash, next: prev };
2579
2663
  return {
2580
2664
  fire: true,
2665
+ unclaimedDialogHash,
2581
2666
  next: { ...prev, fires: prev.fires + 1, lastFireAt: now }
2582
2667
  };
2583
2668
  }
@@ -2622,6 +2707,15 @@ function checkChannelInputs(codeNames, io, config = {}, states = sharedStates) {
2622
2707
  for (const key of [...giveUpCounts.keys()]) {
2623
2708
  if (!live.has(key)) giveUpCounts.delete(key);
2624
2709
  }
2710
+ for (const key of [...unclaimedDialogCounts.keys()]) {
2711
+ if (!live.has(key)) unclaimedDialogCounts.delete(key);
2712
+ }
2713
+ for (const key of [...unclaimedDialogSeen.keys()]) {
2714
+ if (!live.has(key)) unclaimedDialogSeen.delete(key);
2715
+ }
2716
+ for (const key of [...usageLimitSignalled.keys()]) {
2717
+ if (!live.has(key)) usageLimitSignalled.delete(key);
2718
+ }
2625
2719
  }
2626
2720
  function checkOne(codeName, io, config, states) {
2627
2721
  const pane = io.capturePane(codeName);
@@ -2635,7 +2729,7 @@ function checkOne(codeName, io, config, states) {
2635
2729
  stuckThresholdMs: config.attachedStuckThresholdMs ?? ATTACHED_STUCK_THRESHOLD_MS
2636
2730
  } : config;
2637
2731
  const prev = states.get(codeName);
2638
- const { fire, dialog, gaveUp, blockedDialog, next } = decide(
2732
+ const { fire, dialog, gaveUp, blockedDialog, unclaimedDialogHash, next } = decide(
2639
2733
  pane,
2640
2734
  prev,
2641
2735
  io.now(),
@@ -2651,14 +2745,23 @@ function checkOne(codeName, io, config, states) {
2651
2745
  } else {
2652
2746
  states.set(codeName, next);
2653
2747
  }
2748
+ trackUnclaimedDialog(codeName, unclaimedDialogHash, io, effectiveConfig);
2749
+ const onUsageLimitModal = dialog?.kind === "usage-limit-choice" || blockedDialog === "usage-limit-choice-unrecognised-options";
2750
+ if (!onUsageLimitModal) usageLimitSignalled.delete(codeName);
2654
2751
  if (dialog) {
2655
2752
  io.log(
2656
2753
  `[channel-input-watchdog] '${codeName}': ${dialog.logMessage} (dialog was blocking the input box)`
2657
2754
  );
2755
+ if (dialog.kind === "usage-limit-choice") {
2756
+ maybeSignalUsageLimit(codeName, pane, io);
2757
+ }
2658
2758
  io.sendKeys(codeName, dialog.keys, dialog.interKeyDelayMs);
2659
2759
  return;
2660
2760
  }
2661
2761
  if (blockedDialog) {
2762
+ if (blockedDialog === "usage-limit-choice-unrecognised-options") {
2763
+ maybeSignalUsageLimit(codeName, pane, io);
2764
+ }
2662
2765
  io.log(
2663
2766
  `[channel-input-watchdog] '${codeName}': BLOCKED DIALOG (${blockedDialog}) \u2014 refusing to send any key; needs a human to attach to the pane`
2664
2767
  );
@@ -2710,6 +2813,49 @@ function creditWatchdogGiveUpCount(codeName, count) {
2710
2813
  if (count <= 0) return;
2711
2814
  giveUpCounts.set(codeName, (giveUpCounts.get(codeName) ?? 0) + count);
2712
2815
  }
2816
+ var usageLimitSignalled = /* @__PURE__ */ new Map();
2817
+ function maybeSignalUsageLimit(codeName, pane, io) {
2818
+ if (!io.signalUsageLimit) return;
2819
+ const hint = findUsageLimitResetHint(pane);
2820
+ const key = hint ?? "<no-reset-named>";
2821
+ if (usageLimitSignalled.get(codeName) === key) return;
2822
+ usageLimitSignalled.set(codeName, key);
2823
+ try {
2824
+ io.signalUsageLimit(codeName, hint);
2825
+ io.log(
2826
+ `[channel-input-watchdog] '${codeName}': usage-limit modal \u2014 signalled the channel servers so waiting people are told` + (hint ? ` (resets ${hint})` : " (modal named no reset)")
2827
+ );
2828
+ } catch (err) {
2829
+ io.log(
2830
+ `[channel-input-watchdog] '${codeName}': usage-limit signal write failed: ${err.message}`
2831
+ );
2832
+ }
2833
+ }
2834
+ function trackUnclaimedDialog(codeName, hash, io, config) {
2835
+ if (!hash) {
2836
+ unclaimedDialogSeen.delete(codeName);
2837
+ return;
2838
+ }
2839
+ const thresholdMs = config.unclaimedDialogAlertMs ?? UNCLAIMED_DIALOG_ALERT_MS;
2840
+ const now = io.now();
2841
+ const prev = unclaimedDialogSeen.get(codeName);
2842
+ if (!prev || prev.hash !== hash) {
2843
+ unclaimedDialogSeen.set(codeName, { hash, firstSeenAt: now, alerted: false });
2844
+ return;
2845
+ }
2846
+ if (prev.alerted) return;
2847
+ const ageMs = now - prev.firstSeenAt;
2848
+ if (ageMs < thresholdMs) return;
2849
+ unclaimedDialogSeen.set(codeName, { ...prev, alerted: true });
2850
+ unclaimedDialogCounts.set(codeName, (unclaimedDialogCounts.get(codeName) ?? 0) + 1);
2851
+ io.log(
2852
+ `[channel-input-watchdog] '${codeName}': UNCLAIMED DIALOG on pane for ${Math.round(
2853
+ ageMs / 1e3
2854
+ )}s \u2014 a modal is focused and no handler in claude-dialogs.ts recognises it (dialog_hash=${hash}); sending nothing. Claude Code has likely reworded a dialog: attach to the pane, then add the new option labels.`
2855
+ );
2856
+ }
2857
+ var unclaimedDialogCounts = /* @__PURE__ */ new Map();
2858
+ var unclaimedDialogSeen = /* @__PURE__ */ new Map();
2713
2859
 
2714
2860
  // src/lib/persistent-session.ts
2715
2861
  var OPENROUTER_ANTHROPIC_BASE_URL = "https://openrouter.ai/api";
@@ -4167,4 +4313,4 @@ export {
4167
4313
  stopAllSessionsAndWait,
4168
4314
  getProjectDir
4169
4315
  };
4170
- //# sourceMappingURL=chunk-IT2WYXBM.js.map
4316
+ //# sourceMappingURL=chunk-GNTCOXZG.js.map