@integrity-labs/agt-cli 0.19.23 → 0.19.26

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.
@@ -22,7 +22,7 @@ import {
22
22
  resolveChannels,
23
23
  resolveDmTarget,
24
24
  wrapScheduledTaskPrompt
25
- } from "../chunk-EPYGSR3M.js";
25
+ } from "../chunk-V7PNYKIT.js";
26
26
  import {
27
27
  findTaskByTemplate,
28
28
  getProjectDir,
@@ -35,6 +35,7 @@ import {
35
35
  buildAllowedTools,
36
36
  getLastFailureContext,
37
37
  getProjectDir as getProjectDir2,
38
+ getSessionState,
38
39
  injectMessage,
39
40
  isAgentIdle,
40
41
  isSessionHealthy,
@@ -49,7 +50,7 @@ import {
49
50
  startPersistentSession,
50
51
  stopAllSessionsAndWait,
51
52
  stopPersistentSession
52
- } from "../chunk-3K3RO5NS.js";
53
+ } from "../chunk-55SJYI7V.js";
53
54
 
54
55
  // src/lib/manager-worker.ts
55
56
  import { createHash as createHash2 } from "crypto";
@@ -295,6 +296,68 @@ function reapStaleMcpChildren(args) {
295
296
  return targets;
296
297
  }
297
298
 
299
+ // src/lib/mcp-presence-reaper.ts
300
+ import { execFileSync as execFileSync2 } from "child_process";
301
+ var DEFAULT_COLD_START_GRACE_MS = 9e4;
302
+ function findMissingMcpServers(args) {
303
+ const { rows, codeName, mcpJson } = args;
304
+ const declared = Object.keys(mcpJson?.mcpServers ?? {});
305
+ if (declared.length === 0) return [];
306
+ const missing = [];
307
+ for (const key of declared) {
308
+ const live = findMcpChildrenForAgent({
309
+ rows,
310
+ codeName,
311
+ serverKeys: [key],
312
+ mcpJson
313
+ });
314
+ if (live.length === 0) missing.push(key);
315
+ }
316
+ return missing;
317
+ }
318
+ function reapMissingMcpSessions(args) {
319
+ const {
320
+ log: log2,
321
+ codeName,
322
+ mcpJson,
323
+ sessionStartedAt,
324
+ stopSession,
325
+ graceMs = DEFAULT_COLD_START_GRACE_MS
326
+ } = args;
327
+ const now = args.now ?? Date.now;
328
+ const runPs = args.runPs ?? (() => execFileSync2("ps", ["-eo", "pid,ppid,args"], { encoding: "utf-8", timeout: 5e3 }));
329
+ if (!mcpJson?.mcpServers) {
330
+ return { missing: [], restarted: false, reason: "no-mcp-json" };
331
+ }
332
+ const declaredCount = Object.keys(mcpJson.mcpServers).length;
333
+ if (declaredCount === 0) {
334
+ return { missing: [], restarted: false, reason: "no-declared-servers" };
335
+ }
336
+ if (sessionStartedAt === null) {
337
+ return { missing: [], restarted: false, reason: "session-start-unknown" };
338
+ }
339
+ if (now() - sessionStartedAt < graceMs) {
340
+ return { missing: [], restarted: false, reason: "cold-start" };
341
+ }
342
+ let psOutput;
343
+ try {
344
+ psOutput = runPs();
345
+ } catch (err) {
346
+ log2(`[mcp-presence-reaper] ps invocation failed for '${codeName}': ${err.message} \u2014 skipping`);
347
+ return { missing: [], restarted: false, reason: "healthy" };
348
+ }
349
+ const rows = parsePsRows(psOutput);
350
+ const missing = findMissingMcpServers({ rows, codeName, mcpJson });
351
+ if (missing.length === 0) {
352
+ return { missing, restarted: false, reason: "healthy" };
353
+ }
354
+ log2(
355
+ `[mcp-presence-reaper] '${codeName}': declared MCP(s) [${missing.join(", ")}] have no live children \u2014 restarting session`
356
+ );
357
+ stopSession(codeName);
358
+ return { missing, restarted: true };
359
+ }
360
+
298
361
  // src/lib/channel-restart-decision.ts
299
362
  function launchableChannelIds(channelConfigs) {
300
363
  if (!channelConfigs) return /* @__PURE__ */ new Set();
@@ -323,6 +386,22 @@ function decideChannelRestart(input) {
323
386
  return { restart: true, added, removed };
324
387
  }
325
388
 
389
+ // src/lib/poll-backoff.ts
390
+ var POLL_BACKOFF_BASE_MS = 1e3;
391
+ var POLL_BACKOFF_MAX_MS = 6e4;
392
+ function currentPollBackoffMs(consecutivePollFailures2) {
393
+ if (consecutivePollFailures2 <= 0) return 0;
394
+ const expDelay = POLL_BACKOFF_BASE_MS * Math.pow(2, consecutivePollFailures2 - 1);
395
+ return Math.min(POLL_BACKOFF_MAX_MS, expDelay);
396
+ }
397
+ function classifyPollError(message) {
398
+ const msg = message.toLowerCase();
399
+ if (msg.includes("api unreachable") || /\b5\d\d\b/.test(msg) || msg.includes("internal server error") || msg.includes("bad gateway")) return "5xx";
400
+ if (msg.includes("exchange failed") || /\b401\b/.test(msg) || msg.includes("invalid token")) return "auth";
401
+ if (msg.includes("econn") || msg.includes("etimedout") || msg.includes("fetch failed")) return "network";
402
+ return "other";
403
+ }
404
+
326
405
  // src/lib/integration-context-render.ts
327
406
  var PLUGIN_CONTEXT_PLACEHOLDER_RE = /\{\{\s*context\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
328
407
  var TEAM_OVERRIDES_HEADER = "## Team Overrides\n\n> **The following overrides anything you've read above.** If any rule here conflicts with earlier instructions in this skill, follow what is written here. These are user-supplied directives that take precedence over the plugin's default guidance.\n";
@@ -885,7 +964,7 @@ function saveChannelHashCache(source, configDir) {
885
964
  }
886
965
 
887
966
  // src/lib/channel-sweep.ts
888
- import { execFileSync as execFileSync2 } from "child_process";
967
+ import { execFileSync as execFileSync3 } from "child_process";
889
968
  var CHANNEL_BASENAMES = [
890
969
  "slack-channel",
891
970
  "direct-chat-channel",
@@ -1048,7 +1127,7 @@ function resolveLiveAnchorPids(agentCodeNames) {
1048
1127
  for (const codeName of agentCodeNames) {
1049
1128
  const pids = /* @__PURE__ */ new Set();
1050
1129
  try {
1051
- const out = execFileSync2("tmux", ["list-panes", "-t", `agt-${codeName}`, "-F", "#{pane_pid}"], {
1130
+ const out = execFileSync3("tmux", ["list-panes", "-t", `agt-${codeName}`, "-F", "#{pane_pid}"], {
1052
1131
  encoding: "utf-8",
1053
1132
  timeout: 2e3,
1054
1133
  stdio: ["ignore", "pipe", "ignore"]
@@ -1068,7 +1147,7 @@ async function sweepChannelProcesses(opts) {
1068
1147
  const kill = opts.killFn ?? defaultKill;
1069
1148
  let psOutput = "";
1070
1149
  try {
1071
- psOutput = execFileSync2("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
1150
+ psOutput = execFileSync3("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
1072
1151
  encoding: "utf-8",
1073
1152
  timeout: 5e3,
1074
1153
  maxBuffer: 10 * 1024 * 1024
@@ -1105,7 +1184,7 @@ function defaultKillSignal(pid, signal) {
1105
1184
  }
1106
1185
  }
1107
1186
  function defaultPs() {
1108
- return execFileSync2("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
1187
+ return execFileSync3("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
1109
1188
  encoding: "utf-8",
1110
1189
  timeout: 5e3,
1111
1190
  maxBuffer: 10 * 1024 * 1024
@@ -1955,9 +2034,10 @@ function cancelPendingSessionRestart(codeName) {
1955
2034
  var writtenHashes = /* @__PURE__ */ new Map();
1956
2035
  var knownSecretsHashes = /* @__PURE__ */ new Map();
1957
2036
  var runningMcpHashes = /* @__PURE__ */ new Map();
1958
- function projectMcpHash(codeName, projectDir) {
2037
+ function projectMcpHash(_codeName, projectDir) {
1959
2038
  try {
1960
- return createHash2("sha256").update(readFileSync3(join4(projectDir, ".mcp.json"))).digest("hex");
2039
+ const raw = readFileSync3(join4(projectDir, ".mcp.json"), "utf-8");
2040
+ return createHash2("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
1961
2041
  } catch {
1962
2042
  return null;
1963
2043
  }
@@ -2060,10 +2140,10 @@ function clearAgentCaches(agentId, codeName) {
2060
2140
  var cachedFrameworkVersion = null;
2061
2141
  var lastVersionCheckAt = 0;
2062
2142
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
2063
- var agtCliVersion = true ? "0.19.23" : "dev";
2064
- function resolveBrewPath(execFileSync3) {
2143
+ var agtCliVersion = true ? "0.19.26" : "dev";
2144
+ function resolveBrewPath(execFileSync4) {
2065
2145
  try {
2066
- const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
2146
+ const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
2067
2147
  if (out) return out;
2068
2148
  } catch {
2069
2149
  }
@@ -2105,9 +2185,9 @@ async function ensureToolkitCli(toolkitSlug) {
2105
2185
  }
2106
2186
  const { binary, installer, package: pkg, script } = integration.cli_tool;
2107
2187
  const resolvedInstaller = installer ?? "manual";
2108
- const { execFileSync: execFileSync3, execSync } = await import("child_process");
2188
+ const { execFileSync: execFileSync4, execSync } = await import("child_process");
2109
2189
  try {
2110
- execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
2190
+ execFileSync4("which", [binary], { timeout: 5e3, stdio: "pipe" });
2111
2191
  toolkitCliEnsured.add(toolkitSlug);
2112
2192
  toolkitCliRetryAfter.delete(toolkitSlug);
2113
2193
  toolkitCliFailureCount.delete(toolkitSlug);
@@ -2128,14 +2208,14 @@ async function ensureToolkitCli(toolkitSlug) {
2128
2208
  return;
2129
2209
  }
2130
2210
  log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})\u2026`);
2131
- execFileSync3("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
2211
+ execFileSync4("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
2132
2212
  } else if (resolvedInstaller === "brew") {
2133
2213
  if (!pkg) {
2134
2214
  log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);
2135
2215
  toolkitCliEnsured.add(toolkitSlug);
2136
2216
  return;
2137
2217
  }
2138
- const brewPath = resolveBrewPath(execFileSync3);
2218
+ const brewPath = resolveBrewPath(execFileSync4);
2139
2219
  if (!brewPath) {
2140
2220
  log(`[toolkit-install] ${toolkitSlug}: installer=brew but Homebrew not available \u2014 install manually: brew install ${pkg}`);
2141
2221
  toolkitCliEnsured.add(toolkitSlug);
@@ -2145,9 +2225,9 @@ async function ensureToolkitCli(toolkitSlug) {
2145
2225
  const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
2146
2226
  log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})\u2026`);
2147
2227
  if (isRoot) {
2148
- execFileSync3("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe", cwd: "/tmp" });
2228
+ execFileSync4("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe", cwd: "/tmp" });
2149
2229
  } else {
2150
- execFileSync3(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
2230
+ execFileSync4(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
2151
2231
  }
2152
2232
  } else if (resolvedInstaller === "script") {
2153
2233
  if (!script) {
@@ -2167,7 +2247,7 @@ async function ensureToolkitCli(toolkitSlug) {
2167
2247
  process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
2168
2248
  }
2169
2249
  try {
2170
- execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
2250
+ execFileSync4("which", [binary], { timeout: 5e3, stdio: "pipe" });
2171
2251
  log(`[toolkit-install] ${toolkitSlug}: installed \u2014 ${binary} now on PATH`);
2172
2252
  toolkitCliEnsured.add(toolkitSlug);
2173
2253
  toolkitCliRetryAfter.delete(toolkitSlug);
@@ -2220,8 +2300,8 @@ async function ensureFrameworkBinary(frameworkId) {
2220
2300
  if (frameworkId !== "claude-code") return;
2221
2301
  if (frameworkBinaryChecked.has(frameworkId)) return;
2222
2302
  frameworkBinaryChecked.add(frameworkId);
2223
- const { execFileSync: execFileSync3 } = await import("child_process");
2224
- const brewPath = resolveBrewPath(execFileSync3);
2303
+ const { execFileSync: execFileSync4 } = await import("child_process");
2304
+ const brewPath = resolveBrewPath(execFileSync4);
2225
2305
  if (!brewPath) {
2226
2306
  log("Homebrew not found (no `brew` on PATH, no /home/linuxbrew/.linuxbrew/bin/brew). Cannot auto-install Claude Code. Install manually: https://claude.ai/download");
2227
2307
  return;
@@ -2237,7 +2317,7 @@ async function ensureFrameworkBinary(frameworkId) {
2237
2317
  let claudeExists = existsSync3("/home/linuxbrew/.linuxbrew/bin/claude");
2238
2318
  if (!claudeExists) {
2239
2319
  try {
2240
- execFileSync3("which", ["claude"], { timeout: 5e3 });
2320
+ execFileSync4("which", ["claude"], { timeout: 5e3 });
2241
2321
  claudeExists = true;
2242
2322
  } catch {
2243
2323
  }
@@ -2329,16 +2409,16 @@ async function checkAndUpdateCli() {
2329
2409
  }
2330
2410
  }
2331
2411
  async function checkAndUpdateCliViaBrew() {
2332
- const { execFileSync: execFileSync3 } = await import("child_process");
2333
- const brewPath = resolveBrewPath(execFileSync3);
2412
+ const { execFileSync: execFileSync4 } = await import("child_process");
2413
+ const brewPath = resolveBrewPath(execFileSync4);
2334
2414
  if (!brewPath) return;
2335
2415
  try {
2336
- execFileSync3(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
2416
+ execFileSync4(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
2337
2417
  } catch (err) {
2338
2418
  log(`[self-update] brew update failed (continuing with stale cache): ${err.message}`);
2339
2419
  }
2340
2420
  try {
2341
- const outdated = execFileSync3(brewPath, ["outdated", "--json=v2"], {
2421
+ const outdated = execFileSync4(brewPath, ["outdated", "--json=v2"], {
2342
2422
  timeout: 3e4,
2343
2423
  encoding: "utf-8"
2344
2424
  });
@@ -2349,7 +2429,7 @@ async function checkAndUpdateCliViaBrew() {
2349
2429
  const latest = agtOutdated.current_version ?? "unknown";
2350
2430
  log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading via brew...`);
2351
2431
  try {
2352
- execFileSync3(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
2432
+ execFileSync4(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
2353
2433
  timeout: 12e4,
2354
2434
  stdio: "pipe"
2355
2435
  });
@@ -2368,7 +2448,7 @@ async function checkAndUpdateCliViaBrew() {
2368
2448
  }
2369
2449
  }
2370
2450
  async function checkAndUpdateCliViaNpm() {
2371
- const { execFileSync: execFileSync3 } = await import("child_process");
2451
+ const { execFileSync: execFileSync4 } = await import("child_process");
2372
2452
  if (agtCliVersion === "dev") return;
2373
2453
  let latest;
2374
2454
  try {
@@ -2417,7 +2497,7 @@ async function checkAndUpdateCliViaNpm() {
2417
2497
  "--registry=https://registry.npmjs.org"
2418
2498
  ];
2419
2499
  try {
2420
- execFileSync3(cmd, args, { timeout: 18e4, stdio: "pipe" });
2500
+ execFileSync4(cmd, args, { timeout: 18e4, stdio: "pipe" });
2421
2501
  log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
2422
2502
  restartAfterUpgrade = true;
2423
2503
  pendingUpgradeVersion = latest;
@@ -2895,6 +2975,7 @@ Automatic restart failed: ${err.message}`
2895
2975
  }
2896
2976
  }
2897
2977
  }
2978
+ var consecutivePollFailures = 0;
2898
2979
  async function pollCycle() {
2899
2980
  if (!config) return;
2900
2981
  if (restartAfterUpgrade) return;
@@ -2963,7 +3044,7 @@ async function pollCycle() {
2963
3044
  }
2964
3045
  try {
2965
3046
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
2966
- const { collectDiagnostics } = await import("../persistent-session-M2GVL6Z6.js");
3047
+ const { collectDiagnostics } = await import("../persistent-session-BVR3HES5.js");
2967
3048
  const diagCodeNames = [...persistentSessionAgents];
2968
3049
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
2969
3050
  let tailscaleHostname;
@@ -3193,17 +3274,23 @@ async function pollCycle() {
3193
3274
  pollCount: state.pollCount + 1,
3194
3275
  agents: agentStates
3195
3276
  };
3277
+ if (consecutivePollFailures > 0) {
3278
+ log(`[poll-backoff] recovered after ${consecutivePollFailures} failure(s), resuming normal interval`);
3279
+ consecutivePollFailures = 0;
3280
+ }
3196
3281
  send({ type: "state-update", state });
3197
3282
  } catch (err) {
3198
3283
  state.errorCount++;
3199
3284
  const message = err.message;
3200
3285
  log(`Poll error: ${message}`);
3201
3286
  send({ type: "error", message });
3202
- if (message.includes("exchange failed") || message.includes("401")) {
3203
- log("Fatal auth error, exiting for watchdog restart");
3204
- send({ type: "shutdown" });
3205
- process.exit(1);
3206
- }
3287
+ consecutivePollFailures += 1;
3288
+ const scheduledDelayMs = Math.max(
3289
+ config?.intervalMs ?? 0,
3290
+ currentPollBackoffMs(consecutivePollFailures)
3291
+ );
3292
+ const errorClass = classifyPollError(message);
3293
+ log(`[poll-backoff] attempt=${consecutivePollFailures} class=${errorClass} next_retry_ms=${scheduledDelayMs}`);
3207
3294
  }
3208
3295
  }
3209
3296
  async function getOrCacheRegisteredAgents(adapter, profile) {
@@ -4136,6 +4223,24 @@ async function processAgent(agent, agentStates) {
4136
4223
  } catch (err) {
4137
4224
  log(`[hot-reload] .mcp.json drift check failed for '${agent.code_name}': ${err.message}`);
4138
4225
  }
4226
+ try {
4227
+ const sess = getSessionState(agent.code_name);
4228
+ let mcpJsonParsed = null;
4229
+ try {
4230
+ const mcpPath = join4(getProjectDir2(agent.code_name), ".mcp.json");
4231
+ mcpJsonParsed = JSON.parse(readFileSync3(mcpPath, "utf-8"));
4232
+ } catch {
4233
+ }
4234
+ reapMissingMcpSessions({
4235
+ log,
4236
+ codeName: agent.code_name,
4237
+ mcpJson: mcpJsonParsed,
4238
+ sessionStartedAt: sess?.startedAt ?? null,
4239
+ stopSession: (codeName) => stopPersistentSessionAndForgetMcpBaseline(codeName)
4240
+ });
4241
+ } catch (err) {
4242
+ log(`[mcp-presence-reaper] presence check failed for '${agent.code_name}': ${err.message}`);
4243
+ }
4139
4244
  } else if (agentFw === "claude-code" && tasks.length > 0) {
4140
4245
  await syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData);
4141
4246
  } else if (frameworkAdapter.syncScheduledTasks && gatewayRunning && gatewayPort) {
@@ -4951,6 +5056,11 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
4951
5056
  } catch (err) {
4952
5057
  log(`[persistent-session] Failed to provision isolation hook for '${codeName}': ${err.message}`);
4953
5058
  }
5059
+ const sessionRunResult = await startRun({
5060
+ agent_id: agent.agent_id,
5061
+ source_type: "system",
5062
+ metadata: { type: "persistent_session_boot" }
5063
+ });
4954
5064
  startPersistentSession({
4955
5065
  codeName,
4956
5066
  agentId: agent.agent_id,
@@ -4962,6 +5072,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
4962
5072
  apiHost: requireHost(),
4963
5073
  claudeAuthMode,
4964
5074
  anthropicApiKey,
5075
+ runId: sessionRunResult.run_id,
4965
5076
  log
4966
5077
  });
4967
5078
  persistentSessionAgents.add(codeName);
@@ -6299,7 +6410,7 @@ async function processClaudePairSessions(agents) {
6299
6410
  killPairSession,
6300
6411
  pairTmuxSession,
6301
6412
  finalizeClaudePairOnboarding
6302
- } = await import("../claude-pair-runtime-UF4OMFCA.js");
6413
+ } = await import("../claude-pair-runtime-WA4BYPN5.js");
6303
6414
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
6304
6415
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
6305
6416
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -6810,13 +6921,15 @@ function scheduleNext() {
6810
6921
  restartNow();
6811
6922
  return;
6812
6923
  }
6924
+ const backoffMs = currentPollBackoffMs(consecutivePollFailures);
6925
+ const delayMs = Math.max(config.intervalMs, backoffMs);
6813
6926
  pollTimer = setTimeout(() => {
6814
6927
  if (restartAfterUpgrade) {
6815
6928
  restartNow();
6816
6929
  return;
6817
6930
  }
6818
6931
  void pollCycle().then(scheduleNext);
6819
- }, config.intervalMs);
6932
+ }, delayMs);
6820
6933
  }
6821
6934
  async function killAllAgtTmuxSessions() {
6822
6935
  try {