@integrity-labs/agt-cli 0.19.14 → 0.19.15

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.
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-CFDGW7QE.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-M2GVL6Z6.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  try {
106
106
  await execFileAsync("tmux", [
@@ -357,4 +357,4 @@ export {
357
357
  startClaudePair,
358
358
  submitClaudePairCode
359
359
  };
360
- //# sourceMappingURL=claude-pair-runtime-VXN4NVGR.js.map
360
+ //# sourceMappingURL=claude-pair-runtime-UF4OMFCA.js.map
@@ -22,7 +22,7 @@ import {
22
22
  resolveChannels,
23
23
  resolveDmTarget,
24
24
  wrapScheduledTaskPrompt
25
- } from "../chunk-KLNFWXOI.js";
25
+ } from "../chunk-DVWBVANP.js";
26
26
  import {
27
27
  findTaskByTemplate,
28
28
  getProjectDir,
@@ -39,6 +39,7 @@ import {
39
39
  isAgentIdle,
40
40
  isSessionHealthy,
41
41
  isStaleForToday,
42
+ parsePsRows,
42
43
  peekCurrentSession,
43
44
  prepareForRespawn,
44
45
  reapOrphanChannelMcps,
@@ -48,7 +49,7 @@ import {
48
49
  startPersistentSession,
49
50
  stopAllSessionsAndWait,
50
51
  stopPersistentSession
51
- } from "../chunk-HA4IUBVC.js";
52
+ } from "../chunk-3K3RO5NS.js";
52
53
 
53
54
  // src/lib/manager-worker.ts
54
55
  import { createHash } from "crypto";
@@ -59,6 +60,160 @@ import { join as join4, dirname } from "path";
59
60
  import { homedir as homedir3 } from "os";
60
61
  import { fileURLToPath } from "url";
61
62
 
63
+ // src/lib/stale-mcp-reaper.ts
64
+ import { execFileSync } from "child_process";
65
+ function parseEnvIntegrationsVars(content) {
66
+ const names = /* @__PURE__ */ new Set();
67
+ for (const raw of content.split(/\r?\n/)) {
68
+ const line = raw.trim();
69
+ if (!line || line.startsWith("#")) continue;
70
+ const eq = line.indexOf("=");
71
+ if (eq <= 0) continue;
72
+ const name = line.slice(0, eq).trim();
73
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) continue;
74
+ names.add(name);
75
+ }
76
+ return [...names];
77
+ }
78
+ function findMcpServersUsingVars(mcp, changedVars) {
79
+ const changedSet = new Set(changedVars);
80
+ if (!mcp?.mcpServers || changedSet.size === 0) return [];
81
+ const result = [];
82
+ for (const [serverKey, entry] of Object.entries(mcp.mcpServers)) {
83
+ if (!entry || typeof entry !== "object") continue;
84
+ const env = entry.env;
85
+ if (!env || typeof env !== "object") continue;
86
+ let matches = false;
87
+ for (const value of Object.values(env)) {
88
+ if (typeof value !== "string") continue;
89
+ const placeholderMatches = value.match(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g);
90
+ if (placeholderMatches) {
91
+ for (const ph of placeholderMatches) {
92
+ const name = ph.slice(2, -1);
93
+ if (changedSet.has(name)) {
94
+ matches = true;
95
+ break;
96
+ }
97
+ }
98
+ }
99
+ if (matches) break;
100
+ }
101
+ if (matches) result.push(serverKey);
102
+ }
103
+ return result;
104
+ }
105
+ function buildArgvMatchers(serverKeys) {
106
+ const patterns = [];
107
+ for (const key of serverKeys) {
108
+ const safe = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
109
+ patterns.push(new RegExp(`\\b${safe}\\b`));
110
+ if (safe.includes("_")) {
111
+ const dashed = safe.replace(/_/g, "-");
112
+ patterns.push(new RegExp(`\\b${dashed}\\b`));
113
+ }
114
+ }
115
+ return patterns;
116
+ }
117
+ function buildClaudeAgentMatcher(codeName) {
118
+ const safe = codeName.replace(/[^A-Za-z0-9_-]/g, "");
119
+ return new RegExp(`\\bclaude\\b.*--name\\s+agt-${safe}(?=\\s|$)`);
120
+ }
121
+ function findMcpChildrenForAgent(args) {
122
+ const { rows, codeName, serverKeys } = args;
123
+ const maxDepth = args.maxDepth ?? 8;
124
+ if (serverKeys.length === 0 || rows.length === 0) return [];
125
+ const claudeMatcher = buildClaudeAgentMatcher(codeName);
126
+ const argvMatchers = buildArgvMatchers(serverKeys);
127
+ const byPid = new Map(rows.map((r) => [r.pid, r]));
128
+ const matched = [];
129
+ for (const row of rows) {
130
+ if (!argvMatchers.some((re) => re.test(row.args))) continue;
131
+ if (/\bclaude\b/.test(row.args) && row.args.includes(`--name agt-${codeName}`)) continue;
132
+ let cur = byPid.get(row.ppid);
133
+ let belongs = false;
134
+ for (let depth = 0; depth < maxDepth && cur; depth++) {
135
+ if (claudeMatcher.test(cur.args)) {
136
+ belongs = true;
137
+ break;
138
+ }
139
+ if (cur.pid === 1) break;
140
+ cur = byPid.get(cur.ppid);
141
+ }
142
+ if (belongs) matched.push(row.pid);
143
+ }
144
+ return matched;
145
+ }
146
+ function reapStaleMcpChildren(args) {
147
+ const { log: log2, codeName, serverKeys, graceMs = 5e3 } = args;
148
+ if (serverKeys.length === 0) return [];
149
+ const runPs = args.runPs ?? (() => execFileSync("ps", ["-eo", "pid,ppid,args"], { encoding: "utf-8", timeout: 5e3 }));
150
+ const killProcess = args.killProcess ?? ((pid, signal) => {
151
+ try {
152
+ process.kill(pid, signal);
153
+ } catch {
154
+ }
155
+ });
156
+ const isAlive = args.isAlive ?? ((pid) => {
157
+ try {
158
+ process.kill(pid, 0);
159
+ return true;
160
+ } catch {
161
+ return false;
162
+ }
163
+ });
164
+ let psOutput;
165
+ try {
166
+ psOutput = runPs();
167
+ } catch (err) {
168
+ log2(`[stale-mcp-reaper] ps invocation failed for '${codeName}': ${err.message} \u2014 skipping reap`);
169
+ return [];
170
+ }
171
+ const rows = parsePsRows(psOutput);
172
+ const targets = findMcpChildrenForAgent({ rows, codeName, serverKeys });
173
+ if (targets.length === 0) return [];
174
+ const byPid = new Map(rows.map((r) => [r.pid, r]));
175
+ const describe = (pid) => {
176
+ const argv = byPid.get(pid)?.args ?? "";
177
+ const pkgMatch = argv.match(/(@[a-z0-9_-]+\/[a-z0-9_-]+|[a-z0-9_-]+-mcp-server|[a-z0-9_-]+-mcp\b)/i);
178
+ return pkgMatch ? `${pkgMatch[1]} (pid ${pid})` : `pid ${pid}`;
179
+ };
180
+ log2(
181
+ `[stale-mcp-reaper] '${codeName}': rotating ${targets.length} stale MCP child(ren) for [${serverKeys.join(", ")}]: ${targets.map(describe).join(", ")}`
182
+ );
183
+ for (const pid of targets) {
184
+ killProcess(pid, "SIGTERM");
185
+ }
186
+ setTimeout(() => {
187
+ try {
188
+ let freshPsOutput;
189
+ try {
190
+ freshPsOutput = runPs();
191
+ } catch (err) {
192
+ log2(`[stale-mcp-reaper] '${codeName}': fresh ps for SIGKILL re-verify failed: ${err.message} \u2014 skipping SIGKILL pass`);
193
+ return;
194
+ }
195
+ const stillOwned = new Set(
196
+ findMcpChildrenForAgent({
197
+ rows: parsePsRows(freshPsOutput),
198
+ codeName,
199
+ serverKeys
200
+ })
201
+ );
202
+ const stragglers = targets.filter((pid) => isAlive(pid) && stillOwned.has(pid));
203
+ if (stragglers.length === 0) return;
204
+ log2(
205
+ `[stale-mcp-reaper] '${codeName}': ${stragglers.length} child(ren) survived SIGTERM; sending SIGKILL: ${stragglers.map(describe).join(", ")}`
206
+ );
207
+ for (const pid of stragglers) {
208
+ killProcess(pid, "SIGKILL");
209
+ }
210
+ } catch (err) {
211
+ log2(`[stale-mcp-reaper] '${codeName}': error in SIGKILL pass: ${err.message}`);
212
+ }
213
+ }, graceMs).unref();
214
+ return targets;
215
+ }
216
+
62
217
  // src/lib/channel-restart-decision.ts
63
218
  function launchableChannelIds(channelConfigs) {
64
219
  if (!channelConfigs) return /* @__PURE__ */ new Set();
@@ -649,7 +804,7 @@ function saveChannelHashCache(source, configDir) {
649
804
  }
650
805
 
651
806
  // src/lib/channel-sweep.ts
652
- import { execFileSync } from "child_process";
807
+ import { execFileSync as execFileSync2 } from "child_process";
653
808
  var CHANNEL_BASENAMES = [
654
809
  "slack-channel",
655
810
  "direct-chat-channel",
@@ -812,7 +967,7 @@ function resolveLiveAnchorPids(agentCodeNames) {
812
967
  for (const codeName of agentCodeNames) {
813
968
  const pids = /* @__PURE__ */ new Set();
814
969
  try {
815
- const out = execFileSync("tmux", ["list-panes", "-t", `agt-${codeName}`, "-F", "#{pane_pid}"], {
970
+ const out = execFileSync2("tmux", ["list-panes", "-t", `agt-${codeName}`, "-F", "#{pane_pid}"], {
816
971
  encoding: "utf-8",
817
972
  timeout: 2e3,
818
973
  stdio: ["ignore", "pipe", "ignore"]
@@ -832,7 +987,7 @@ async function sweepChannelProcesses(opts) {
832
987
  const kill = opts.killFn ?? defaultKill;
833
988
  let psOutput = "";
834
989
  try {
835
- psOutput = execFileSync("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
990
+ psOutput = execFileSync2("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
836
991
  encoding: "utf-8",
837
992
  timeout: 5e3,
838
993
  maxBuffer: 10 * 1024 * 1024
@@ -869,7 +1024,7 @@ function defaultKillSignal(pid, signal) {
869
1024
  }
870
1025
  }
871
1026
  function defaultPs() {
872
- return execFileSync("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
1027
+ return execFileSync2("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
873
1028
  encoding: "utf-8",
874
1029
  timeout: 5e3,
875
1030
  maxBuffer: 10 * 1024 * 1024
@@ -1681,10 +1836,10 @@ function clearAgentCaches(agentId, codeName) {
1681
1836
  var cachedFrameworkVersion = null;
1682
1837
  var lastVersionCheckAt = 0;
1683
1838
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
1684
- var agtCliVersion = true ? "0.19.14" : "dev";
1685
- function resolveBrewPath(execFileSync2) {
1839
+ var agtCliVersion = true ? "0.19.15" : "dev";
1840
+ function resolveBrewPath(execFileSync3) {
1686
1841
  try {
1687
- const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
1842
+ const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
1688
1843
  if (out) return out;
1689
1844
  } catch {
1690
1845
  }
@@ -1726,9 +1881,9 @@ async function ensureToolkitCli(toolkitSlug) {
1726
1881
  }
1727
1882
  const { binary, installer, package: pkg, script } = integration.cli_tool;
1728
1883
  const resolvedInstaller = installer ?? "manual";
1729
- const { execFileSync: execFileSync2, execSync } = await import("child_process");
1884
+ const { execFileSync: execFileSync3, execSync } = await import("child_process");
1730
1885
  try {
1731
- execFileSync2("which", [binary], { timeout: 5e3, stdio: "pipe" });
1886
+ execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
1732
1887
  toolkitCliEnsured.add(toolkitSlug);
1733
1888
  toolkitCliRetryAfter.delete(toolkitSlug);
1734
1889
  toolkitCliFailureCount.delete(toolkitSlug);
@@ -1749,14 +1904,14 @@ async function ensureToolkitCli(toolkitSlug) {
1749
1904
  return;
1750
1905
  }
1751
1906
  log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})\u2026`);
1752
- execFileSync2("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
1907
+ execFileSync3("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
1753
1908
  } else if (resolvedInstaller === "brew") {
1754
1909
  if (!pkg) {
1755
1910
  log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);
1756
1911
  toolkitCliEnsured.add(toolkitSlug);
1757
1912
  return;
1758
1913
  }
1759
- const brewPath = resolveBrewPath(execFileSync2);
1914
+ const brewPath = resolveBrewPath(execFileSync3);
1760
1915
  if (!brewPath) {
1761
1916
  log(`[toolkit-install] ${toolkitSlug}: installer=brew but Homebrew not available \u2014 install manually: brew install ${pkg}`);
1762
1917
  toolkitCliEnsured.add(toolkitSlug);
@@ -1766,9 +1921,9 @@ async function ensureToolkitCli(toolkitSlug) {
1766
1921
  const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
1767
1922
  log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})\u2026`);
1768
1923
  if (isRoot) {
1769
- execFileSync2("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe", cwd: "/tmp" });
1924
+ execFileSync3("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe", cwd: "/tmp" });
1770
1925
  } else {
1771
- execFileSync2(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
1926
+ execFileSync3(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
1772
1927
  }
1773
1928
  } else if (resolvedInstaller === "script") {
1774
1929
  if (!script) {
@@ -1788,7 +1943,7 @@ async function ensureToolkitCli(toolkitSlug) {
1788
1943
  process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
1789
1944
  }
1790
1945
  try {
1791
- execFileSync2("which", [binary], { timeout: 5e3, stdio: "pipe" });
1946
+ execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
1792
1947
  log(`[toolkit-install] ${toolkitSlug}: installed \u2014 ${binary} now on PATH`);
1793
1948
  toolkitCliEnsured.add(toolkitSlug);
1794
1949
  toolkitCliRetryAfter.delete(toolkitSlug);
@@ -1841,8 +1996,8 @@ async function ensureFrameworkBinary(frameworkId) {
1841
1996
  if (frameworkId !== "claude-code") return;
1842
1997
  if (frameworkBinaryChecked.has(frameworkId)) return;
1843
1998
  frameworkBinaryChecked.add(frameworkId);
1844
- const { execFileSync: execFileSync2 } = await import("child_process");
1845
- const brewPath = resolveBrewPath(execFileSync2);
1999
+ const { execFileSync: execFileSync3 } = await import("child_process");
2000
+ const brewPath = resolveBrewPath(execFileSync3);
1846
2001
  if (!brewPath) {
1847
2002
  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");
1848
2003
  return;
@@ -1858,7 +2013,7 @@ async function ensureFrameworkBinary(frameworkId) {
1858
2013
  let claudeExists = existsSync3("/home/linuxbrew/.linuxbrew/bin/claude");
1859
2014
  if (!claudeExists) {
1860
2015
  try {
1861
- execFileSync2("which", ["claude"], { timeout: 5e3 });
2016
+ execFileSync3("which", ["claude"], { timeout: 5e3 });
1862
2017
  claudeExists = true;
1863
2018
  } catch {
1864
2019
  }
@@ -1950,16 +2105,16 @@ async function checkAndUpdateCli() {
1950
2105
  }
1951
2106
  }
1952
2107
  async function checkAndUpdateCliViaBrew() {
1953
- const { execFileSync: execFileSync2 } = await import("child_process");
1954
- const brewPath = resolveBrewPath(execFileSync2);
2108
+ const { execFileSync: execFileSync3 } = await import("child_process");
2109
+ const brewPath = resolveBrewPath(execFileSync3);
1955
2110
  if (!brewPath) return;
1956
2111
  try {
1957
- execFileSync2(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
2112
+ execFileSync3(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
1958
2113
  } catch (err) {
1959
2114
  log(`[self-update] brew update failed (continuing with stale cache): ${err.message}`);
1960
2115
  }
1961
2116
  try {
1962
- const outdated = execFileSync2(brewPath, ["outdated", "--json=v2"], {
2117
+ const outdated = execFileSync3(brewPath, ["outdated", "--json=v2"], {
1963
2118
  timeout: 3e4,
1964
2119
  encoding: "utf-8"
1965
2120
  });
@@ -1970,7 +2125,7 @@ async function checkAndUpdateCliViaBrew() {
1970
2125
  const latest = agtOutdated.current_version ?? "unknown";
1971
2126
  log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading via brew...`);
1972
2127
  try {
1973
- execFileSync2(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
2128
+ execFileSync3(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
1974
2129
  timeout: 12e4,
1975
2130
  stdio: "pipe"
1976
2131
  });
@@ -1989,7 +2144,7 @@ async function checkAndUpdateCliViaBrew() {
1989
2144
  }
1990
2145
  }
1991
2146
  async function checkAndUpdateCliViaNpm() {
1992
- const { execFileSync: execFileSync2 } = await import("child_process");
2147
+ const { execFileSync: execFileSync3 } = await import("child_process");
1993
2148
  if (agtCliVersion === "dev") return;
1994
2149
  let latest;
1995
2150
  try {
@@ -2038,7 +2193,7 @@ async function checkAndUpdateCliViaNpm() {
2038
2193
  "--registry=https://registry.npmjs.org"
2039
2194
  ];
2040
2195
  try {
2041
- execFileSync2(cmd, args, { timeout: 18e4, stdio: "pipe" });
2196
+ execFileSync3(cmd, args, { timeout: 18e4, stdio: "pipe" });
2042
2197
  log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
2043
2198
  restartAfterUpgrade = true;
2044
2199
  pendingUpgradeVersion = latest;
@@ -2585,7 +2740,7 @@ async function pollCycle() {
2585
2740
  }
2586
2741
  try {
2587
2742
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
2588
- const { collectDiagnostics } = await import("../persistent-session-CFDGW7QE.js");
2743
+ const { collectDiagnostics } = await import("../persistent-session-M2GVL6Z6.js");
2589
2744
  const diagCodeNames = [...persistentSessionAgents];
2590
2745
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
2591
2746
  let tailscaleHostname;
@@ -3333,12 +3488,33 @@ async function processAgent(agent, agentStates) {
3333
3488
  log(`Integrations provisioned for '${agent.code_name}' (${integrations.length} integration(s))`);
3334
3489
  const fw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
3335
3490
  if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
3491
+ let affectedServerKeys = [];
3492
+ try {
3493
+ const projectDir = join4(homedir3(), ".augmented", agent.code_name, "project");
3494
+ const envIntPath = join4(projectDir, ".env.integrations");
3495
+ const projectMcpPath = join4(projectDir, ".mcp.json");
3496
+ const envContent = readFileSync3(envIntPath, "utf-8");
3497
+ const mcpContent = readFileSync3(projectMcpPath, "utf-8");
3498
+ const changedVars = parseEnvIntegrationsVars(envContent);
3499
+ const mcpJson = JSON.parse(mcpContent);
3500
+ affectedServerKeys = findMcpServersUsingVars(mcpJson, changedVars);
3501
+ } catch (err) {
3502
+ log(`[hot-reload] Failed to compute affected MCP servers for '${agent.code_name}': ${err.message}`);
3503
+ }
3504
+ if (affectedServerKeys.length > 0) {
3505
+ reapStaleMcpChildren({
3506
+ log,
3507
+ codeName: agent.code_name,
3508
+ serverKeys: affectedServerKeys
3509
+ });
3510
+ }
3336
3511
  const names = integrations.map((i) => i.display_name || i.definition_id).join(", ");
3337
- injectMessage(agent.code_name, "system", `Your integrations have been updated. You now have access to: ${names}. Your .env.integrations and CLAUDE.md have been refreshed \u2014 credentials are available immediately via environment variables.`, {
3512
+ const reapNote = affectedServerKeys.length > 0 ? ` The MCP servers that depend on rotating credentials (${affectedServerKeys.join(", ")}) have been signalled to reconnect.` : "";
3513
+ injectMessage(agent.code_name, "system", `Your integrations have been refreshed. You have access to: ${names}.${reapNote}`, {
3338
3514
  task_name: "integration-update"
3339
3515
  }, log).catch(() => {
3340
3516
  });
3341
- log(`[hot-reload] Notified '${agent.code_name}' about integration update: ${names}`);
3517
+ log(`[hot-reload] Notified '${agent.code_name}' about integration update: ${names} (reaped ${affectedServerKeys.length} stale MCP server(s))`);
3342
3518
  }
3343
3519
  needsGatewayRestart = true;
3344
3520
  }
@@ -5822,7 +5998,7 @@ async function processClaudePairSessions(agents) {
5822
5998
  killPairSession,
5823
5999
  pairTmuxSession,
5824
6000
  finalizeClaudePairOnboarding
5825
- } = await import("../claude-pair-runtime-VXN4NVGR.js");
6001
+ } = await import("../claude-pair-runtime-UF4OMFCA.js");
5826
6002
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
5827
6003
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
5828
6004
  const killed = await killPairSession(pairTmuxSession(pairId));