@integrity-labs/agt-cli 0.19.14 → 0.19.16
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 +3 -3
- package/dist/{chunk-HA4IUBVC.js → chunk-3K3RO5NS.js} +2 -1
- package/dist/{chunk-KLNFWXOI.js → chunk-NT26H4DO.js} +382 -141
- package/dist/chunk-NT26H4DO.js.map +1 -0
- package/dist/{claude-pair-runtime-VXN4NVGR.js → claude-pair-runtime-UF4OMFCA.js} +2 -2
- package/dist/lib/manager-worker.js +326 -49
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/{persistent-session-CFDGW7QE.js → persistent-session-M2GVL6Z6.js} +2 -2
- package/mcp/slack-channel.js +111 -0
- package/package.json +1 -1
- package/dist/chunk-KLNFWXOI.js.map +0 -1
- /package/dist/{chunk-HA4IUBVC.js.map → chunk-3K3RO5NS.js.map} +0 -0
- /package/dist/{claude-pair-runtime-VXN4NVGR.js.map → claude-pair-runtime-UF4OMFCA.js.map} +0 -0
- /package/dist/{persistent-session-CFDGW7QE.js.map → persistent-session-M2GVL6Z6.js.map} +0 -0
|
@@ -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-
|
|
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-
|
|
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-
|
|
25
|
+
} from "../chunk-NT26H4DO.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-
|
|
52
|
+
} from "../chunk-3K3RO5NS.js";
|
|
52
53
|
|
|
53
54
|
// src/lib/manager-worker.ts
|
|
54
55
|
import { createHash } from "crypto";
|
|
@@ -59,6 +60,220 @@ 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/mcp-config-drift.ts
|
|
64
|
+
function decideMcpDriftAction(currentHash, knownHash) {
|
|
65
|
+
if (!currentHash) return { kind: "no-config" };
|
|
66
|
+
if (!knownHash) return { kind: "baseline", hash: currentHash };
|
|
67
|
+
if (knownHash === currentHash) return { kind: "no-drift" };
|
|
68
|
+
return { kind: "drift", previous: knownHash, current: currentHash };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/lib/stale-mcp-reaper.ts
|
|
72
|
+
import { execFileSync } from "child_process";
|
|
73
|
+
function parseEnvIntegrationsEntries(content) {
|
|
74
|
+
const entries = /* @__PURE__ */ new Map();
|
|
75
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
76
|
+
const line = raw.trim();
|
|
77
|
+
if (!line || line.startsWith("#")) continue;
|
|
78
|
+
const eq = line.indexOf("=");
|
|
79
|
+
if (eq <= 0) continue;
|
|
80
|
+
const name = line.slice(0, eq).trim();
|
|
81
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) continue;
|
|
82
|
+
const value = line.slice(eq + 1);
|
|
83
|
+
entries.set(name, value);
|
|
84
|
+
}
|
|
85
|
+
return entries;
|
|
86
|
+
}
|
|
87
|
+
function diffEnvIntegrations(oldContent, newContent) {
|
|
88
|
+
const oldEntries = oldContent === void 0 ? /* @__PURE__ */ new Map() : parseEnvIntegrationsEntries(oldContent);
|
|
89
|
+
const newEntries = parseEnvIntegrationsEntries(newContent);
|
|
90
|
+
const changed = /* @__PURE__ */ new Set();
|
|
91
|
+
for (const [name, value] of newEntries) {
|
|
92
|
+
if (oldEntries.get(name) !== value) changed.add(name);
|
|
93
|
+
}
|
|
94
|
+
for (const name of oldEntries.keys()) {
|
|
95
|
+
if (!newEntries.has(name)) changed.add(name);
|
|
96
|
+
}
|
|
97
|
+
return [...changed];
|
|
98
|
+
}
|
|
99
|
+
function findMcpServersUsingVars(mcp, changedVars) {
|
|
100
|
+
const changedSet = new Set(changedVars);
|
|
101
|
+
if (!mcp?.mcpServers || changedSet.size === 0) return [];
|
|
102
|
+
const result = [];
|
|
103
|
+
for (const [serverKey, entry] of Object.entries(mcp.mcpServers)) {
|
|
104
|
+
if (!entry || typeof entry !== "object") continue;
|
|
105
|
+
const env = entry.env;
|
|
106
|
+
if (!env || typeof env !== "object") continue;
|
|
107
|
+
let matches = false;
|
|
108
|
+
for (const value of Object.values(env)) {
|
|
109
|
+
if (typeof value !== "string") continue;
|
|
110
|
+
const placeholderMatches = value.match(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g);
|
|
111
|
+
if (placeholderMatches) {
|
|
112
|
+
for (const ph of placeholderMatches) {
|
|
113
|
+
const name = ph.slice(2, -1);
|
|
114
|
+
if (changedSet.has(name)) {
|
|
115
|
+
matches = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (matches) break;
|
|
121
|
+
}
|
|
122
|
+
if (matches) result.push(serverKey);
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
function buildArgvMatchersForEntry(key, entry) {
|
|
127
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
128
|
+
const patterns = [];
|
|
129
|
+
const args = entry?.args ?? [];
|
|
130
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
131
|
+
const arg = args[i];
|
|
132
|
+
if (typeof arg !== "string" || !arg) continue;
|
|
133
|
+
if (arg.startsWith("-")) continue;
|
|
134
|
+
const stripped = arg.replace(
|
|
135
|
+
/@[^/@]*$/,
|
|
136
|
+
(m) => (
|
|
137
|
+
// @latest, @1.2.3, etc. — drop. But @scope at the START of a
|
|
138
|
+
// package spec must NOT be stripped, so we only strip a tail
|
|
139
|
+
// `@...` if it doesn't itself start with a slash inside.
|
|
140
|
+
// The regex above only matches a single trailing `@...`
|
|
141
|
+
// segment after the last `/`, so this is safe for `@scope/pkg`.
|
|
142
|
+
m.includes("/") ? m : ""
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
const tail = "(?![A-Za-z0-9_-])";
|
|
146
|
+
if (/^@?[a-z0-9]([a-z0-9._-]*\/)?[a-z0-9._-]+$/i.test(stripped)) {
|
|
147
|
+
patterns.push(new RegExp(`${escapeRe(stripped)}${tail}`));
|
|
148
|
+
const basename = stripped.split("/").pop();
|
|
149
|
+
if (basename && basename !== stripped) {
|
|
150
|
+
patterns.push(new RegExp(`${escapeRe(basename)}${tail}`));
|
|
151
|
+
}
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
if (stripped.includes("/")) {
|
|
155
|
+
const basename = stripped.split("/").pop();
|
|
156
|
+
if (basename) {
|
|
157
|
+
patterns.push(new RegExp(`${escapeRe(basename)}${tail}`));
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (patterns.length === 0) {
|
|
163
|
+
const safe = escapeRe(key);
|
|
164
|
+
patterns.push(new RegExp(`(?<![A-Za-z0-9_])${safe}(?![A-Za-z0-9_])`));
|
|
165
|
+
if (safe.includes("_")) {
|
|
166
|
+
const dashed = safe.replace(/_/g, "-");
|
|
167
|
+
patterns.push(new RegExp(`(?<![A-Za-z0-9_])${dashed}(?![A-Za-z0-9_])`));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return patterns;
|
|
171
|
+
}
|
|
172
|
+
function buildClaudeAgentMatcher(codeName) {
|
|
173
|
+
const safe = codeName.replace(/[^A-Za-z0-9_-]/g, "");
|
|
174
|
+
return new RegExp(`\\bclaude\\b.*--name\\s+agt-${safe}(?=\\s|$)`);
|
|
175
|
+
}
|
|
176
|
+
function findMcpChildrenForAgent(args) {
|
|
177
|
+
const { rows, codeName, serverKeys, mcpJson } = args;
|
|
178
|
+
const maxDepth = args.maxDepth ?? 8;
|
|
179
|
+
if (serverKeys.length === 0 || rows.length === 0) return [];
|
|
180
|
+
const claudeMatcher = buildClaudeAgentMatcher(codeName);
|
|
181
|
+
const argvMatchers = [];
|
|
182
|
+
for (const key of serverKeys) {
|
|
183
|
+
const entry = mcpJson?.mcpServers?.[key];
|
|
184
|
+
argvMatchers.push(...buildArgvMatchersForEntry(key, entry));
|
|
185
|
+
}
|
|
186
|
+
const byPid = new Map(rows.map((r) => [r.pid, r]));
|
|
187
|
+
const matched = [];
|
|
188
|
+
for (const row of rows) {
|
|
189
|
+
if (!argvMatchers.some((re) => re.test(row.args))) continue;
|
|
190
|
+
if (/\bclaude\b/.test(row.args) && row.args.includes(`--name agt-${codeName}`)) continue;
|
|
191
|
+
let cur = byPid.get(row.ppid);
|
|
192
|
+
let belongs = false;
|
|
193
|
+
for (let depth = 0; depth < maxDepth && cur; depth++) {
|
|
194
|
+
if (claudeMatcher.test(cur.args)) {
|
|
195
|
+
belongs = true;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (cur.pid === 1) break;
|
|
199
|
+
cur = byPid.get(cur.ppid);
|
|
200
|
+
}
|
|
201
|
+
if (belongs) matched.push(row.pid);
|
|
202
|
+
}
|
|
203
|
+
return matched;
|
|
204
|
+
}
|
|
205
|
+
function reapStaleMcpChildren(args) {
|
|
206
|
+
const { log: log2, codeName, serverKeys, mcpJson, graceMs = 5e3 } = args;
|
|
207
|
+
if (serverKeys.length === 0) return [];
|
|
208
|
+
const runPs = args.runPs ?? (() => execFileSync("ps", ["-eo", "pid,ppid,args"], { encoding: "utf-8", timeout: 5e3 }));
|
|
209
|
+
const killProcess = args.killProcess ?? ((pid, signal) => {
|
|
210
|
+
try {
|
|
211
|
+
process.kill(pid, signal);
|
|
212
|
+
} catch {
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
const isAlive = args.isAlive ?? ((pid) => {
|
|
216
|
+
try {
|
|
217
|
+
process.kill(pid, 0);
|
|
218
|
+
return true;
|
|
219
|
+
} catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
let psOutput;
|
|
224
|
+
try {
|
|
225
|
+
psOutput = runPs();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
log2(`[stale-mcp-reaper] ps invocation failed for '${codeName}': ${err.message} \u2014 skipping reap`);
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
const rows = parsePsRows(psOutput);
|
|
231
|
+
const targets = findMcpChildrenForAgent({ rows, codeName, serverKeys, mcpJson });
|
|
232
|
+
if (targets.length === 0) return [];
|
|
233
|
+
const byPid = new Map(rows.map((r) => [r.pid, r]));
|
|
234
|
+
const describe = (pid) => {
|
|
235
|
+
const argv = byPid.get(pid)?.args ?? "";
|
|
236
|
+
const pkgMatch = argv.match(/(@[a-z0-9_-]+\/[a-z0-9_-]+|[a-z0-9_-]+-mcp-server|[a-z0-9_-]+-mcp\b)/i);
|
|
237
|
+
return pkgMatch ? `${pkgMatch[1]} (pid ${pid})` : `pid ${pid}`;
|
|
238
|
+
};
|
|
239
|
+
log2(
|
|
240
|
+
`[stale-mcp-reaper] '${codeName}': rotating ${targets.length} stale MCP child(ren) for [${serverKeys.join(", ")}]: ${targets.map(describe).join(", ")}`
|
|
241
|
+
);
|
|
242
|
+
for (const pid of targets) {
|
|
243
|
+
killProcess(pid, "SIGTERM");
|
|
244
|
+
}
|
|
245
|
+
setTimeout(() => {
|
|
246
|
+
try {
|
|
247
|
+
let freshPsOutput;
|
|
248
|
+
try {
|
|
249
|
+
freshPsOutput = runPs();
|
|
250
|
+
} catch (err) {
|
|
251
|
+
log2(`[stale-mcp-reaper] '${codeName}': fresh ps for SIGKILL re-verify failed: ${err.message} \u2014 skipping SIGKILL pass`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const stillOwned = new Set(
|
|
255
|
+
findMcpChildrenForAgent({
|
|
256
|
+
rows: parsePsRows(freshPsOutput),
|
|
257
|
+
codeName,
|
|
258
|
+
serverKeys,
|
|
259
|
+
mcpJson
|
|
260
|
+
})
|
|
261
|
+
);
|
|
262
|
+
const stragglers = targets.filter((pid) => isAlive(pid) && stillOwned.has(pid));
|
|
263
|
+
if (stragglers.length === 0) return;
|
|
264
|
+
log2(
|
|
265
|
+
`[stale-mcp-reaper] '${codeName}': ${stragglers.length} child(ren) survived SIGTERM; sending SIGKILL: ${stragglers.map(describe).join(", ")}`
|
|
266
|
+
);
|
|
267
|
+
for (const pid of stragglers) {
|
|
268
|
+
killProcess(pid, "SIGKILL");
|
|
269
|
+
}
|
|
270
|
+
} catch (err) {
|
|
271
|
+
log2(`[stale-mcp-reaper] '${codeName}': error in SIGKILL pass: ${err.message}`);
|
|
272
|
+
}
|
|
273
|
+
}, graceMs).unref();
|
|
274
|
+
return targets;
|
|
275
|
+
}
|
|
276
|
+
|
|
62
277
|
// src/lib/channel-restart-decision.ts
|
|
63
278
|
function launchableChannelIds(channelConfigs) {
|
|
64
279
|
if (!channelConfigs) return /* @__PURE__ */ new Set();
|
|
@@ -649,7 +864,7 @@ function saveChannelHashCache(source, configDir) {
|
|
|
649
864
|
}
|
|
650
865
|
|
|
651
866
|
// src/lib/channel-sweep.ts
|
|
652
|
-
import { execFileSync } from "child_process";
|
|
867
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
653
868
|
var CHANNEL_BASENAMES = [
|
|
654
869
|
"slack-channel",
|
|
655
870
|
"direct-chat-channel",
|
|
@@ -812,7 +1027,7 @@ function resolveLiveAnchorPids(agentCodeNames) {
|
|
|
812
1027
|
for (const codeName of agentCodeNames) {
|
|
813
1028
|
const pids = /* @__PURE__ */ new Set();
|
|
814
1029
|
try {
|
|
815
|
-
const out =
|
|
1030
|
+
const out = execFileSync2("tmux", ["list-panes", "-t", `agt-${codeName}`, "-F", "#{pane_pid}"], {
|
|
816
1031
|
encoding: "utf-8",
|
|
817
1032
|
timeout: 2e3,
|
|
818
1033
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -832,7 +1047,7 @@ async function sweepChannelProcesses(opts) {
|
|
|
832
1047
|
const kill = opts.killFn ?? defaultKill;
|
|
833
1048
|
let psOutput = "";
|
|
834
1049
|
try {
|
|
835
|
-
psOutput =
|
|
1050
|
+
psOutput = execFileSync2("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
|
|
836
1051
|
encoding: "utf-8",
|
|
837
1052
|
timeout: 5e3,
|
|
838
1053
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -869,7 +1084,7 @@ function defaultKillSignal(pid, signal) {
|
|
|
869
1084
|
}
|
|
870
1085
|
}
|
|
871
1086
|
function defaultPs() {
|
|
872
|
-
return
|
|
1087
|
+
return execFileSync2("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
|
|
873
1088
|
encoding: "utf-8",
|
|
874
1089
|
timeout: 5e3,
|
|
875
1090
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -1593,6 +1808,7 @@ function scheduleSessionRestart(codeName, delayMs, reason) {
|
|
|
1593
1808
|
const timer = setTimeout(() => {
|
|
1594
1809
|
pendingSessionRestarts.delete(codeName);
|
|
1595
1810
|
stopPersistentSession(codeName, log);
|
|
1811
|
+
runningMcpHashes.delete(codeName);
|
|
1596
1812
|
log(`[hot-reload] Session stopped for '${codeName}' \u2014 will respawn with ${reason}`);
|
|
1597
1813
|
}, delayMs);
|
|
1598
1814
|
timer.unref?.();
|
|
@@ -1607,6 +1823,38 @@ function cancelPendingSessionRestart(codeName) {
|
|
|
1607
1823
|
}
|
|
1608
1824
|
var writtenHashes = /* @__PURE__ */ new Map();
|
|
1609
1825
|
var knownSecretsHashes = /* @__PURE__ */ new Map();
|
|
1826
|
+
var runningMcpHashes = /* @__PURE__ */ new Map();
|
|
1827
|
+
function projectMcpHash(codeName, projectDir) {
|
|
1828
|
+
try {
|
|
1829
|
+
return createHash("sha256").update(readFileSync3(join4(projectDir, ".mcp.json"))).digest("hex");
|
|
1830
|
+
} catch {
|
|
1831
|
+
return null;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
function stopPersistentSessionAndForgetMcpBaseline(codeName) {
|
|
1835
|
+
cancelPendingSessionRestart(codeName);
|
|
1836
|
+
stopPersistentSession(codeName, log);
|
|
1837
|
+
runningMcpHashes.delete(codeName);
|
|
1838
|
+
}
|
|
1839
|
+
function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
|
|
1840
|
+
const currentHash = projectMcpHash(codeName, projectDir);
|
|
1841
|
+
const action = decideMcpDriftAction(currentHash, runningMcpHashes.get(codeName));
|
|
1842
|
+
switch (action.kind) {
|
|
1843
|
+
case "no-config":
|
|
1844
|
+
case "no-drift":
|
|
1845
|
+
return;
|
|
1846
|
+
case "baseline":
|
|
1847
|
+
runningMcpHashes.set(codeName, action.hash);
|
|
1848
|
+
return;
|
|
1849
|
+
case "drift":
|
|
1850
|
+
log(
|
|
1851
|
+
`[hot-reload] .mcp.json content changed for '${codeName}' (${action.previous.slice(0, 12)} \u2192 ${action.current.slice(0, 12)}); scheduling restart (ENG-4897)`
|
|
1852
|
+
);
|
|
1853
|
+
scheduleSessionRestart(codeName, 0, ".mcp.json content change (ENG-4897)");
|
|
1854
|
+
runningMcpHashes.delete(codeName);
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1610
1858
|
var knownChannelConfigHashes = /* @__PURE__ */ new Map();
|
|
1611
1859
|
var knownModels = /* @__PURE__ */ new Map();
|
|
1612
1860
|
var knownTasksHashes = /* @__PURE__ */ new Map();
|
|
@@ -1681,10 +1929,10 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
1681
1929
|
var cachedFrameworkVersion = null;
|
|
1682
1930
|
var lastVersionCheckAt = 0;
|
|
1683
1931
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1684
|
-
var agtCliVersion = true ? "0.19.
|
|
1685
|
-
function resolveBrewPath(
|
|
1932
|
+
var agtCliVersion = true ? "0.19.16" : "dev";
|
|
1933
|
+
function resolveBrewPath(execFileSync3) {
|
|
1686
1934
|
try {
|
|
1687
|
-
const out =
|
|
1935
|
+
const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
1688
1936
|
if (out) return out;
|
|
1689
1937
|
} catch {
|
|
1690
1938
|
}
|
|
@@ -1726,9 +1974,9 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
1726
1974
|
}
|
|
1727
1975
|
const { binary, installer, package: pkg, script } = integration.cli_tool;
|
|
1728
1976
|
const resolvedInstaller = installer ?? "manual";
|
|
1729
|
-
const { execFileSync:
|
|
1977
|
+
const { execFileSync: execFileSync3, execSync } = await import("child_process");
|
|
1730
1978
|
try {
|
|
1731
|
-
|
|
1979
|
+
execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
|
|
1732
1980
|
toolkitCliEnsured.add(toolkitSlug);
|
|
1733
1981
|
toolkitCliRetryAfter.delete(toolkitSlug);
|
|
1734
1982
|
toolkitCliFailureCount.delete(toolkitSlug);
|
|
@@ -1749,14 +1997,14 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
1749
1997
|
return;
|
|
1750
1998
|
}
|
|
1751
1999
|
log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})\u2026`);
|
|
1752
|
-
|
|
2000
|
+
execFileSync3("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
|
|
1753
2001
|
} else if (resolvedInstaller === "brew") {
|
|
1754
2002
|
if (!pkg) {
|
|
1755
2003
|
log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);
|
|
1756
2004
|
toolkitCliEnsured.add(toolkitSlug);
|
|
1757
2005
|
return;
|
|
1758
2006
|
}
|
|
1759
|
-
const brewPath = resolveBrewPath(
|
|
2007
|
+
const brewPath = resolveBrewPath(execFileSync3);
|
|
1760
2008
|
if (!brewPath) {
|
|
1761
2009
|
log(`[toolkit-install] ${toolkitSlug}: installer=brew but Homebrew not available \u2014 install manually: brew install ${pkg}`);
|
|
1762
2010
|
toolkitCliEnsured.add(toolkitSlug);
|
|
@@ -1766,9 +2014,9 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
1766
2014
|
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
1767
2015
|
log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})\u2026`);
|
|
1768
2016
|
if (isRoot) {
|
|
1769
|
-
|
|
2017
|
+
execFileSync3("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe", cwd: "/tmp" });
|
|
1770
2018
|
} else {
|
|
1771
|
-
|
|
2019
|
+
execFileSync3(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
|
|
1772
2020
|
}
|
|
1773
2021
|
} else if (resolvedInstaller === "script") {
|
|
1774
2022
|
if (!script) {
|
|
@@ -1788,7 +2036,7 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
1788
2036
|
process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
|
|
1789
2037
|
}
|
|
1790
2038
|
try {
|
|
1791
|
-
|
|
2039
|
+
execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
|
|
1792
2040
|
log(`[toolkit-install] ${toolkitSlug}: installed \u2014 ${binary} now on PATH`);
|
|
1793
2041
|
toolkitCliEnsured.add(toolkitSlug);
|
|
1794
2042
|
toolkitCliRetryAfter.delete(toolkitSlug);
|
|
@@ -1841,8 +2089,8 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1841
2089
|
if (frameworkId !== "claude-code") return;
|
|
1842
2090
|
if (frameworkBinaryChecked.has(frameworkId)) return;
|
|
1843
2091
|
frameworkBinaryChecked.add(frameworkId);
|
|
1844
|
-
const { execFileSync:
|
|
1845
|
-
const brewPath = resolveBrewPath(
|
|
2092
|
+
const { execFileSync: execFileSync3 } = await import("child_process");
|
|
2093
|
+
const brewPath = resolveBrewPath(execFileSync3);
|
|
1846
2094
|
if (!brewPath) {
|
|
1847
2095
|
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
2096
|
return;
|
|
@@ -1858,7 +2106,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1858
2106
|
let claudeExists = existsSync3("/home/linuxbrew/.linuxbrew/bin/claude");
|
|
1859
2107
|
if (!claudeExists) {
|
|
1860
2108
|
try {
|
|
1861
|
-
|
|
2109
|
+
execFileSync3("which", ["claude"], { timeout: 5e3 });
|
|
1862
2110
|
claudeExists = true;
|
|
1863
2111
|
} catch {
|
|
1864
2112
|
}
|
|
@@ -1950,16 +2198,16 @@ async function checkAndUpdateCli() {
|
|
|
1950
2198
|
}
|
|
1951
2199
|
}
|
|
1952
2200
|
async function checkAndUpdateCliViaBrew() {
|
|
1953
|
-
const { execFileSync:
|
|
1954
|
-
const brewPath = resolveBrewPath(
|
|
2201
|
+
const { execFileSync: execFileSync3 } = await import("child_process");
|
|
2202
|
+
const brewPath = resolveBrewPath(execFileSync3);
|
|
1955
2203
|
if (!brewPath) return;
|
|
1956
2204
|
try {
|
|
1957
|
-
|
|
2205
|
+
execFileSync3(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
|
|
1958
2206
|
} catch (err) {
|
|
1959
2207
|
log(`[self-update] brew update failed (continuing with stale cache): ${err.message}`);
|
|
1960
2208
|
}
|
|
1961
2209
|
try {
|
|
1962
|
-
const outdated =
|
|
2210
|
+
const outdated = execFileSync3(brewPath, ["outdated", "--json=v2"], {
|
|
1963
2211
|
timeout: 3e4,
|
|
1964
2212
|
encoding: "utf-8"
|
|
1965
2213
|
});
|
|
@@ -1970,7 +2218,7 @@ async function checkAndUpdateCliViaBrew() {
|
|
|
1970
2218
|
const latest = agtOutdated.current_version ?? "unknown";
|
|
1971
2219
|
log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading via brew...`);
|
|
1972
2220
|
try {
|
|
1973
|
-
|
|
2221
|
+
execFileSync3(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
|
|
1974
2222
|
timeout: 12e4,
|
|
1975
2223
|
stdio: "pipe"
|
|
1976
2224
|
});
|
|
@@ -1989,7 +2237,7 @@ async function checkAndUpdateCliViaBrew() {
|
|
|
1989
2237
|
}
|
|
1990
2238
|
}
|
|
1991
2239
|
async function checkAndUpdateCliViaNpm() {
|
|
1992
|
-
const { execFileSync:
|
|
2240
|
+
const { execFileSync: execFileSync3 } = await import("child_process");
|
|
1993
2241
|
if (agtCliVersion === "dev") return;
|
|
1994
2242
|
let latest;
|
|
1995
2243
|
try {
|
|
@@ -2038,7 +2286,7 @@ async function checkAndUpdateCliViaNpm() {
|
|
|
2038
2286
|
"--registry=https://registry.npmjs.org"
|
|
2039
2287
|
];
|
|
2040
2288
|
try {
|
|
2041
|
-
|
|
2289
|
+
execFileSync3(cmd, args, { timeout: 18e4, stdio: "pipe" });
|
|
2042
2290
|
log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
|
|
2043
2291
|
restartAfterUpgrade = true;
|
|
2044
2292
|
pendingUpgradeVersion = latest;
|
|
@@ -2524,8 +2772,7 @@ async function pollCycle() {
|
|
|
2524
2772
|
log,
|
|
2525
2773
|
resolveFramework: (codeName) => agentFrameworkCache.get(codeName) ?? null,
|
|
2526
2774
|
stopSession: (codeName) => {
|
|
2527
|
-
|
|
2528
|
-
stopPersistentSession(codeName, log);
|
|
2775
|
+
stopPersistentSessionAndForgetMcpBaseline(codeName);
|
|
2529
2776
|
persistentSessionAgents.delete(codeName);
|
|
2530
2777
|
claudeAuthTupleBySession.delete(codeName);
|
|
2531
2778
|
},
|
|
@@ -2585,7 +2832,7 @@ async function pollCycle() {
|
|
|
2585
2832
|
}
|
|
2586
2833
|
try {
|
|
2587
2834
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
2588
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
2835
|
+
const { collectDiagnostics } = await import("../persistent-session-M2GVL6Z6.js");
|
|
2589
2836
|
const diagCodeNames = [...persistentSessionAgents];
|
|
2590
2837
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
2591
2838
|
let tailscaleHostname;
|
|
@@ -2685,8 +2932,7 @@ async function pollCycle() {
|
|
|
2685
2932
|
log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
|
|
2686
2933
|
const adapter = resolveAgentFramework(prev.codeName);
|
|
2687
2934
|
await stopGatewayIfRunning(prev.codeName, adapter);
|
|
2688
|
-
|
|
2689
|
-
stopPersistentSession(prev.codeName, log);
|
|
2935
|
+
stopPersistentSessionAndForgetMcpBaseline(prev.codeName);
|
|
2690
2936
|
try {
|
|
2691
2937
|
const { execSync: es } = await import("child_process");
|
|
2692
2938
|
es(`tmux kill-session -t agt-${prev.codeName} 2>/dev/null`, { stdio: "ignore" });
|
|
@@ -2823,8 +3069,7 @@ async function processAgent(agent, agentStates) {
|
|
|
2823
3069
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
2824
3070
|
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
2825
3071
|
await stopGatewayIfRunning(agent.code_name, adapter);
|
|
2826
|
-
|
|
2827
|
-
stopPersistentSession(agent.code_name, log);
|
|
3072
|
+
stopPersistentSessionAndForgetMcpBaseline(agent.code_name);
|
|
2828
3073
|
try {
|
|
2829
3074
|
const { execSync: es } = await import("child_process");
|
|
2830
3075
|
es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: "ignore" });
|
|
@@ -2852,8 +3097,7 @@ async function processAgent(agent, agentStates) {
|
|
|
2852
3097
|
if (agent.status === "revoked") {
|
|
2853
3098
|
log(`Agent '${agent.code_name}' is revoked, cleaning up`);
|
|
2854
3099
|
await stopGatewayIfRunning(agent.code_name, adapter);
|
|
2855
|
-
|
|
2856
|
-
stopPersistentSession(agent.code_name, log);
|
|
3100
|
+
stopPersistentSessionAndForgetMcpBaseline(agent.code_name);
|
|
2857
3101
|
try {
|
|
2858
3102
|
const { execSync: es } = await import("child_process");
|
|
2859
3103
|
es(`tmux kill-session -t agt-${agent.code_name} 2>/dev/null`, { stdio: "ignore" });
|
|
@@ -3326,19 +3570,50 @@ async function processAgent(agent, agentStates) {
|
|
|
3326
3570
|
const intHash = createHash("sha256").update(JSON.stringify(integrations.map((i) => `${i.definition_id}:${JSON.stringify(i.credentials)}`))).digest("hex").slice(0, 16);
|
|
3327
3571
|
const prevIntHash = knownIntegrationHashes.get(agent.agent_id);
|
|
3328
3572
|
if (intHash !== prevIntHash) {
|
|
3573
|
+
const projectDir = join4(homedir3(), ".augmented", agent.code_name, "project");
|
|
3574
|
+
const envIntPath = join4(projectDir, ".env.integrations");
|
|
3575
|
+
let preWriteEnv;
|
|
3576
|
+
try {
|
|
3577
|
+
preWriteEnv = readFileSync3(envIntPath, "utf-8");
|
|
3578
|
+
} catch {
|
|
3579
|
+
preWriteEnv = void 0;
|
|
3580
|
+
}
|
|
3329
3581
|
if (frameworkAdapter.writeIntegrations) {
|
|
3330
3582
|
frameworkAdapter.writeIntegrations(agent.code_name, integrations);
|
|
3331
3583
|
}
|
|
3332
|
-
knownIntegrationHashes.set(agent.agent_id, intHash);
|
|
3333
3584
|
log(`Integrations provisioned for '${agent.code_name}' (${integrations.length} integration(s))`);
|
|
3334
3585
|
const fw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
3586
|
+
let rotationHandled = true;
|
|
3335
3587
|
if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3588
|
+
try {
|
|
3589
|
+
const projectMcpPath = join4(projectDir, ".mcp.json");
|
|
3590
|
+
const postWriteEnv = readFileSync3(envIntPath, "utf-8");
|
|
3591
|
+
const mcpContent = readFileSync3(projectMcpPath, "utf-8");
|
|
3592
|
+
const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
|
|
3593
|
+
const mcpJsonForReap = JSON.parse(mcpContent);
|
|
3594
|
+
const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
|
|
3595
|
+
if (affectedServerKeys.length > 0) {
|
|
3596
|
+
reapStaleMcpChildren({
|
|
3597
|
+
log,
|
|
3598
|
+
codeName: agent.code_name,
|
|
3599
|
+
serverKeys: affectedServerKeys,
|
|
3600
|
+
mcpJson: mcpJsonForReap
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3603
|
+
const names = integrations.map((i) => i.display_name || i.definition_id).join(", ");
|
|
3604
|
+
const reapNote = affectedServerKeys.length > 0 ? ` The MCP servers that depend on rotating credentials (${affectedServerKeys.join(", ")}) have been signalled to reconnect.` : "";
|
|
3605
|
+
injectMessage(agent.code_name, "system", `Your integrations have been refreshed. You have access to: ${names}.${reapNote}`, {
|
|
3606
|
+
task_name: "integration-update"
|
|
3607
|
+
}, log).catch(() => {
|
|
3608
|
+
});
|
|
3609
|
+
log(`[hot-reload] Notified '${agent.code_name}' about integration update: ${names} (reaped ${affectedServerKeys.length} stale MCP server(s))`);
|
|
3610
|
+
} catch (err) {
|
|
3611
|
+
rotationHandled = false;
|
|
3612
|
+
log(`[hot-reload] Failed to compute / reap affected MCP servers for '${agent.code_name}': ${err.message} \u2014 will retry next tick`);
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
if (rotationHandled) {
|
|
3616
|
+
knownIntegrationHashes.set(agent.agent_id, intHash);
|
|
3342
3617
|
}
|
|
3343
3618
|
needsGatewayRestart = true;
|
|
3344
3619
|
}
|
|
@@ -3656,6 +3931,11 @@ async function processAgent(agent, agentStates) {
|
|
|
3656
3931
|
const sessionMode = refreshData.agent.session_mode ?? "oneshot";
|
|
3657
3932
|
if (agentFw === "claude-code" && sessionMode === "persistent") {
|
|
3658
3933
|
await ensurePersistentSession(agent, tasks, boardItems, refreshData);
|
|
3934
|
+
try {
|
|
3935
|
+
checkMcpConfigDriftAndScheduleRestart(agent.code_name, getProjectDir2(agent.code_name));
|
|
3936
|
+
} catch (err) {
|
|
3937
|
+
log(`[hot-reload] .mcp.json drift check failed for '${agent.code_name}': ${err.message}`);
|
|
3938
|
+
}
|
|
3659
3939
|
} else if (agentFw === "claude-code" && tasks.length > 0) {
|
|
3660
3940
|
await syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData);
|
|
3661
3941
|
} else if (frameworkAdapter.syncScheduledTasks && gatewayRunning && gatewayPort) {
|
|
@@ -4407,8 +4687,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4407
4687
|
const msg = err.message;
|
|
4408
4688
|
log(`[persistent-session] Failed to resolve auth for '${codeName}': ${msg} \u2014 refusing to spawn`);
|
|
4409
4689
|
if (isSessionHealthy(codeName)) {
|
|
4410
|
-
|
|
4411
|
-
stopPersistentSession(codeName, log);
|
|
4690
|
+
stopPersistentSessionAndForgetMcpBaseline(codeName);
|
|
4412
4691
|
persistentSessionAgents.delete(codeName);
|
|
4413
4692
|
claudeAuthTupleBySession.delete(codeName);
|
|
4414
4693
|
}
|
|
@@ -4430,8 +4709,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4430
4709
|
const recordedAuthTuple = claudeAuthTupleBySession.get(codeName);
|
|
4431
4710
|
if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {
|
|
4432
4711
|
log(`[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} \u2192 ${currentAuthTuple}) \u2014 restarting session`);
|
|
4433
|
-
|
|
4434
|
-
stopPersistentSession(codeName, log);
|
|
4712
|
+
stopPersistentSessionAndForgetMcpBaseline(codeName);
|
|
4435
4713
|
persistentSessionAgents.delete(codeName);
|
|
4436
4714
|
}
|
|
4437
4715
|
if (isStaleForToday(codeName) && isSessionHealthy(codeName)) {
|
|
@@ -4442,8 +4720,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4442
4720
|
log(
|
|
4443
4721
|
`[persistent-session] Day rollover for '${codeName}' (yesterday=${current.date}) \u2014 agent idle, restarting to mint fresh session`
|
|
4444
4722
|
);
|
|
4445
|
-
|
|
4446
|
-
stopPersistentSession(codeName, log);
|
|
4723
|
+
stopPersistentSessionAndForgetMcpBaseline(codeName);
|
|
4447
4724
|
persistentSessionAgents.delete(codeName);
|
|
4448
4725
|
} else {
|
|
4449
4726
|
log(
|
|
@@ -5822,7 +6099,7 @@ async function processClaudePairSessions(agents) {
|
|
|
5822
6099
|
killPairSession,
|
|
5823
6100
|
pairTmuxSession,
|
|
5824
6101
|
finalizeClaudePairOnboarding
|
|
5825
|
-
} = await import("../claude-pair-runtime-
|
|
6102
|
+
} = await import("../claude-pair-runtime-UF4OMFCA.js");
|
|
5826
6103
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
5827
6104
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
5828
6105
|
const killed = await killPairSession(pairTmuxSession(pairId));
|