@integrity-labs/agt-cli 0.7.7 → 0.7.8

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.
@@ -6,9 +6,10 @@ import {
6
6
  getFramework,
7
7
  getHostId,
8
8
  provision,
9
+ provisionStopHook,
9
10
  requireHost,
10
11
  resolveChannels
11
- } from "../chunk-QS2GCIWQ.js";
12
+ } from "../chunk-B47JW4MM.js";
12
13
  import {
13
14
  findTaskByTemplate,
14
15
  getProjectDir,
@@ -20,10 +21,42 @@ import {
20
21
 
21
22
  // src/lib/manager-worker.ts
22
23
  import { createHash } from "crypto";
23
- import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync as existsSync2, rmSync, readdirSync, statSync, unlinkSync } from "fs";
24
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2, rmSync, readdirSync, statSync, unlinkSync } from "fs";
24
25
  import https from "https";
25
26
  import { join as join2 } from "path";
26
27
 
28
+ // src/lib/mcp-sanitize.ts
29
+ import { readFileSync, writeFileSync } from "fs";
30
+ function sanitizeMcpJson(mcpConfigPath, apiHost) {
31
+ try {
32
+ const mcpRaw = JSON.parse(readFileSync(mcpConfigPath, "utf-8"));
33
+ const servers = mcpRaw.mcpServers;
34
+ if (!servers) return false;
35
+ let changed = false;
36
+ for (const [key, val] of Object.entries(servers)) {
37
+ if (typeof val?.url !== "string") continue;
38
+ if (val.url.startsWith("/")) {
39
+ if (apiHost) {
40
+ val.url = `${apiHost}${val.url}`;
41
+ changed = true;
42
+ } else {
43
+ delete servers[key];
44
+ changed = true;
45
+ continue;
46
+ }
47
+ }
48
+ if (!val.type) {
49
+ val.type = "sse";
50
+ changed = true;
51
+ }
52
+ }
53
+ if (changed) writeFileSync(mcpConfigPath, JSON.stringify(mcpRaw, null, 2));
54
+ return changed;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+
27
60
  // src/lib/gateway-client.ts
28
61
  import { EventEmitter } from "events";
29
62
  import WebSocket from "ws";
@@ -297,7 +330,7 @@ var GatewayClientPool = class extends EventEmitter {
297
330
  import { spawn, execSync } from "child_process";
298
331
  import { join } from "path";
299
332
  import { homedir } from "os";
300
- import { existsSync, readFileSync } from "fs";
333
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
301
334
  var sessions = /* @__PURE__ */ new Map();
302
335
  function startPersistentSession(config2) {
303
336
  const existing = sessions.get(config2.codeName);
@@ -318,6 +351,7 @@ function startPersistentSession(config2) {
318
351
  }
319
352
  function spawnSession(config2, session) {
320
353
  const { codeName, projectDir, mcpConfigPath, claudeMdPath, channels, devChannels, log: log2 } = config2;
354
+ sanitizeMcpJson(mcpConfigPath);
321
355
  const args = [];
322
356
  if (channels.length > 0) {
323
357
  args.push("--channels", ...channels);
@@ -346,7 +380,7 @@ function spawnSession(config2, session) {
346
380
  let envPrefix = "";
347
381
  if (existsSync(envIntegrationsPath)) {
348
382
  try {
349
- const envContent = readFileSync(envIntegrationsPath, "utf-8");
383
+ const envContent = readFileSync2(envIntegrationsPath, "utf-8");
350
384
  const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
351
385
  const eqIdx = line.indexOf("=");
352
386
  const key = line.slice(0, eqIdx);
@@ -490,6 +524,32 @@ function resetRestartCount(codeName) {
490
524
  const session = sessions.get(codeName);
491
525
  if (session) session.restartCount = 0;
492
526
  }
527
+ async function stopAllSessionsAndWait(log2, opts) {
528
+ const codeNames = [...sessions.keys()];
529
+ if (codeNames.length === 0) return;
530
+ const exitPromises = [];
531
+ for (const codeName of codeNames) {
532
+ const session = sessions.get(codeName);
533
+ const proc = session?.process;
534
+ stopPersistentSession(codeName, log2);
535
+ if (proc && !proc.killed && proc.exitCode === null) {
536
+ exitPromises.push(
537
+ new Promise((resolve) => {
538
+ proc.on("close", resolve);
539
+ proc.on("error", resolve);
540
+ })
541
+ );
542
+ }
543
+ }
544
+ if (exitPromises.length === 0) return;
545
+ await Promise.race([
546
+ Promise.all(exitPromises),
547
+ new Promise((resolve) => setTimeout(() => {
548
+ log2(`[persistent-session] Shutdown timeout (${opts.timeoutMs}ms), force-killing remaining sessions`);
549
+ resolve();
550
+ }, opts.timeoutMs))
551
+ ]);
552
+ }
493
553
  function getProjectDir2(codeName) {
494
554
  return join(homedir(), ".augmented", codeName, "project");
495
555
  }
@@ -934,14 +994,14 @@ function checkClaudeAuth(execFileSync) {
934
994
  }
935
995
  function loadGatewayPorts() {
936
996
  try {
937
- return JSON.parse(readFileSync2(GATEWAY_PORTS_FILE, "utf-8"));
997
+ return JSON.parse(readFileSync3(GATEWAY_PORTS_FILE, "utf-8"));
938
998
  } catch {
939
999
  return {};
940
1000
  }
941
1001
  }
942
1002
  function saveGatewayPorts(ports) {
943
1003
  mkdirSync(AUGMENTED_DIR, { recursive: true });
944
- writeFileSync(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
1004
+ writeFileSync2(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
945
1005
  }
946
1006
  function allocatePort(codeName) {
947
1007
  const ports = loadGatewayPorts();
@@ -967,7 +1027,7 @@ var STATE_FILE = join2(process.env["HOME"] ?? "/tmp", ".augmented", "manager-sta
967
1027
  function send(msg) {
968
1028
  if (msg.type === "state-update") {
969
1029
  try {
970
- writeFileSync(STATE_FILE, JSON.stringify(msg.state, null, 2));
1030
+ writeFileSync2(STATE_FILE, JSON.stringify(msg.state, null, 2));
971
1031
  } catch {
972
1032
  }
973
1033
  }
@@ -989,7 +1049,7 @@ function sha256(content) {
989
1049
  }
990
1050
  function hashFile(filePath) {
991
1051
  try {
992
- const content = readFileSync2(filePath, "utf-8");
1052
+ const content = readFileSync3(filePath, "utf-8");
993
1053
  return sha256(content);
994
1054
  } catch {
995
1055
  return null;
@@ -1000,7 +1060,7 @@ async function migrateToProfiles() {
1000
1060
  const sharedConfigPath = join2(homeDir, ".openclaw", "openclaw.json");
1001
1061
  let sharedConfig;
1002
1062
  try {
1003
- sharedConfig = JSON.parse(readFileSync2(sharedConfigPath, "utf-8"));
1063
+ sharedConfig = JSON.parse(readFileSync3(sharedConfigPath, "utf-8"));
1004
1064
  } catch {
1005
1065
  return;
1006
1066
  }
@@ -1024,8 +1084,8 @@ async function migrateToProfiles() {
1024
1084
  const authFile = join2(sharedAuthDir, "auth-profiles.json");
1025
1085
  if (existsSync2(authFile)) {
1026
1086
  mkdirSync(profileAuthDir, { recursive: true });
1027
- const authContent = readFileSync2(authFile, "utf-8");
1028
- writeFileSync(join2(profileAuthDir, "auth-profiles.json"), authContent);
1087
+ const authContent = readFileSync3(authFile, "utf-8");
1088
+ writeFileSync2(join2(profileAuthDir, "auth-profiles.json"), authContent);
1029
1089
  }
1030
1090
  allocatePort(codeName);
1031
1091
  migrated++;
@@ -1063,7 +1123,7 @@ function readGatewayToken(codeName) {
1063
1123
  }
1064
1124
  const homeDir = process.env["HOME"] ?? "/tmp";
1065
1125
  try {
1066
- const cfg = JSON.parse(readFileSync2(join2(homeDir, `.openclaw-${codeName}`, "openclaw.json"), "utf-8"));
1126
+ const cfg = JSON.parse(readFileSync3(join2(homeDir, `.openclaw-${codeName}`, "openclaw.json"), "utf-8"));
1067
1127
  return cfg?.gateway?.auth?.token;
1068
1128
  } catch {
1069
1129
  return void 0;
@@ -1075,7 +1135,7 @@ function isGatewayHung(codeName) {
1075
1135
  const jobsPath = join2(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
1076
1136
  if (!existsSync2(jobsPath)) return false;
1077
1137
  try {
1078
- const data = JSON.parse(readFileSync2(jobsPath, "utf-8"));
1138
+ const data = JSON.parse(readFileSync3(jobsPath, "utf-8"));
1079
1139
  const jobs = data.jobs ?? data;
1080
1140
  if (!Array.isArray(jobs)) return false;
1081
1141
  const now = Date.now();
@@ -1116,11 +1176,11 @@ async function ensureGatewayRunning(codeName, adapter) {
1116
1176
  const homeDir = process.env["HOME"] ?? "/tmp";
1117
1177
  const configPath = join2(homeDir, `.openclaw-${codeName}`, "openclaw.json");
1118
1178
  if (existsSync2(configPath)) {
1119
- const cfg = JSON.parse(readFileSync2(configPath, "utf-8"));
1179
+ const cfg = JSON.parse(readFileSync3(configPath, "utf-8"));
1120
1180
  if (cfg.gateway?.port !== status.port) {
1121
1181
  if (!cfg.gateway) cfg.gateway = {};
1122
1182
  cfg.gateway.port = status.port;
1123
- writeFileSync(configPath, JSON.stringify(cfg, null, 2));
1183
+ writeFileSync2(configPath, JSON.stringify(cfg, null, 2));
1124
1184
  }
1125
1185
  }
1126
1186
  } catch {
@@ -1142,10 +1202,10 @@ async function ensureGatewayRunning(codeName, adapter) {
1142
1202
  const homeDir = process.env["HOME"] ?? "/tmp";
1143
1203
  const configPath = join2(homeDir, `.openclaw-${codeName}`, "openclaw.json");
1144
1204
  if (existsSync2(configPath)) {
1145
- const cfg = JSON.parse(readFileSync2(configPath, "utf-8"));
1205
+ const cfg = JSON.parse(readFileSync3(configPath, "utf-8"));
1146
1206
  if (!cfg.gateway) cfg.gateway = {};
1147
1207
  cfg.gateway.port = port;
1148
- writeFileSync(configPath, JSON.stringify(cfg, null, 2));
1208
+ writeFileSync2(configPath, JSON.stringify(cfg, null, 2));
1149
1209
  }
1150
1210
  } catch {
1151
1211
  }
@@ -1545,7 +1605,7 @@ async function processAgent(agent, agentStates) {
1545
1605
  const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
1546
1606
  log(`${verb} '${agent.code_name}': ${fileNames}`);
1547
1607
  for (const file of changedFiles) {
1548
- writeFileSync(join2(agentDir, file.relativePath), file.content);
1608
+ writeFileSync2(join2(agentDir, file.relativePath), file.content);
1549
1609
  }
1550
1610
  lastProvisionAt = (/* @__PURE__ */ new Date()).toISOString();
1551
1611
  knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });
@@ -1737,6 +1797,44 @@ async function processAgent(agent, agentStates) {
1737
1797
  }
1738
1798
  knownIntegrationHashes.set(agent.agent_id, intHash);
1739
1799
  log(`Integrations provisioned for '${agent.code_name}' (${integrations.length} integration(s))`);
1800
+ const managedIntegrations = integrations.filter((i) => i.auth_type === "managed");
1801
+ if (managedIntegrations.length > 0 && frameworkAdapter.writeMcpServer) {
1802
+ try {
1803
+ const toolkitData = await api.post("/host/managed-toolkits", { agent_ids: [agent.agent_id] });
1804
+ const expectedServerIds = /* @__PURE__ */ new Set();
1805
+ for (const tk of toolkitData.toolkits) {
1806
+ if (tk.agent_id !== agent.agent_id) continue;
1807
+ const serverId = tk.toolkit_id.replace(/\//g, "-");
1808
+ expectedServerIds.add(serverId);
1809
+ const proxyUrl = tk.proxy_url.startsWith("/") ? `${requireHost()}${tk.proxy_url}` : tk.proxy_url;
1810
+ frameworkAdapter.writeMcpServer(agent.code_name, serverId, {
1811
+ url: proxyUrl
1812
+ });
1813
+ log(`[managed-toolkit] ${agent.code_name}: ${tk.toolkit_name} \u2192 ${proxyUrl}`);
1814
+ }
1815
+ if (frameworkAdapter.removeMcpServer) {
1816
+ try {
1817
+ const { readFileSync: readFileSync4 } = await import("fs");
1818
+ const { join: join3 } = await import("path");
1819
+ const { homedir: homedir2 } = await import("os");
1820
+ const mcpPath = join3(homedir2(), ".augmented", "agents", agent.code_name, "provision", ".mcp.json");
1821
+ const mcpConfig = JSON.parse(readFileSync4(mcpPath, "utf-8"));
1822
+ if (mcpConfig.mcpServers) {
1823
+ const managedPrefixes = ["composio-", "one-", "nango-", "paragon-"];
1824
+ for (const key of Object.keys(mcpConfig.mcpServers)) {
1825
+ if (managedPrefixes.some((p) => key.startsWith(p)) && !expectedServerIds.has(key)) {
1826
+ frameworkAdapter.removeMcpServer(agent.code_name, key);
1827
+ log(`[managed-toolkit] Removed stale MCP server '${key}' for '${agent.code_name}'`);
1828
+ }
1829
+ }
1830
+ }
1831
+ } catch {
1832
+ }
1833
+ }
1834
+ } catch (err) {
1835
+ log(`[managed-toolkit] Failed to provision for '${agent.code_name}': ${err.message}`);
1836
+ }
1837
+ }
1740
1838
  needsGatewayRestart = true;
1741
1839
  const hasLcm = integrations.some((i) => i.definition_id === "lossless-claw");
1742
1840
  if (hasLcm && !losslessClawInstalled.get(agent.code_name)) {
@@ -1983,8 +2081,6 @@ async function processAgent(agent, agentStates) {
1983
2081
  agentDisplayName: agent.display_name
1984
2082
  });
1985
2083
  }
1986
- const ccInFlight = agentFw === "claude-code" ? claudeTaskConcurrency.get(agent.code_name) ?? 0 : null;
1987
- log(`[${agent.code_name}] Harvest gate: gatewayRunning=${gatewayRunning} gatewayPort=${gatewayPort} tasks=${tasks.length} fw=${agentFw}${ccInFlight !== null ? ` claude-p=${ccInFlight}/${MAX_CLAUDE_CONCURRENCY}` : ""}`);
1988
2084
  if (agentFw === "openclaw" && gatewayRunning && gatewayPort && tasks.length > 0) {
1989
2085
  const lastHarvest = lastHarvestAt.get(agent.code_name) ?? 0;
1990
2086
  if (Date.now() - lastHarvest >= HARVEST_INTERVAL_MS) {
@@ -2006,7 +2102,7 @@ async function processAgent(agent, agentStates) {
2006
2102
  const jobsPath = join2(homeDir, `.openclaw-${agent.code_name}`, "cron", "jobs.json");
2007
2103
  if (existsSync2(jobsPath)) {
2008
2104
  try {
2009
- const jobsData = JSON.parse(readFileSync2(jobsPath, "utf-8"));
2105
+ const jobsData = JSON.parse(readFileSync3(jobsPath, "utf-8"));
2010
2106
  const kanbanJob = (jobsData.jobs ?? []).find(
2011
2107
  (j) => typeof j.name === "string" && j.name.includes("kanban-work")
2012
2108
  );
@@ -2019,7 +2115,20 @@ async function processAgent(agent, agentStates) {
2019
2115
  }
2020
2116
  }
2021
2117
  } else if (agentFw === "claude-code") {
2022
- fireClaudeWorkTrigger(agent.code_name, agent.agent_id, boardItems);
2118
+ if (sessionMode === "persistent") {
2119
+ if (isSessionHealthy(agent.code_name)) {
2120
+ const todayItem = boardItems.find((b) => b.status === "today");
2121
+ const taskHint = todayItem ? ` Top item: "${todayItem.title}" (priority ${todayItem.priority}).` : "";
2122
+ injectMessage(agent.code_name, "task", `New work triggered. Check your kanban board with kanban_list and pick up the next item.${taskHint}`, {
2123
+ task_name: "kanban-work-trigger"
2124
+ });
2125
+ log(`[persistent-session] Work trigger injected for '${agent.code_name}'`);
2126
+ } else {
2127
+ log(`[persistent-session] Work trigger skipped for '${agent.code_name}' \u2014 session not yet healthy`);
2128
+ }
2129
+ } else {
2130
+ fireClaudeWorkTrigger(agent.code_name, agent.agent_id, boardItems);
2131
+ }
2023
2132
  }
2024
2133
  }
2025
2134
  }
@@ -2150,7 +2259,7 @@ function cleanupCronSessions(sessionsDir, keepCount) {
2150
2259
  const indexPath = join2(sessionsDir, "sessions.json");
2151
2260
  if (!existsSync2(indexPath)) return;
2152
2261
  try {
2153
- const raw = readFileSync2(indexPath, "utf-8");
2262
+ const raw = readFileSync3(indexPath, "utf-8");
2154
2263
  const index = JSON.parse(raw);
2155
2264
  const cronRunKeys = Object.keys(index).filter((k) => k.includes(":cron:") && k.includes(":run:")).map((k) => ({
2156
2265
  key: k,
@@ -2195,7 +2304,7 @@ function cleanupCronSessions(sessionsDir, keepCount) {
2195
2304
  }
2196
2305
  }
2197
2306
  }
2198
- writeFileSync(indexPath, JSON.stringify(index));
2307
+ writeFileSync2(indexPath, JSON.stringify(index));
2199
2308
  if (toDelete.length > 0) {
2200
2309
  log(`Cleaned ${toDelete.length} cron session(s) and ${deletedFiles} file(s) from ${sessionsDir}`);
2201
2310
  }
@@ -2206,7 +2315,7 @@ var STALE_RUN_TIMEOUT_MS = 5 * 6e4;
2206
2315
  function clearStaleCronRunState(jobsPath) {
2207
2316
  if (!existsSync2(jobsPath)) return;
2208
2317
  try {
2209
- const raw = readFileSync2(jobsPath, "utf-8");
2318
+ const raw = readFileSync3(jobsPath, "utf-8");
2210
2319
  const data = JSON.parse(raw);
2211
2320
  const jobs = data.jobs ?? data;
2212
2321
  if (!Array.isArray(jobs)) return;
@@ -2231,7 +2340,7 @@ function clearStaleCronRunState(jobsPath) {
2231
2340
  }
2232
2341
  }
2233
2342
  if (changed) {
2234
- writeFileSync(jobsPath, JSON.stringify(data, null, 2));
2343
+ writeFileSync2(jobsPath, JSON.stringify(data, null, 2));
2235
2344
  }
2236
2345
  } catch {
2237
2346
  }
@@ -2334,6 +2443,7 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
2334
2443
  async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
2335
2444
  const projectDir = getProjectDir(codeName);
2336
2445
  const mcpConfigPath = join2(projectDir, ".mcp.json");
2446
+ sanitizeMcpJson(mcpConfigPath, requireHost());
2337
2447
  try {
2338
2448
  const claudeMdPath = join2(projectDir, "CLAUDE.md");
2339
2449
  const claudeArgs = [
@@ -2464,6 +2574,11 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2464
2574
  if (persistentSessionAgents.has(codeName)) {
2465
2575
  log(`[persistent-session] Session for '${codeName}' is unhealthy, will restart`);
2466
2576
  }
2577
+ try {
2578
+ provisionStopHook(codeName);
2579
+ } catch (err) {
2580
+ log(`[persistent-session] Failed to provision Stop hook for '${codeName}': ${err.message}`);
2581
+ }
2467
2582
  startPersistentSession({
2468
2583
  codeName,
2469
2584
  agentId: agent.agent_id,
@@ -2505,6 +2620,21 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2505
2620
  }
2506
2621
  }
2507
2622
  log(`[persistent-session] Injecting task '${task.name}' into '${codeName}'`);
2623
+ const projectDir2 = getProjectDir2(codeName);
2624
+ const markerDir = join2(projectDir2, ".claude");
2625
+ const markerPath = join2(markerDir, ".agt-pending-task.json");
2626
+ try {
2627
+ mkdirSync(markerDir, { recursive: true });
2628
+ writeFileSync2(markerPath, JSON.stringify({
2629
+ agent_id: agent.agent_id,
2630
+ task_id: task.taskId,
2631
+ template_id: task.templateId,
2632
+ task_name: task.name,
2633
+ injected_at: (/* @__PURE__ */ new Date()).toISOString()
2634
+ }), "utf-8");
2635
+ } catch (err) {
2636
+ log(`[persistent-session] Failed to write task marker for '${codeName}': ${err.message}`);
2637
+ }
2508
2638
  const injected = await injectMessage(codeName, "task", prompt, {
2509
2639
  task_id: task.taskId,
2510
2640
  template_id: task.templateId,
@@ -3041,7 +3171,7 @@ function getBuiltInSkillContent(skillId) {
3041
3171
  ];
3042
3172
  for (const candidate of candidates) {
3043
3173
  if (existsSync2(candidate)) {
3044
- const content = readFileSync2(candidate, "utf-8");
3174
+ const content = readFileSync3(candidate, "utf-8");
3045
3175
  const files = [{ relativePath: "SKILL.md", content }];
3046
3176
  builtInSkillCache.set(skillId, files);
3047
3177
  return files;
@@ -3559,16 +3689,50 @@ function scheduleNext() {
3559
3689
  void pollCycle().then(scheduleNext);
3560
3690
  }, config.intervalMs);
3561
3691
  }
3692
+ async function killAllAgtTmuxSessions() {
3693
+ try {
3694
+ const { stdout: output } = await execFilePromiseLong(
3695
+ "tmux",
3696
+ ["list-sessions", "-F", "#{session_name}"],
3697
+ { timeout: 2e3, stdin: "ignore" }
3698
+ );
3699
+ const sessions2 = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
3700
+ for (const session of sessions2) {
3701
+ try {
3702
+ await execFilePromiseLong("tmux", ["kill-session", "-t", session], {
3703
+ timeout: 2e3,
3704
+ stdin: "ignore"
3705
+ });
3706
+ log(`Killed tmux session '${session}'`);
3707
+ } catch {
3708
+ }
3709
+ }
3710
+ if (sessions2.length > 0) {
3711
+ log(`Cleaned up ${sessions2.length} agt-* tmux session(s)`);
3712
+ }
3713
+ } catch {
3714
+ }
3715
+ }
3562
3716
  async function stopPolling() {
3563
3717
  running = false;
3564
3718
  if (pollTimer) {
3565
3719
  clearTimeout(pollTimer);
3566
3720
  pollTimer = null;
3567
3721
  }
3722
+ const shutdownTimer = setTimeout(() => {
3723
+ log("Shutdown timeout exceeded (5s), forcing exit");
3724
+ process.exit(1);
3725
+ }, 5e3);
3726
+ shutdownTimer.unref();
3568
3727
  stopCaffeinate();
3569
3728
  stopRealtimeChat();
3570
3729
  stopGatewayPool();
3730
+ log("Stopping persistent sessions...");
3731
+ await stopAllSessionsAndWait(log, { timeoutMs: 4e3 });
3732
+ log("Stopping gateway processes...");
3571
3733
  await stopAllGateways();
3734
+ await killAllAgtTmuxSessions();
3735
+ clearTimeout(shutdownTimer);
3572
3736
  }
3573
3737
  function startManager(opts) {
3574
3738
  config = opts;