@integrity-labs/agt-cli 0.19.22 → 0.19.25

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-TD4ZSQ74.js";
25
+ } from "../chunk-EJKRQX3I.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-ZQNOWGHB.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
@@ -2060,10 +2139,10 @@ function clearAgentCaches(agentId, codeName) {
2060
2139
  var cachedFrameworkVersion = null;
2061
2140
  var lastVersionCheckAt = 0;
2062
2141
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
2063
- var agtCliVersion = true ? "0.19.22" : "dev";
2064
- function resolveBrewPath(execFileSync3) {
2142
+ var agtCliVersion = true ? "0.19.25" : "dev";
2143
+ function resolveBrewPath(execFileSync4) {
2065
2144
  try {
2066
- const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
2145
+ const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
2067
2146
  if (out) return out;
2068
2147
  } catch {
2069
2148
  }
@@ -2105,9 +2184,9 @@ async function ensureToolkitCli(toolkitSlug) {
2105
2184
  }
2106
2185
  const { binary, installer, package: pkg, script } = integration.cli_tool;
2107
2186
  const resolvedInstaller = installer ?? "manual";
2108
- const { execFileSync: execFileSync3, execSync } = await import("child_process");
2187
+ const { execFileSync: execFileSync4, execSync } = await import("child_process");
2109
2188
  try {
2110
- execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
2189
+ execFileSync4("which", [binary], { timeout: 5e3, stdio: "pipe" });
2111
2190
  toolkitCliEnsured.add(toolkitSlug);
2112
2191
  toolkitCliRetryAfter.delete(toolkitSlug);
2113
2192
  toolkitCliFailureCount.delete(toolkitSlug);
@@ -2128,14 +2207,14 @@ async function ensureToolkitCli(toolkitSlug) {
2128
2207
  return;
2129
2208
  }
2130
2209
  log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})\u2026`);
2131
- execFileSync3("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
2210
+ execFileSync4("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
2132
2211
  } else if (resolvedInstaller === "brew") {
2133
2212
  if (!pkg) {
2134
2213
  log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);
2135
2214
  toolkitCliEnsured.add(toolkitSlug);
2136
2215
  return;
2137
2216
  }
2138
- const brewPath = resolveBrewPath(execFileSync3);
2217
+ const brewPath = resolveBrewPath(execFileSync4);
2139
2218
  if (!brewPath) {
2140
2219
  log(`[toolkit-install] ${toolkitSlug}: installer=brew but Homebrew not available \u2014 install manually: brew install ${pkg}`);
2141
2220
  toolkitCliEnsured.add(toolkitSlug);
@@ -2145,9 +2224,9 @@ async function ensureToolkitCli(toolkitSlug) {
2145
2224
  const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
2146
2225
  log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})\u2026`);
2147
2226
  if (isRoot) {
2148
- execFileSync3("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe", cwd: "/tmp" });
2227
+ execFileSync4("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe", cwd: "/tmp" });
2149
2228
  } else {
2150
- execFileSync3(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
2229
+ execFileSync4(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
2151
2230
  }
2152
2231
  } else if (resolvedInstaller === "script") {
2153
2232
  if (!script) {
@@ -2167,7 +2246,7 @@ async function ensureToolkitCli(toolkitSlug) {
2167
2246
  process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
2168
2247
  }
2169
2248
  try {
2170
- execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
2249
+ execFileSync4("which", [binary], { timeout: 5e3, stdio: "pipe" });
2171
2250
  log(`[toolkit-install] ${toolkitSlug}: installed \u2014 ${binary} now on PATH`);
2172
2251
  toolkitCliEnsured.add(toolkitSlug);
2173
2252
  toolkitCliRetryAfter.delete(toolkitSlug);
@@ -2220,8 +2299,8 @@ async function ensureFrameworkBinary(frameworkId) {
2220
2299
  if (frameworkId !== "claude-code") return;
2221
2300
  if (frameworkBinaryChecked.has(frameworkId)) return;
2222
2301
  frameworkBinaryChecked.add(frameworkId);
2223
- const { execFileSync: execFileSync3 } = await import("child_process");
2224
- const brewPath = resolveBrewPath(execFileSync3);
2302
+ const { execFileSync: execFileSync4 } = await import("child_process");
2303
+ const brewPath = resolveBrewPath(execFileSync4);
2225
2304
  if (!brewPath) {
2226
2305
  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
2306
  return;
@@ -2237,7 +2316,7 @@ async function ensureFrameworkBinary(frameworkId) {
2237
2316
  let claudeExists = existsSync3("/home/linuxbrew/.linuxbrew/bin/claude");
2238
2317
  if (!claudeExists) {
2239
2318
  try {
2240
- execFileSync3("which", ["claude"], { timeout: 5e3 });
2319
+ execFileSync4("which", ["claude"], { timeout: 5e3 });
2241
2320
  claudeExists = true;
2242
2321
  } catch {
2243
2322
  }
@@ -2329,16 +2408,16 @@ async function checkAndUpdateCli() {
2329
2408
  }
2330
2409
  }
2331
2410
  async function checkAndUpdateCliViaBrew() {
2332
- const { execFileSync: execFileSync3 } = await import("child_process");
2333
- const brewPath = resolveBrewPath(execFileSync3);
2411
+ const { execFileSync: execFileSync4 } = await import("child_process");
2412
+ const brewPath = resolveBrewPath(execFileSync4);
2334
2413
  if (!brewPath) return;
2335
2414
  try {
2336
- execFileSync3(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
2415
+ execFileSync4(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
2337
2416
  } catch (err) {
2338
2417
  log(`[self-update] brew update failed (continuing with stale cache): ${err.message}`);
2339
2418
  }
2340
2419
  try {
2341
- const outdated = execFileSync3(brewPath, ["outdated", "--json=v2"], {
2420
+ const outdated = execFileSync4(brewPath, ["outdated", "--json=v2"], {
2342
2421
  timeout: 3e4,
2343
2422
  encoding: "utf-8"
2344
2423
  });
@@ -2349,7 +2428,7 @@ async function checkAndUpdateCliViaBrew() {
2349
2428
  const latest = agtOutdated.current_version ?? "unknown";
2350
2429
  log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading via brew...`);
2351
2430
  try {
2352
- execFileSync3(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
2431
+ execFileSync4(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
2353
2432
  timeout: 12e4,
2354
2433
  stdio: "pipe"
2355
2434
  });
@@ -2368,7 +2447,7 @@ async function checkAndUpdateCliViaBrew() {
2368
2447
  }
2369
2448
  }
2370
2449
  async function checkAndUpdateCliViaNpm() {
2371
- const { execFileSync: execFileSync3 } = await import("child_process");
2450
+ const { execFileSync: execFileSync4 } = await import("child_process");
2372
2451
  if (agtCliVersion === "dev") return;
2373
2452
  let latest;
2374
2453
  try {
@@ -2417,7 +2496,7 @@ async function checkAndUpdateCliViaNpm() {
2417
2496
  "--registry=https://registry.npmjs.org"
2418
2497
  ];
2419
2498
  try {
2420
- execFileSync3(cmd, args, { timeout: 18e4, stdio: "pipe" });
2499
+ execFileSync4(cmd, args, { timeout: 18e4, stdio: "pipe" });
2421
2500
  log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
2422
2501
  restartAfterUpgrade = true;
2423
2502
  pendingUpgradeVersion = latest;
@@ -2895,6 +2974,7 @@ Automatic restart failed: ${err.message}`
2895
2974
  }
2896
2975
  }
2897
2976
  }
2977
+ var consecutivePollFailures = 0;
2898
2978
  async function pollCycle() {
2899
2979
  if (!config) return;
2900
2980
  if (restartAfterUpgrade) return;
@@ -2963,7 +3043,7 @@ async function pollCycle() {
2963
3043
  }
2964
3044
  try {
2965
3045
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
2966
- const { collectDiagnostics } = await import("../persistent-session-M2GVL6Z6.js");
3046
+ const { collectDiagnostics } = await import("../persistent-session-J2K3JFJQ.js");
2967
3047
  const diagCodeNames = [...persistentSessionAgents];
2968
3048
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
2969
3049
  let tailscaleHostname;
@@ -3193,17 +3273,23 @@ async function pollCycle() {
3193
3273
  pollCount: state.pollCount + 1,
3194
3274
  agents: agentStates
3195
3275
  };
3276
+ if (consecutivePollFailures > 0) {
3277
+ log(`[poll-backoff] recovered after ${consecutivePollFailures} failure(s), resuming normal interval`);
3278
+ consecutivePollFailures = 0;
3279
+ }
3196
3280
  send({ type: "state-update", state });
3197
3281
  } catch (err) {
3198
3282
  state.errorCount++;
3199
3283
  const message = err.message;
3200
3284
  log(`Poll error: ${message}`);
3201
3285
  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
- }
3286
+ consecutivePollFailures += 1;
3287
+ const scheduledDelayMs = Math.max(
3288
+ config?.intervalMs ?? 0,
3289
+ currentPollBackoffMs(consecutivePollFailures)
3290
+ );
3291
+ const errorClass = classifyPollError(message);
3292
+ log(`[poll-backoff] attempt=${consecutivePollFailures} class=${errorClass} next_retry_ms=${scheduledDelayMs}`);
3207
3293
  }
3208
3294
  }
3209
3295
  async function getOrCacheRegisteredAgents(adapter, profile) {
@@ -4136,6 +4222,24 @@ async function processAgent(agent, agentStates) {
4136
4222
  } catch (err) {
4137
4223
  log(`[hot-reload] .mcp.json drift check failed for '${agent.code_name}': ${err.message}`);
4138
4224
  }
4225
+ try {
4226
+ const sess = getSessionState(agent.code_name);
4227
+ let mcpJsonParsed = null;
4228
+ try {
4229
+ const mcpPath = join4(getProjectDir2(agent.code_name), ".mcp.json");
4230
+ mcpJsonParsed = JSON.parse(readFileSync3(mcpPath, "utf-8"));
4231
+ } catch {
4232
+ }
4233
+ reapMissingMcpSessions({
4234
+ log,
4235
+ codeName: agent.code_name,
4236
+ mcpJson: mcpJsonParsed,
4237
+ sessionStartedAt: sess?.startedAt ?? null,
4238
+ stopSession: (codeName) => stopPersistentSessionAndForgetMcpBaseline(codeName)
4239
+ });
4240
+ } catch (err) {
4241
+ log(`[mcp-presence-reaper] presence check failed for '${agent.code_name}': ${err.message}`);
4242
+ }
4139
4243
  } else if (agentFw === "claude-code" && tasks.length > 0) {
4140
4244
  await syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData);
4141
4245
  } else if (frameworkAdapter.syncScheduledTasks && gatewayRunning && gatewayPort) {
@@ -4951,6 +5055,11 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
4951
5055
  } catch (err) {
4952
5056
  log(`[persistent-session] Failed to provision isolation hook for '${codeName}': ${err.message}`);
4953
5057
  }
5058
+ const sessionRunResult = await startRun({
5059
+ agent_id: agent.agent_id,
5060
+ source_type: "system",
5061
+ metadata: { type: "persistent_session_boot" }
5062
+ });
4954
5063
  startPersistentSession({
4955
5064
  codeName,
4956
5065
  agentId: agent.agent_id,
@@ -4962,6 +5071,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
4962
5071
  apiHost: requireHost(),
4963
5072
  claudeAuthMode,
4964
5073
  anthropicApiKey,
5074
+ runId: sessionRunResult.run_id,
4965
5075
  log
4966
5076
  });
4967
5077
  persistentSessionAgents.add(codeName);
@@ -6299,7 +6409,7 @@ async function processClaudePairSessions(agents) {
6299
6409
  killPairSession,
6300
6410
  pairTmuxSession,
6301
6411
  finalizeClaudePairOnboarding
6302
- } = await import("../claude-pair-runtime-UF4OMFCA.js");
6412
+ } = await import("../claude-pair-runtime-LI2ISCVI.js");
6303
6413
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
6304
6414
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
6305
6415
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -6810,13 +6920,15 @@ function scheduleNext() {
6810
6920
  restartNow();
6811
6921
  return;
6812
6922
  }
6923
+ const backoffMs = currentPollBackoffMs(consecutivePollFailures);
6924
+ const delayMs = Math.max(config.intervalMs, backoffMs);
6813
6925
  pollTimer = setTimeout(() => {
6814
6926
  if (restartAfterUpgrade) {
6815
6927
  restartNow();
6816
6928
  return;
6817
6929
  }
6818
6930
  void pollCycle().then(scheduleNext);
6819
- }, config.intervalMs);
6931
+ }, delayMs);
6820
6932
  }
6821
6933
  async function killAllAgtTmuxSessions() {
6822
6934
  try {