@integrity-labs/agt-cli 0.27.28 → 0.27.30
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-2ZTHGYH7.js → chunk-2L2CHPOJ.js} +1 -1
- package/dist/lib/manager-worker.js +442 -132
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/slack-channel.js +76 -0
- package/package.json +1 -1
- /package/dist/{chunk-2ZTHGYH7.js.map → chunk-2L2CHPOJ.js.map} +0 -0
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
provisionOrientHook,
|
|
16
16
|
provisionStopHook,
|
|
17
17
|
requireHost
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-2L2CHPOJ.js";
|
|
19
19
|
import {
|
|
20
20
|
getProjectDir as getProjectDir2,
|
|
21
21
|
getReadyTasks,
|
|
@@ -77,10 +77,10 @@ import {
|
|
|
77
77
|
|
|
78
78
|
// src/lib/manager-worker.ts
|
|
79
79
|
import { createHash as createHash3 } from "crypto";
|
|
80
|
-
import { readFileSync as
|
|
80
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, appendFileSync, mkdirSync as mkdirSync4, chmodSync, existsSync as existsSync5, rmSync as rmSync2, readdirSync as readdirSync3, statSync as statSync2, unlinkSync, copyFileSync } from "fs";
|
|
81
81
|
import https from "https";
|
|
82
82
|
import { execFileSync as syncExecFile } from "child_process";
|
|
83
|
-
import { join as join6, dirname } from "path";
|
|
83
|
+
import { join as join6, dirname as dirname3 } from "path";
|
|
84
84
|
import { homedir as homedir4 } from "os";
|
|
85
85
|
import { fileURLToPath } from "url";
|
|
86
86
|
|
|
@@ -941,6 +941,142 @@ function runCliProbe(binary, args, opts = {}) {
|
|
|
941
941
|
});
|
|
942
942
|
}
|
|
943
943
|
|
|
944
|
+
// src/lib/self-update-coalesce.ts
|
|
945
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
946
|
+
import { dirname } from "path";
|
|
947
|
+
var DEFAULT_SELF_UPDATE_COALESCE_MS = 30 * 60 * 1e3;
|
|
948
|
+
function resolveCoalesceWindowMs(env = process.env) {
|
|
949
|
+
const raw = env.AGT_SELF_UPDATE_COALESCE_MS;
|
|
950
|
+
if (raw == null || raw === "") return DEFAULT_SELF_UPDATE_COALESCE_MS;
|
|
951
|
+
const parsed = Number(raw);
|
|
952
|
+
if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_SELF_UPDATE_COALESCE_MS;
|
|
953
|
+
return Math.trunc(parsed);
|
|
954
|
+
}
|
|
955
|
+
function readLastSelfUpdateAppliedMs(markerPath, now = Date.now(), read = (p) => readFileSync(p, "utf-8")) {
|
|
956
|
+
let raw;
|
|
957
|
+
try {
|
|
958
|
+
raw = read(markerPath);
|
|
959
|
+
} catch {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
const v = parseInt(raw.trim(), 10);
|
|
963
|
+
if (!Number.isFinite(v) || v <= 0) return null;
|
|
964
|
+
if (v > now) return null;
|
|
965
|
+
return v;
|
|
966
|
+
}
|
|
967
|
+
function stampLastSelfUpdateApplied(markerPath, now = Date.now(), write = (p, v) => {
|
|
968
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
969
|
+
writeFileSync(p, v);
|
|
970
|
+
}) {
|
|
971
|
+
try {
|
|
972
|
+
write(markerPath, String(now));
|
|
973
|
+
} catch {
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function decideSelfUpdateCoalesce(inputs) {
|
|
977
|
+
const { windowMs, lastAppliedMs, now } = inputs;
|
|
978
|
+
if (windowMs <= 0) return { proceed: true, remainingMs: 0, lastAppliedMs: null };
|
|
979
|
+
if (lastAppliedMs == null) return { proceed: true, remainingMs: 0, lastAppliedMs: null };
|
|
980
|
+
const elapsed = now - lastAppliedMs;
|
|
981
|
+
if (elapsed < 0) return { proceed: true, remainingMs: 0, lastAppliedMs: null };
|
|
982
|
+
if (elapsed >= windowMs) return { proceed: true, remainingMs: 0, lastAppliedMs: null };
|
|
983
|
+
return { proceed: false, remainingMs: windowMs - elapsed, lastAppliedMs };
|
|
984
|
+
}
|
|
985
|
+
function formatCoalesceDeferLogLine(opts) {
|
|
986
|
+
const remainingSec = Math.ceil(opts.remainingMs / 1e3);
|
|
987
|
+
const windowMin = Math.round(opts.windowMs / 6e4);
|
|
988
|
+
return `[self-update] coalescing \u2014 ${opts.installed} \u2192 ${opts.latest} (${opts.channelLabel}) available, deferring for ${remainingSec}s (last restart inside ${windowMin}min window). See ENG-5862. Override with AGT_SELF_UPDATE_COALESCE_MS=0 during incident response.`;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// src/lib/atomic-write.ts
|
|
992
|
+
import { closeSync, fsyncSync, openSync, writeSync, renameSync, mkdirSync as mkdirSync2 } from "fs";
|
|
993
|
+
import { dirname as dirname2 } from "path";
|
|
994
|
+
function atomicWriteFileSync(path, data) {
|
|
995
|
+
const dirPath = dirname2(path);
|
|
996
|
+
const tmpPath = `${path}.tmp.${process.pid}.${Math.random().toString(36).slice(2, 8)}`;
|
|
997
|
+
try {
|
|
998
|
+
mkdirSync2(dirPath, { recursive: true });
|
|
999
|
+
} catch {
|
|
1000
|
+
}
|
|
1001
|
+
const fd = openSync(tmpPath, "w", 420);
|
|
1002
|
+
try {
|
|
1003
|
+
writeSync(fd, data);
|
|
1004
|
+
try {
|
|
1005
|
+
fsyncSync(fd);
|
|
1006
|
+
} catch {
|
|
1007
|
+
}
|
|
1008
|
+
} finally {
|
|
1009
|
+
closeSync(fd);
|
|
1010
|
+
}
|
|
1011
|
+
renameSync(tmpPath, path);
|
|
1012
|
+
try {
|
|
1013
|
+
const dirFd = openSync(dirPath, "r");
|
|
1014
|
+
try {
|
|
1015
|
+
fsyncSync(dirFd);
|
|
1016
|
+
} finally {
|
|
1017
|
+
closeSync(dirFd);
|
|
1018
|
+
}
|
|
1019
|
+
} catch {
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/lib/claude-pid-tracker.ts
|
|
1024
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
1025
|
+
function readPidFile(path) {
|
|
1026
|
+
if (!existsSync(path)) return { version: 1, spawns: [] };
|
|
1027
|
+
try {
|
|
1028
|
+
const raw = JSON.parse(readFileSync2(path, "utf-8"));
|
|
1029
|
+
if (raw.version !== 1 || !Array.isArray(raw.spawns)) return { version: 1, spawns: [] };
|
|
1030
|
+
const spawns = raw.spawns.filter(
|
|
1031
|
+
(s) => !!s && typeof s.pid === "number" && Number.isFinite(s.pid) && s.pid > 0
|
|
1032
|
+
);
|
|
1033
|
+
return { version: 1, spawns };
|
|
1034
|
+
} catch {
|
|
1035
|
+
return { version: 1, spawns: [] };
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function writePidFile(path, file) {
|
|
1039
|
+
atomicWriteFileSync(path, JSON.stringify(file, null, 2));
|
|
1040
|
+
}
|
|
1041
|
+
function addSpawn(path, record) {
|
|
1042
|
+
const file = readPidFile(path);
|
|
1043
|
+
const filtered = file.spawns.filter((s) => s.pid !== record.pid);
|
|
1044
|
+
writePidFile(path, { version: 1, spawns: [...filtered, record] });
|
|
1045
|
+
}
|
|
1046
|
+
function removeSpawn(path, pid) {
|
|
1047
|
+
const file = readPidFile(path);
|
|
1048
|
+
const next = file.spawns.filter((s) => s.pid !== pid);
|
|
1049
|
+
if (next.length !== file.spawns.length) writePidFile(path, { version: 1, spawns: next });
|
|
1050
|
+
}
|
|
1051
|
+
function decideReaperActions(file, isAlive, looksLikeClaude = () => true) {
|
|
1052
|
+
const toKill = [];
|
|
1053
|
+
const alreadyDead = [];
|
|
1054
|
+
const pidReusedSkipped = [];
|
|
1055
|
+
for (const s of file.spawns) {
|
|
1056
|
+
if (!isAlive(s.pid)) {
|
|
1057
|
+
alreadyDead.push(s);
|
|
1058
|
+
continue;
|
|
1059
|
+
}
|
|
1060
|
+
if (!looksLikeClaude(s.pid)) {
|
|
1061
|
+
pidReusedSkipped.push(s);
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
toKill.push(s);
|
|
1065
|
+
}
|
|
1066
|
+
const toResetKanban = toKill.filter(
|
|
1067
|
+
(s) => s.kind === "kanban-work" && typeof s.kanban_card_id === "string" && s.kanban_card_id.length > 0
|
|
1068
|
+
);
|
|
1069
|
+
return { toKill, alreadyDead, pidReusedSkipped, toResetKanban };
|
|
1070
|
+
}
|
|
1071
|
+
function formatDrainShutdownLine(opts) {
|
|
1072
|
+
const killed = opts.killedPids && opts.killedPids.length > 0 ? ` killed_pids=${opts.killedPids.join(",")}` : "";
|
|
1073
|
+
const note = opts.note ? ` note="${opts.note.replace(/"/g, '\\"')}"` : "";
|
|
1074
|
+
return `[drain] step=${opts.step} elapsed_ms=${opts.elapsedMs}${killed}${note}`;
|
|
1075
|
+
}
|
|
1076
|
+
function formatReaperBootLine(opts) {
|
|
1077
|
+
return `[drain] step=boot-reaper total=${opts.totalRecorded} killed=${opts.killed} already_dead=${opts.alreadyDead} pid_reused_skipped=${opts.pidReusedSkipped} kanban_reset=${opts.kanbanReset}`;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
944
1080
|
// src/lib/usage-banner-monitor.ts
|
|
945
1081
|
var MIN_CHECK_INTERVAL_MS = 6e4;
|
|
946
1082
|
var PANE_TAIL_LINES_FOR_BANNER = 200;
|
|
@@ -997,7 +1133,7 @@ async function maybeReportUsageBanner(args) {
|
|
|
997
1133
|
}
|
|
998
1134
|
|
|
999
1135
|
// src/lib/token-usage-monitor.ts
|
|
1000
|
-
import { readdirSync, readFileSync, statSync } from "fs";
|
|
1136
|
+
import { readdirSync, readFileSync as readFileSync3, statSync } from "fs";
|
|
1001
1137
|
import { join } from "path";
|
|
1002
1138
|
var MIN_CHECK_INTERVAL_MS2 = 6e4;
|
|
1003
1139
|
var TRANSCRIPT_MTIME_WINDOW_MS = 2 * 24 * 60 * 60 * 1e3;
|
|
@@ -1043,7 +1179,7 @@ async function maybeReportTokenUsage(args) {
|
|
|
1043
1179
|
}
|
|
1044
1180
|
let content;
|
|
1045
1181
|
try {
|
|
1046
|
-
content =
|
|
1182
|
+
content = readFileSync3(path, "utf-8");
|
|
1047
1183
|
} catch (err) {
|
|
1048
1184
|
log2(`[token-usage] read failed for '${codeName}/${name}': ${err.message}`);
|
|
1049
1185
|
continue;
|
|
@@ -1124,7 +1260,7 @@ async function maybeReportTokenUsage(args) {
|
|
|
1124
1260
|
}
|
|
1125
1261
|
|
|
1126
1262
|
// src/lib/activity-cache-monitor.ts
|
|
1127
|
-
import { existsSync, readFileSync as
|
|
1263
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4 } from "fs";
|
|
1128
1264
|
import { homedir } from "os";
|
|
1129
1265
|
import { join as join2 } from "path";
|
|
1130
1266
|
var MIN_CHECK_INTERVAL_MS3 = 6e4;
|
|
@@ -1171,12 +1307,12 @@ async function maybeReportActivityCache(args) {
|
|
|
1171
1307
|
const nowMs = now.getTime();
|
|
1172
1308
|
if (nowMs - state3.lastCheckedAt < MIN_CHECK_INTERVAL_MS3) return;
|
|
1173
1309
|
state3.lastCheckedAt = nowMs;
|
|
1174
|
-
if (!
|
|
1310
|
+
if (!existsSync2(STATS_CACHE_PATH)) {
|
|
1175
1311
|
return;
|
|
1176
1312
|
}
|
|
1177
1313
|
let raw;
|
|
1178
1314
|
try {
|
|
1179
|
-
raw =
|
|
1315
|
+
raw = readFileSync4(STATS_CACHE_PATH, "utf-8");
|
|
1180
1316
|
} catch (err) {
|
|
1181
1317
|
log2(`[activity-cache] readFileSync failed: ${err.message}`);
|
|
1182
1318
|
return;
|
|
@@ -1743,7 +1879,7 @@ function normalize(value) {
|
|
|
1743
1879
|
}
|
|
1744
1880
|
|
|
1745
1881
|
// src/lib/channel-hash-cache.ts
|
|
1746
|
-
import { existsSync as
|
|
1882
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
1747
1883
|
import { join as join4 } from "path";
|
|
1748
1884
|
var CACHE_FILENAME = "channel-hash-cache.json";
|
|
1749
1885
|
function getChannelHashCacheFile(configDir) {
|
|
@@ -1751,10 +1887,10 @@ function getChannelHashCacheFile(configDir) {
|
|
|
1751
1887
|
}
|
|
1752
1888
|
function loadChannelHashCache(target, configDir) {
|
|
1753
1889
|
const path = getChannelHashCacheFile(configDir);
|
|
1754
|
-
if (!
|
|
1890
|
+
if (!existsSync3(path)) return;
|
|
1755
1891
|
let parsed;
|
|
1756
1892
|
try {
|
|
1757
|
-
parsed = JSON.parse(
|
|
1893
|
+
parsed = JSON.parse(readFileSync5(path, "utf-8"));
|
|
1758
1894
|
} catch {
|
|
1759
1895
|
return;
|
|
1760
1896
|
}
|
|
@@ -1768,7 +1904,7 @@ function saveChannelHashCache(source, configDir) {
|
|
|
1768
1904
|
const obj = {};
|
|
1769
1905
|
for (const [key, value] of source) obj[key] = value;
|
|
1770
1906
|
try {
|
|
1771
|
-
|
|
1907
|
+
writeFileSync2(path, JSON.stringify(obj, null, 2));
|
|
1772
1908
|
} catch {
|
|
1773
1909
|
}
|
|
1774
1910
|
}
|
|
@@ -2299,7 +2435,7 @@ function clearAgentState(agentId, codeName) {
|
|
|
2299
2435
|
}
|
|
2300
2436
|
|
|
2301
2437
|
// src/lib/restart-flags.ts
|
|
2302
|
-
import { existsSync as
|
|
2438
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync6, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
2303
2439
|
import { homedir as homedir3 } from "os";
|
|
2304
2440
|
import { join as join5 } from "path";
|
|
2305
2441
|
import { randomUUID } from "crypto";
|
|
@@ -2311,12 +2447,12 @@ function flagPath(codeName) {
|
|
|
2311
2447
|
}
|
|
2312
2448
|
function readRestartFlags() {
|
|
2313
2449
|
const dir = restartFlagsDir();
|
|
2314
|
-
if (!
|
|
2450
|
+
if (!existsSync4(dir)) return [];
|
|
2315
2451
|
const out = [];
|
|
2316
2452
|
for (const entry of readdirSync2(dir)) {
|
|
2317
2453
|
if (!entry.endsWith(".flag")) continue;
|
|
2318
2454
|
try {
|
|
2319
|
-
const raw =
|
|
2455
|
+
const raw = readFileSync6(join5(dir, entry), "utf8");
|
|
2320
2456
|
const parsed = JSON.parse(raw);
|
|
2321
2457
|
if (typeof parsed.codeName !== "string" || parsed.codeName.length === 0) {
|
|
2322
2458
|
parsed.codeName = entry.replace(/\.flag$/, "");
|
|
@@ -2334,7 +2470,7 @@ function readRestartFlags() {
|
|
|
2334
2470
|
}
|
|
2335
2471
|
function deleteRestartFlag(codeName) {
|
|
2336
2472
|
const path = flagPath(codeName);
|
|
2337
|
-
if (
|
|
2473
|
+
if (existsSync4(path)) {
|
|
2338
2474
|
rmSync(path, { force: true });
|
|
2339
2475
|
}
|
|
2340
2476
|
}
|
|
@@ -3015,7 +3151,7 @@ var runningMcpHashes = /* @__PURE__ */ new Map();
|
|
|
3015
3151
|
var runningMcpServerKeys = /* @__PURE__ */ new Map();
|
|
3016
3152
|
function projectMcpHash(_codeName, projectDir) {
|
|
3017
3153
|
try {
|
|
3018
|
-
const raw =
|
|
3154
|
+
const raw = readFileSync7(join6(projectDir, ".mcp.json"), "utf-8");
|
|
3019
3155
|
return createHash3("sha256").update(canonicalJson(JSON.parse(raw))).digest("hex");
|
|
3020
3156
|
} catch {
|
|
3021
3157
|
return null;
|
|
@@ -3023,7 +3159,7 @@ function projectMcpHash(_codeName, projectDir) {
|
|
|
3023
3159
|
}
|
|
3024
3160
|
function projectMcpKeys(_codeName, projectDir) {
|
|
3025
3161
|
try {
|
|
3026
|
-
const raw =
|
|
3162
|
+
const raw = readFileSync7(join6(projectDir, ".mcp.json"), "utf-8");
|
|
3027
3163
|
const parsed = JSON.parse(raw);
|
|
3028
3164
|
const servers = parsed.mcpServers;
|
|
3029
3165
|
if (!servers || typeof servers !== "object") return /* @__PURE__ */ new Set();
|
|
@@ -3034,7 +3170,7 @@ function projectMcpKeys(_codeName, projectDir) {
|
|
|
3034
3170
|
}
|
|
3035
3171
|
function readMcpHttpServerConfig(projectDir, serverKey) {
|
|
3036
3172
|
try {
|
|
3037
|
-
const raw =
|
|
3173
|
+
const raw = readFileSync7(join6(projectDir, ".mcp.json"), "utf-8");
|
|
3038
3174
|
const servers = JSON.parse(raw).mcpServers ?? {};
|
|
3039
3175
|
const entry = servers[serverKey];
|
|
3040
3176
|
if (entry && typeof entry.url === "string" && (entry.type === "http" || entry.type === void 0)) {
|
|
@@ -3208,7 +3344,7 @@ var cachedFrameworkVersion = null;
|
|
|
3208
3344
|
var lastVersionCheckAt = 0;
|
|
3209
3345
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
3210
3346
|
var lastResponsivenessProbeAt = 0;
|
|
3211
|
-
var agtCliVersion = true ? "0.27.
|
|
3347
|
+
var agtCliVersion = true ? "0.27.30" : "dev";
|
|
3212
3348
|
function resolveBrewPath(execFileSync4) {
|
|
3213
3349
|
try {
|
|
3214
3350
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -3221,7 +3357,7 @@ function resolveBrewPath(execFileSync4) {
|
|
|
3221
3357
|
"/usr/local/bin/brew"
|
|
3222
3358
|
];
|
|
3223
3359
|
for (const path of fallbacks) {
|
|
3224
|
-
if (
|
|
3360
|
+
if (existsSync5(path)) return path;
|
|
3225
3361
|
}
|
|
3226
3362
|
return null;
|
|
3227
3363
|
}
|
|
@@ -3231,7 +3367,7 @@ function claudeBinaryInstalled(execFileSync4) {
|
|
|
3231
3367
|
"/opt/homebrew/bin/claude",
|
|
3232
3368
|
"/usr/local/bin/claude"
|
|
3233
3369
|
];
|
|
3234
|
-
if (canonical.some((path) =>
|
|
3370
|
+
if (canonical.some((path) => existsSync5(path))) return true;
|
|
3235
3371
|
try {
|
|
3236
3372
|
execFileSync4("which", ["claude"], { timeout: 5e3 });
|
|
3237
3373
|
return true;
|
|
@@ -3303,7 +3439,7 @@ async function ensureToolkitCli(toolkitSlug) {
|
|
|
3303
3439
|
toolkitCliEnsured.add(toolkitSlug);
|
|
3304
3440
|
return;
|
|
3305
3441
|
}
|
|
3306
|
-
brewBinDir =
|
|
3442
|
+
brewBinDir = dirname3(brewPath);
|
|
3307
3443
|
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
3308
3444
|
log(`[toolkit-install] ${toolkitSlug}: installing via brew (${pkg})\u2026`);
|
|
3309
3445
|
if (isRoot) {
|
|
@@ -3384,8 +3520,8 @@ function claudeManagedSettingsPath() {
|
|
|
3384
3520
|
function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
3385
3521
|
try {
|
|
3386
3522
|
let settings = {};
|
|
3387
|
-
if (
|
|
3388
|
-
const raw =
|
|
3523
|
+
if (existsSync5(path)) {
|
|
3524
|
+
const raw = readFileSync7(path, "utf-8").trim();
|
|
3389
3525
|
if (raw) {
|
|
3390
3526
|
let parsed;
|
|
3391
3527
|
try {
|
|
@@ -3401,8 +3537,8 @@ function ensureClaudeManagedSettings(path = claudeManagedSettingsPath()) {
|
|
|
3401
3537
|
}
|
|
3402
3538
|
if (settings.channelsEnabled === true) return;
|
|
3403
3539
|
settings.channelsEnabled = true;
|
|
3404
|
-
|
|
3405
|
-
|
|
3540
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
3541
|
+
writeFileSync4(path, `${JSON.stringify(settings, null, 2)}
|
|
3406
3542
|
`);
|
|
3407
3543
|
log(`[managed-settings] set channelsEnabled:true in ${path} (ENG-5786 \u2014 unblocks Claude Code channels)`);
|
|
3408
3544
|
} catch (err) {
|
|
@@ -3441,11 +3577,11 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
3441
3577
|
log(`Claude Code install failed: ${err.message}`);
|
|
3442
3578
|
return;
|
|
3443
3579
|
}
|
|
3444
|
-
const brewBinDir =
|
|
3580
|
+
const brewBinDir = dirname3(brewPath);
|
|
3445
3581
|
if (!process.env.PATH?.split(":").includes(brewBinDir)) {
|
|
3446
3582
|
process.env.PATH = `${brewBinDir}:${process.env.PATH ?? ""}`;
|
|
3447
3583
|
}
|
|
3448
|
-
if (
|
|
3584
|
+
if (existsSync5("/home/linuxbrew/.linuxbrew/bin/claude")) {
|
|
3449
3585
|
log("Claude Code installed successfully");
|
|
3450
3586
|
} else {
|
|
3451
3587
|
log("Claude Code install completed but binary not found at expected path \u2014 check brew logs");
|
|
@@ -3461,13 +3597,13 @@ function claudeCodeUpgradeMarkerPath() {
|
|
|
3461
3597
|
}
|
|
3462
3598
|
function stampClaudeCodeUpgradeMarker() {
|
|
3463
3599
|
try {
|
|
3464
|
-
|
|
3600
|
+
writeFileSync4(claudeCodeUpgradeMarkerPath(), String(Date.now()));
|
|
3465
3601
|
} catch {
|
|
3466
3602
|
}
|
|
3467
3603
|
}
|
|
3468
3604
|
function claudeCodeUpgradeThrottled() {
|
|
3469
3605
|
try {
|
|
3470
|
-
const lastCheck = parseInt(
|
|
3606
|
+
const lastCheck = parseInt(readFileSync7(claudeCodeUpgradeMarkerPath(), "utf-8").trim(), 10);
|
|
3471
3607
|
if (!Number.isFinite(lastCheck)) return false;
|
|
3472
3608
|
return Date.now() - lastCheck < CLAUDE_CODE_UPGRADE_CHECK_INTERVAL_MS;
|
|
3473
3609
|
} catch {
|
|
@@ -3516,6 +3652,9 @@ ${r.stderr}`;
|
|
|
3516
3652
|
});
|
|
3517
3653
|
}
|
|
3518
3654
|
var UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
3655
|
+
function selfUpdateAppliedMarkerPath() {
|
|
3656
|
+
return join6(homedir4(), ".augmented", ".last-self-update-applied");
|
|
3657
|
+
}
|
|
3519
3658
|
var selfUpdateUpToDateLogged = false;
|
|
3520
3659
|
var restartAfterUpgrade = false;
|
|
3521
3660
|
var pendingUpgradeVersion = null;
|
|
@@ -3579,6 +3718,22 @@ async function checkAndUpdateCliViaBrew() {
|
|
|
3579
3718
|
if (agtOutdated) {
|
|
3580
3719
|
const installed = agtOutdated.installed_versions?.[0] ?? "unknown";
|
|
3581
3720
|
const latest = agtOutdated.current_version ?? "unknown";
|
|
3721
|
+
const coalesceWindowMs = resolveCoalesceWindowMs();
|
|
3722
|
+
const coalesce = decideSelfUpdateCoalesce({
|
|
3723
|
+
windowMs: coalesceWindowMs,
|
|
3724
|
+
lastAppliedMs: readLastSelfUpdateAppliedMs(selfUpdateAppliedMarkerPath()),
|
|
3725
|
+
now: Date.now()
|
|
3726
|
+
});
|
|
3727
|
+
if (!coalesce.proceed) {
|
|
3728
|
+
log(formatCoalesceDeferLogLine({
|
|
3729
|
+
installed,
|
|
3730
|
+
latest,
|
|
3731
|
+
channelLabel: "brew",
|
|
3732
|
+
remainingMs: coalesce.remainingMs,
|
|
3733
|
+
windowMs: coalesceWindowMs
|
|
3734
|
+
}));
|
|
3735
|
+
return;
|
|
3736
|
+
}
|
|
3582
3737
|
log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading via brew...`);
|
|
3583
3738
|
try {
|
|
3584
3739
|
execFileSync4(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
|
|
@@ -3586,6 +3741,7 @@ async function checkAndUpdateCliViaBrew() {
|
|
|
3586
3741
|
stdio: "pipe"
|
|
3587
3742
|
});
|
|
3588
3743
|
log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
|
|
3744
|
+
stampLastSelfUpdateApplied(selfUpdateAppliedMarkerPath());
|
|
3589
3745
|
restartAfterUpgrade = true;
|
|
3590
3746
|
pendingUpgradeVersion = latest;
|
|
3591
3747
|
} catch (err) {
|
|
@@ -3646,6 +3802,22 @@ async function checkAndUpdateCliViaNpm() {
|
|
|
3646
3802
|
}
|
|
3647
3803
|
return;
|
|
3648
3804
|
}
|
|
3805
|
+
const coalesceWindowMs = resolveCoalesceWindowMs();
|
|
3806
|
+
const coalesce = decideSelfUpdateCoalesce({
|
|
3807
|
+
windowMs: coalesceWindowMs,
|
|
3808
|
+
lastAppliedMs: readLastSelfUpdateAppliedMs(selfUpdateAppliedMarkerPath()),
|
|
3809
|
+
now: Date.now()
|
|
3810
|
+
});
|
|
3811
|
+
if (!coalesce.proceed) {
|
|
3812
|
+
log(formatCoalesceDeferLogLine({
|
|
3813
|
+
installed: agtCliVersion,
|
|
3814
|
+
latest,
|
|
3815
|
+
channelLabel: `npm, channel=${channel}`,
|
|
3816
|
+
remainingMs: coalesce.remainingMs,
|
|
3817
|
+
windowMs: coalesceWindowMs
|
|
3818
|
+
}));
|
|
3819
|
+
return;
|
|
3820
|
+
}
|
|
3649
3821
|
log(`[self-update] agt CLI update available: ${agtCliVersion} \u2192 ${latest} (channel=${channel}). Upgrading via npm...`);
|
|
3650
3822
|
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
3651
3823
|
const cmd = isRoot ? "npm" : "sudo";
|
|
@@ -3665,6 +3837,7 @@ async function checkAndUpdateCliViaNpm() {
|
|
|
3665
3837
|
try {
|
|
3666
3838
|
execFileSync4(cmd, args, { timeout: 18e4, stdio: "pipe" });
|
|
3667
3839
|
log(`[self-update] agt CLI upgraded to ${latest}. Scheduling manager restart so the new binary takes effect.`);
|
|
3840
|
+
stampLastSelfUpdateApplied(selfUpdateAppliedMarkerPath());
|
|
3668
3841
|
restartAfterUpgrade = true;
|
|
3669
3842
|
pendingUpgradeVersion = latest;
|
|
3670
3843
|
} catch (err) {
|
|
@@ -3727,7 +3900,7 @@ async function applyClaudeAuthToEnv(childEnv, label) {
|
|
|
3727
3900
|
const claudeDir = join6(homedir4(), ".claude");
|
|
3728
3901
|
for (const filename of [".credentials.json", "credentials.json"]) {
|
|
3729
3902
|
const p = join6(claudeDir, filename);
|
|
3730
|
-
if (
|
|
3903
|
+
if (existsSync5(p)) {
|
|
3731
3904
|
try {
|
|
3732
3905
|
rmSync2(p, { force: true });
|
|
3733
3906
|
log(`[${label}] Removed ${p} (api_key mode \u2014 preventing OAuth fallback)`);
|
|
@@ -3741,14 +3914,14 @@ async function applyClaudeAuthToEnv(childEnv, label) {
|
|
|
3741
3914
|
}
|
|
3742
3915
|
function loadGatewayPorts() {
|
|
3743
3916
|
try {
|
|
3744
|
-
return JSON.parse(
|
|
3917
|
+
return JSON.parse(readFileSync7(GATEWAY_PORTS_FILE, "utf-8"));
|
|
3745
3918
|
} catch {
|
|
3746
3919
|
return {};
|
|
3747
3920
|
}
|
|
3748
3921
|
}
|
|
3749
3922
|
function saveGatewayPorts(ports) {
|
|
3750
|
-
|
|
3751
|
-
|
|
3923
|
+
mkdirSync4(AUGMENTED_DIR, { recursive: true });
|
|
3924
|
+
writeFileSync4(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
|
|
3752
3925
|
}
|
|
3753
3926
|
function allocatePort(codeName) {
|
|
3754
3927
|
const ports = loadGatewayPorts();
|
|
@@ -3785,7 +3958,7 @@ function saveChannelHashCache2() {
|
|
|
3785
3958
|
function send(msg) {
|
|
3786
3959
|
if (msg.type === "state-update") {
|
|
3787
3960
|
try {
|
|
3788
|
-
|
|
3961
|
+
atomicWriteFileSync(getStateFile(), JSON.stringify(msg.state, null, 2));
|
|
3789
3962
|
} catch {
|
|
3790
3963
|
}
|
|
3791
3964
|
}
|
|
@@ -3817,8 +3990,8 @@ function log(msg) {
|
|
|
3817
3990
|
if (!managerLogPath) {
|
|
3818
3991
|
try {
|
|
3819
3992
|
managerLogPath = join6(homedir4(), ".augmented", "manager.log");
|
|
3820
|
-
|
|
3821
|
-
if (
|
|
3993
|
+
mkdirSync4(dirname3(managerLogPath), { recursive: true });
|
|
3994
|
+
if (existsSync5(managerLogPath)) {
|
|
3822
3995
|
chmodSync(managerLogPath, 384);
|
|
3823
3996
|
}
|
|
3824
3997
|
} catch {
|
|
@@ -3846,7 +4019,7 @@ function sha256(content) {
|
|
|
3846
4019
|
}
|
|
3847
4020
|
function hashFile(filePath) {
|
|
3848
4021
|
try {
|
|
3849
|
-
const content =
|
|
4022
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
3850
4023
|
return sha256(content);
|
|
3851
4024
|
} catch {
|
|
3852
4025
|
return null;
|
|
@@ -3870,7 +4043,7 @@ function parseSkillFrontmatter(content) {
|
|
|
3870
4043
|
return out;
|
|
3871
4044
|
}
|
|
3872
4045
|
async function refreshSkillsIndexInClaudeMd(configDir, codeName, log2) {
|
|
3873
|
-
const { readdirSync: readdirSync4, readFileSync: rfs, existsSync: ex, writeFileSync:
|
|
4046
|
+
const { readdirSync: readdirSync4, readFileSync: rfs, existsSync: ex, writeFileSync: writeFileSync5 } = await import("fs");
|
|
3874
4047
|
const skillsDir = join6(configDir, codeName, "project", ".claude", "skills");
|
|
3875
4048
|
const claudeMdPath = join6(configDir, codeName, "project", "CLAUDE.md");
|
|
3876
4049
|
if (!ex(skillsDir) || !ex(claudeMdPath)) return;
|
|
@@ -3918,7 +4091,7 @@ ${SKILLS_INDEX_END}`;
|
|
|
3918
4091
|
next = current.trimEnd() + "\n\n" + section + "\n";
|
|
3919
4092
|
}
|
|
3920
4093
|
if (next !== current) {
|
|
3921
|
-
|
|
4094
|
+
writeFileSync5(claudeMdPath, next, "utf-8");
|
|
3922
4095
|
log2(`Refreshed skills index in CLAUDE.md for '${codeName}' (${entries.length} skills)`);
|
|
3923
4096
|
}
|
|
3924
4097
|
}
|
|
@@ -3927,7 +4100,7 @@ async function migrateToProfiles() {
|
|
|
3927
4100
|
const sharedConfigPath = join6(homeDir, ".openclaw", "openclaw.json");
|
|
3928
4101
|
let sharedConfig;
|
|
3929
4102
|
try {
|
|
3930
|
-
sharedConfig = JSON.parse(
|
|
4103
|
+
sharedConfig = JSON.parse(readFileSync7(sharedConfigPath, "utf-8"));
|
|
3931
4104
|
} catch {
|
|
3932
4105
|
return;
|
|
3933
4106
|
}
|
|
@@ -3941,7 +4114,7 @@ async function migrateToProfiles() {
|
|
|
3941
4114
|
if (!codeName) continue;
|
|
3942
4115
|
if (codeName === "main") continue;
|
|
3943
4116
|
const profileDir = join6(homeDir, `.openclaw-${codeName}`);
|
|
3944
|
-
if (
|
|
4117
|
+
if (existsSync5(join6(profileDir, "openclaw.json"))) continue;
|
|
3945
4118
|
log(`Migrating agent '${codeName}' to per-agent profile`);
|
|
3946
4119
|
if (adapter.seedProfileConfig) {
|
|
3947
4120
|
adapter.seedProfileConfig(codeName);
|
|
@@ -3949,10 +4122,10 @@ async function migrateToProfiles() {
|
|
|
3949
4122
|
const sharedAuthDir = join6(homeDir, ".openclaw", "agents", codeName, "agent");
|
|
3950
4123
|
const profileAuthDir = join6(profileDir, "agents", codeName, "agent");
|
|
3951
4124
|
const authFile = join6(sharedAuthDir, "auth-profiles.json");
|
|
3952
|
-
if (
|
|
3953
|
-
|
|
3954
|
-
const authContent =
|
|
3955
|
-
|
|
4125
|
+
if (existsSync5(authFile)) {
|
|
4126
|
+
mkdirSync4(profileAuthDir, { recursive: true });
|
|
4127
|
+
const authContent = readFileSync7(authFile, "utf-8");
|
|
4128
|
+
writeFileSync4(join6(profileAuthDir, "auth-profiles.json"), authContent);
|
|
3956
4129
|
}
|
|
3957
4130
|
allocatePort(codeName);
|
|
3958
4131
|
migrated++;
|
|
@@ -3990,7 +4163,7 @@ function readGatewayToken(codeName) {
|
|
|
3990
4163
|
}
|
|
3991
4164
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
3992
4165
|
try {
|
|
3993
|
-
const cfg = JSON.parse(
|
|
4166
|
+
const cfg = JSON.parse(readFileSync7(join6(homeDir, `.openclaw-${codeName}`, "openclaw.json"), "utf-8"));
|
|
3994
4167
|
return cfg?.gateway?.auth?.token;
|
|
3995
4168
|
} catch {
|
|
3996
4169
|
return void 0;
|
|
@@ -4000,9 +4173,9 @@ var GATEWAY_HUNG_TIMEOUT_MS = 5 * 6e4;
|
|
|
4000
4173
|
function isGatewayHung(codeName) {
|
|
4001
4174
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
4002
4175
|
const jobsPath = join6(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
4003
|
-
if (!
|
|
4176
|
+
if (!existsSync5(jobsPath)) return false;
|
|
4004
4177
|
try {
|
|
4005
|
-
const data = JSON.parse(
|
|
4178
|
+
const data = JSON.parse(readFileSync7(jobsPath, "utf-8"));
|
|
4006
4179
|
const jobs = data.jobs ?? data;
|
|
4007
4180
|
if (!Array.isArray(jobs)) return false;
|
|
4008
4181
|
const now = Date.now();
|
|
@@ -4042,12 +4215,12 @@ async function ensureGatewayRunning(codeName, adapter) {
|
|
|
4042
4215
|
try {
|
|
4043
4216
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
4044
4217
|
const configPath = join6(homeDir, `.openclaw-${codeName}`, "openclaw.json");
|
|
4045
|
-
if (
|
|
4046
|
-
const cfg = JSON.parse(
|
|
4218
|
+
if (existsSync5(configPath)) {
|
|
4219
|
+
const cfg = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
4047
4220
|
if (cfg.gateway?.port !== status.port) {
|
|
4048
4221
|
if (!cfg.gateway) cfg.gateway = {};
|
|
4049
4222
|
cfg.gateway.port = status.port;
|
|
4050
|
-
|
|
4223
|
+
writeFileSync4(configPath, JSON.stringify(cfg, null, 2));
|
|
4051
4224
|
}
|
|
4052
4225
|
}
|
|
4053
4226
|
} catch {
|
|
@@ -4068,11 +4241,11 @@ async function ensureGatewayRunning(codeName, adapter) {
|
|
|
4068
4241
|
try {
|
|
4069
4242
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
4070
4243
|
const configPath = join6(homeDir, `.openclaw-${codeName}`, "openclaw.json");
|
|
4071
|
-
if (
|
|
4072
|
-
const cfg = JSON.parse(
|
|
4244
|
+
if (existsSync5(configPath)) {
|
|
4245
|
+
const cfg = JSON.parse(readFileSync7(configPath, "utf-8"));
|
|
4073
4246
|
if (!cfg.gateway) cfg.gateway = {};
|
|
4074
4247
|
cfg.gateway.port = port;
|
|
4075
|
-
|
|
4248
|
+
writeFileSync4(configPath, JSON.stringify(cfg, null, 2));
|
|
4076
4249
|
}
|
|
4077
4250
|
} catch {
|
|
4078
4251
|
}
|
|
@@ -4405,7 +4578,7 @@ async function pollCycle() {
|
|
|
4405
4578
|
if (restartAckStateChanged) {
|
|
4406
4579
|
try {
|
|
4407
4580
|
const ackedState = { ...state4, agents: agentStates };
|
|
4408
|
-
|
|
4581
|
+
atomicWriteFileSync(getStateFile(), JSON.stringify(ackedState, null, 2));
|
|
4409
4582
|
} catch (err) {
|
|
4410
4583
|
log(`[restart] failed to persist ack immediately: ${err.message}`);
|
|
4411
4584
|
}
|
|
@@ -4625,7 +4798,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4625
4798
|
const residuals = {
|
|
4626
4799
|
gatewayRunning: gatewayLiveness.running,
|
|
4627
4800
|
portAllocated: Object.prototype.hasOwnProperty.call(ports, agent.code_name),
|
|
4628
|
-
provisionDirExists:
|
|
4801
|
+
provisionDirExists: existsSync5(agentDir)
|
|
4629
4802
|
};
|
|
4630
4803
|
if (!hasRevokedResiduals(residuals)) {
|
|
4631
4804
|
agentStates.push({
|
|
@@ -4784,7 +4957,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4784
4957
|
try {
|
|
4785
4958
|
const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
|
|
4786
4959
|
const changedFiles = [];
|
|
4787
|
-
|
|
4960
|
+
mkdirSync4(agentDir, { recursive: true });
|
|
4788
4961
|
for (const artifact of artifacts) {
|
|
4789
4962
|
const filePath = join6(agentDir, artifact.relativePath);
|
|
4790
4963
|
let existingHash;
|
|
@@ -4806,7 +4979,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4806
4979
|
newHash = sha256(stripDynamicSections(artifact.content));
|
|
4807
4980
|
try {
|
|
4808
4981
|
const projectClaudeMd = join6(config.configDir, agent.code_name, "project", "CLAUDE.md");
|
|
4809
|
-
const existing =
|
|
4982
|
+
const existing = readFileSync7(projectClaudeMd, "utf-8");
|
|
4810
4983
|
existingHash = sha256(stripDynamicSections(existing));
|
|
4811
4984
|
} catch {
|
|
4812
4985
|
existingHash = null;
|
|
@@ -4824,7 +4997,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4824
4997
|
const generatorKeys = Object.keys(generatorServers);
|
|
4825
4998
|
let existingRaw = "";
|
|
4826
4999
|
try {
|
|
4827
|
-
existingRaw =
|
|
5000
|
+
existingRaw = readFileSync7(filePath, "utf-8");
|
|
4828
5001
|
} catch {
|
|
4829
5002
|
}
|
|
4830
5003
|
const existingServers = parseMcp(existingRaw);
|
|
@@ -4846,18 +5019,18 @@ async function processAgent(agent, agentStates) {
|
|
|
4846
5019
|
}
|
|
4847
5020
|
}
|
|
4848
5021
|
if (changedFiles.length > 0) {
|
|
4849
|
-
const isFirst = !
|
|
5022
|
+
const isFirst = !existsSync5(join6(agentDir, "CHARTER.md"));
|
|
4850
5023
|
const verb = isFirst ? "Provisioning" : "Updating";
|
|
4851
5024
|
const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
|
|
4852
5025
|
log(`${verb} '${agent.code_name}': ${fileNames}`);
|
|
4853
5026
|
for (const file of changedFiles) {
|
|
4854
5027
|
const filePath = join6(agentDir, file.relativePath);
|
|
4855
|
-
|
|
4856
|
-
|
|
5028
|
+
mkdirSync4(dirname3(filePath), { recursive: true });
|
|
5029
|
+
writeFileSync4(filePath, file.content);
|
|
4857
5030
|
}
|
|
4858
5031
|
try {
|
|
4859
5032
|
const provSkillsDir = join6(agentDir, ".claude", "skills");
|
|
4860
|
-
if (
|
|
5033
|
+
if (existsSync5(provSkillsDir)) {
|
|
4861
5034
|
for (const folder of readdirSync3(provSkillsDir)) {
|
|
4862
5035
|
if (folder.startsWith("knowledge-")) {
|
|
4863
5036
|
try {
|
|
@@ -4939,7 +5112,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4939
5112
|
}
|
|
4940
5113
|
let lastDriftCheckAt = now;
|
|
4941
5114
|
const written = agentState.writtenHashes.get(agent.agent_id);
|
|
4942
|
-
if (written &&
|
|
5115
|
+
if (written && existsSync5(agentDir)) {
|
|
4943
5116
|
const driftedFiles = [];
|
|
4944
5117
|
for (const [file, expectedHash] of written) {
|
|
4945
5118
|
const localHash = hashFile(join6(agentDir, file));
|
|
@@ -5156,13 +5329,13 @@ async function processAgent(agent, agentStates) {
|
|
|
5156
5329
|
try {
|
|
5157
5330
|
const agentProvisionDir = agentDir;
|
|
5158
5331
|
const projectDir = join6(homedir4(), ".augmented", agent.code_name, "project");
|
|
5159
|
-
|
|
5160
|
-
|
|
5332
|
+
mkdirSync4(agentProvisionDir, { recursive: true });
|
|
5333
|
+
mkdirSync4(projectDir, { recursive: true });
|
|
5161
5334
|
const provisionMcpPath = join6(agentProvisionDir, ".mcp.json");
|
|
5162
5335
|
const projectMcpPath = join6(projectDir, ".mcp.json");
|
|
5163
5336
|
let mcpConfig = { mcpServers: {} };
|
|
5164
5337
|
try {
|
|
5165
|
-
mcpConfig = JSON.parse(
|
|
5338
|
+
mcpConfig = JSON.parse(readFileSync7(provisionMcpPath, "utf-8"));
|
|
5166
5339
|
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
5167
5340
|
} catch {
|
|
5168
5341
|
}
|
|
@@ -5172,7 +5345,7 @@ async function processAgent(agent, agentStates) {
|
|
|
5172
5345
|
const tz = directChatTeamSettings?.["timezone"];
|
|
5173
5346
|
return typeof tz === "string" && tz.trim() !== "" ? tz.trim() : void 0;
|
|
5174
5347
|
})();
|
|
5175
|
-
if (
|
|
5348
|
+
if (existsSync5(localDirectChatChannel) && !mcpConfig.mcpServers["direct-chat"]) {
|
|
5176
5349
|
mcpConfig.mcpServers["direct-chat"] = {
|
|
5177
5350
|
command: "node",
|
|
5178
5351
|
args: [localDirectChatChannel],
|
|
@@ -5184,12 +5357,12 @@ async function processAgent(agent, agentStates) {
|
|
|
5184
5357
|
}
|
|
5185
5358
|
};
|
|
5186
5359
|
const serialized = JSON.stringify(mcpConfig, null, 2);
|
|
5187
|
-
|
|
5188
|
-
|
|
5360
|
+
writeFileSync4(provisionMcpPath, serialized);
|
|
5361
|
+
writeFileSync4(projectMcpPath, serialized);
|
|
5189
5362
|
log(`Channel credentials written for '${agent.code_name}/direct-chat'`);
|
|
5190
5363
|
}
|
|
5191
5364
|
const staleChannelsPath = join6(projectDir, ".mcp-channels.json");
|
|
5192
|
-
if (
|
|
5365
|
+
if (existsSync5(staleChannelsPath)) {
|
|
5193
5366
|
try {
|
|
5194
5367
|
rmSync2(staleChannelsPath, { force: true });
|
|
5195
5368
|
} catch {
|
|
@@ -5267,7 +5440,7 @@ async function processAgent(agent, agentStates) {
|
|
|
5267
5440
|
const envIntPath = join6(projectDir, ".env.integrations");
|
|
5268
5441
|
let preWriteEnv;
|
|
5269
5442
|
try {
|
|
5270
|
-
preWriteEnv =
|
|
5443
|
+
preWriteEnv = readFileSync7(envIntPath, "utf-8");
|
|
5271
5444
|
} catch {
|
|
5272
5445
|
preWriteEnv = void 0;
|
|
5273
5446
|
}
|
|
@@ -5280,8 +5453,8 @@ async function processAgent(agent, agentStates) {
|
|
|
5280
5453
|
if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
|
|
5281
5454
|
try {
|
|
5282
5455
|
const projectMcpPath = join6(projectDir, ".mcp.json");
|
|
5283
|
-
const postWriteEnv =
|
|
5284
|
-
const mcpContent =
|
|
5456
|
+
const postWriteEnv = readFileSync7(envIntPath, "utf-8");
|
|
5457
|
+
const mcpContent = readFileSync7(projectMcpPath, "utf-8");
|
|
5285
5458
|
const changedVars = diffEnvIntegrations(preWriteEnv, postWriteEnv);
|
|
5286
5459
|
const mcpJsonForReap = JSON.parse(mcpContent);
|
|
5287
5460
|
const affectedServerKeys = findMcpServersUsingVars(mcpJsonForReap, changedVars);
|
|
@@ -5349,8 +5522,8 @@ async function processAgent(agent, agentStates) {
|
|
|
5349
5522
|
const mcpPath = frameworkAdapter.getMcpPath(agent.code_name);
|
|
5350
5523
|
if (mcpPath) {
|
|
5351
5524
|
try {
|
|
5352
|
-
const { readFileSync:
|
|
5353
|
-
const mcpConfig = JSON.parse(
|
|
5525
|
+
const { readFileSync: readFileSync8 } = await import("fs");
|
|
5526
|
+
const mcpConfig = JSON.parse(readFileSync8(mcpPath, "utf-8"));
|
|
5354
5527
|
if (mcpConfig.mcpServers) {
|
|
5355
5528
|
const managedPrefixes = [
|
|
5356
5529
|
"composio_",
|
|
@@ -5447,7 +5620,7 @@ async function processAgent(agent, agentStates) {
|
|
|
5447
5620
|
if (frameworkAdapter.installPlugin) {
|
|
5448
5621
|
try {
|
|
5449
5622
|
const pluginPath = join6(process.cwd(), "packages", "openclaw-plugin-augmented", "src", "index.ts");
|
|
5450
|
-
if (
|
|
5623
|
+
if (existsSync5(pluginPath)) {
|
|
5451
5624
|
frameworkAdapter.installPlugin(agent.code_name, "augmented", pluginPath, {
|
|
5452
5625
|
agtHost: requireHost(),
|
|
5453
5626
|
agtApiKey: getApiKey() ?? void 0,
|
|
@@ -5526,7 +5699,7 @@ async function processAgent(agent, agentStates) {
|
|
|
5526
5699
|
// install target but cheap to sweep.
|
|
5527
5700
|
join6(agentDir, ".claude", "skills")
|
|
5528
5701
|
];
|
|
5529
|
-
const existingDirs = candidateSkillDirs.filter((d) =>
|
|
5702
|
+
const existingDirs = candidateSkillDirs.filter((d) => existsSync5(d));
|
|
5530
5703
|
const discoveredEntries = /* @__PURE__ */ new Set();
|
|
5531
5704
|
for (const dir of existingDirs) {
|
|
5532
5705
|
try {
|
|
@@ -5541,7 +5714,7 @@ async function processAgent(agent, agentStates) {
|
|
|
5541
5714
|
const removeSkillFolder = (entry, reason) => {
|
|
5542
5715
|
for (const dir of existingDirs) {
|
|
5543
5716
|
const p = join6(dir, entry);
|
|
5544
|
-
if (
|
|
5717
|
+
if (existsSync5(p)) {
|
|
5545
5718
|
rmSync3(p, { recursive: true, force: true });
|
|
5546
5719
|
}
|
|
5547
5720
|
}
|
|
@@ -5703,7 +5876,7 @@ async function processAgent(agent, agentStates) {
|
|
|
5703
5876
|
let mcpJsonParsed = null;
|
|
5704
5877
|
try {
|
|
5705
5878
|
const mcpPath = join6(getProjectDir(agent.code_name), ".mcp.json");
|
|
5706
|
-
mcpJsonParsed = JSON.parse(
|
|
5879
|
+
mcpJsonParsed = JSON.parse(readFileSync7(mcpPath, "utf-8"));
|
|
5707
5880
|
} catch {
|
|
5708
5881
|
}
|
|
5709
5882
|
reapMissingMcpSessions({
|
|
@@ -5858,9 +6031,9 @@ async function processAgent(agent, agentStates) {
|
|
|
5858
6031
|
if (agentFw === "openclaw" && gatewayRunning && gatewayPort) {
|
|
5859
6032
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
5860
6033
|
const jobsPath = join6(homeDir, `.openclaw-${agent.code_name}`, "cron", "jobs.json");
|
|
5861
|
-
if (
|
|
6034
|
+
if (existsSync5(jobsPath)) {
|
|
5862
6035
|
try {
|
|
5863
|
-
const jobsData = JSON.parse(
|
|
6036
|
+
const jobsData = JSON.parse(readFileSync7(jobsPath, "utf-8"));
|
|
5864
6037
|
const kanbanJob = (jobsData.jobs ?? []).find(
|
|
5865
6038
|
(j) => typeof j.name === "string" && j.name.includes("kanban-work")
|
|
5866
6039
|
);
|
|
@@ -5994,7 +6167,7 @@ In progress for ${age} minutes \u2014 auto-failed`).catch(() => {
|
|
|
5994
6167
|
}
|
|
5995
6168
|
}
|
|
5996
6169
|
const trackedFiles = frameworkAdapter.driftTrackedFiles();
|
|
5997
|
-
if (trackedFiles.length > 0 &&
|
|
6170
|
+
if (trackedFiles.length > 0 && existsSync5(agentDir)) {
|
|
5998
6171
|
const hashes = /* @__PURE__ */ new Map();
|
|
5999
6172
|
for (const file of trackedFiles) {
|
|
6000
6173
|
const h = hashFile(join6(agentDir, file));
|
|
@@ -6041,9 +6214,9 @@ function cleanupStaleSessions(codeName) {
|
|
|
6041
6214
|
}
|
|
6042
6215
|
function cleanupCronSessions(sessionsDir, keepCount) {
|
|
6043
6216
|
const indexPath = join6(sessionsDir, "sessions.json");
|
|
6044
|
-
if (!
|
|
6217
|
+
if (!existsSync5(indexPath)) return;
|
|
6045
6218
|
try {
|
|
6046
|
-
const raw =
|
|
6219
|
+
const raw = readFileSync7(indexPath, "utf-8");
|
|
6047
6220
|
const index = JSON.parse(raw);
|
|
6048
6221
|
const cronRunKeys = Object.keys(index).filter((k) => k.includes(":cron:") && k.includes(":run:")).map((k) => ({
|
|
6049
6222
|
key: k,
|
|
@@ -6058,7 +6231,7 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
6058
6231
|
if (entry.sessionId) {
|
|
6059
6232
|
const sessionFile = join6(sessionsDir, `${entry.sessionId}.jsonl`);
|
|
6060
6233
|
try {
|
|
6061
|
-
if (
|
|
6234
|
+
if (existsSync5(sessionFile)) {
|
|
6062
6235
|
unlinkSync(sessionFile);
|
|
6063
6236
|
deletedFiles++;
|
|
6064
6237
|
}
|
|
@@ -6079,7 +6252,7 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
6079
6252
|
if (parentSessionId) {
|
|
6080
6253
|
try {
|
|
6081
6254
|
const f = join6(sessionsDir, `${parentSessionId}.jsonl`);
|
|
6082
|
-
if (
|
|
6255
|
+
if (existsSync5(f)) {
|
|
6083
6256
|
unlinkSync(f);
|
|
6084
6257
|
deletedFiles++;
|
|
6085
6258
|
}
|
|
@@ -6088,7 +6261,7 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
6088
6261
|
}
|
|
6089
6262
|
}
|
|
6090
6263
|
}
|
|
6091
|
-
|
|
6264
|
+
writeFileSync4(indexPath, JSON.stringify(index));
|
|
6092
6265
|
if (toDelete.length > 0) {
|
|
6093
6266
|
log(`Cleaned ${toDelete.length} cron session(s) and ${deletedFiles} file(s) from ${sessionsDir}`);
|
|
6094
6267
|
}
|
|
@@ -6097,9 +6270,9 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
6097
6270
|
}
|
|
6098
6271
|
var STALE_RUN_TIMEOUT_MS = 5 * 6e4;
|
|
6099
6272
|
function clearStaleCronRunState(jobsPath) {
|
|
6100
|
-
if (!
|
|
6273
|
+
if (!existsSync5(jobsPath)) return;
|
|
6101
6274
|
try {
|
|
6102
|
-
const raw =
|
|
6275
|
+
const raw = readFileSync7(jobsPath, "utf-8");
|
|
6103
6276
|
const data = JSON.parse(raw);
|
|
6104
6277
|
const jobs = data.jobs ?? data;
|
|
6105
6278
|
if (!Array.isArray(jobs)) return;
|
|
@@ -6124,13 +6297,13 @@ function clearStaleCronRunState(jobsPath) {
|
|
|
6124
6297
|
}
|
|
6125
6298
|
}
|
|
6126
6299
|
if (changed) {
|
|
6127
|
-
|
|
6300
|
+
writeFileSync4(jobsPath, JSON.stringify(data, null, 2));
|
|
6128
6301
|
}
|
|
6129
6302
|
} catch {
|
|
6130
6303
|
}
|
|
6131
6304
|
}
|
|
6132
6305
|
function cleanupOldFiles(dir, maxAgeDays, ext) {
|
|
6133
|
-
if (!
|
|
6306
|
+
if (!existsSync5(dir)) return;
|
|
6134
6307
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
6135
6308
|
let removed = 0;
|
|
6136
6309
|
try {
|
|
@@ -6155,6 +6328,25 @@ function cleanupOldFiles(dir, maxAgeDays, ext) {
|
|
|
6155
6328
|
var inFlightClaudeTasks = /* @__PURE__ */ new Set();
|
|
6156
6329
|
var claudeTaskConcurrency = /* @__PURE__ */ new Map();
|
|
6157
6330
|
var MAX_CLAUDE_CONCURRENCY = 2;
|
|
6331
|
+
function claudePidFilePath() {
|
|
6332
|
+
return join6(homedir4(), ".augmented", "manager-claude-pids.json");
|
|
6333
|
+
}
|
|
6334
|
+
var inFlightClaudePids = /* @__PURE__ */ new Map();
|
|
6335
|
+
function registerClaudeSpawn(record) {
|
|
6336
|
+
inFlightClaudePids.set(record.pid, record);
|
|
6337
|
+
try {
|
|
6338
|
+
addSpawn(claudePidFilePath(), record);
|
|
6339
|
+
} catch (err) {
|
|
6340
|
+
log(`[drain] pid-file write failed: ${err.message}`);
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
function unregisterClaudeSpawn(pid) {
|
|
6344
|
+
inFlightClaudePids.delete(pid);
|
|
6345
|
+
try {
|
|
6346
|
+
removeSpawn(claudePidFilePath(), pid);
|
|
6347
|
+
} catch {
|
|
6348
|
+
}
|
|
6349
|
+
}
|
|
6158
6350
|
var claudeSchedulerStates = /* @__PURE__ */ new Map();
|
|
6159
6351
|
async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData) {
|
|
6160
6352
|
const codeName = agent.code_name;
|
|
@@ -6426,9 +6618,9 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
6426
6618
|
try {
|
|
6427
6619
|
const claudeMdPath = join6(projectDir, "CLAUDE.md");
|
|
6428
6620
|
const serverNames = [];
|
|
6429
|
-
if (
|
|
6621
|
+
if (existsSync5(mcpConfigPath)) {
|
|
6430
6622
|
try {
|
|
6431
|
-
const d = JSON.parse(
|
|
6623
|
+
const d = JSON.parse(readFileSync7(mcpConfigPath, "utf-8"));
|
|
6432
6624
|
if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));
|
|
6433
6625
|
} catch {
|
|
6434
6626
|
}
|
|
@@ -6447,14 +6639,14 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
6447
6639
|
"--allowedTools",
|
|
6448
6640
|
allowedTools
|
|
6449
6641
|
];
|
|
6450
|
-
if (
|
|
6642
|
+
if (existsSync5(claudeMdPath)) {
|
|
6451
6643
|
claudeArgs.push("--system-prompt-file", claudeMdPath);
|
|
6452
6644
|
}
|
|
6453
6645
|
const childEnv = { ...process.env };
|
|
6454
6646
|
const envIntPath = join6(projectDir, ".env.integrations");
|
|
6455
|
-
if (
|
|
6647
|
+
if (existsSync5(envIntPath)) {
|
|
6456
6648
|
try {
|
|
6457
|
-
for (const line of
|
|
6649
|
+
for (const line of readFileSync7(envIntPath, "utf-8").split("\n")) {
|
|
6458
6650
|
if (!line || line.startsWith("#") || !line.includes("=")) continue;
|
|
6459
6651
|
const eqIdx = line.indexOf("=");
|
|
6460
6652
|
childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);
|
|
@@ -6478,11 +6670,21 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
6478
6670
|
runId = startResult.run_id;
|
|
6479
6671
|
kanbanItemId = startResult.kanban_item_id;
|
|
6480
6672
|
if (runId) childEnv["AGT_RUN_ID"] = runId;
|
|
6673
|
+
const claudeKind = task.templateId === "kanban-work" ? "kanban-work" : "scheduled-task";
|
|
6481
6674
|
const { stdout, stderr } = await execFilePromiseLong(resolveClaudeBinary(), claudeArgs, {
|
|
6482
6675
|
cwd: projectDir,
|
|
6483
6676
|
timeout: 3e5,
|
|
6484
6677
|
stdin: "ignore",
|
|
6485
|
-
env: childEnv
|
|
6678
|
+
env: childEnv,
|
|
6679
|
+
onSpawn: (pid) => registerClaudeSpawn({
|
|
6680
|
+
pid,
|
|
6681
|
+
started_at: Date.now(),
|
|
6682
|
+
kind: claudeKind,
|
|
6683
|
+
agent_id: agentId,
|
|
6684
|
+
agent_code_name: codeName,
|
|
6685
|
+
kanban_card_id: kanbanItemId ?? void 0
|
|
6686
|
+
}),
|
|
6687
|
+
onExit: (pid) => unregisterClaudeSpawn(pid)
|
|
6486
6688
|
});
|
|
6487
6689
|
if (stderr) {
|
|
6488
6690
|
log(`[claude-scheduler] Task '${task.name}' stderr for '${codeName}': ${stderr.slice(0, 500)}`);
|
|
@@ -7256,9 +7458,9 @@ ${escapeXml(msg.content)}
|
|
|
7256
7458
|
const projDir = ccProjectDir(agent.codeName);
|
|
7257
7459
|
const mcpConfigPath = join6(projDir, ".mcp.json");
|
|
7258
7460
|
const serverNames = [];
|
|
7259
|
-
if (
|
|
7461
|
+
if (existsSync5(mcpConfigPath)) {
|
|
7260
7462
|
try {
|
|
7261
|
-
const d = JSON.parse(
|
|
7463
|
+
const d = JSON.parse(readFileSync7(mcpConfigPath, "utf-8"));
|
|
7262
7464
|
if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));
|
|
7263
7465
|
} catch {
|
|
7264
7466
|
}
|
|
@@ -7278,14 +7480,14 @@ ${escapeXml(msg.content)}
|
|
|
7278
7480
|
allowedTools
|
|
7279
7481
|
];
|
|
7280
7482
|
const chatClaudeMd = join6(projDir, "CLAUDE.md");
|
|
7281
|
-
if (
|
|
7483
|
+
if (existsSync5(chatClaudeMd)) {
|
|
7282
7484
|
chatArgs.push("--system-prompt-file", chatClaudeMd);
|
|
7283
7485
|
}
|
|
7284
7486
|
const envIntPath = join6(projDir, ".env.integrations");
|
|
7285
7487
|
const childEnv = { ...process.env };
|
|
7286
|
-
if (
|
|
7488
|
+
if (existsSync5(envIntPath)) {
|
|
7287
7489
|
try {
|
|
7288
|
-
for (const line of
|
|
7490
|
+
for (const line of readFileSync7(envIntPath, "utf-8").split("\n")) {
|
|
7289
7491
|
if (!line || line.startsWith("#") || !line.includes("=")) continue;
|
|
7290
7492
|
const eqIdx = line.indexOf("=");
|
|
7291
7493
|
childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);
|
|
@@ -7298,7 +7500,19 @@ ${escapeXml(msg.content)}
|
|
|
7298
7500
|
} catch (err) {
|
|
7299
7501
|
throw new Error(`Auth resolve failed for '${agent.codeName}': ${err.message}`);
|
|
7300
7502
|
}
|
|
7301
|
-
const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), chatArgs, {
|
|
7503
|
+
const { stdout } = await execFilePromiseLong(resolveClaudeBinary(), chatArgs, {
|
|
7504
|
+
cwd: projDir,
|
|
7505
|
+
stdin: "ignore",
|
|
7506
|
+
env: childEnv,
|
|
7507
|
+
onSpawn: (pid) => registerClaudeSpawn({
|
|
7508
|
+
pid,
|
|
7509
|
+
started_at: Date.now(),
|
|
7510
|
+
kind: "direct-chat",
|
|
7511
|
+
agent_id: agent.agentId,
|
|
7512
|
+
agent_code_name: agent.codeName
|
|
7513
|
+
}),
|
|
7514
|
+
onExit: (pid) => unregisterClaudeSpawn(pid)
|
|
7515
|
+
});
|
|
7302
7516
|
reply = stdout.trim() || "[No response from agent]";
|
|
7303
7517
|
} else {
|
|
7304
7518
|
const { stdout } = await execFilePromiseLong("openclaw", [
|
|
@@ -7650,8 +7864,8 @@ function getBuiltInSkillContent(skillId) {
|
|
|
7650
7864
|
join6(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "skills", skillId, "SKILL.md")
|
|
7651
7865
|
];
|
|
7652
7866
|
for (const candidate of candidates) {
|
|
7653
|
-
if (
|
|
7654
|
-
const content =
|
|
7867
|
+
if (existsSync5(candidate)) {
|
|
7868
|
+
const content = readFileSync7(candidate, "utf-8");
|
|
7655
7869
|
const files = [{ relativePath: "SKILL.md", content }];
|
|
7656
7870
|
builtInSkillCache.set(skillId, files);
|
|
7657
7871
|
return files;
|
|
@@ -7822,6 +8036,12 @@ async function execFilePromiseLong(cmd, args, opts) {
|
|
|
7822
8036
|
stdio: [opts?.stdin === "ignore" ? "ignore" : "pipe", "pipe", "pipe"],
|
|
7823
8037
|
...opts?.env ? { env: opts.env } : {}
|
|
7824
8038
|
});
|
|
8039
|
+
if (opts?.onSpawn && typeof child.pid === "number") {
|
|
8040
|
+
try {
|
|
8041
|
+
opts.onSpawn(child.pid);
|
|
8042
|
+
} catch {
|
|
8043
|
+
}
|
|
8044
|
+
}
|
|
7825
8045
|
let stdout = "";
|
|
7826
8046
|
let stderr = "";
|
|
7827
8047
|
child.stdout?.on("data", (d) => {
|
|
@@ -7836,6 +8056,12 @@ async function execFilePromiseLong(cmd, args, opts) {
|
|
|
7836
8056
|
}, opts?.timeout ?? 12e4);
|
|
7837
8057
|
child.on("close", (code) => {
|
|
7838
8058
|
clearTimeout(timer);
|
|
8059
|
+
if (opts?.onExit && typeof child.pid === "number") {
|
|
8060
|
+
try {
|
|
8061
|
+
opts.onExit(child.pid);
|
|
8062
|
+
} catch {
|
|
8063
|
+
}
|
|
8064
|
+
}
|
|
7839
8065
|
if (code !== 0) reject(new ChildProcessError(code, stdout, stderr));
|
|
7840
8066
|
else resolve({ stdout, stderr });
|
|
7841
8067
|
});
|
|
@@ -8604,14 +8830,14 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
8604
8830
|
}
|
|
8605
8831
|
pendingFreshMemorySync.delete(agent.agent_id);
|
|
8606
8832
|
}
|
|
8607
|
-
if (
|
|
8833
|
+
if (existsSync5(memoryDir)) {
|
|
8608
8834
|
const prevHashes = memoryFileHashes.get(agent.agent_id) ?? /* @__PURE__ */ new Map();
|
|
8609
8835
|
const currentHashes = /* @__PURE__ */ new Map();
|
|
8610
8836
|
const changedMemories = [];
|
|
8611
8837
|
for (const file of readdirSync3(memoryDir)) {
|
|
8612
8838
|
if (!file.endsWith(".md")) continue;
|
|
8613
8839
|
try {
|
|
8614
|
-
const raw =
|
|
8840
|
+
const raw = readFileSync7(join6(memoryDir, file), "utf-8");
|
|
8615
8841
|
const fileHash = createHash3("sha256").update(raw).digest("hex").slice(0, 16);
|
|
8616
8842
|
currentHashes.set(file, fileHash);
|
|
8617
8843
|
if (prevHashes.get(file) === fileHash) continue;
|
|
@@ -8636,7 +8862,7 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
8636
8862
|
} catch (err) {
|
|
8637
8863
|
for (const mem of changedMemories) {
|
|
8638
8864
|
for (const [file] of currentHashes) {
|
|
8639
|
-
const parsed = parseMemoryFile(
|
|
8865
|
+
const parsed = parseMemoryFile(readFileSync7(join6(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
|
|
8640
8866
|
if (parsed?.name === mem.name) currentHashes.delete(file);
|
|
8641
8867
|
}
|
|
8642
8868
|
}
|
|
@@ -8649,7 +8875,7 @@ async function syncMemories(agent, configDir, log2) {
|
|
|
8649
8875
|
}
|
|
8650
8876
|
}
|
|
8651
8877
|
async function downloadMemories(agent, memoryDir, log2, { force }) {
|
|
8652
|
-
const localFiles =
|
|
8878
|
+
const localFiles = existsSync5(memoryDir) ? readdirSync3(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
|
|
8653
8879
|
const localListHash = createHash3("sha256").update(localFiles.join(",")).digest("hex").slice(0, 16);
|
|
8654
8880
|
const prevLocalHash = lastLocalFileHash.get(agent.agent_id);
|
|
8655
8881
|
const prevDownload = lastDownloadHash.get(agent.agent_id);
|
|
@@ -8664,7 +8890,7 @@ async function downloadMemories(agent, memoryDir, log2, { force }) {
|
|
|
8664
8890
|
lastDownloadHash.set(agent.agent_id, responseHash);
|
|
8665
8891
|
lastLocalFileHash.set(agent.agent_id, localListHash);
|
|
8666
8892
|
if (dbMemories.memories?.length) {
|
|
8667
|
-
|
|
8893
|
+
mkdirSync4(memoryDir, { recursive: true });
|
|
8668
8894
|
let written = 0;
|
|
8669
8895
|
let overwritten = 0;
|
|
8670
8896
|
for (let i = 0; i < dbMemories.memories.length; i++) {
|
|
@@ -8680,17 +8906,17 @@ description: ${JSON.stringify(mem.content.slice(0, 200))}
|
|
|
8680
8906
|
|
|
8681
8907
|
${mem.content}
|
|
8682
8908
|
`;
|
|
8683
|
-
if (
|
|
8909
|
+
if (existsSync5(filePath)) {
|
|
8684
8910
|
let existing = "";
|
|
8685
8911
|
try {
|
|
8686
|
-
existing =
|
|
8912
|
+
existing = readFileSync7(filePath, "utf-8");
|
|
8687
8913
|
} catch {
|
|
8688
8914
|
}
|
|
8689
8915
|
if (existing === desired) continue;
|
|
8690
|
-
|
|
8916
|
+
writeFileSync4(filePath, desired);
|
|
8691
8917
|
overwritten++;
|
|
8692
8918
|
} else {
|
|
8693
|
-
|
|
8919
|
+
writeFileSync4(filePath, desired);
|
|
8694
8920
|
written++;
|
|
8695
8921
|
}
|
|
8696
8922
|
}
|
|
@@ -8707,7 +8933,7 @@ ${mem.content}
|
|
|
8707
8933
|
}
|
|
8708
8934
|
}
|
|
8709
8935
|
async function cleanupAgentFiles(codeName, agentDir) {
|
|
8710
|
-
if (
|
|
8936
|
+
if (existsSync5(agentDir)) {
|
|
8711
8937
|
try {
|
|
8712
8938
|
rmSync2(agentDir, { recursive: true, force: true });
|
|
8713
8939
|
log(`Removed provision directory for '${codeName}'`);
|
|
@@ -8863,9 +9089,24 @@ async function stopPolling(opts = {}) {
|
|
|
8863
9089
|
process.exit(opts.forcedExitCode ?? 1);
|
|
8864
9090
|
}, 15e3);
|
|
8865
9091
|
shutdownTimer.unref();
|
|
9092
|
+
const drainStartedAt = Date.now();
|
|
9093
|
+
const livePids = [...inFlightClaudePids.keys()];
|
|
9094
|
+
if (livePids.length > 0) {
|
|
9095
|
+
for (const pid of livePids) {
|
|
9096
|
+
try {
|
|
9097
|
+
process.kill(pid, "SIGTERM");
|
|
9098
|
+
} catch {
|
|
9099
|
+
}
|
|
9100
|
+
}
|
|
9101
|
+
log(formatDrainShutdownLine({ step: "sigterm-claude-pids", elapsedMs: Date.now() - drainStartedAt, killedPids: livePids }));
|
|
9102
|
+
} else {
|
|
9103
|
+
log(formatDrainShutdownLine({ step: "sigterm-claude-pids", elapsedMs: 0, note: "no in-flight claude -p children" }));
|
|
9104
|
+
}
|
|
9105
|
+
const subsysStartedAt = Date.now();
|
|
8866
9106
|
stopCaffeinate();
|
|
8867
9107
|
stopRealtimeChat();
|
|
8868
9108
|
stopGatewayPool();
|
|
9109
|
+
log(formatDrainShutdownLine({ step: "stop-subsystems", elapsedMs: Date.now() - subsysStartedAt }));
|
|
8869
9110
|
for (const codeName of [...scheduledRunsByCode.keys()]) {
|
|
8870
9111
|
closeScheduledRunsForCode(codeName, "cancelled", "manager shutdown");
|
|
8871
9112
|
}
|
|
@@ -8881,8 +9122,8 @@ function startManager(opts) {
|
|
|
8881
9122
|
config = opts;
|
|
8882
9123
|
try {
|
|
8883
9124
|
const stateFile = getStateFile();
|
|
8884
|
-
if (
|
|
8885
|
-
const raw =
|
|
9125
|
+
if (existsSync5(stateFile)) {
|
|
9126
|
+
const raw = readFileSync7(stateFile, "utf-8");
|
|
8886
9127
|
const parsed = JSON.parse(raw);
|
|
8887
9128
|
if (Array.isArray(parsed.agents)) {
|
|
8888
9129
|
state4.agents = parsed.agents;
|
|
@@ -8902,8 +9143,77 @@ function startManager(opts) {
|
|
|
8902
9143
|
);
|
|
8903
9144
|
deployMcpAssets();
|
|
8904
9145
|
reapOrphanChannelMcps({ log });
|
|
8905
|
-
void
|
|
8906
|
-
|
|
9146
|
+
void (async () => {
|
|
9147
|
+
try {
|
|
9148
|
+
await reapOrphanedClaudePids();
|
|
9149
|
+
} catch (err) {
|
|
9150
|
+
log(`[drain] boot reaper failed: ${err.message}`);
|
|
9151
|
+
}
|
|
9152
|
+
void ensureHostFrameworkBinaries();
|
|
9153
|
+
startPolling();
|
|
9154
|
+
})();
|
|
9155
|
+
}
|
|
9156
|
+
async function reapOrphanedClaudePids() {
|
|
9157
|
+
const path = claudePidFilePath();
|
|
9158
|
+
const file = readPidFile(path);
|
|
9159
|
+
if (file.spawns.length === 0) return;
|
|
9160
|
+
const looksLikeClaude = (pid) => {
|
|
9161
|
+
if (process.platform !== "linux") return true;
|
|
9162
|
+
try {
|
|
9163
|
+
const comm = readFileSync7(`/proc/${pid}/comm`, "utf-8").trim().toLowerCase();
|
|
9164
|
+
return comm.includes("claude");
|
|
9165
|
+
} catch {
|
|
9166
|
+
return false;
|
|
9167
|
+
}
|
|
9168
|
+
};
|
|
9169
|
+
const decision = decideReaperActions(
|
|
9170
|
+
file,
|
|
9171
|
+
(pid) => {
|
|
9172
|
+
try {
|
|
9173
|
+
process.kill(pid, 0);
|
|
9174
|
+
return true;
|
|
9175
|
+
} catch {
|
|
9176
|
+
return false;
|
|
9177
|
+
}
|
|
9178
|
+
},
|
|
9179
|
+
looksLikeClaude
|
|
9180
|
+
);
|
|
9181
|
+
for (const spawn of decision.toKill) {
|
|
9182
|
+
try {
|
|
9183
|
+
process.kill(spawn.pid, "SIGKILL");
|
|
9184
|
+
} catch (err) {
|
|
9185
|
+
log(`[drain] reaper SIGKILL pid=${spawn.pid} failed: ${err.message}`);
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
for (const spawn of decision.pidReusedSkipped) {
|
|
9189
|
+
log(
|
|
9190
|
+
`[drain] reaper skipped pid=${spawn.pid} agent=${spawn.agent_code_name} card=${spawn.kanban_card_id ?? "none"} \u2014 PID reuse suspected (identity probe rejected)`
|
|
9191
|
+
);
|
|
9192
|
+
}
|
|
9193
|
+
let kanbanReset = 0;
|
|
9194
|
+
for (const spawn of decision.toResetKanban) {
|
|
9195
|
+
try {
|
|
9196
|
+
await api.post("/host/kanban", {
|
|
9197
|
+
agent_id: spawn.agent_id,
|
|
9198
|
+
update: [{ id: spawn.kanban_card_id, status: "backlog" }]
|
|
9199
|
+
});
|
|
9200
|
+
kanbanReset += 1;
|
|
9201
|
+
} catch (err) {
|
|
9202
|
+
log(`[drain] reaper kanban-reset failed for card=${spawn.kanban_card_id} agent=${spawn.agent_code_name}: ${err.message}`);
|
|
9203
|
+
}
|
|
9204
|
+
}
|
|
9205
|
+
log(formatReaperBootLine({
|
|
9206
|
+
totalRecorded: file.spawns.length,
|
|
9207
|
+
killed: decision.toKill.length,
|
|
9208
|
+
alreadyDead: decision.alreadyDead.length,
|
|
9209
|
+
pidReusedSkipped: decision.pidReusedSkipped.length,
|
|
9210
|
+
kanbanReset
|
|
9211
|
+
}));
|
|
9212
|
+
try {
|
|
9213
|
+
writePidFile(path, { version: 1, spawns: [] });
|
|
9214
|
+
} catch (err) {
|
|
9215
|
+
log(`[drain] reaper file truncate failed: ${err.message}`);
|
|
9216
|
+
}
|
|
8907
9217
|
}
|
|
8908
9218
|
async function ensureHostFrameworkBinaries() {
|
|
8909
9219
|
const apiKey = getApiKey();
|
|
@@ -8961,17 +9271,17 @@ function restartRunningChannelMcps(basenames) {
|
|
|
8961
9271
|
}
|
|
8962
9272
|
function deployMcpAssets() {
|
|
8963
9273
|
const targetDir = join6(homedir4(), ".augmented", "_mcp");
|
|
8964
|
-
|
|
8965
|
-
const moduleDir =
|
|
9274
|
+
mkdirSync4(targetDir, { recursive: true });
|
|
9275
|
+
const moduleDir = dirname3(fileURLToPath(import.meta.url));
|
|
8966
9276
|
let mcpSourceDir = "";
|
|
8967
9277
|
let dir = moduleDir;
|
|
8968
9278
|
for (let i = 0; i < 6; i++) {
|
|
8969
9279
|
const candidate = join6(dir, "dist", "mcp");
|
|
8970
|
-
if (
|
|
9280
|
+
if (existsSync5(join6(candidate, "index.js"))) {
|
|
8971
9281
|
mcpSourceDir = candidate;
|
|
8972
9282
|
break;
|
|
8973
9283
|
}
|
|
8974
|
-
const parent =
|
|
9284
|
+
const parent = dirname3(dir);
|
|
8975
9285
|
if (parent === dir) break;
|
|
8976
9286
|
dir = parent;
|
|
8977
9287
|
}
|
|
@@ -8982,8 +9292,8 @@ function deployMcpAssets() {
|
|
|
8982
9292
|
const changedBasenames = [];
|
|
8983
9293
|
const fileHash = (p) => {
|
|
8984
9294
|
try {
|
|
8985
|
-
if (!
|
|
8986
|
-
return createHash3("sha256").update(
|
|
9295
|
+
if (!existsSync5(p)) return null;
|
|
9296
|
+
return createHash3("sha256").update(readFileSync7(p)).digest("hex");
|
|
8987
9297
|
} catch {
|
|
8988
9298
|
return null;
|
|
8989
9299
|
}
|
|
@@ -8996,7 +9306,7 @@ function deployMcpAssets() {
|
|
|
8996
9306
|
for (const file of ["index.js", "slack-channel.js", "direct-chat-channel.js", "telegram-channel.js"]) {
|
|
8997
9307
|
const src = join6(mcpSourceDir, file);
|
|
8998
9308
|
const dst = join6(targetDir, file);
|
|
8999
|
-
if (!
|
|
9309
|
+
if (!existsSync5(src)) continue;
|
|
9000
9310
|
const before = fileHash(dst);
|
|
9001
9311
|
try {
|
|
9002
9312
|
copyFileSync(src, dst);
|
|
@@ -9016,20 +9326,20 @@ function deployMcpAssets() {
|
|
|
9016
9326
|
const localMcpPath = join6(targetDir, "index.js");
|
|
9017
9327
|
try {
|
|
9018
9328
|
const agentsDir = join6(homedir4(), ".augmented", "agents");
|
|
9019
|
-
if (
|
|
9329
|
+
if (existsSync5(agentsDir)) {
|
|
9020
9330
|
for (const entry of readdirSync3(agentsDir, { withFileTypes: true })) {
|
|
9021
9331
|
if (!entry.isDirectory()) continue;
|
|
9022
9332
|
for (const subdir of ["provision", "project"]) {
|
|
9023
9333
|
const mcpJsonPath = join6(agentsDir, entry.name, subdir, ".mcp.json");
|
|
9024
9334
|
try {
|
|
9025
|
-
const raw =
|
|
9335
|
+
const raw = readFileSync7(mcpJsonPath, "utf-8");
|
|
9026
9336
|
if (!raw.includes("@integrity-labs/augmented-mcp")) continue;
|
|
9027
9337
|
const mcpConfig = JSON.parse(raw);
|
|
9028
9338
|
const augServer = mcpConfig.mcpServers?.["augmented"];
|
|
9029
9339
|
if (!augServer) continue;
|
|
9030
9340
|
augServer.command = "node";
|
|
9031
9341
|
augServer.args = [localMcpPath];
|
|
9032
|
-
|
|
9342
|
+
writeFileSync4(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
|
|
9033
9343
|
log(`[manager] Patched ${entry.name}/${subdir}/.mcp.json: npx \u2192 node`);
|
|
9034
9344
|
} catch {
|
|
9035
9345
|
}
|