@integrity-labs/agt-cli 0.19.13 → 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.
- package/dist/bin/agt.js +3 -3
- package/dist/{chunk-QFWR2NV5.js → chunk-3K3RO5NS.js} +102 -7
- package/dist/chunk-3K3RO5NS.js.map +1 -0
- package/dist/{chunk-SUUTWC6M.js → chunk-DVWBVANP.js} +419 -141
- package/dist/chunk-DVWBVANP.js.map +1 -0
- package/dist/{claude-pair-runtime-SJDLJNYF.js → claude-pair-runtime-UF4OMFCA.js} +2 -2
- package/dist/lib/manager-worker.js +296 -36
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/{persistent-session-XBRQN7XE.js → persistent-session-M2GVL6Z6.js} +2 -2
- package/mcp/index.js +1 -1
- package/mcp/slack-channel.js +216 -1
- package/package.json +1 -1
- package/dist/chunk-QFWR2NV5.js.map +0 -1
- package/dist/chunk-SUUTWC6M.js.map +0 -1
- /package/dist/{claude-pair-runtime-SJDLJNYF.js.map → claude-pair-runtime-UF4OMFCA.js.map} +0 -0
- /package/dist/{persistent-session-XBRQN7XE.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-DVWBVANP.js";
|
|
26
26
|
import {
|
|
27
27
|
findTaskByTemplate,
|
|
28
28
|
getProjectDir,
|
|
@@ -39,15 +39,17 @@ import {
|
|
|
39
39
|
isAgentIdle,
|
|
40
40
|
isSessionHealthy,
|
|
41
41
|
isStaleForToday,
|
|
42
|
+
parsePsRows,
|
|
42
43
|
peekCurrentSession,
|
|
43
44
|
prepareForRespawn,
|
|
45
|
+
reapOrphanChannelMcps,
|
|
44
46
|
resetRestartCount,
|
|
45
47
|
resolveClaudeBinary,
|
|
46
48
|
sanitizeMcpJson,
|
|
47
49
|
startPersistentSession,
|
|
48
50
|
stopAllSessionsAndWait,
|
|
49
51
|
stopPersistentSession
|
|
50
|
-
} from "../chunk-
|
|
52
|
+
} from "../chunk-3K3RO5NS.js";
|
|
51
53
|
|
|
52
54
|
// src/lib/manager-worker.ts
|
|
53
55
|
import { createHash } from "crypto";
|
|
@@ -58,6 +60,188 @@ import { join as join4, dirname } from "path";
|
|
|
58
60
|
import { homedir as homedir3 } from "os";
|
|
59
61
|
import { fileURLToPath } from "url";
|
|
60
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
|
+
|
|
217
|
+
// src/lib/channel-restart-decision.ts
|
|
218
|
+
function launchableChannelIds(channelConfigs) {
|
|
219
|
+
if (!channelConfigs) return /* @__PURE__ */ new Set();
|
|
220
|
+
const result = /* @__PURE__ */ new Set();
|
|
221
|
+
for (const [channelId, entry] of Object.entries(channelConfigs)) {
|
|
222
|
+
if (!entry) continue;
|
|
223
|
+
if (entry.config == null) continue;
|
|
224
|
+
if (entry.status !== "active" && entry.status !== "pending") continue;
|
|
225
|
+
result.add(channelId);
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
function decideChannelRestart(input) {
|
|
230
|
+
const { previousChannelIds, currentChannelIds, sessionMode, framework, sessionHealthy } = input;
|
|
231
|
+
if (previousChannelIds === void 0) {
|
|
232
|
+
return { restart: false, added: [], removed: [] };
|
|
233
|
+
}
|
|
234
|
+
const added = [...currentChannelIds].filter((c) => !previousChannelIds.has(c));
|
|
235
|
+
const removed = [...previousChannelIds].filter((c) => !currentChannelIds.has(c));
|
|
236
|
+
if (added.length === 0 && removed.length === 0) {
|
|
237
|
+
return { restart: false, added, removed };
|
|
238
|
+
}
|
|
239
|
+
if (sessionMode !== "persistent") return { restart: false, added, removed };
|
|
240
|
+
if (framework !== "claude-code") return { restart: false, added, removed };
|
|
241
|
+
if (!sessionHealthy) return { restart: false, added, removed };
|
|
242
|
+
return { restart: true, added, removed };
|
|
243
|
+
}
|
|
244
|
+
|
|
61
245
|
// src/lib/integration-context-render.ts
|
|
62
246
|
var PLUGIN_CONTEXT_PLACEHOLDER_RE = /\{\{\s*context\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
|
|
63
247
|
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";
|
|
@@ -620,7 +804,7 @@ function saveChannelHashCache(source, configDir) {
|
|
|
620
804
|
}
|
|
621
805
|
|
|
622
806
|
// src/lib/channel-sweep.ts
|
|
623
|
-
import { execFileSync } from "child_process";
|
|
807
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
624
808
|
var CHANNEL_BASENAMES = [
|
|
625
809
|
"slack-channel",
|
|
626
810
|
"direct-chat-channel",
|
|
@@ -783,7 +967,7 @@ function resolveLiveAnchorPids(agentCodeNames) {
|
|
|
783
967
|
for (const codeName of agentCodeNames) {
|
|
784
968
|
const pids = /* @__PURE__ */ new Set();
|
|
785
969
|
try {
|
|
786
|
-
const out =
|
|
970
|
+
const out = execFileSync2("tmux", ["list-panes", "-t", `agt-${codeName}`, "-F", "#{pane_pid}"], {
|
|
787
971
|
encoding: "utf-8",
|
|
788
972
|
timeout: 2e3,
|
|
789
973
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -803,7 +987,7 @@ async function sweepChannelProcesses(opts) {
|
|
|
803
987
|
const kill = opts.killFn ?? defaultKill;
|
|
804
988
|
let psOutput = "";
|
|
805
989
|
try {
|
|
806
|
-
psOutput =
|
|
990
|
+
psOutput = execFileSync2("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
|
|
807
991
|
encoding: "utf-8",
|
|
808
992
|
timeout: 5e3,
|
|
809
993
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -840,7 +1024,7 @@ function defaultKillSignal(pid, signal) {
|
|
|
840
1024
|
}
|
|
841
1025
|
}
|
|
842
1026
|
function defaultPs() {
|
|
843
|
-
return
|
|
1027
|
+
return execFileSync2("ps", ["eww", "-o", "pid=,ppid=,etime=,command="], {
|
|
844
1028
|
encoding: "utf-8",
|
|
845
1029
|
timeout: 5e3,
|
|
846
1030
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -1554,6 +1738,28 @@ var KNOWN_SAFE_TAIL_SIGNATURES = /* @__PURE__ */ new Set(["session_id_in_use"]);
|
|
|
1554
1738
|
var knownVersions = /* @__PURE__ */ new Map();
|
|
1555
1739
|
var knownStatuses = /* @__PURE__ */ new Map();
|
|
1556
1740
|
var knownChannels = /* @__PURE__ */ new Map();
|
|
1741
|
+
var pendingSessionRestarts = /* @__PURE__ */ new Map();
|
|
1742
|
+
function scheduleSessionRestart(codeName, delayMs, reason) {
|
|
1743
|
+
const existing = pendingSessionRestarts.get(codeName);
|
|
1744
|
+
if (existing) {
|
|
1745
|
+
clearTimeout(existing);
|
|
1746
|
+
log(`[hot-reload] Coalesced restart for '${codeName}': replacing pending timer with ${reason}`);
|
|
1747
|
+
}
|
|
1748
|
+
const timer = setTimeout(() => {
|
|
1749
|
+
pendingSessionRestarts.delete(codeName);
|
|
1750
|
+
stopPersistentSession(codeName, log);
|
|
1751
|
+
log(`[hot-reload] Session stopped for '${codeName}' \u2014 will respawn with ${reason}`);
|
|
1752
|
+
}, delayMs);
|
|
1753
|
+
timer.unref?.();
|
|
1754
|
+
pendingSessionRestarts.set(codeName, timer);
|
|
1755
|
+
}
|
|
1756
|
+
function cancelPendingSessionRestart(codeName) {
|
|
1757
|
+
const existing = pendingSessionRestarts.get(codeName);
|
|
1758
|
+
if (!existing) return;
|
|
1759
|
+
clearTimeout(existing);
|
|
1760
|
+
pendingSessionRestarts.delete(codeName);
|
|
1761
|
+
log(`[hot-reload] Cancelled pending restart timer for '${codeName}' (another teardown path is handling it)`);
|
|
1762
|
+
}
|
|
1557
1763
|
var writtenHashes = /* @__PURE__ */ new Map();
|
|
1558
1764
|
var knownSecretsHashes = /* @__PURE__ */ new Map();
|
|
1559
1765
|
var knownChannelConfigHashes = /* @__PURE__ */ new Map();
|
|
@@ -1630,10 +1836,10 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
1630
1836
|
var cachedFrameworkVersion = null;
|
|
1631
1837
|
var lastVersionCheckAt = 0;
|
|
1632
1838
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
1633
|
-
var agtCliVersion = true ? "0.19.
|
|
1634
|
-
function resolveBrewPath(
|
|
1839
|
+
var agtCliVersion = true ? "0.19.15" : "dev";
|
|
1840
|
+
function resolveBrewPath(execFileSync3) {
|
|
1635
1841
|
try {
|
|
1636
|
-
const out =
|
|
1842
|
+
const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
1637
1843
|
if (out) return out;
|
|
1638
1844
|
} catch {
|
|
1639
1845
|
}
|
|
@@ -1675,9 +1881,9 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
1675
1881
|
}
|
|
1676
1882
|
const { binary, installer, package: pkg, script } = integration.cli_tool;
|
|
1677
1883
|
const resolvedInstaller = installer ?? "manual";
|
|
1678
|
-
const { execFileSync:
|
|
1884
|
+
const { execFileSync: execFileSync3, execSync } = await import("child_process");
|
|
1679
1885
|
try {
|
|
1680
|
-
|
|
1886
|
+
execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
|
|
1681
1887
|
toolkitCliEnsured.add(toolkitSlug);
|
|
1682
1888
|
toolkitCliRetryAfter.delete(toolkitSlug);
|
|
1683
1889
|
toolkitCliFailureCount.delete(toolkitSlug);
|
|
@@ -1698,14 +1904,14 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
1698
1904
|
return;
|
|
1699
1905
|
}
|
|
1700
1906
|
log(`[toolkit-install] ${toolkitSlug}: installing via npm (${pkg})\u2026`);
|
|
1701
|
-
|
|
1907
|
+
execFileSync3("npm", ["install", "-g", pkg], { timeout: 18e4, stdio: "pipe" });
|
|
1702
1908
|
} else if (resolvedInstaller === "brew") {
|
|
1703
1909
|
if (!pkg) {
|
|
1704
1910
|
log(`[toolkit-install] ${toolkitSlug}: installer=brew but no package declared`);
|
|
1705
1911
|
toolkitCliEnsured.add(toolkitSlug);
|
|
1706
1912
|
return;
|
|
1707
1913
|
}
|
|
1708
|
-
const brewPath = resolveBrewPath(
|
|
1914
|
+
const brewPath = resolveBrewPath(execFileSync3);
|
|
1709
1915
|
if (!brewPath) {
|
|
1710
1916
|
log(`[toolkit-install] ${toolkitSlug}: installer=brew but Homebrew not available \u2014 install manually: brew install ${pkg}`);
|
|
1711
1917
|
toolkitCliEnsured.add(toolkitSlug);
|
|
@@ -1715,9 +1921,9 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
1715
1921
|
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
1716
1922
|
log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})\u2026`);
|
|
1717
1923
|
if (isRoot) {
|
|
1718
|
-
|
|
1924
|
+
execFileSync3("sudo", ["-u", "ec2-user", "-H", brewPath, "install", pkg], { timeout: 18e4, stdio: "pipe", cwd: "/tmp" });
|
|
1719
1925
|
} else {
|
|
1720
|
-
|
|
1926
|
+
execFileSync3(brewPath, ["install", pkg], { timeout: 18e4, stdio: "pipe" });
|
|
1721
1927
|
}
|
|
1722
1928
|
} else if (resolvedInstaller === "script") {
|
|
1723
1929
|
if (!script) {
|
|
@@ -1737,7 +1943,7 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
1737
1943
|
process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
|
|
1738
1944
|
}
|
|
1739
1945
|
try {
|
|
1740
|
-
|
|
1946
|
+
execFileSync3("which", [binary], { timeout: 5e3, stdio: "pipe" });
|
|
1741
1947
|
log(`[toolkit-install] ${toolkitSlug}: installed \u2014 ${binary} now on PATH`);
|
|
1742
1948
|
toolkitCliEnsured.add(toolkitSlug);
|
|
1743
1949
|
toolkitCliRetryAfter.delete(toolkitSlug);
|
|
@@ -1790,8 +1996,8 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1790
1996
|
if (frameworkId !== "claude-code") return;
|
|
1791
1997
|
if (frameworkBinaryChecked.has(frameworkId)) return;
|
|
1792
1998
|
frameworkBinaryChecked.add(frameworkId);
|
|
1793
|
-
const { execFileSync:
|
|
1794
|
-
const brewPath = resolveBrewPath(
|
|
1999
|
+
const { execFileSync: execFileSync3 } = await import("child_process");
|
|
2000
|
+
const brewPath = resolveBrewPath(execFileSync3);
|
|
1795
2001
|
if (!brewPath) {
|
|
1796
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");
|
|
1797
2003
|
return;
|
|
@@ -1807,7 +2013,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1807
2013
|
let claudeExists = existsSync3("/home/linuxbrew/.linuxbrew/bin/claude");
|
|
1808
2014
|
if (!claudeExists) {
|
|
1809
2015
|
try {
|
|
1810
|
-
|
|
2016
|
+
execFileSync3("which", ["claude"], { timeout: 5e3 });
|
|
1811
2017
|
claudeExists = true;
|
|
1812
2018
|
} catch {
|
|
1813
2019
|
}
|
|
@@ -1899,16 +2105,16 @@ async function checkAndUpdateCli() {
|
|
|
1899
2105
|
}
|
|
1900
2106
|
}
|
|
1901
2107
|
async function checkAndUpdateCliViaBrew() {
|
|
1902
|
-
const { execFileSync:
|
|
1903
|
-
const brewPath = resolveBrewPath(
|
|
2108
|
+
const { execFileSync: execFileSync3 } = await import("child_process");
|
|
2109
|
+
const brewPath = resolveBrewPath(execFileSync3);
|
|
1904
2110
|
if (!brewPath) return;
|
|
1905
2111
|
try {
|
|
1906
|
-
|
|
2112
|
+
execFileSync3(brewPath, ["update", "--quiet"], { timeout: 6e4, stdio: "pipe" });
|
|
1907
2113
|
} catch (err) {
|
|
1908
2114
|
log(`[self-update] brew update failed (continuing with stale cache): ${err.message}`);
|
|
1909
2115
|
}
|
|
1910
2116
|
try {
|
|
1911
|
-
const outdated =
|
|
2117
|
+
const outdated = execFileSync3(brewPath, ["outdated", "--json=v2"], {
|
|
1912
2118
|
timeout: 3e4,
|
|
1913
2119
|
encoding: "utf-8"
|
|
1914
2120
|
});
|
|
@@ -1919,7 +2125,7 @@ async function checkAndUpdateCliViaBrew() {
|
|
|
1919
2125
|
const latest = agtOutdated.current_version ?? "unknown";
|
|
1920
2126
|
log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading via brew...`);
|
|
1921
2127
|
try {
|
|
1922
|
-
|
|
2128
|
+
execFileSync3(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
|
|
1923
2129
|
timeout: 12e4,
|
|
1924
2130
|
stdio: "pipe"
|
|
1925
2131
|
});
|
|
@@ -1938,7 +2144,7 @@ async function checkAndUpdateCliViaBrew() {
|
|
|
1938
2144
|
}
|
|
1939
2145
|
}
|
|
1940
2146
|
async function checkAndUpdateCliViaNpm() {
|
|
1941
|
-
const { execFileSync:
|
|
2147
|
+
const { execFileSync: execFileSync3 } = await import("child_process");
|
|
1942
2148
|
if (agtCliVersion === "dev") return;
|
|
1943
2149
|
let latest;
|
|
1944
2150
|
try {
|
|
@@ -1987,7 +2193,7 @@ async function checkAndUpdateCliViaNpm() {
|
|
|
1987
2193
|
"--registry=https://registry.npmjs.org"
|
|
1988
2194
|
];
|
|
1989
2195
|
try {
|
|
1990
|
-
|
|
2196
|
+
execFileSync3(cmd, args, { timeout: 18e4, stdio: "pipe" });
|
|
1991
2197
|
log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
|
|
1992
2198
|
restartAfterUpgrade = true;
|
|
1993
2199
|
pendingUpgradeVersion = latest;
|
|
@@ -2473,6 +2679,7 @@ async function pollCycle() {
|
|
|
2473
2679
|
log,
|
|
2474
2680
|
resolveFramework: (codeName) => agentFrameworkCache.get(codeName) ?? null,
|
|
2475
2681
|
stopSession: (codeName) => {
|
|
2682
|
+
cancelPendingSessionRestart(codeName);
|
|
2476
2683
|
stopPersistentSession(codeName, log);
|
|
2477
2684
|
persistentSessionAgents.delete(codeName);
|
|
2478
2685
|
claudeAuthTupleBySession.delete(codeName);
|
|
@@ -2533,7 +2740,7 @@ async function pollCycle() {
|
|
|
2533
2740
|
}
|
|
2534
2741
|
try {
|
|
2535
2742
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
2536
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
2743
|
+
const { collectDiagnostics } = await import("../persistent-session-M2GVL6Z6.js");
|
|
2537
2744
|
const diagCodeNames = [...persistentSessionAgents];
|
|
2538
2745
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
2539
2746
|
let tailscaleHostname;
|
|
@@ -2633,6 +2840,7 @@ async function pollCycle() {
|
|
|
2633
2840
|
log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
|
|
2634
2841
|
const adapter = resolveAgentFramework(prev.codeName);
|
|
2635
2842
|
await stopGatewayIfRunning(prev.codeName, adapter);
|
|
2843
|
+
cancelPendingSessionRestart(prev.codeName);
|
|
2636
2844
|
stopPersistentSession(prev.codeName, log);
|
|
2637
2845
|
try {
|
|
2638
2846
|
const { execSync: es } = await import("child_process");
|
|
@@ -2770,6 +2978,7 @@ async function processAgent(agent, agentStates) {
|
|
|
2770
2978
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
2771
2979
|
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
2772
2980
|
await stopGatewayIfRunning(agent.code_name, adapter);
|
|
2981
|
+
cancelPendingSessionRestart(agent.code_name);
|
|
2773
2982
|
stopPersistentSession(agent.code_name, log);
|
|
2774
2983
|
try {
|
|
2775
2984
|
const { execSync: es } = await import("child_process");
|
|
@@ -2798,6 +3007,7 @@ async function processAgent(agent, agentStates) {
|
|
|
2798
3007
|
if (agent.status === "revoked") {
|
|
2799
3008
|
log(`Agent '${agent.code_name}' is revoked, cleaning up`);
|
|
2800
3009
|
await stopGatewayIfRunning(agent.code_name, adapter);
|
|
3010
|
+
cancelPendingSessionRestart(agent.code_name);
|
|
2801
3011
|
stopPersistentSession(agent.code_name, log);
|
|
2802
3012
|
try {
|
|
2803
3013
|
const { execSync: es } = await import("child_process");
|
|
@@ -2905,9 +3115,10 @@ async function processAgent(agent, agentStates) {
|
|
|
2905
3115
|
const toolsVersion = refreshData.tools.version;
|
|
2906
3116
|
const known = knownVersions.get(agent.agent_id);
|
|
2907
3117
|
let lastProvisionAt = state.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
|
|
2908
|
-
const currentChannelIds =
|
|
3118
|
+
const currentChannelIds = launchableChannelIds(refreshData.channel_configs);
|
|
2909
3119
|
const previousChannelIds = knownChannels.get(agent.agent_id);
|
|
2910
3120
|
const channelsChanged = !previousChannelIds || currentChannelIds.size !== previousChannelIds.size || [...currentChannelIds].some((ch) => !previousChannelIds.has(ch)) || [...previousChannelIds].some((ch) => !currentChannelIds.has(ch));
|
|
3121
|
+
let channelConfigConverged = true;
|
|
2911
3122
|
if (previousChannelIds && channelsChanged && frameworkAdapter.removeChannelCredentials) {
|
|
2912
3123
|
for (const ch of previousChannelIds) {
|
|
2913
3124
|
if (!currentChannelIds.has(ch)) {
|
|
@@ -2915,12 +3126,12 @@ async function processAgent(agent, agentStates) {
|
|
|
2915
3126
|
frameworkAdapter.removeChannelCredentials(agent.code_name, ch);
|
|
2916
3127
|
log(`Removed ${ch} credentials for '${agent.code_name}'`);
|
|
2917
3128
|
} catch (err) {
|
|
3129
|
+
channelConfigConverged = false;
|
|
2918
3130
|
log(`Failed to remove ${ch} credentials for '${agent.code_name}': ${err.message}`);
|
|
2919
3131
|
}
|
|
2920
3132
|
}
|
|
2921
3133
|
}
|
|
2922
3134
|
}
|
|
2923
|
-
knownChannels.set(agent.agent_id, currentChannelIds);
|
|
2924
3135
|
try {
|
|
2925
3136
|
const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
|
|
2926
3137
|
const changedFiles = [];
|
|
@@ -3133,6 +3344,7 @@ async function processAgent(agent, agentStates) {
|
|
|
3133
3344
|
saveChannelHashCache2();
|
|
3134
3345
|
log(`Channel credentials written for '${agent.code_name}/${channelId}' (reason=${reason}, hash=${configHash.slice(0, 8)}${prevHash ? `, prev=${prevHash.slice(0, 8)}` : ""})`);
|
|
3135
3346
|
} catch (err) {
|
|
3347
|
+
channelConfigConverged = false;
|
|
3136
3348
|
log(`Failed to write channel credentials for '${agent.code_name}/${channelId}': ${err.message}`);
|
|
3137
3349
|
}
|
|
3138
3350
|
}
|
|
@@ -3145,6 +3357,32 @@ async function processAgent(agent, agentStates) {
|
|
|
3145
3357
|
}
|
|
3146
3358
|
}
|
|
3147
3359
|
}
|
|
3360
|
+
if (channelConfigConverged) {
|
|
3361
|
+
knownChannels.set(agent.agent_id, currentChannelIds);
|
|
3362
|
+
} else {
|
|
3363
|
+
log(`[channels] Credential sync did not converge for '${agent.code_name}' \u2014 leaving diff live for next tick retry`);
|
|
3364
|
+
}
|
|
3365
|
+
const restartDecision = channelConfigConverged ? decideChannelRestart({
|
|
3366
|
+
previousChannelIds,
|
|
3367
|
+
currentChannelIds,
|
|
3368
|
+
sessionMode: refreshData.agent.session_mode,
|
|
3369
|
+
framework: agentFrameworkCache.get(agent.code_name) ?? "openclaw",
|
|
3370
|
+
sessionHealthy: isSessionHealthy(agent.code_name)
|
|
3371
|
+
}) : { restart: false, added: [], removed: [] };
|
|
3372
|
+
if (restartDecision.restart) {
|
|
3373
|
+
const reasonParts = [];
|
|
3374
|
+
if (restartDecision.added.length > 0) reasonParts.push(`added=${restartDecision.added.join(",")}`);
|
|
3375
|
+
if (restartDecision.removed.length > 0) reasonParts.push(`removed=${restartDecision.removed.join(",")}`);
|
|
3376
|
+
const reason = reasonParts.join(" ");
|
|
3377
|
+
log(`[hot-reload] Channel set changed for '${agent.code_name}' (${reason}) \u2014 restarting session`);
|
|
3378
|
+
const restartNotice = restartDecision.added.length > 0 ? `New channels have been wired up (${restartDecision.added.join(", ")}). Note: channels require a session restart to attach their MCP servers as channel listeners. Your manager will restart your session shortly.` : `Channels were removed (${restartDecision.removed.join(", ")}). Your manager will restart your session shortly so the launch flags drop those channels.`;
|
|
3379
|
+
const delivered = await injectMessage(agent.code_name, "system", restartNotice, { task_name: "channel-update" }, log).catch(() => false);
|
|
3380
|
+
const delay = delivered ? 8e3 : 3e3;
|
|
3381
|
+
if (!delivered) {
|
|
3382
|
+
log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' \u2014 proceeding with shorter delay`);
|
|
3383
|
+
}
|
|
3384
|
+
scheduleSessionRestart(agent.code_name, delay, "new channel set");
|
|
3385
|
+
}
|
|
3148
3386
|
const agentSessionMode = refreshData.agent.session_mode;
|
|
3149
3387
|
if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? "openclaw") === "claude-code") {
|
|
3150
3388
|
try {
|
|
@@ -3250,12 +3488,33 @@ async function processAgent(agent, agentStates) {
|
|
|
3250
3488
|
log(`Integrations provisioned for '${agent.code_name}' (${integrations.length} integration(s))`);
|
|
3251
3489
|
const fw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
3252
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
|
+
}
|
|
3253
3511
|
const names = integrations.map((i) => i.display_name || i.definition_id).join(", ");
|
|
3254
|
-
|
|
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}`, {
|
|
3255
3514
|
task_name: "integration-update"
|
|
3256
3515
|
}, log).catch(() => {
|
|
3257
3516
|
});
|
|
3258
|
-
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))`);
|
|
3259
3518
|
}
|
|
3260
3519
|
needsGatewayRestart = true;
|
|
3261
3520
|
}
|
|
@@ -3334,10 +3593,7 @@ async function processAgent(agent, agentStates) {
|
|
|
3334
3593
|
if (!delivered) {
|
|
3335
3594
|
log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' \u2014 proceeding with shorter delay`);
|
|
3336
3595
|
}
|
|
3337
|
-
|
|
3338
|
-
stopPersistentSession(agent.code_name, log);
|
|
3339
|
-
log(`[hot-reload] Session stopped for '${agent.code_name}' \u2014 will respawn with new MCP servers`);
|
|
3340
|
-
}, delay);
|
|
3596
|
+
scheduleSessionRestart(agent.code_name, delay, "new MCP servers");
|
|
3341
3597
|
}
|
|
3342
3598
|
}
|
|
3343
3599
|
} catch (err) {
|
|
@@ -4327,6 +4583,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4327
4583
|
const msg = err.message;
|
|
4328
4584
|
log(`[persistent-session] Failed to resolve auth for '${codeName}': ${msg} \u2014 refusing to spawn`);
|
|
4329
4585
|
if (isSessionHealthy(codeName)) {
|
|
4586
|
+
cancelPendingSessionRestart(codeName);
|
|
4330
4587
|
stopPersistentSession(codeName, log);
|
|
4331
4588
|
persistentSessionAgents.delete(codeName);
|
|
4332
4589
|
claudeAuthTupleBySession.delete(codeName);
|
|
@@ -4349,6 +4606,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4349
4606
|
const recordedAuthTuple = claudeAuthTupleBySession.get(codeName);
|
|
4350
4607
|
if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {
|
|
4351
4608
|
log(`[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} \u2192 ${currentAuthTuple}) \u2014 restarting session`);
|
|
4609
|
+
cancelPendingSessionRestart(codeName);
|
|
4352
4610
|
stopPersistentSession(codeName, log);
|
|
4353
4611
|
persistentSessionAgents.delete(codeName);
|
|
4354
4612
|
}
|
|
@@ -4360,6 +4618,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4360
4618
|
log(
|
|
4361
4619
|
`[persistent-session] Day rollover for '${codeName}' (yesterday=${current.date}) \u2014 agent idle, restarting to mint fresh session`
|
|
4362
4620
|
);
|
|
4621
|
+
cancelPendingSessionRestart(codeName);
|
|
4363
4622
|
stopPersistentSession(codeName, log);
|
|
4364
4623
|
persistentSessionAgents.delete(codeName);
|
|
4365
4624
|
} else {
|
|
@@ -5739,7 +5998,7 @@ async function processClaudePairSessions(agents) {
|
|
|
5739
5998
|
killPairSession,
|
|
5740
5999
|
pairTmuxSession,
|
|
5741
6000
|
finalizeClaudePairOnboarding
|
|
5742
|
-
} = await import("../claude-pair-runtime-
|
|
6001
|
+
} = await import("../claude-pair-runtime-UF4OMFCA.js");
|
|
5743
6002
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
5744
6003
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
5745
6004
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -6301,6 +6560,7 @@ function startManager(opts) {
|
|
|
6301
6560
|
`[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join4(homedir3(), ".augmented", "manager.log")}`
|
|
6302
6561
|
);
|
|
6303
6562
|
deployMcpAssets();
|
|
6563
|
+
reapOrphanChannelMcps({ log });
|
|
6304
6564
|
void ensureHostFrameworkBinaries();
|
|
6305
6565
|
startPolling();
|
|
6306
6566
|
}
|