@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.
@@ -15,7 +15,7 @@ import {
15
15
  provisionOrientHook,
16
16
  provisionStopHook,
17
17
  requireHost
18
- } from "../chunk-2ZTHGYH7.js";
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 readFileSync5, writeFileSync as writeFileSync3, appendFileSync, mkdirSync as mkdirSync2, chmodSync, existsSync as existsSync4, rmSync as rmSync2, readdirSync as readdirSync3, statSync as statSync2, unlinkSync, copyFileSync } from "fs";
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 = readFileSync(path, "utf-8");
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 readFileSync2 } from "fs";
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 (!existsSync(STATS_CACHE_PATH)) {
1310
+ if (!existsSync2(STATS_CACHE_PATH)) {
1175
1311
  return;
1176
1312
  }
1177
1313
  let raw;
1178
1314
  try {
1179
- raw = readFileSync2(STATS_CACHE_PATH, "utf-8");
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 existsSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
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 (!existsSync2(path)) return;
1890
+ if (!existsSync3(path)) return;
1755
1891
  let parsed;
1756
1892
  try {
1757
- parsed = JSON.parse(readFileSync3(path, "utf-8"));
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
- writeFileSync(path, JSON.stringify(obj, null, 2));
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 existsSync3, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
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 (!existsSync3(dir)) return [];
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 = readFileSync4(join5(dir, entry), "utf8");
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 (existsSync3(path)) {
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 = readFileSync5(join6(projectDir, ".mcp.json"), "utf-8");
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 = readFileSync5(join6(projectDir, ".mcp.json"), "utf-8");
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 = readFileSync5(join6(projectDir, ".mcp.json"), "utf-8");
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.28" : "dev";
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 (existsSync4(path)) return path;
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) => existsSync4(path))) return true;
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 = dirname(brewPath);
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 (existsSync4(path)) {
3388
- const raw = readFileSync5(path, "utf-8").trim();
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
- mkdirSync2(dirname(path), { recursive: true });
3405
- writeFileSync3(path, `${JSON.stringify(settings, null, 2)}
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 = dirname(brewPath);
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 (existsSync4("/home/linuxbrew/.linuxbrew/bin/claude")) {
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
- writeFileSync3(claudeCodeUpgradeMarkerPath(), String(Date.now()));
3600
+ writeFileSync4(claudeCodeUpgradeMarkerPath(), String(Date.now()));
3465
3601
  } catch {
3466
3602
  }
3467
3603
  }
3468
3604
  function claudeCodeUpgradeThrottled() {
3469
3605
  try {
3470
- const lastCheck = parseInt(readFileSync5(claudeCodeUpgradeMarkerPath(), "utf-8").trim(), 10);
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 (existsSync4(p)) {
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(readFileSync5(GATEWAY_PORTS_FILE, "utf-8"));
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
- mkdirSync2(AUGMENTED_DIR, { recursive: true });
3751
- writeFileSync3(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
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
- writeFileSync3(getStateFile(), JSON.stringify(msg.state, null, 2));
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
- mkdirSync2(dirname(managerLogPath), { recursive: true });
3821
- if (existsSync4(managerLogPath)) {
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 = readFileSync5(filePath, "utf-8");
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: writeFileSync4 } = await import("fs");
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
- writeFileSync4(claudeMdPath, next, "utf-8");
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(readFileSync5(sharedConfigPath, "utf-8"));
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 (existsSync4(join6(profileDir, "openclaw.json"))) continue;
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 (existsSync4(authFile)) {
3953
- mkdirSync2(profileAuthDir, { recursive: true });
3954
- const authContent = readFileSync5(authFile, "utf-8");
3955
- writeFileSync3(join6(profileAuthDir, "auth-profiles.json"), authContent);
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(readFileSync5(join6(homeDir, `.openclaw-${codeName}`, "openclaw.json"), "utf-8"));
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 (!existsSync4(jobsPath)) return false;
4176
+ if (!existsSync5(jobsPath)) return false;
4004
4177
  try {
4005
- const data = JSON.parse(readFileSync5(jobsPath, "utf-8"));
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 (existsSync4(configPath)) {
4046
- const cfg = JSON.parse(readFileSync5(configPath, "utf-8"));
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
- writeFileSync3(configPath, JSON.stringify(cfg, null, 2));
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 (existsSync4(configPath)) {
4072
- const cfg = JSON.parse(readFileSync5(configPath, "utf-8"));
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
- writeFileSync3(configPath, JSON.stringify(cfg, null, 2));
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
- writeFileSync3(getStateFile(), JSON.stringify(ackedState, null, 2));
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: existsSync4(agentDir)
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
- mkdirSync2(agentDir, { recursive: true });
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 = readFileSync5(projectClaudeMd, "utf-8");
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 = readFileSync5(filePath, "utf-8");
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 = !existsSync4(join6(agentDir, "CHARTER.md"));
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
- mkdirSync2(dirname(filePath), { recursive: true });
4856
- writeFileSync3(filePath, file.content);
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 (existsSync4(provSkillsDir)) {
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 && existsSync4(agentDir)) {
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
- mkdirSync2(agentProvisionDir, { recursive: true });
5160
- mkdirSync2(projectDir, { recursive: true });
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(readFileSync5(provisionMcpPath, "utf-8"));
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 (existsSync4(localDirectChatChannel) && !mcpConfig.mcpServers["direct-chat"]) {
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
- writeFileSync3(provisionMcpPath, serialized);
5188
- writeFileSync3(projectMcpPath, serialized);
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 (existsSync4(staleChannelsPath)) {
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 = readFileSync5(envIntPath, "utf-8");
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 = readFileSync5(envIntPath, "utf-8");
5284
- const mcpContent = readFileSync5(projectMcpPath, "utf-8");
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: readFileSync6 } = await import("fs");
5353
- const mcpConfig = JSON.parse(readFileSync6(mcpPath, "utf-8"));
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 (existsSync4(pluginPath)) {
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) => existsSync4(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 (existsSync4(p)) {
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(readFileSync5(mcpPath, "utf-8"));
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 (existsSync4(jobsPath)) {
6034
+ if (existsSync5(jobsPath)) {
5862
6035
  try {
5863
- const jobsData = JSON.parse(readFileSync5(jobsPath, "utf-8"));
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 && existsSync4(agentDir)) {
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 (!existsSync4(indexPath)) return;
6217
+ if (!existsSync5(indexPath)) return;
6045
6218
  try {
6046
- const raw = readFileSync5(indexPath, "utf-8");
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 (existsSync4(sessionFile)) {
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 (existsSync4(f)) {
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
- writeFileSync3(indexPath, JSON.stringify(index));
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 (!existsSync4(jobsPath)) return;
6273
+ if (!existsSync5(jobsPath)) return;
6101
6274
  try {
6102
- const raw = readFileSync5(jobsPath, "utf-8");
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
- writeFileSync3(jobsPath, JSON.stringify(data, null, 2));
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 (!existsSync4(dir)) return;
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 (existsSync4(mcpConfigPath)) {
6621
+ if (existsSync5(mcpConfigPath)) {
6430
6622
  try {
6431
- const d = JSON.parse(readFileSync5(mcpConfigPath, "utf-8"));
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 (existsSync4(claudeMdPath)) {
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 (existsSync4(envIntPath)) {
6647
+ if (existsSync5(envIntPath)) {
6456
6648
  try {
6457
- for (const line of readFileSync5(envIntPath, "utf-8").split("\n")) {
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 (existsSync4(mcpConfigPath)) {
7461
+ if (existsSync5(mcpConfigPath)) {
7260
7462
  try {
7261
- const d = JSON.parse(readFileSync5(mcpConfigPath, "utf-8"));
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 (existsSync4(chatClaudeMd)) {
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 (existsSync4(envIntPath)) {
7488
+ if (existsSync5(envIntPath)) {
7287
7489
  try {
7288
- for (const line of readFileSync5(envIntPath, "utf-8").split("\n")) {
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, { cwd: projDir, stdin: "ignore", env: childEnv });
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 (existsSync4(candidate)) {
7654
- const content = readFileSync5(candidate, "utf-8");
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 (existsSync4(memoryDir)) {
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 = readFileSync5(join6(memoryDir, file), "utf-8");
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(readFileSync5(join6(memoryDir, file), "utf-8"), file.replace(/\.md$/, ""));
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 = existsSync4(memoryDir) ? readdirSync3(memoryDir).filter((f) => f.endsWith(".md")).sort() : [];
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
- mkdirSync2(memoryDir, { recursive: true });
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 (existsSync4(filePath)) {
8909
+ if (existsSync5(filePath)) {
8684
8910
  let existing = "";
8685
8911
  try {
8686
- existing = readFileSync5(filePath, "utf-8");
8912
+ existing = readFileSync7(filePath, "utf-8");
8687
8913
  } catch {
8688
8914
  }
8689
8915
  if (existing === desired) continue;
8690
- writeFileSync3(filePath, desired);
8916
+ writeFileSync4(filePath, desired);
8691
8917
  overwritten++;
8692
8918
  } else {
8693
- writeFileSync3(filePath, desired);
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 (existsSync4(agentDir)) {
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 (existsSync4(stateFile)) {
8885
- const raw = readFileSync5(stateFile, "utf-8");
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 ensureHostFrameworkBinaries();
8906
- startPolling();
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
- mkdirSync2(targetDir, { recursive: true });
8965
- const moduleDir = dirname(fileURLToPath(import.meta.url));
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 (existsSync4(join6(candidate, "index.js"))) {
9280
+ if (existsSync5(join6(candidate, "index.js"))) {
8971
9281
  mcpSourceDir = candidate;
8972
9282
  break;
8973
9283
  }
8974
- const parent = dirname(dir);
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 (!existsSync4(p)) return null;
8986
- return createHash3("sha256").update(readFileSync5(p)).digest("hex");
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 (!existsSync4(src)) continue;
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 (existsSync4(agentsDir)) {
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 = readFileSync5(mcpJsonPath, "utf-8");
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
- writeFileSync3(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
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
  }