@integrity-labs/agt-cli 0.28.208 → 0.28.209
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 +230 -146
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-ELBYWACS.js → chunk-WFCCBTA6.js} +8463 -6842
- package/dist/chunk-WFCCBTA6.js.map +1 -0
- package/dist/{chunk-3MHPMBN4.js → chunk-Z62QHIR3.js} +792 -38
- package/dist/chunk-Z62QHIR3.js.map +1 -0
- package/dist/{claude-pair-runtime-Q4Q2OTTV.js → claude-pair-runtime-UUTPSE2V.js} +2 -2
- package/dist/lib/manager-worker.js +84 -796
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/{persistent-session-6662WBSH.js → persistent-session-Z46SCCQM.js} +2 -3
- package/dist/{responsiveness-probe-TOLTTG6I.js → responsiveness-probe-4MUX7FBH.js} +2 -3
- package/dist/{responsiveness-probe-TOLTTG6I.js.map → responsiveness-probe-4MUX7FBH.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-3MHPMBN4.js.map +0 -1
- package/dist/chunk-ELBYWACS.js.map +0 -1
- package/dist/chunk-GWBOA7ZY.js +0 -1634
- package/dist/chunk-GWBOA7ZY.js.map +0 -1
- /package/dist/{claude-pair-runtime-Q4Q2OTTV.js.map → claude-pair-runtime-UUTPSE2V.js.map} +0 -0
- /package/dist/{persistent-session-6662WBSH.js.map → persistent-session-Z46SCCQM.js.map} +0 -0
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
expandTemplateVars,
|
|
6
6
|
getFlagDefinition,
|
|
7
7
|
getFramework,
|
|
8
|
+
isolationMode,
|
|
8
9
|
listFlagDefinitions,
|
|
9
10
|
normalizeFlagValue,
|
|
10
11
|
parseEnvIntegrations,
|
|
@@ -17,7 +18,10 @@ import {
|
|
|
17
18
|
resolveConnectivityProbe,
|
|
18
19
|
worseConnectivityOutcome,
|
|
19
20
|
wrapScheduledTaskPrompt
|
|
20
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-WFCCBTA6.js";
|
|
22
|
+
import {
|
|
23
|
+
parsePsRows
|
|
24
|
+
} from "./chunk-XWVM4KPK.js";
|
|
21
25
|
|
|
22
26
|
// ../../packages/core/dist/provisioning/mcp-config-guards.js
|
|
23
27
|
import { chmodSync, existsSync, readFileSync, renameSync, writeFileSync, unlinkSync } from "fs";
|
|
@@ -4153,14 +4157,14 @@ ${sections}`
|
|
|
4153
4157
|
const augDir = join2(homeDir, ".augmented");
|
|
4154
4158
|
const agents = /* @__PURE__ */ new Set();
|
|
4155
4159
|
try {
|
|
4156
|
-
const { readdirSync: readdirSync2, statSync:
|
|
4160
|
+
const { readdirSync: readdirSync2, statSync: statSync3 } = await import("fs");
|
|
4157
4161
|
const entries = readdirSync2(augDir);
|
|
4158
4162
|
for (const entry of entries) {
|
|
4159
4163
|
if (entry.startsWith("_") || entry.startsWith("."))
|
|
4160
4164
|
continue;
|
|
4161
4165
|
const agentRoot = join2(augDir, entry);
|
|
4162
4166
|
try {
|
|
4163
|
-
if (!
|
|
4167
|
+
if (!statSync3(agentRoot).isDirectory())
|
|
4164
4168
|
continue;
|
|
4165
4169
|
} catch {
|
|
4166
4170
|
continue;
|
|
@@ -5525,7 +5529,7 @@ function requireHost() {
|
|
|
5525
5529
|
}
|
|
5526
5530
|
|
|
5527
5531
|
// src/lib/api-client.ts
|
|
5528
|
-
var agtCliVersion = true ? "0.28.
|
|
5532
|
+
var agtCliVersion = true ? "0.28.209" : "dev";
|
|
5529
5533
|
var lastConfigHash = null;
|
|
5530
5534
|
function setConfigHash(hash) {
|
|
5531
5535
|
lastConfigHash = hash && hash.length > 0 ? hash : null;
|
|
@@ -5920,22 +5924,558 @@ var HostFlagStore = class {
|
|
|
5920
5924
|
}
|
|
5921
5925
|
};
|
|
5922
5926
|
|
|
5923
|
-
//
|
|
5924
|
-
import {
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
+
// src/lib/stale-mcp-reaper.ts
|
|
5928
|
+
import { execFileSync } from "child_process";
|
|
5929
|
+
var rotationTimestamps = /* @__PURE__ */ new Map();
|
|
5930
|
+
var DEFAULT_ROTATION_GRACE_MS = 15e3;
|
|
5931
|
+
function rotationKey(codeName, serverKey) {
|
|
5932
|
+
return `${codeName}\0${serverKey}`;
|
|
5933
|
+
}
|
|
5934
|
+
function recordStaleRotation(codeName, serverKeys, now = Date.now) {
|
|
5935
|
+
const ts = now();
|
|
5936
|
+
for (const key of serverKeys) {
|
|
5937
|
+
rotationTimestamps.set(rotationKey(codeName, key), ts);
|
|
5938
|
+
}
|
|
5939
|
+
}
|
|
5940
|
+
function wasRecentlyRotated(codeName, serverKey, withinMs, now = Date.now) {
|
|
5941
|
+
const k = rotationKey(codeName, serverKey);
|
|
5942
|
+
const ts = rotationTimestamps.get(k);
|
|
5943
|
+
if (ts === void 0) return false;
|
|
5944
|
+
if (now() - ts < withinMs) return true;
|
|
5945
|
+
rotationTimestamps.delete(k);
|
|
5946
|
+
return false;
|
|
5947
|
+
}
|
|
5948
|
+
function parseEnvIntegrationsEntries(content) {
|
|
5949
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5950
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
5951
|
+
const line = raw.trim();
|
|
5952
|
+
if (!line || line.startsWith("#")) continue;
|
|
5953
|
+
const eq = line.indexOf("=");
|
|
5954
|
+
if (eq <= 0) continue;
|
|
5955
|
+
const name = line.slice(0, eq).trim();
|
|
5956
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) continue;
|
|
5957
|
+
const value = line.slice(eq + 1);
|
|
5958
|
+
entries.set(name, value);
|
|
5959
|
+
}
|
|
5960
|
+
return entries;
|
|
5961
|
+
}
|
|
5962
|
+
function diffEnvIntegrations(oldContent, newContent) {
|
|
5963
|
+
const oldEntries = oldContent === void 0 ? /* @__PURE__ */ new Map() : parseEnvIntegrationsEntries(oldContent);
|
|
5964
|
+
const newEntries = parseEnvIntegrationsEntries(newContent);
|
|
5965
|
+
const changed = /* @__PURE__ */ new Set();
|
|
5966
|
+
for (const [name, value] of newEntries) {
|
|
5967
|
+
if (oldEntries.get(name) !== value) changed.add(name);
|
|
5968
|
+
}
|
|
5969
|
+
for (const name of oldEntries.keys()) {
|
|
5970
|
+
if (!newEntries.has(name)) changed.add(name);
|
|
5971
|
+
}
|
|
5972
|
+
return [...changed];
|
|
5973
|
+
}
|
|
5974
|
+
function findMcpServersUsingVars(mcp, changedVars) {
|
|
5975
|
+
const changedSet = new Set(changedVars);
|
|
5976
|
+
if (!mcp?.mcpServers || changedSet.size === 0) return [];
|
|
5977
|
+
const result = [];
|
|
5978
|
+
for (const [serverKey, entry] of Object.entries(mcp.mcpServers)) {
|
|
5979
|
+
if (!entry || typeof entry !== "object") continue;
|
|
5980
|
+
const env = entry.env;
|
|
5981
|
+
if (!env || typeof env !== "object") continue;
|
|
5982
|
+
let matches = false;
|
|
5983
|
+
for (const value of Object.values(env)) {
|
|
5984
|
+
if (typeof value !== "string") continue;
|
|
5985
|
+
const placeholderMatches = value.match(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g);
|
|
5986
|
+
if (placeholderMatches) {
|
|
5987
|
+
for (const ph of placeholderMatches) {
|
|
5988
|
+
const name = ph.slice(2, -1);
|
|
5989
|
+
if (changedSet.has(name)) {
|
|
5990
|
+
matches = true;
|
|
5991
|
+
break;
|
|
5992
|
+
}
|
|
5993
|
+
}
|
|
5994
|
+
}
|
|
5995
|
+
if (matches) break;
|
|
5996
|
+
}
|
|
5997
|
+
if (matches) result.push(serverKey);
|
|
5998
|
+
}
|
|
5999
|
+
return result;
|
|
5927
6000
|
}
|
|
5928
|
-
function
|
|
5929
|
-
const
|
|
5930
|
-
const
|
|
5931
|
-
const
|
|
5932
|
-
|
|
5933
|
-
|
|
6001
|
+
function buildArgvMatchersForEntry(key, entry) {
|
|
6002
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6003
|
+
const patterns = [];
|
|
6004
|
+
const args = entry?.args ?? [];
|
|
6005
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
6006
|
+
const arg = args[i];
|
|
6007
|
+
if (typeof arg !== "string" || !arg) continue;
|
|
6008
|
+
if (arg.startsWith("-")) continue;
|
|
6009
|
+
const stripped = arg.replace(
|
|
6010
|
+
/@[^/@]*$/,
|
|
6011
|
+
(m) => (
|
|
6012
|
+
// @latest, @1.2.3, etc. — drop. But @scope at the START of a
|
|
6013
|
+
// package spec must NOT be stripped, so we only strip a tail
|
|
6014
|
+
// `@...` if it doesn't itself start with a slash inside.
|
|
6015
|
+
// The regex above only matches a single trailing `@...`
|
|
6016
|
+
// segment after the last `/`, so this is safe for `@scope/pkg`.
|
|
6017
|
+
m.includes("/") ? m : ""
|
|
6018
|
+
)
|
|
6019
|
+
);
|
|
6020
|
+
const tail = "(?![A-Za-z0-9_-])";
|
|
6021
|
+
if (/^@?[a-z0-9]([a-z0-9._-]*\/)?[a-z0-9._-]+$/i.test(stripped)) {
|
|
6022
|
+
patterns.push(new RegExp(`${escapeRe(stripped)}${tail}`));
|
|
6023
|
+
const basename = stripped.split("/").pop();
|
|
6024
|
+
if (basename && basename !== stripped) {
|
|
6025
|
+
patterns.push(new RegExp(`${escapeRe(basename)}${tail}`));
|
|
6026
|
+
}
|
|
6027
|
+
break;
|
|
6028
|
+
}
|
|
6029
|
+
if (stripped.includes("/")) {
|
|
6030
|
+
const basename = stripped.split("/").pop();
|
|
6031
|
+
if (basename) {
|
|
6032
|
+
patterns.push(new RegExp(`${escapeRe(basename)}${tail}`));
|
|
6033
|
+
break;
|
|
6034
|
+
}
|
|
6035
|
+
}
|
|
6036
|
+
}
|
|
6037
|
+
if (patterns.length === 0) {
|
|
6038
|
+
const safe = escapeRe(key);
|
|
6039
|
+
patterns.push(new RegExp(`(?<![A-Za-z0-9_])${safe}(?![A-Za-z0-9_])`));
|
|
6040
|
+
if (safe.includes("_")) {
|
|
6041
|
+
const dashed = safe.replace(/_/g, "-");
|
|
6042
|
+
patterns.push(new RegExp(`(?<![A-Za-z0-9_])${dashed}(?![A-Za-z0-9_])`));
|
|
6043
|
+
}
|
|
6044
|
+
}
|
|
6045
|
+
return patterns;
|
|
6046
|
+
}
|
|
6047
|
+
function buildClaudeAgentMatcher(codeName) {
|
|
6048
|
+
const safe = codeName.replace(/[^A-Za-z0-9_-]/g, "");
|
|
6049
|
+
return new RegExp(`\\bclaude\\b.*--name\\s+agt-${safe}(?=\\s|$)`);
|
|
6050
|
+
}
|
|
6051
|
+
function findMcpChildrenForAgent(args) {
|
|
6052
|
+
const { rows, codeName, serverKeys, mcpJson } = args;
|
|
6053
|
+
const maxDepth = args.maxDepth ?? 8;
|
|
6054
|
+
const inContainer = args.inContainer ?? false;
|
|
6055
|
+
if (serverKeys.length === 0 || rows.length === 0) return [];
|
|
6056
|
+
const claudeMatcher = inContainer ? /\bclaude\b/ : buildClaudeAgentMatcher(codeName);
|
|
6057
|
+
const argvMatchers = [];
|
|
6058
|
+
for (const key of serverKeys) {
|
|
6059
|
+
const entry = mcpJson?.mcpServers?.[key];
|
|
6060
|
+
argvMatchers.push(...buildArgvMatchersForEntry(key, entry));
|
|
6061
|
+
}
|
|
6062
|
+
const byPid = new Map(rows.map((r) => [r.pid, r]));
|
|
6063
|
+
const matched = [];
|
|
6064
|
+
for (const row of rows) {
|
|
6065
|
+
if (!argvMatchers.some((re) => re.test(row.args))) continue;
|
|
6066
|
+
if (/\bclaude\b/.test(row.args) && row.args.includes(`--name agt-${codeName}`)) continue;
|
|
6067
|
+
let cur = byPid.get(row.ppid);
|
|
6068
|
+
let belongs = false;
|
|
6069
|
+
for (let depth = 0; depth < maxDepth && cur; depth++) {
|
|
6070
|
+
if (claudeMatcher.test(cur.args)) {
|
|
6071
|
+
belongs = true;
|
|
6072
|
+
break;
|
|
6073
|
+
}
|
|
6074
|
+
if (cur.pid === 1) break;
|
|
6075
|
+
cur = byPid.get(cur.ppid);
|
|
6076
|
+
}
|
|
6077
|
+
if (belongs) matched.push(row.pid);
|
|
6078
|
+
}
|
|
6079
|
+
return matched;
|
|
6080
|
+
}
|
|
6081
|
+
function reapStaleMcpChildren(args) {
|
|
6082
|
+
const { log, codeName, serverKeys, mcpJson, graceMs = 5e3, isolated = false } = args;
|
|
6083
|
+
if (serverKeys.length === 0) return [];
|
|
6084
|
+
const runPs = args.runPs ?? (isolated ? () => execFileSync("docker", ["exec", `agt-${codeName}`, "ps", "-eo", "pid,ppid,args"], {
|
|
6085
|
+
encoding: "utf-8",
|
|
6086
|
+
timeout: 8e3
|
|
6087
|
+
}) : () => execFileSync("ps", ["-eo", "pid,ppid,args"], { encoding: "utf-8", timeout: 5e3 }));
|
|
6088
|
+
const killProcess = args.killProcess ?? (isolated ? (pid, signal) => {
|
|
6089
|
+
try {
|
|
6090
|
+
execFileSync(
|
|
6091
|
+
"docker",
|
|
6092
|
+
["exec", `agt-${codeName}`, "kill", signal === "SIGKILL" ? "-KILL" : "-TERM", String(pid)],
|
|
6093
|
+
{ timeout: 8e3, stdio: "ignore" }
|
|
6094
|
+
);
|
|
6095
|
+
} catch {
|
|
6096
|
+
}
|
|
6097
|
+
} : (pid, signal) => {
|
|
6098
|
+
try {
|
|
6099
|
+
process.kill(pid, signal);
|
|
6100
|
+
} catch {
|
|
6101
|
+
}
|
|
6102
|
+
});
|
|
6103
|
+
const isAlive = args.isAlive ?? (isolated ? (pid) => {
|
|
6104
|
+
try {
|
|
6105
|
+
execFileSync("docker", ["exec", `agt-${codeName}`, "kill", "-0", String(pid)], {
|
|
6106
|
+
timeout: 8e3,
|
|
6107
|
+
stdio: "ignore"
|
|
6108
|
+
});
|
|
6109
|
+
return true;
|
|
6110
|
+
} catch {
|
|
6111
|
+
return false;
|
|
6112
|
+
}
|
|
6113
|
+
} : (pid) => {
|
|
6114
|
+
try {
|
|
6115
|
+
process.kill(pid, 0);
|
|
6116
|
+
return true;
|
|
6117
|
+
} catch {
|
|
6118
|
+
return false;
|
|
6119
|
+
}
|
|
6120
|
+
});
|
|
6121
|
+
let psOutput;
|
|
6122
|
+
try {
|
|
6123
|
+
psOutput = runPs();
|
|
6124
|
+
} catch (err) {
|
|
6125
|
+
log(`[stale-mcp-reaper] ps invocation failed for '${codeName}': ${err.message} \u2014 skipping reap`);
|
|
6126
|
+
return [];
|
|
6127
|
+
}
|
|
6128
|
+
const rows = parsePsRows(psOutput);
|
|
6129
|
+
const targets = findMcpChildrenForAgent({ rows, codeName, serverKeys, mcpJson, inContainer: isolated });
|
|
6130
|
+
if (targets.length === 0) return [];
|
|
6131
|
+
const byPid = new Map(rows.map((r) => [r.pid, r]));
|
|
6132
|
+
const describe = (pid) => {
|
|
6133
|
+
const argv = byPid.get(pid)?.args ?? "";
|
|
6134
|
+
const pkgMatch = argv.match(/(@[a-z0-9_-]+\/[a-z0-9_-]+|[a-z0-9_-]+-mcp-server|[a-z0-9_-]+-mcp\b)/i);
|
|
6135
|
+
return pkgMatch ? `${pkgMatch[1]} (pid ${pid})` : `pid ${pid}`;
|
|
6136
|
+
};
|
|
6137
|
+
log(
|
|
6138
|
+
`[stale-mcp-reaper] '${codeName}': rotating ${targets.length} stale MCP child(ren) for [${serverKeys.join(", ")}]: ${targets.map(describe).join(", ")}`
|
|
6139
|
+
);
|
|
6140
|
+
for (const pid of targets) {
|
|
6141
|
+
killProcess(pid, "SIGTERM");
|
|
6142
|
+
}
|
|
6143
|
+
const killedKeys = /* @__PURE__ */ new Set();
|
|
6144
|
+
for (const pid of targets) {
|
|
6145
|
+
const argv = byPid.get(pid)?.args ?? "";
|
|
6146
|
+
for (const key of serverKeys) {
|
|
6147
|
+
const entry = mcpJson?.mcpServers?.[key];
|
|
6148
|
+
const matchers = buildArgvMatchersForEntry(key, entry);
|
|
6149
|
+
if (matchers.some((re) => re.test(argv))) killedKeys.add(key);
|
|
6150
|
+
}
|
|
6151
|
+
}
|
|
6152
|
+
if (killedKeys.size > 0) recordStaleRotation(codeName, killedKeys);
|
|
6153
|
+
setTimeout(() => {
|
|
6154
|
+
try {
|
|
6155
|
+
let freshPsOutput;
|
|
6156
|
+
try {
|
|
6157
|
+
freshPsOutput = runPs();
|
|
6158
|
+
} catch (err) {
|
|
6159
|
+
log(`[stale-mcp-reaper] '${codeName}': fresh ps for SIGKILL re-verify failed: ${err.message} \u2014 skipping SIGKILL pass`);
|
|
6160
|
+
return;
|
|
6161
|
+
}
|
|
6162
|
+
const stillOwned = new Set(
|
|
6163
|
+
findMcpChildrenForAgent({
|
|
6164
|
+
rows: parsePsRows(freshPsOutput),
|
|
6165
|
+
codeName,
|
|
6166
|
+
serverKeys,
|
|
6167
|
+
mcpJson,
|
|
6168
|
+
inContainer: isolated
|
|
6169
|
+
})
|
|
6170
|
+
);
|
|
6171
|
+
const stragglers = targets.filter((pid) => isAlive(pid) && stillOwned.has(pid));
|
|
6172
|
+
if (stragglers.length === 0) return;
|
|
6173
|
+
log(
|
|
6174
|
+
`[stale-mcp-reaper] '${codeName}': ${stragglers.length} child(ren) survived SIGTERM; sending SIGKILL: ${stragglers.map(describe).join(", ")}`
|
|
6175
|
+
);
|
|
6176
|
+
for (const pid of stragglers) {
|
|
6177
|
+
killProcess(pid, "SIGKILL");
|
|
6178
|
+
}
|
|
6179
|
+
} catch (err) {
|
|
6180
|
+
log(`[stale-mcp-reaper] '${codeName}': error in SIGKILL pass: ${err.message}`);
|
|
6181
|
+
}
|
|
6182
|
+
}, graceMs).unref();
|
|
6183
|
+
return targets;
|
|
6184
|
+
}
|
|
6185
|
+
|
|
6186
|
+
// src/lib/mcp-presence-reaper.ts
|
|
6187
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
6188
|
+
var DEFAULT_COLD_START_GRACE_MS = 9e4;
|
|
6189
|
+
var MAX_PRESENCE_RESTART_ATTEMPTS = 3;
|
|
6190
|
+
var BACKOFF_AFTER_RESTARTS = MAX_PRESENCE_RESTART_ATTEMPTS;
|
|
6191
|
+
var BACKOFF_BASE_MS = 6e4;
|
|
6192
|
+
var BACKOFF_MAX_MS = 30 * 6e4;
|
|
6193
|
+
var BACKOFF_WINDOW_MS = 60 * 6e4;
|
|
6194
|
+
var BACKOFF_GIVE_UP_RESTARTS = 2 * MAX_PRESENCE_RESTART_ATTEMPTS;
|
|
6195
|
+
var BACKOFF_STABLE_RESET_MS = 5 * 6e4;
|
|
6196
|
+
var presenceReaperState = /* @__PURE__ */ new Map();
|
|
6197
|
+
function stateKey(codeName, serverKey) {
|
|
6198
|
+
return `${codeName}\0${serverKey}`;
|
|
6199
|
+
}
|
|
6200
|
+
function clearPresenceReaperState(codeName) {
|
|
6201
|
+
const prefix = `${codeName}\0`;
|
|
6202
|
+
for (const key of presenceReaperState.keys()) {
|
|
6203
|
+
if (key.startsWith(prefix)) presenceReaperState.delete(key);
|
|
6204
|
+
}
|
|
6205
|
+
}
|
|
6206
|
+
function clearPresenceReaperStateForKeys(codeName, keys) {
|
|
6207
|
+
for (const key of keys) {
|
|
6208
|
+
presenceReaperState.delete(stateKey(codeName, key));
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
function givenUpMcpServerKeys(codeName) {
|
|
6212
|
+
const prefix = `${codeName}\0`;
|
|
6213
|
+
const out = /* @__PURE__ */ new Set();
|
|
6214
|
+
for (const [key, st] of presenceReaperState) {
|
|
6215
|
+
if (key.startsWith(prefix) && (st.gaveUp || st.attempts >= MAX_PRESENCE_RESTART_ATTEMPTS)) {
|
|
6216
|
+
out.add(key.slice(prefix.length));
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
return out;
|
|
6220
|
+
}
|
|
6221
|
+
function findMissingMcpServers(args) {
|
|
6222
|
+
const { rows, codeName, mcpJson, inContainer } = args;
|
|
6223
|
+
const servers = mcpJson?.mcpServers ?? {};
|
|
6224
|
+
const declared = Object.keys(servers);
|
|
6225
|
+
if (declared.length === 0) return [];
|
|
6226
|
+
const missing = [];
|
|
6227
|
+
for (const key of declared) {
|
|
6228
|
+
const entry = servers[key];
|
|
6229
|
+
const isStdio = typeof entry?.command === "string";
|
|
6230
|
+
if (!isStdio) continue;
|
|
6231
|
+
const live = findMcpChildrenForAgent({
|
|
6232
|
+
rows,
|
|
6233
|
+
codeName,
|
|
6234
|
+
serverKeys: [key],
|
|
6235
|
+
mcpJson,
|
|
6236
|
+
inContainer
|
|
6237
|
+
});
|
|
6238
|
+
if (live.length === 0) missing.push(key);
|
|
6239
|
+
}
|
|
6240
|
+
return missing;
|
|
6241
|
+
}
|
|
6242
|
+
function reapMissingMcpSessions(args) {
|
|
6243
|
+
const {
|
|
6244
|
+
log,
|
|
6245
|
+
codeName,
|
|
6246
|
+
mcpJson,
|
|
6247
|
+
sessionStartedAt,
|
|
6248
|
+
restartPending = false,
|
|
6249
|
+
stopSession,
|
|
6250
|
+
graceMs = DEFAULT_COLD_START_GRACE_MS,
|
|
6251
|
+
rotationGraceMs = DEFAULT_ROTATION_GRACE_MS,
|
|
6252
|
+
onGiveUp,
|
|
6253
|
+
onRestart,
|
|
6254
|
+
// ENG-5932: defaults make quarantine a strict no-op (classify everything
|
|
6255
|
+
// essential, mode off) so legacy callers/tests are unaffected.
|
|
6256
|
+
classifyKey = () => "essential",
|
|
6257
|
+
quarantineDwellMs = 0,
|
|
6258
|
+
quarantineMode = "off",
|
|
6259
|
+
onQuarantine,
|
|
6260
|
+
isolated = false
|
|
6261
|
+
} = args;
|
|
6262
|
+
const now = args.now ?? Date.now;
|
|
6263
|
+
const runPs = args.runPs ?? (isolated ? () => execFileSync2("docker", ["exec", `agt-${codeName}`, "ps", "-eo", "pid,ppid,args"], {
|
|
6264
|
+
encoding: "utf-8",
|
|
6265
|
+
timeout: 8e3
|
|
6266
|
+
}) : () => execFileSync2("ps", ["-eo", "pid,ppid,args"], { encoding: "utf-8", timeout: 5e3 }));
|
|
6267
|
+
if (!mcpJson?.mcpServers) {
|
|
6268
|
+
return { missing: [], restarted: false, reason: "no-mcp-json" };
|
|
6269
|
+
}
|
|
6270
|
+
const declaredCount = Object.keys(mcpJson.mcpServers).length;
|
|
6271
|
+
if (declaredCount === 0) {
|
|
6272
|
+
return { missing: [], restarted: false, reason: "no-declared-servers" };
|
|
6273
|
+
}
|
|
6274
|
+
if (restartPending) {
|
|
6275
|
+
return { missing: [], restarted: false, reason: "restart-pending" };
|
|
6276
|
+
}
|
|
6277
|
+
if (sessionStartedAt === null) {
|
|
6278
|
+
return { missing: [], restarted: false, reason: "session-start-unknown" };
|
|
6279
|
+
}
|
|
6280
|
+
if (now() - sessionStartedAt < graceMs) {
|
|
6281
|
+
return { missing: [], restarted: false, reason: "cold-start" };
|
|
6282
|
+
}
|
|
6283
|
+
let psOutput;
|
|
6284
|
+
try {
|
|
6285
|
+
psOutput = runPs();
|
|
6286
|
+
} catch (err) {
|
|
6287
|
+
log(`[mcp-presence-reaper] ps invocation failed for '${codeName}': ${err.message} \u2014 skipping`);
|
|
6288
|
+
return { missing: [], restarted: false, reason: "healthy" };
|
|
6289
|
+
}
|
|
6290
|
+
const rows = parsePsRows(psOutput);
|
|
6291
|
+
const missingRaw = findMissingMcpServers({ rows, codeName, mcpJson, inContainer: isolated });
|
|
6292
|
+
const rotationGraced = [];
|
|
6293
|
+
const missing = [];
|
|
6294
|
+
for (const key of missingRaw) {
|
|
6295
|
+
if (wasRecentlyRotated(codeName, key, rotationGraceMs, now)) {
|
|
6296
|
+
rotationGraced.push(key);
|
|
6297
|
+
} else {
|
|
6298
|
+
missing.push(key);
|
|
6299
|
+
}
|
|
6300
|
+
}
|
|
6301
|
+
if (rotationGraced.length > 0) {
|
|
6302
|
+
log(
|
|
6303
|
+
`[mcp-presence-reaper] '${codeName}': skipping [${rotationGraced.join(", ")}] within rotation grace window (${rotationGraceMs}ms) \u2014 child(ren) just SIGTERM'd by stale-mcp-reaper, awaiting respawn on next tool call (ENG-5344)`
|
|
6304
|
+
);
|
|
6305
|
+
}
|
|
6306
|
+
const declared = mcpJson.mcpServers ?? {};
|
|
6307
|
+
const liveKeys = /* @__PURE__ */ new Set();
|
|
6308
|
+
for (const key of Object.keys(declared)) {
|
|
6309
|
+
const entry = declared[key];
|
|
6310
|
+
if (typeof entry?.command !== "string") continue;
|
|
6311
|
+
if (!missingRaw.includes(key)) liveKeys.add(key);
|
|
6312
|
+
}
|
|
6313
|
+
for (const key of liveKeys) {
|
|
6314
|
+
const state = presenceReaperState.get(stateKey(codeName, key));
|
|
6315
|
+
if (state) {
|
|
6316
|
+
state.attempts = 0;
|
|
6317
|
+
state.lastSeenLiveAt = now();
|
|
6318
|
+
state.lastAttemptedSessionStartedAt = null;
|
|
6319
|
+
state.gaveUpLogged = false;
|
|
6320
|
+
state.firstMissingAt = null;
|
|
6321
|
+
state.quarantineLogged = false;
|
|
6322
|
+
state.gaveUp = false;
|
|
6323
|
+
if (state.stableLiveSince === null) state.stableLiveSince = now();
|
|
6324
|
+
if (now() - state.stableLiveSince >= BACKOFF_STABLE_RESET_MS) {
|
|
6325
|
+
state.restartHistory = [];
|
|
6326
|
+
}
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
if (missing.length === 0) {
|
|
6330
|
+
return {
|
|
6331
|
+
missing,
|
|
6332
|
+
restarted: false,
|
|
6333
|
+
reason: "healthy",
|
|
6334
|
+
rotationGraced: rotationGraced.length > 0 ? rotationGraced : void 0
|
|
6335
|
+
};
|
|
6336
|
+
}
|
|
6337
|
+
const nowMs = now();
|
|
6338
|
+
const givenUp = [];
|
|
6339
|
+
const active = [];
|
|
6340
|
+
const backingOff = [];
|
|
6341
|
+
const newGenerationKeys = /* @__PURE__ */ new Set();
|
|
6342
|
+
const quarantined = [];
|
|
6343
|
+
const wouldQuarantine = [];
|
|
6344
|
+
for (const key of missing) {
|
|
6345
|
+
const sk = stateKey(codeName, key);
|
|
6346
|
+
const state = presenceReaperState.get(sk) ?? {
|
|
6347
|
+
attempts: 0,
|
|
6348
|
+
lastSeenLiveAt: null,
|
|
6349
|
+
lastAttemptedSessionStartedAt: null,
|
|
6350
|
+
gaveUpLogged: false,
|
|
6351
|
+
firstMissingAt: null,
|
|
6352
|
+
quarantineLogged: false,
|
|
6353
|
+
restartHistory: [],
|
|
6354
|
+
stableLiveSince: null,
|
|
6355
|
+
gaveUp: false
|
|
6356
|
+
};
|
|
6357
|
+
if (state.firstMissingAt === null) state.firstMissingAt = nowMs;
|
|
6358
|
+
state.stableLiveSince = null;
|
|
6359
|
+
if (state.lastAttemptedSessionStartedAt !== sessionStartedAt) {
|
|
6360
|
+
state.attempts += 1;
|
|
6361
|
+
state.lastAttemptedSessionStartedAt = sessionStartedAt;
|
|
6362
|
+
newGenerationKeys.add(key);
|
|
6363
|
+
}
|
|
6364
|
+
state.restartHistory = state.restartHistory.filter((t) => nowMs - t < BACKOFF_WINDOW_MS);
|
|
6365
|
+
const restartsInWindow = state.restartHistory.length;
|
|
6366
|
+
presenceReaperState.set(sk, state);
|
|
6367
|
+
const overAttemptsCap = state.attempts > MAX_PRESENCE_RESTART_ATTEMPTS;
|
|
6368
|
+
const overRateCap = restartsInWindow >= BACKOFF_GIVE_UP_RESTARTS;
|
|
6369
|
+
if (overAttemptsCap || overRateCap) {
|
|
6370
|
+
givenUp.push(key);
|
|
6371
|
+
state.gaveUp = true;
|
|
6372
|
+
if (!state.gaveUpLogged) {
|
|
6373
|
+
const cause = overAttemptsCap ? `${MAX_PRESENCE_RESTART_ATTEMPTS} consecutive failed restarts \u2014 declared but never recovers (likely orphaned config; see ENG-5279)` : `${restartsInWindow} restarts within ${Math.round(BACKOFF_WINDOW_MS / 6e4)}m \u2014 persistently flapping (crash-loop; ENG-6480)`;
|
|
6374
|
+
log(
|
|
6375
|
+
`[mcp-presence-reaper] giving up on '${codeName}:${key}' after ${cause}`
|
|
6376
|
+
);
|
|
6377
|
+
state.gaveUpLogged = true;
|
|
6378
|
+
if (onGiveUp) {
|
|
6379
|
+
try {
|
|
6380
|
+
onGiveUp(codeName, key);
|
|
6381
|
+
} catch (err) {
|
|
6382
|
+
log(
|
|
6383
|
+
`[mcp-presence-reaper] onGiveUp callback threw for '${codeName}:${key}' (suppressed; reaper continues): ${err.message}`
|
|
6384
|
+
);
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
if (quarantineMode !== "off" && !state.quarantineLogged && classifyKey(key) === "optional") {
|
|
6389
|
+
const dwellElapsed = quarantineDwellMs <= 0 || state.firstMissingAt !== null && nowMs - state.firstMissingAt >= quarantineDwellMs;
|
|
6390
|
+
if (dwellElapsed) {
|
|
6391
|
+
state.quarantineLogged = true;
|
|
6392
|
+
if (quarantineMode === "enforce") {
|
|
6393
|
+
quarantined.push(key);
|
|
6394
|
+
log(
|
|
6395
|
+
`[mcp-presence-reaper] QUARANTINE '${codeName}:${key}' \u2014 optional channel dead past restart budget + ${quarantineDwellMs}ms dwell; dropping from provisioned set so the dead channel stops restarting the whole session (ENG-5932)`
|
|
6396
|
+
);
|
|
6397
|
+
if (onQuarantine) {
|
|
6398
|
+
try {
|
|
6399
|
+
onQuarantine(codeName, key);
|
|
6400
|
+
} catch (err) {
|
|
6401
|
+
log(
|
|
6402
|
+
`[mcp-presence-reaper] onQuarantine callback threw for '${codeName}:${key}' (suppressed; reaper continues): ${err.message}`
|
|
6403
|
+
);
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
} else {
|
|
6407
|
+
wouldQuarantine.push(key);
|
|
6408
|
+
log(
|
|
6409
|
+
`[mcp-presence-reaper] SHADOW would-quarantine '${codeName}:${key}' \u2014 optional channel dead past restart budget + ${quarantineDwellMs}ms dwell; no action taken (ENG-5932 shadow mode)`
|
|
6410
|
+
);
|
|
6411
|
+
}
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
} else {
|
|
6415
|
+
const backoffMs = restartsInWindow < BACKOFF_AFTER_RESTARTS ? 0 : Math.min(
|
|
6416
|
+
BACKOFF_BASE_MS * 2 ** (restartsInWindow - BACKOFF_AFTER_RESTARTS),
|
|
6417
|
+
BACKOFF_MAX_MS
|
|
6418
|
+
);
|
|
6419
|
+
const lastRestartAt = state.restartHistory[state.restartHistory.length - 1] ?? null;
|
|
6420
|
+
if (lastRestartAt !== null && nowMs - lastRestartAt < backoffMs) {
|
|
6421
|
+
backingOff.push(key);
|
|
6422
|
+
} else {
|
|
6423
|
+
active.push(key);
|
|
6424
|
+
}
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
if (active.length === 0) {
|
|
6428
|
+
const reason = backingOff.length > 0 ? "backoff" : "all-keys-over-cap";
|
|
6429
|
+
if (backingOff.length > 0) {
|
|
6430
|
+
log(
|
|
6431
|
+
`[mcp-presence-reaper] '${codeName}': holding off restart for [${backingOff.join(", ")}] \u2014 escalating backoff interval not yet elapsed (ENG-6480)${givenUp.length > 0 ? ` (given up: [${givenUp.join(", ")}])` : ""}`
|
|
6432
|
+
);
|
|
6433
|
+
}
|
|
6434
|
+
return {
|
|
6435
|
+
missing,
|
|
6436
|
+
restarted: false,
|
|
6437
|
+
reason,
|
|
6438
|
+
givenUp,
|
|
6439
|
+
backingOff: backingOff.length > 0 ? backingOff : void 0,
|
|
6440
|
+
rotationGraced: rotationGraced.length > 0 ? rotationGraced : void 0,
|
|
6441
|
+
quarantined: quarantined.length > 0 ? quarantined : void 0,
|
|
6442
|
+
wouldQuarantine: wouldQuarantine.length > 0 ? wouldQuarantine : void 0
|
|
6443
|
+
};
|
|
6444
|
+
}
|
|
6445
|
+
const givenUpSuffix = givenUp.length > 0 ? ` (skipping over-cap: [${givenUp.join(", ")}])` : "";
|
|
6446
|
+
log(
|
|
6447
|
+
`[mcp-presence-reaper] '${codeName}': declared MCP(s) [${active.join(", ")}] have no live children \u2014 restarting session${givenUpSuffix}`
|
|
6448
|
+
);
|
|
6449
|
+
if (onRestart) {
|
|
6450
|
+
try {
|
|
6451
|
+
const maybePromise = onRestart(codeName, { activeKeys: active, givenUpKeys: givenUp });
|
|
6452
|
+
if (maybePromise && typeof maybePromise.then === "function") {
|
|
6453
|
+
void maybePromise.then(void 0, (err) => {
|
|
6454
|
+
log(
|
|
6455
|
+
`[mcp-presence-reaper] onRestart callback rejected for '${codeName}' (suppressed; restart proceeds): ${err.message}`
|
|
6456
|
+
);
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
} catch (err) {
|
|
6460
|
+
log(
|
|
6461
|
+
`[mcp-presence-reaper] onRestart callback threw for '${codeName}' (suppressed; restart proceeds): ${err.message}`
|
|
6462
|
+
);
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6465
|
+
stopSession(codeName, { activeKeys: active });
|
|
6466
|
+
for (const key of active) {
|
|
6467
|
+
if (!newGenerationKeys.has(key)) continue;
|
|
6468
|
+
const st = presenceReaperState.get(stateKey(codeName, key));
|
|
6469
|
+
if (st) st.restartHistory.push(nowMs);
|
|
6470
|
+
}
|
|
5934
6471
|
return {
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
6472
|
+
missing,
|
|
6473
|
+
restarted: true,
|
|
6474
|
+
givenUp: givenUp.length > 0 ? givenUp : void 0,
|
|
6475
|
+
backingOff: backingOff.length > 0 ? backingOff : void 0,
|
|
6476
|
+
rotationGraced: rotationGraced.length > 0 ? rotationGraced : void 0,
|
|
6477
|
+
quarantined: quarantined.length > 0 ? quarantined : void 0,
|
|
6478
|
+
wouldQuarantine: wouldQuarantine.length > 0 ? wouldQuarantine : void 0
|
|
5939
6479
|
};
|
|
5940
6480
|
}
|
|
5941
6481
|
|
|
@@ -6097,23 +6637,229 @@ function buildConnectivityProbeDeps(projectDir, probeEnv) {
|
|
|
6097
6637
|
};
|
|
6098
6638
|
}
|
|
6099
6639
|
|
|
6640
|
+
// src/lib/session-tool-bind-probe-host.ts
|
|
6641
|
+
import { execFileSync as syncExecFile } from "child_process";
|
|
6642
|
+
import { readFileSync as readFileSync7, statSync as statSync2 } from "fs";
|
|
6643
|
+
import { join as join6 } from "path";
|
|
6644
|
+
|
|
6645
|
+
// src/lib/session-tool-probe.ts
|
|
6646
|
+
function classifyMcpServer(entry) {
|
|
6647
|
+
if (!entry || typeof entry !== "object") return "unknown";
|
|
6648
|
+
const e = entry;
|
|
6649
|
+
if (typeof e.command === "string") return "stdio";
|
|
6650
|
+
if (typeof e.url === "string") return "http";
|
|
6651
|
+
return "unknown";
|
|
6652
|
+
}
|
|
6653
|
+
function splitServerKeysByKind(mcpJson, serverKeys) {
|
|
6654
|
+
const servers = mcpJson?.mcpServers ?? {};
|
|
6655
|
+
const stdio = [];
|
|
6656
|
+
const http = [];
|
|
6657
|
+
for (const key of serverKeys) {
|
|
6658
|
+
const kind = classifyMcpServer(servers[key]);
|
|
6659
|
+
if (kind === "stdio") stdio.push(key);
|
|
6660
|
+
else if (kind === "http") http.push(key);
|
|
6661
|
+
}
|
|
6662
|
+
return { stdio, http };
|
|
6663
|
+
}
|
|
6664
|
+
function computeSessionToolBindStatus(input) {
|
|
6665
|
+
const { stdioServerKeys, httpServerKeys, missingStdioKeys, httpReachable, httpSessionLoaded } = input;
|
|
6666
|
+
if (stdioServerKeys.length === 0 && httpServerKeys.length === 0) return "unknown";
|
|
6667
|
+
if (stdioServerKeys.some((k) => missingStdioKeys.has(k))) return "missing";
|
|
6668
|
+
let anyHttpUnconfirmed = false;
|
|
6669
|
+
for (const k of httpServerKeys) {
|
|
6670
|
+
const reachable = httpReachable.get(k);
|
|
6671
|
+
if (reachable === false) return "unreachable";
|
|
6672
|
+
if (reachable === void 0) {
|
|
6673
|
+
anyHttpUnconfirmed = true;
|
|
6674
|
+
} else if (!httpSessionLoaded) {
|
|
6675
|
+
anyHttpUnconfirmed = true;
|
|
6676
|
+
}
|
|
6677
|
+
}
|
|
6678
|
+
if (anyHttpUnconfirmed && stdioServerKeys.length === 0) return "unknown";
|
|
6679
|
+
return "bound";
|
|
6680
|
+
}
|
|
6681
|
+
|
|
6682
|
+
// src/lib/session-tool-bind-runner.ts
|
|
6683
|
+
var DEFAULT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
6684
|
+
var DEFAULT_MAX_PER_RUN = 25;
|
|
6685
|
+
function sanitizeServerKey(definitionId) {
|
|
6686
|
+
return definitionId.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
6687
|
+
}
|
|
6688
|
+
function resolveIntegrationServerKeys(definitionId, declaredKeys, hint) {
|
|
6689
|
+
const declared = new Set(declaredKeys);
|
|
6690
|
+
const candidates = [hint, definitionId, sanitizeServerKey(definitionId)].filter(
|
|
6691
|
+
(k) => typeof k === "string" && k.length > 0
|
|
6692
|
+
);
|
|
6693
|
+
const out = /* @__PURE__ */ new Set();
|
|
6694
|
+
for (const c of candidates) if (declared.has(c)) out.add(c);
|
|
6695
|
+
return [...out];
|
|
6696
|
+
}
|
|
6697
|
+
function isDue(last, now, intervalMs) {
|
|
6698
|
+
if (!last) return true;
|
|
6699
|
+
const t = Date.parse(last);
|
|
6700
|
+
if (Number.isNaN(t)) return true;
|
|
6701
|
+
return now - t >= intervalMs;
|
|
6702
|
+
}
|
|
6703
|
+
async function runSessionToolBindProbes(integrations, options) {
|
|
6704
|
+
const now = (options.now?.() ?? /* @__PURE__ */ new Date()).getTime();
|
|
6705
|
+
const intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
6706
|
+
const maxPerRun = options.maxPerRun ?? DEFAULT_MAX_PER_RUN;
|
|
6707
|
+
const due = integrations.filter((i) => isDue(i.last_session_tool_bind_at, now, intervalMs)).sort((a, b) => {
|
|
6708
|
+
const ta = a.last_session_tool_bind_at ? Date.parse(a.last_session_tool_bind_at) : 0;
|
|
6709
|
+
const tb = b.last_session_tool_bind_at ? Date.parse(b.last_session_tool_bind_at) : 0;
|
|
6710
|
+
return ta - tb;
|
|
6711
|
+
});
|
|
6712
|
+
const batch = due.slice(0, maxPerRun);
|
|
6713
|
+
const declaredKeys = Object.keys(options.mcpJson?.mcpServers ?? {});
|
|
6714
|
+
const reports = [];
|
|
6715
|
+
let skipped = 0;
|
|
6716
|
+
let probed = 0;
|
|
6717
|
+
for (const integ of batch) {
|
|
6718
|
+
const keys = resolveIntegrationServerKeys(
|
|
6719
|
+
integ.definition_id,
|
|
6720
|
+
declaredKeys,
|
|
6721
|
+
integ.mcp_server_key
|
|
6722
|
+
);
|
|
6723
|
+
if (keys.length === 0) {
|
|
6724
|
+
skipped += 1;
|
|
6725
|
+
continue;
|
|
6726
|
+
}
|
|
6727
|
+
probed += 1;
|
|
6728
|
+
const { stdio, http } = splitServerKeysByKind(options.mcpJson, keys);
|
|
6729
|
+
const httpReachable = /* @__PURE__ */ new Map();
|
|
6730
|
+
for (const key of http) {
|
|
6731
|
+
let reachable;
|
|
6732
|
+
try {
|
|
6733
|
+
reachable = await options.probeHttp(key);
|
|
6734
|
+
} catch {
|
|
6735
|
+
reachable = void 0;
|
|
6736
|
+
}
|
|
6737
|
+
if (reachable !== void 0) httpReachable.set(key, reachable);
|
|
6738
|
+
}
|
|
6739
|
+
const status = computeSessionToolBindStatus({
|
|
6740
|
+
stdioServerKeys: stdio,
|
|
6741
|
+
httpServerKeys: http,
|
|
6742
|
+
missingStdioKeys: options.missingStdioKeys,
|
|
6743
|
+
httpReachable,
|
|
6744
|
+
httpSessionLoaded: options.httpSessionLoaded ?? false
|
|
6745
|
+
});
|
|
6746
|
+
if (status === "unknown") {
|
|
6747
|
+
skipped += 1;
|
|
6748
|
+
continue;
|
|
6749
|
+
}
|
|
6750
|
+
reports.push({ integration_id: integ.id, scope: integ.scope, status });
|
|
6751
|
+
}
|
|
6752
|
+
return { reports, due: due.length, probed, skipped };
|
|
6753
|
+
}
|
|
6754
|
+
|
|
6755
|
+
// src/lib/session-tool-bind-probe-host.ts
|
|
6756
|
+
async function gatherSessionToolBindProbe(agent, integrations, projectDir, opts) {
|
|
6757
|
+
if (integrations.length === 0) return null;
|
|
6758
|
+
let mcpJson = null;
|
|
6759
|
+
try {
|
|
6760
|
+
mcpJson = JSON.parse(readFileSync7(join6(projectDir, ".mcp.json"), "utf-8"));
|
|
6761
|
+
} catch {
|
|
6762
|
+
return null;
|
|
6763
|
+
}
|
|
6764
|
+
if (!mcpJson?.mcpServers || Object.keys(mcpJson.mcpServers).length === 0) return null;
|
|
6765
|
+
const isolated = isolationMode(agent.code_name) === "docker";
|
|
6766
|
+
const hasDeclaredStdio = Object.values(mcpJson.mcpServers).some(
|
|
6767
|
+
(entry) => typeof entry?.command === "string"
|
|
6768
|
+
);
|
|
6769
|
+
let missingStdioKeys = /* @__PURE__ */ new Set();
|
|
6770
|
+
try {
|
|
6771
|
+
const psOutput = isolated ? syncExecFile("docker", ["exec", `agt-${agent.code_name}`, "ps", "-eo", "pid,ppid,args"], {
|
|
6772
|
+
encoding: "utf-8",
|
|
6773
|
+
timeout: 8e3
|
|
6774
|
+
}) : syncExecFile("ps", ["-eo", "pid,ppid,args"], { encoding: "utf-8", timeout: 5e3 });
|
|
6775
|
+
missingStdioKeys = new Set(
|
|
6776
|
+
findMissingMcpServers({
|
|
6777
|
+
rows: parsePsRows(psOutput),
|
|
6778
|
+
codeName: agent.code_name,
|
|
6779
|
+
mcpJson,
|
|
6780
|
+
inContainer: isolated
|
|
6781
|
+
})
|
|
6782
|
+
);
|
|
6783
|
+
} catch {
|
|
6784
|
+
if (hasDeclaredStdio) return null;
|
|
6785
|
+
}
|
|
6786
|
+
const probeEnv = buildProbeEnv(projectDir);
|
|
6787
|
+
const probeDeps = buildConnectivityProbeDeps(projectDir, probeEnv);
|
|
6788
|
+
const probeHttp = async (serverKey) => {
|
|
6789
|
+
if (!probeDeps.mcpProbe) return void 0;
|
|
6790
|
+
const outcome = await probeDeps.mcpProbe({ serverKey, definitionId: serverKey });
|
|
6791
|
+
if (outcome.status === "ok") return true;
|
|
6792
|
+
if (outcome.status === "down") return false;
|
|
6793
|
+
return void 0;
|
|
6794
|
+
};
|
|
6795
|
+
let httpSessionLoaded = false;
|
|
6796
|
+
try {
|
|
6797
|
+
const mcpMtimeMs = statSync2(join6(projectDir, ".mcp.json")).mtimeMs;
|
|
6798
|
+
httpSessionLoaded = opts.sessionStartedMs != null && opts.sessionStartedMs >= mcpMtimeMs;
|
|
6799
|
+
} catch {
|
|
6800
|
+
}
|
|
6801
|
+
return runSessionToolBindProbes(
|
|
6802
|
+
integrations.map((i) => ({
|
|
6803
|
+
id: i.id,
|
|
6804
|
+
definition_id: i.definition_id,
|
|
6805
|
+
scope: i.scope,
|
|
6806
|
+
last_session_tool_bind_at: i.last_session_tool_bind_at ?? null,
|
|
6807
|
+
// Server-key hint for managed/remote-MCP kinds; the runner also tries the
|
|
6808
|
+
// raw + sanitised definition_id against the real `.mcp.json` keys.
|
|
6809
|
+
mcp_server_key: deriveMcpServerKey({
|
|
6810
|
+
definitionId: i.definition_id,
|
|
6811
|
+
sourceType: i.source_type ?? null,
|
|
6812
|
+
authType: i.auth_type,
|
|
6813
|
+
connectivityTest: i.connectivity_test ?? null
|
|
6814
|
+
})
|
|
6815
|
+
})),
|
|
6816
|
+
{
|
|
6817
|
+
mcpJson,
|
|
6818
|
+
missingStdioKeys,
|
|
6819
|
+
probeHttp,
|
|
6820
|
+
httpSessionLoaded,
|
|
6821
|
+
intervalMs: opts.intervalMs,
|
|
6822
|
+
...opts.maxPerRun !== void 0 ? { maxPerRun: opts.maxPerRun } : {}
|
|
6823
|
+
}
|
|
6824
|
+
);
|
|
6825
|
+
}
|
|
6826
|
+
|
|
6827
|
+
// ../../packages/core/dist/provisioning/provisioner.js
|
|
6828
|
+
import { createHash } from "crypto";
|
|
6829
|
+
function sha256(content) {
|
|
6830
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
6831
|
+
}
|
|
6832
|
+
function provision(input, frameworkId = "claude-code") {
|
|
6833
|
+
const adapter = getFramework(frameworkId);
|
|
6834
|
+
const artifacts = adapter.buildArtifacts(input);
|
|
6835
|
+
const charterHash = sha256(input.charterContent);
|
|
6836
|
+
const toolsHash = sha256(input.toolsContent);
|
|
6837
|
+
const teamDir = `.augmented/${input.agent.code_name}`;
|
|
6838
|
+
return {
|
|
6839
|
+
teamDir,
|
|
6840
|
+
artifacts,
|
|
6841
|
+
charterHash,
|
|
6842
|
+
toolsHash
|
|
6843
|
+
};
|
|
6844
|
+
}
|
|
6845
|
+
|
|
6100
6846
|
// src/commands/manager.ts
|
|
6101
6847
|
import chalk3 from "chalk";
|
|
6102
6848
|
import { existsSync as existsSync8, realpathSync } from "fs";
|
|
6103
|
-
import { join as
|
|
6849
|
+
import { join as join8 } from "path";
|
|
6104
6850
|
import { homedir as homedir4, userInfo } from "os";
|
|
6105
6851
|
import { spawn as spawn2 } from "child_process";
|
|
6106
6852
|
|
|
6107
6853
|
// src/lib/watchdog.ts
|
|
6108
|
-
import { readFileSync as
|
|
6109
|
-
import { join as
|
|
6110
|
-
import { spawn, execFileSync } from "child_process";
|
|
6111
|
-
var DEFAULT_CONFIG_DIR =
|
|
6854
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, existsSync as existsSync7, mkdirSync as mkdirSync5, openSync as openSync2, closeSync as closeSync2, chmodSync as chmodSync4 } from "fs";
|
|
6855
|
+
import { join as join7 } from "path";
|
|
6856
|
+
import { spawn, execFileSync as execFileSync3 } from "child_process";
|
|
6857
|
+
var DEFAULT_CONFIG_DIR = join7(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
6112
6858
|
function getManagerPaths(configDir) {
|
|
6113
6859
|
return {
|
|
6114
|
-
pidFile:
|
|
6115
|
-
stateFile:
|
|
6116
|
-
logFile:
|
|
6860
|
+
pidFile: join7(configDir, "manager.pid"),
|
|
6861
|
+
stateFile: join7(configDir, "manager-state.json"),
|
|
6862
|
+
logFile: join7(configDir, "manager.log")
|
|
6117
6863
|
};
|
|
6118
6864
|
}
|
|
6119
6865
|
function ensureDir(configDir) {
|
|
@@ -6127,7 +6873,7 @@ function writePidFile(configDir, pid) {
|
|
|
6127
6873
|
}
|
|
6128
6874
|
function readPidFile(configDir) {
|
|
6129
6875
|
try {
|
|
6130
|
-
const raw =
|
|
6876
|
+
const raw = readFileSync8(getManagerPaths(configDir).pidFile, "utf-8").trim();
|
|
6131
6877
|
const pid = parseInt(raw, 10);
|
|
6132
6878
|
return isNaN(pid) ? null : pid;
|
|
6133
6879
|
} catch {
|
|
@@ -6158,14 +6904,14 @@ function findOtherManagerPids(pgrepImpl = defaultPgrep, selfPid = process.pid) {
|
|
|
6158
6904
|
return out.split("\n").map((line) => parseInt(line.trim(), 10)).filter((pid) => !isNaN(pid) && pid !== selfPid);
|
|
6159
6905
|
}
|
|
6160
6906
|
function defaultPgrep() {
|
|
6161
|
-
return
|
|
6907
|
+
return execFileSync3("pgrep", ["-f", "agt manager start"], {
|
|
6162
6908
|
encoding: "utf-8",
|
|
6163
6909
|
stdio: ["ignore", "pipe", "ignore"]
|
|
6164
6910
|
});
|
|
6165
6911
|
}
|
|
6166
6912
|
function readStateFile(configDir) {
|
|
6167
6913
|
try {
|
|
6168
|
-
const raw =
|
|
6914
|
+
const raw = readFileSync8(getManagerPaths(configDir).stateFile, "utf-8");
|
|
6169
6915
|
return JSON.parse(raw);
|
|
6170
6916
|
} catch {
|
|
6171
6917
|
return null;
|
|
@@ -6349,7 +7095,7 @@ function managerStartCommand(opts) {
|
|
|
6349
7095
|
process.exitCode = 1;
|
|
6350
7096
|
return;
|
|
6351
7097
|
}
|
|
6352
|
-
const configDir = opts.configDir ??
|
|
7098
|
+
const configDir = opts.configDir ?? join8(homedir4(), ".augmented");
|
|
6353
7099
|
if (opts.supervise) {
|
|
6354
7100
|
if (json) {
|
|
6355
7101
|
jsonOutput({ ok: false, error: "--supervise is not supported with --json" });
|
|
@@ -6445,7 +7191,7 @@ function runSupervisorLoop(intervalSec, configDir) {
|
|
|
6445
7191
|
}
|
|
6446
7192
|
async function managerStopCommand(opts = {}) {
|
|
6447
7193
|
const json = isJsonMode();
|
|
6448
|
-
const configDir = opts.configDir ??
|
|
7194
|
+
const configDir = opts.configDir ?? join8(homedir4(), ".augmented");
|
|
6449
7195
|
try {
|
|
6450
7196
|
const result = await stopWatchdog(configDir);
|
|
6451
7197
|
if (!result.stopped && !result.pid) {
|
|
@@ -6473,7 +7219,7 @@ async function managerStopCommand(opts = {}) {
|
|
|
6473
7219
|
}
|
|
6474
7220
|
function managerStatusCommand(opts = {}) {
|
|
6475
7221
|
const json = isJsonMode();
|
|
6476
|
-
const configDir = opts.configDir ??
|
|
7222
|
+
const configDir = opts.configDir ?? join8(homedir4(), ".augmented");
|
|
6477
7223
|
const status = getManagerStatus(configDir);
|
|
6478
7224
|
if (!status) {
|
|
6479
7225
|
if (json) {
|
|
@@ -6565,7 +7311,7 @@ async function managerInstallCommand(opts = {}) {
|
|
|
6565
7311
|
process.exitCode = 1;
|
|
6566
7312
|
return;
|
|
6567
7313
|
}
|
|
6568
|
-
const configDir = opts.configDir ??
|
|
7314
|
+
const configDir = opts.configDir ?? join8(homedir4(), ".augmented");
|
|
6569
7315
|
const rawAgtBin = process.argv[1];
|
|
6570
7316
|
if (!rawAgtBin) {
|
|
6571
7317
|
const msg = "Could not resolve the agt binary path from argv. Re-run via the installed `agt` command.";
|
|
@@ -6578,7 +7324,7 @@ async function managerInstallCommand(opts = {}) {
|
|
|
6578
7324
|
if (process.platform === "darwin") {
|
|
6579
7325
|
const home = homedir4();
|
|
6580
7326
|
const protectedRoots = ["Documents", "Downloads", "Desktop", "Movies", "Music", "Pictures"];
|
|
6581
|
-
const offending = protectedRoots.map((r) =>
|
|
7327
|
+
const offending = protectedRoots.map((r) => join8(home, r)).find((p) => agtBin === p || agtBin.startsWith(`${p}/`));
|
|
6582
7328
|
if (offending) {
|
|
6583
7329
|
const msg = `agt binary at ${agtBin} sits inside a macOS TCC-protected folder (${offending}). launchd-spawned processes cannot read files there and the manager would EPERM on startup. Either install agt globally (\`npm install -g @integrity-labs/agt-cli\`) or copy the dist outside protected folders before running this command.`;
|
|
6584
7330
|
if (json) jsonOutput({ ok: false, error: msg });
|
|
@@ -6646,7 +7392,7 @@ async function managerInstallSystemUnitCommand(opts = {}) {
|
|
|
6646
7392
|
return;
|
|
6647
7393
|
}
|
|
6648
7394
|
const user = opts.user ?? "root";
|
|
6649
|
-
const configDir = opts.configDir ?? (user === "root" ? "/root/.augmented" :
|
|
7395
|
+
const configDir = opts.configDir ?? (user === "root" ? "/root/.augmented" : join8("/home", user, ".augmented"));
|
|
6650
7396
|
const rawAgtBin = process.argv[1];
|
|
6651
7397
|
if (!rawAgtBin) {
|
|
6652
7398
|
const msg = "Could not resolve the agt binary path from argv. Re-run via the installed `agt` command.";
|
|
@@ -6817,11 +7563,19 @@ export {
|
|
|
6817
7563
|
resolveFlagFromLayers,
|
|
6818
7564
|
resolveAllFlags,
|
|
6819
7565
|
HostFlagStore,
|
|
6820
|
-
|
|
6821
|
-
|
|
7566
|
+
diffEnvIntegrations,
|
|
7567
|
+
findMcpServersUsingVars,
|
|
7568
|
+
reapStaleMcpChildren,
|
|
7569
|
+
clearPresenceReaperState,
|
|
7570
|
+
clearPresenceReaperStateForKeys,
|
|
7571
|
+
givenUpMcpServerKeys,
|
|
7572
|
+
reapMissingMcpSessions,
|
|
6822
7573
|
deriveMcpServerKey,
|
|
6823
7574
|
buildProbeEnv,
|
|
6824
7575
|
buildConnectivityProbeDeps,
|
|
7576
|
+
gatherSessionToolBindProbe,
|
|
7577
|
+
provision,
|
|
7578
|
+
executeConnectivityProbe,
|
|
6825
7579
|
getManagerPaths,
|
|
6826
7580
|
startWatchdog,
|
|
6827
7581
|
managerStartCommand,
|
|
@@ -6833,4 +7587,4 @@ export {
|
|
|
6833
7587
|
managerInstallSystemUnitCommand,
|
|
6834
7588
|
managerUninstallSystemUnitCommand
|
|
6835
7589
|
};
|
|
6836
|
-
//# sourceMappingURL=chunk-
|
|
7590
|
+
//# sourceMappingURL=chunk-Z62QHIR3.js.map
|