@integrity-labs/agt-cli 0.28.526 → 0.28.528

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-ONTG4Z5K.js";
43
+ } from "../chunk-7N55RDMG.js";
44
44
  import {
45
45
  AnchorSessionClient,
46
46
  CHANNEL_REGISTRY,
@@ -71,7 +71,7 @@ import {
71
71
  requiredMcpWildcard,
72
72
  resolveChannels,
73
73
  serializeManifestForSlackCli
74
- } from "../chunk-Z4P4VEDU.js";
74
+ } from "../chunk-PR3QSOLI.js";
75
75
  import "../chunk-XWVM4KPK.js";
76
76
 
77
77
  // src/bin/agt.ts
@@ -4834,7 +4834,7 @@ import { execFileSync, execSync } from "child_process";
4834
4834
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4835
4835
  import chalk18 from "chalk";
4836
4836
  import ora16 from "ora";
4837
- var cliVersion = true ? "0.28.526" : "dev";
4837
+ var cliVersion = true ? "0.28.528" : "dev";
4838
4838
  async function fetchLatestVersion() {
4839
4839
  const host2 = getHost();
4840
4840
  if (!host2) return null;
@@ -6006,7 +6006,7 @@ function handleError(err) {
6006
6006
  }
6007
6007
 
6008
6008
  // src/bin/agt.ts
6009
- var cliVersion2 = true ? "0.28.526" : "dev";
6009
+ var cliVersion2 = true ? "0.28.528" : "dev";
6010
6010
  var program = new Command();
6011
6011
  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");
6012
6012
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -42,7 +42,7 @@ import {
42
42
  resolveConnectivityProbe,
43
43
  worseConnectivityOutcome,
44
44
  wrapScheduledTaskPrompt
45
- } from "./chunk-Z4P4VEDU.js";
45
+ } from "./chunk-PR3QSOLI.js";
46
46
  import {
47
47
  parsePsRows
48
48
  } from "./chunk-XWVM4KPK.js";
@@ -2313,6 +2313,7 @@ function provisionStopHook(codeName) {
2313
2313
  "exit 0"
2314
2314
  ].join("\n") + "\n";
2315
2315
  writeFileSync3(ghostHookPath, ghostHookScript, { mode: 493 });
2316
+ const backlogHookPath = provisionBacklogPullHook(codeName);
2316
2317
  const settingsPath = join2(claudeDir, "settings.local.json");
2317
2318
  let settings = {};
2318
2319
  try {
@@ -2324,7 +2325,8 @@ function provisionStopHook(codeName) {
2324
2325
  {
2325
2326
  hooks: [
2326
2327
  { type: "command", command: hookScriptPath },
2327
- { type: "command", command: ghostHookPath }
2328
+ { type: "command", command: ghostHookPath },
2329
+ { type: "command", command: backlogHookPath }
2328
2330
  ]
2329
2331
  }
2330
2332
  ];
@@ -2417,6 +2419,138 @@ function provisionIsolationHook(codeName, agentId) {
2417
2419
  settings["hooks"] = hooks;
2418
2420
  writeFileSync3(settingsPath, JSON.stringify(settings, null, 2));
2419
2421
  }
2422
+ function provisionBacklogPullHook(codeName) {
2423
+ const projectDir = getProjectDir(codeName);
2424
+ const claudeDir = join2(projectDir, ".claude");
2425
+ mkdirSync2(claudeDir, { recursive: true });
2426
+ const hookScriptPath = join2(claudeDir, "agt-backlog-pull-hook.sh");
2427
+ const hookScript = `#!/usr/bin/env bash
2428
+ # Auto-generated by Augmented (CS-1549) \u2014 Stop hook: don't end a turn idle
2429
+ # beside a non-empty backlog. Fail-open on every path; blocks at most once per
2430
+ # distinct backlog item so it can never wedge a session.
2431
+ set -uo pipefail
2432
+ trap 'exit 0' ERR
2433
+
2434
+ INPUT="$(cat 2>/dev/null || true)"
2435
+ [ -n "$INPUT" ] || INPUT="\${CLAUDE_HOOK_INPUT:-}"
2436
+ [ -n "$INPUT" ] || exit 0
2437
+ command -v jq >/dev/null 2>&1 || exit 0
2438
+ command -v curl >/dev/null 2>&1 || exit 0
2439
+
2440
+ # Never re-block a turn that Claude Code is already replaying because a hook
2441
+ # blocked it. Without this two Stop hooks can ping-pong.
2442
+ STOP_ACTIVE="$(printf '%s' "$INPUT" | jq -r '.stop_hook_active // false' 2>/dev/null || echo false)"
2443
+ # ENG-4660 form. \`[ test ] && exit 0\` is safe here only because this script omits
2444
+ # \`set -e\` and the list is not last; adding either re-creates the silent non-zero
2445
+ # hook exit. Use the explicit form both other Stop hooks were converted to.
2446
+ if [ "$STOP_ACTIVE" = "true" ]; then exit 0; fi
2447
+
2448
+ # CodeRabbit on #4134, and the finding that most undermines this hook:
2449
+ # stop_hook_active alone is NOT enough. On the FIRST Stop event Claude Code runs
2450
+ # all three commands in the same hook group, with stop_hook_active=false for
2451
+ # every one of them. So if ghost-reply blocks, this hook still runs, still burns
2452
+ # its one-shot ledger entry, and its reason is never the one shown - the backlog
2453
+ # nudge is consumed unseen, and that item can never nudge again.
2454
+ #
2455
+ # Standing down whenever a channel reply is OWED fixes it, and matches the
2456
+ # ordering documented at the Stop registration: an unanswered person beats an
2457
+ # unpulled card. Cheap - a directory test, and it runs before either curl.
2458
+ CWD_IN="$(printf '%s' "$INPUT" | jq -r '.cwd // empty' 2>/dev/null || true)"
2459
+ if [ -n "$CWD_IN" ]; then
2460
+ OWED_BASE="$(dirname "$CWD_IN")"
2461
+ for d in telegram-pending-inbound slack-pending-inbound direct-chat-pending-inbound msteams-pending-inbound/.markers; do
2462
+ if [ -d "$OWED_BASE/$d" ] && [ -n "$(ls -A "$OWED_BASE/$d" 2>/dev/null || true)" ]; then
2463
+ echo "agt-backlog-pull-hook: standing down, a channel reply is owed" >&2
2464
+ exit 0
2465
+ fi
2466
+ done
2467
+ fi
2468
+
2469
+ # Identity. AGT_AGENT_ID is NOT exported into the parent claude's env (see
2470
+ # ENG-4823), so .mcp.json is the reliable source \u2014 the manager writes it
2471
+ # literally into every augmented MCP server's env block.
2472
+ PROJECT_DIR="\${CLAUDE_PROJECT_DIR:-$PWD}"
2473
+ AGENT_ID="\${AGT_AGENT_ID:-}"
2474
+ if [ -z "$AGENT_ID" ] && [ -f "$PROJECT_DIR/.mcp.json" ]; then
2475
+ AGENT_ID="$(jq -r '[.mcpServers[]?.env?.AGT_AGENT_ID // empty] | map(select(. != "")) | .[0] // empty' "$PROJECT_DIR/.mcp.json" 2>/dev/null || true)"
2476
+ fi
2477
+ HOST="\${AGT_HOST:-}"
2478
+ KEY="\${AGT_API_KEY:-}"
2479
+ [ -n "$AGENT_ID" ] && [ -n "$HOST" ] && [ -n "$KEY" ] || exit 0
2480
+
2481
+ JWT="$(curl -sf --connect-timeout 2 --max-time 5 -X POST "\${HOST}/host/exchange" -H 'Content-Type: application/json' -d "$(jq -n --arg k "$KEY" '{api_key:$k}')" 2>/dev/null | jq -r '.token // empty' 2>/dev/null || true)"
2482
+ [ -n "$JWT" ] || exit 0
2483
+
2484
+ BOARD="$(curl -sf --connect-timeout 2 --max-time 5 -X POST "\${HOST}/host/my-kanban" -H 'Content-Type: application/json' -H "Authorization: Bearer $JWT" -d "$(jq -n --arg a "$AGENT_ID" '{agent_id:$a}')" 2>/dev/null || true)"
2485
+ [ -n "$BOARD" ] || exit 0
2486
+
2487
+ # The counts that matter. \`waiting\` is deliberately NOT counted as work: a card
2488
+ # parked on a PR review or a human is someone else's work in progress, and
2489
+ # treating it as load is the exact loophole CS-1549 names. \`needs_attention\` is
2490
+ # excluded from the pullable set because the reaper parks stalled cards there
2491
+ # for a human \u2014 pulling one would just re-stall it.
2492
+ COUNTS="$(printf '%s' "$BOARD" | jq -c '
2493
+ (.items // []) as $i
2494
+ | {
2495
+ in_progress: ([$i[] | select(.status == "in_progress")] | length),
2496
+ todo: ([$i[] | select(.status == "todo")] | length),
2497
+ pullable: [$i[] | select(.status == "backlog")]
2498
+ }
2499
+ | {in_progress, todo, n: ($i | length), top: (.pullable | first), count: (.pullable | length)}
2500
+ ' 2>/dev/null || true)"
2501
+ [ -n "$COUNTS" ] || exit 0
2502
+
2503
+ IN_PROGRESS="$(printf '%s' "$COUNTS" | jq -r '.in_progress // 0')"
2504
+ TODO="$(printf '%s' "$COUNTS" | jq -r '.todo // 0')"
2505
+ BACKLOG_N="$(printf '%s' "$COUNTS" | jq -r '.count // 0')"
2506
+ TOP_ID="$(printf '%s' "$COUNTS" | jq -r '.top.id // empty')"
2507
+ TOP_TITLE="$(printf '%s' "$COUNTS" | jq -r '.top.title // empty')"
2508
+
2509
+ # Only fire when the agent is genuinely idle beside pullable work.
2510
+ [ "$IN_PROGRESS" = "0" ] || exit 0
2511
+ [ "$TODO" = "0" ] || exit 0
2512
+ [ "$BACKLOG_N" -gt 0 ] 2>/dev/null || exit 0
2513
+ [ -n "$TOP_ID" ] || exit 0
2514
+
2515
+ # Ledger: at most one block per distinct top-of-backlog item. If the agent
2516
+ # declines and states a reason, the turn ends and this item never blocks again \u2014
2517
+ # a NEW top item re-arms it. This is the override: it needs no approval rail,
2518
+ # and the stated reason lands in the transcript where it is reviewable.
2519
+ LEDGER_DIR="\${HOME:-/tmp}/.augmented/.backlog-pull/\${AGENT_ID}"
2520
+ mkdir -p "$LEDGER_DIR" 2>/dev/null || exit 0
2521
+ SAFE_ID="$(printf '%s' "$TOP_ID" | tr -c 'A-Za-z0-9_.-' '_')"
2522
+ LEDGER="$LEDGER_DIR/$SAFE_ID"
2523
+ # ATOMIC acquire (CodeRabbit on #4134). A [ -e ] test then a : > write is a TOCTOU:
2524
+ # two concurrent Stop events can both observe no entry and both block. noclobber
2525
+ # makes create-if-absent one atomic operation - what every other ledger in this
2526
+ # file already uses. A failure means either "already claimed" or "unwritable";
2527
+ # both must NOT block, so either way exit 0.
2528
+ if ! ( set -o noclobber; : > "$LEDGER" ) 2>/dev/null; then exit 0; fi
2529
+
2530
+ # Bound the ledger: one file per backlog item forever grows without limit on a
2531
+ # long-lived agent. Keep the newest 200. A pruned entry can at worst re-nudge
2532
+ # about an item that is still genuinely unpulled - the safe direction.
2533
+ LEDGER_N="$(ls -1 "$LEDGER_DIR" 2>/dev/null | wc -l | tr -d ' ')"
2534
+ if [ "\${LEDGER_N:-0}" -gt 200 ] 2>/dev/null; then
2535
+ ls -1t "$LEDGER_DIR" 2>/dev/null | tail -n +201 | while IFS= read -r stale; do
2536
+ rm -f "$LEDGER_DIR/$stale" 2>/dev/null || true
2537
+ done
2538
+ fi
2539
+
2540
+ echo "agt-backlog-pull-hook: FIRED backlog=\${BACKLOG_N} top=\${TOP_ID}" >&2
2541
+
2542
+ REASON="You are ending this turn with nothing in progress and nothing in todo, while \${BACKLOG_N} item(s) sit in your backlog. That is idle, not busy \u2014 cards in \\\`waiting\\\` are parked on someone else, so they are not your work in progress.
2543
+
2544
+ Pull the top item now: kanban_move(\\"\${TOP_ID}\\", \\"in_progress\\") \u2014 \\"\${TOP_TITLE}\\" \u2014 and start it.
2545
+
2546
+ If you genuinely should not pull it, say so in one line and why (it is deliberately parked, it is ambiguous and you have asked a specific question, or you are mid-flight on long-running external work). Then end the turn \u2014 this will not ask you again about this item."
2547
+
2548
+ jq -cn --arg r "$REASON" '{decision:"block", reason:$r}'
2549
+ exit 0
2550
+ `;
2551
+ writeFileSync3(hookScriptPath, hookScript, { mode: 493 });
2552
+ return hookScriptPath;
2553
+ }
2420
2554
  function provisionAutoKanbanProgressHook(codeName) {
2421
2555
  const projectDir = getProjectDir(codeName);
2422
2556
  const claudeDir = join2(projectDir, ".claude");
@@ -5317,7 +5451,7 @@ function exchangeFailureKind(err) {
5317
5451
  }
5318
5452
 
5319
5453
  // src/lib/api-client.ts
5320
- var agtCliVersion = true ? "0.28.526" : "dev";
5454
+ var agtCliVersion = true ? "0.28.528" : "dev";
5321
5455
  var lastConfigHash = null;
5322
5456
  function setConfigHash(hash) {
5323
5457
  lastConfigHash = hash && hash.length > 0 ? hash : null;
@@ -8629,4 +8763,4 @@ export {
8629
8763
  managerInstallSystemUnitCommand,
8630
8764
  managerUninstallSystemUnitCommand
8631
8765
  };
8632
- //# sourceMappingURL=chunk-ONTG4Z5K.js.map
8766
+ //# sourceMappingURL=chunk-7N55RDMG.js.map