@integrity-labs/agt-cli 0.20.9 → 0.21.1
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-UMGWKBRQ.js → chunk-NLEW5BPX.js} +67 -6
- package/dist/chunk-NLEW5BPX.js.map +1 -0
- package/dist/lib/manager-worker.js +265 -10
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/direct-chat-channel.js +12 -6
- package/mcp/slack-channel.js +12 -6
- package/mcp/telegram-channel.js +462 -2
- package/package.json +1 -1
- package/dist/chunk-UMGWKBRQ.js.map +0 -1
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveChannels,
|
|
23
23
|
resolveDmTarget,
|
|
24
24
|
wrapScheduledTaskPrompt
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-NLEW5BPX.js";
|
|
26
26
|
import {
|
|
27
27
|
findTaskByTemplate,
|
|
28
28
|
getProjectDir,
|
|
@@ -92,6 +92,25 @@ function computeIntegrationsHash(integrations) {
|
|
|
92
92
|
|
|
93
93
|
// src/lib/stale-mcp-reaper.ts
|
|
94
94
|
import { execFileSync } from "child_process";
|
|
95
|
+
var rotationTimestamps = /* @__PURE__ */ new Map();
|
|
96
|
+
var DEFAULT_ROTATION_GRACE_MS = 15e3;
|
|
97
|
+
function rotationKey(codeName, serverKey) {
|
|
98
|
+
return `${codeName}\0${serverKey}`;
|
|
99
|
+
}
|
|
100
|
+
function recordStaleRotation(codeName, serverKeys, now = Date.now) {
|
|
101
|
+
const ts = now();
|
|
102
|
+
for (const key of serverKeys) {
|
|
103
|
+
rotationTimestamps.set(rotationKey(codeName, key), ts);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function wasRecentlyRotated(codeName, serverKey, withinMs, now = Date.now) {
|
|
107
|
+
const k = rotationKey(codeName, serverKey);
|
|
108
|
+
const ts = rotationTimestamps.get(k);
|
|
109
|
+
if (ts === void 0) return false;
|
|
110
|
+
if (now() - ts < withinMs) return true;
|
|
111
|
+
rotationTimestamps.delete(k);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
95
114
|
function parseEnvIntegrationsEntries(content) {
|
|
96
115
|
const entries = /* @__PURE__ */ new Map();
|
|
97
116
|
for (const raw of content.split(/\r?\n/)) {
|
|
@@ -264,6 +283,16 @@ function reapStaleMcpChildren(args) {
|
|
|
264
283
|
for (const pid of targets) {
|
|
265
284
|
killProcess(pid, "SIGTERM");
|
|
266
285
|
}
|
|
286
|
+
const killedKeys = /* @__PURE__ */ new Set();
|
|
287
|
+
for (const pid of targets) {
|
|
288
|
+
const argv = byPid.get(pid)?.args ?? "";
|
|
289
|
+
for (const key of serverKeys) {
|
|
290
|
+
const entry = mcpJson?.mcpServers?.[key];
|
|
291
|
+
const matchers = buildArgvMatchersForEntry(key, entry);
|
|
292
|
+
if (matchers.some((re) => re.test(argv))) killedKeys.add(key);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (killedKeys.size > 0) recordStaleRotation(codeName, killedKeys);
|
|
267
296
|
setTimeout(() => {
|
|
268
297
|
try {
|
|
269
298
|
let freshPsOutput;
|
|
@@ -299,6 +328,22 @@ function reapStaleMcpChildren(args) {
|
|
|
299
328
|
// src/lib/mcp-presence-reaper.ts
|
|
300
329
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
301
330
|
var DEFAULT_COLD_START_GRACE_MS = 9e4;
|
|
331
|
+
var MAX_PRESENCE_RESTART_ATTEMPTS = 3;
|
|
332
|
+
var presenceReaperState = /* @__PURE__ */ new Map();
|
|
333
|
+
function stateKey(codeName, serverKey) {
|
|
334
|
+
return `${codeName}\0${serverKey}`;
|
|
335
|
+
}
|
|
336
|
+
function clearPresenceReaperState(codeName) {
|
|
337
|
+
const prefix = `${codeName}\0`;
|
|
338
|
+
for (const key of presenceReaperState.keys()) {
|
|
339
|
+
if (key.startsWith(prefix)) presenceReaperState.delete(key);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function clearPresenceReaperStateForKeys(codeName, keys) {
|
|
343
|
+
for (const key of keys) {
|
|
344
|
+
presenceReaperState.delete(stateKey(codeName, key));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
302
347
|
function findMissingMcpServers(args) {
|
|
303
348
|
const { rows, codeName, mcpJson } = args;
|
|
304
349
|
const servers = mcpJson?.mcpServers ?? {};
|
|
@@ -326,7 +371,10 @@ function reapMissingMcpSessions(args) {
|
|
|
326
371
|
mcpJson,
|
|
327
372
|
sessionStartedAt,
|
|
328
373
|
stopSession,
|
|
329
|
-
graceMs = DEFAULT_COLD_START_GRACE_MS
|
|
374
|
+
graceMs = DEFAULT_COLD_START_GRACE_MS,
|
|
375
|
+
rotationGraceMs = DEFAULT_ROTATION_GRACE_MS,
|
|
376
|
+
onGiveUp,
|
|
377
|
+
onRestart
|
|
330
378
|
} = args;
|
|
331
379
|
const now = args.now ?? Date.now;
|
|
332
380
|
const runPs = args.runPs ?? (() => execFileSync2("ps", ["-eo", "pid,ppid,args"], { encoding: "utf-8", timeout: 5e3 }));
|
|
@@ -351,15 +399,117 @@ function reapMissingMcpSessions(args) {
|
|
|
351
399
|
return { missing: [], restarted: false, reason: "healthy" };
|
|
352
400
|
}
|
|
353
401
|
const rows = parsePsRows(psOutput);
|
|
354
|
-
const
|
|
402
|
+
const missingRaw = findMissingMcpServers({ rows, codeName, mcpJson });
|
|
403
|
+
const rotationGraced = [];
|
|
404
|
+
const missing = [];
|
|
405
|
+
for (const key of missingRaw) {
|
|
406
|
+
if (wasRecentlyRotated(codeName, key, rotationGraceMs, now)) {
|
|
407
|
+
rotationGraced.push(key);
|
|
408
|
+
} else {
|
|
409
|
+
missing.push(key);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (rotationGraced.length > 0) {
|
|
413
|
+
log2(
|
|
414
|
+
`[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)`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const declared = mcpJson.mcpServers ?? {};
|
|
418
|
+
const liveKeys = /* @__PURE__ */ new Set();
|
|
419
|
+
for (const key of Object.keys(declared)) {
|
|
420
|
+
const entry = declared[key];
|
|
421
|
+
if (typeof entry?.command !== "string") continue;
|
|
422
|
+
if (!missingRaw.includes(key)) liveKeys.add(key);
|
|
423
|
+
}
|
|
424
|
+
for (const key of liveKeys) {
|
|
425
|
+
const state2 = presenceReaperState.get(stateKey(codeName, key));
|
|
426
|
+
if (state2) {
|
|
427
|
+
state2.attempts = 0;
|
|
428
|
+
state2.lastSeenLiveAt = now();
|
|
429
|
+
state2.lastAttemptedSessionStartedAt = null;
|
|
430
|
+
state2.gaveUpLogged = false;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
355
433
|
if (missing.length === 0) {
|
|
356
|
-
return {
|
|
434
|
+
return {
|
|
435
|
+
missing,
|
|
436
|
+
restarted: false,
|
|
437
|
+
reason: "healthy",
|
|
438
|
+
rotationGraced: rotationGraced.length > 0 ? rotationGraced : void 0
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
const givenUp = [];
|
|
442
|
+
const active = [];
|
|
443
|
+
for (const key of missing) {
|
|
444
|
+
const sk = stateKey(codeName, key);
|
|
445
|
+
const state2 = presenceReaperState.get(sk) ?? {
|
|
446
|
+
attempts: 0,
|
|
447
|
+
lastSeenLiveAt: null,
|
|
448
|
+
lastAttemptedSessionStartedAt: null,
|
|
449
|
+
gaveUpLogged: false
|
|
450
|
+
};
|
|
451
|
+
if (state2.lastAttemptedSessionStartedAt !== sessionStartedAt) {
|
|
452
|
+
state2.attempts += 1;
|
|
453
|
+
state2.lastAttemptedSessionStartedAt = sessionStartedAt;
|
|
454
|
+
}
|
|
455
|
+
presenceReaperState.set(sk, state2);
|
|
456
|
+
if (state2.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {
|
|
457
|
+
givenUp.push(key);
|
|
458
|
+
if (!state2.gaveUpLogged) {
|
|
459
|
+
log2(
|
|
460
|
+
`[mcp-presence-reaper] giving up on '${codeName}:${key}' after ${MAX_PRESENCE_RESTART_ATTEMPTS} consecutive failed restarts \u2014 declared but never recovers (likely orphaned config; see ENG-5279)`
|
|
461
|
+
);
|
|
462
|
+
state2.gaveUpLogged = true;
|
|
463
|
+
if (onGiveUp) {
|
|
464
|
+
try {
|
|
465
|
+
onGiveUp(codeName, key);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
log2(
|
|
468
|
+
`[mcp-presence-reaper] onGiveUp callback threw for '${codeName}:${key}' (suppressed; reaper continues): ${err.message}`
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
} else {
|
|
474
|
+
active.push(key);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (active.length === 0) {
|
|
478
|
+
return {
|
|
479
|
+
missing,
|
|
480
|
+
restarted: false,
|
|
481
|
+
reason: "all-keys-over-cap",
|
|
482
|
+
givenUp,
|
|
483
|
+
rotationGraced: rotationGraced.length > 0 ? rotationGraced : void 0
|
|
484
|
+
};
|
|
357
485
|
}
|
|
486
|
+
const givenUpSuffix = givenUp.length > 0 ? ` (skipping over-cap: [${givenUp.join(", ")}])` : "";
|
|
358
487
|
log2(
|
|
359
|
-
`[mcp-presence-reaper] '${codeName}': declared MCP(s) [${
|
|
488
|
+
`[mcp-presence-reaper] '${codeName}': declared MCP(s) [${active.join(", ")}] have no live children \u2014 restarting session${givenUpSuffix}`
|
|
360
489
|
);
|
|
490
|
+
if (onRestart) {
|
|
491
|
+
try {
|
|
492
|
+
const maybePromise = onRestart(codeName, { activeKeys: active, givenUpKeys: givenUp });
|
|
493
|
+
if (maybePromise && typeof maybePromise.then === "function") {
|
|
494
|
+
void maybePromise.then(void 0, (err) => {
|
|
495
|
+
log2(
|
|
496
|
+
`[mcp-presence-reaper] onRestart callback rejected for '${codeName}' (suppressed; restart proceeds): ${err.message}`
|
|
497
|
+
);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
} catch (err) {
|
|
501
|
+
log2(
|
|
502
|
+
`[mcp-presence-reaper] onRestart callback threw for '${codeName}' (suppressed; restart proceeds): ${err.message}`
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
361
506
|
stopSession(codeName);
|
|
362
|
-
return {
|
|
507
|
+
return {
|
|
508
|
+
missing,
|
|
509
|
+
restarted: true,
|
|
510
|
+
givenUp: givenUp.length > 0 ? givenUp : void 0,
|
|
511
|
+
rotationGraced: rotationGraced.length > 0 ? rotationGraced : void 0
|
|
512
|
+
};
|
|
363
513
|
}
|
|
364
514
|
|
|
365
515
|
// src/lib/channel-restart-decision.ts
|
|
@@ -2094,6 +2244,7 @@ function cancelPendingSessionRestart(codeName) {
|
|
|
2094
2244
|
var writtenHashes = /* @__PURE__ */ new Map();
|
|
2095
2245
|
var knownSecretsHashes = /* @__PURE__ */ new Map();
|
|
2096
2246
|
var runningMcpHashes = /* @__PURE__ */ new Map();
|
|
2247
|
+
var runningMcpServerKeys = /* @__PURE__ */ new Map();
|
|
2097
2248
|
function projectMcpHash(_codeName, projectDir) {
|
|
2098
2249
|
try {
|
|
2099
2250
|
const raw = readFileSync3(join4(projectDir, ".mcp.json"), "utf-8");
|
|
@@ -2102,10 +2253,22 @@ function projectMcpHash(_codeName, projectDir) {
|
|
|
2102
2253
|
return null;
|
|
2103
2254
|
}
|
|
2104
2255
|
}
|
|
2256
|
+
function projectMcpKeys(_codeName, projectDir) {
|
|
2257
|
+
try {
|
|
2258
|
+
const raw = readFileSync3(join4(projectDir, ".mcp.json"), "utf-8");
|
|
2259
|
+
const parsed = JSON.parse(raw);
|
|
2260
|
+
const servers = parsed.mcpServers;
|
|
2261
|
+
if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
|
|
2262
|
+
return new Set(Object.keys(servers));
|
|
2263
|
+
} catch {
|
|
2264
|
+
return null;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2105
2267
|
function stopPersistentSessionAndForgetMcpBaseline(codeName) {
|
|
2106
2268
|
cancelPendingSessionRestart(codeName);
|
|
2107
2269
|
stopPersistentSession(codeName, log);
|
|
2108
2270
|
runningMcpHashes.delete(codeName);
|
|
2271
|
+
runningMcpServerKeys.delete(codeName);
|
|
2109
2272
|
}
|
|
2110
2273
|
function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
|
|
2111
2274
|
const currentHash = projectMcpHash(codeName, projectDir);
|
|
@@ -2114,16 +2277,47 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
|
|
|
2114
2277
|
case "no-config":
|
|
2115
2278
|
case "no-drift":
|
|
2116
2279
|
return;
|
|
2117
|
-
case "baseline":
|
|
2280
|
+
case "baseline": {
|
|
2118
2281
|
runningMcpHashes.set(codeName, action.hash);
|
|
2282
|
+
const baselineKeys = projectMcpKeys(codeName, projectDir);
|
|
2283
|
+
if (baselineKeys) {
|
|
2284
|
+
runningMcpServerKeys.set(codeName, baselineKeys);
|
|
2285
|
+
} else {
|
|
2286
|
+
runningMcpServerKeys.delete(codeName);
|
|
2287
|
+
}
|
|
2119
2288
|
return;
|
|
2120
|
-
|
|
2289
|
+
}
|
|
2290
|
+
case "drift": {
|
|
2121
2291
|
log(
|
|
2122
2292
|
`[hot-reload] .mcp.json content changed for '${codeName}' (${action.previous.slice(0, 12)} \u2192 ${action.current.slice(0, 12)}); scheduling restart (ENG-4897)`
|
|
2123
2293
|
);
|
|
2294
|
+
const previousKeys = runningMcpServerKeys.get(codeName);
|
|
2295
|
+
const currentKeys = projectMcpKeys(codeName, projectDir);
|
|
2296
|
+
if (!previousKeys || !currentKeys) {
|
|
2297
|
+
clearPresenceReaperState(codeName);
|
|
2298
|
+
log(
|
|
2299
|
+
`[hot-reload] .mcp.json key membership unavailable for '${codeName}' \u2014 clearing full presence-reaper state (ENG-5285)`
|
|
2300
|
+
);
|
|
2301
|
+
} else {
|
|
2302
|
+
const addedOrRemoved = /* @__PURE__ */ new Set();
|
|
2303
|
+
for (const k of currentKeys) if (!previousKeys.has(k)) addedOrRemoved.add(k);
|
|
2304
|
+
for (const k of previousKeys) if (!currentKeys.has(k)) addedOrRemoved.add(k);
|
|
2305
|
+
if (addedOrRemoved.size > 0) {
|
|
2306
|
+
clearPresenceReaperStateForKeys(codeName, addedOrRemoved);
|
|
2307
|
+
log(
|
|
2308
|
+
`[hot-reload] .mcp.json membership changed for '${codeName}': ${addedOrRemoved.size} key(s) added/removed [${Array.from(addedOrRemoved).sort().join(", ")}] \u2014 reaper state cleared for those keys only (ENG-5285)`
|
|
2309
|
+
);
|
|
2310
|
+
} else {
|
|
2311
|
+
log(
|
|
2312
|
+
`[hot-reload] .mcp.json value-only drift for '${codeName}' \u2014 preserving presence-reaper state for the unchanged declared key set (ENG-5285)`
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
}
|
|
2124
2316
|
scheduleSessionRestart(codeName, 0, ".mcp.json content change (ENG-4897)");
|
|
2125
2317
|
runningMcpHashes.delete(codeName);
|
|
2318
|
+
runningMcpServerKeys.delete(codeName);
|
|
2126
2319
|
return;
|
|
2320
|
+
}
|
|
2127
2321
|
}
|
|
2128
2322
|
}
|
|
2129
2323
|
var knownChannelConfigHashes = /* @__PURE__ */ new Map();
|
|
@@ -2132,6 +2326,10 @@ var knownTasksHashes = /* @__PURE__ */ new Map();
|
|
|
2132
2326
|
var knownIntegrationHashes = /* @__PURE__ */ new Map();
|
|
2133
2327
|
var knownManagedMcpHashes = /* @__PURE__ */ new Map();
|
|
2134
2328
|
var knownSkillHashes = /* @__PURE__ */ new Map();
|
|
2329
|
+
var managedToolkitIdByAgentAndServerId = /* @__PURE__ */ new Map();
|
|
2330
|
+
function managedToolkitMapKey(agentId, serverId) {
|
|
2331
|
+
return `${agentId}\0${serverId}`;
|
|
2332
|
+
}
|
|
2135
2333
|
var lastCronRunTs = /* @__PURE__ */ new Map();
|
|
2136
2334
|
var lastWorkTriggerAt = /* @__PURE__ */ new Map();
|
|
2137
2335
|
var alertedStaleItems = /* @__PURE__ */ new Set();
|
|
@@ -2200,7 +2398,7 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
2200
2398
|
var cachedFrameworkVersion = null;
|
|
2201
2399
|
var lastVersionCheckAt = 0;
|
|
2202
2400
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2203
|
-
var agtCliVersion = true ? "0.
|
|
2401
|
+
var agtCliVersion = true ? "0.21.1" : "dev";
|
|
2204
2402
|
function resolveBrewPath(execFileSync4) {
|
|
2205
2403
|
try {
|
|
2206
2404
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -4011,6 +4209,10 @@ async function processAgent(agent, agentStates) {
|
|
|
4011
4209
|
for (const tk of agentToolkits) {
|
|
4012
4210
|
const serverId = tk.toolkit_id.replace(/[^a-z0-9]/gi, "_").toLowerCase();
|
|
4013
4211
|
expectedServerIds.add(serverId);
|
|
4212
|
+
managedToolkitIdByAgentAndServerId.set(
|
|
4213
|
+
managedToolkitMapKey(agent.agent_id, serverId),
|
|
4214
|
+
tk.toolkit_id
|
|
4215
|
+
);
|
|
4014
4216
|
const mcpUrl = tk.mcp_url;
|
|
4015
4217
|
const mcpHeaders = tk.mcp_headers;
|
|
4016
4218
|
const proxyUrl = tk.proxy_url.startsWith("/") ? `${requireHost()}${tk.proxy_url}` : tk.proxy_url;
|
|
@@ -4362,7 +4564,60 @@ async function processAgent(agent, agentStates) {
|
|
|
4362
4564
|
codeName: agent.code_name,
|
|
4363
4565
|
mcpJson: mcpJsonParsed,
|
|
4364
4566
|
sessionStartedAt: sess?.startedAt ?? null,
|
|
4365
|
-
stopSession: (codeName) => stopPersistentSessionAndForgetMcpBaseline(codeName)
|
|
4567
|
+
stopSession: (codeName) => stopPersistentSessionAndForgetMcpBaseline(codeName),
|
|
4568
|
+
// ENG-5292: when the reaper gives up on a managed MCP (cap from
|
|
4569
|
+
// ENG-5279 + state-preservation from ENG-5285 both said "this
|
|
4570
|
+
// MCP keeps failing after 3 restart cycles"), mark the matching
|
|
4571
|
+
// integration unhealthy server-side. The next /host/managed-
|
|
4572
|
+
// toolkits poll drops it from the response → next writeMcpServer
|
|
4573
|
+
// omits it → broken MCP is cleanly gone instead of declared-but-
|
|
4574
|
+
// dead.
|
|
4575
|
+
//
|
|
4576
|
+
// Async fire-and-forget — the reaper's correctness must not
|
|
4577
|
+
// depend on the API call succeeding. Errors are caught inside
|
|
4578
|
+
// the .then/.catch chain and logged.
|
|
4579
|
+
onGiveUp: (codeName, serverKey) => {
|
|
4580
|
+
const toolkitId = managedToolkitIdByAgentAndServerId.get(
|
|
4581
|
+
managedToolkitMapKey(agent.agent_id, serverKey)
|
|
4582
|
+
);
|
|
4583
|
+
if (!toolkitId) {
|
|
4584
|
+
log(
|
|
4585
|
+
`[mcp-presence-reaper] give-up on '${codeName}:${serverKey}' \u2014 not a managed toolkit (no toolkit_id mapping), skipping integration-health API call`
|
|
4586
|
+
);
|
|
4587
|
+
return;
|
|
4588
|
+
}
|
|
4589
|
+
const agentId = agent.agent_id;
|
|
4590
|
+
api.post("/host/integration-health", {
|
|
4591
|
+
agent_id: agentId,
|
|
4592
|
+
toolkit_id: toolkitId,
|
|
4593
|
+
action: "mark_unhealthy",
|
|
4594
|
+
reason: `mcp-presence-reaper cap (${3} consecutive failed restart cycles) tripped on '${serverKey}'`
|
|
4595
|
+
}).then((res) => {
|
|
4596
|
+
log(
|
|
4597
|
+
`[mcp-presence-reaper] marked integration unhealthy for '${codeName}' toolkit '${toolkitId}' (ENG-5292): ${JSON.stringify(res).slice(0, 200)}`
|
|
4598
|
+
);
|
|
4599
|
+
}).catch((err) => {
|
|
4600
|
+
log(
|
|
4601
|
+
`[mcp-presence-reaper] failed to mark integration unhealthy for '${codeName}' toolkit '${toolkitId}' (ENG-5292): ${err.message} \u2014 local give-up state still recorded; will retry on next agent provisioning cycle if .mcp.json changes`
|
|
4602
|
+
);
|
|
4603
|
+
});
|
|
4604
|
+
},
|
|
4605
|
+
// ENG-5286: record the restart event with the API so the
|
|
4606
|
+
// agent-heartbeat-monitor cron can aggregate per-agent counts
|
|
4607
|
+
// into the McpReaperRestartsHourly CloudWatch metric. Fire-and-
|
|
4608
|
+
// forget — the reaper's correctness must not depend on this.
|
|
4609
|
+
onRestart: (codeName, { activeKeys, givenUpKeys }) => {
|
|
4610
|
+
api.post("/host/mcp-reaper-restart", {
|
|
4611
|
+
agent_id: agent.agent_id,
|
|
4612
|
+
code_name: codeName,
|
|
4613
|
+
server_keys: activeKeys,
|
|
4614
|
+
given_up_keys: givenUpKeys
|
|
4615
|
+
}).catch((err) => {
|
|
4616
|
+
log(
|
|
4617
|
+
`[mcp-presence-reaper] failed to record restart event for '${codeName}' (ENG-5286): ${err.message} \u2014 local restart still proceeded; CloudWatch metric will under-count this event`
|
|
4618
|
+
);
|
|
4619
|
+
});
|
|
4620
|
+
}
|
|
4366
4621
|
});
|
|
4367
4622
|
} catch (err) {
|
|
4368
4623
|
log(`[mcp-presence-reaper] presence check failed for '${agent.code_name}': ${err.message}`);
|