@integrity-labs/agt-cli 0.19.13 → 0.19.14

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.
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-XBRQN7XE.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-CFDGW7QE.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  try {
106
106
  await execFileAsync("tmux", [
@@ -357,4 +357,4 @@ export {
357
357
  startClaudePair,
358
358
  submitClaudePairCode
359
359
  };
360
- //# sourceMappingURL=claude-pair-runtime-SJDLJNYF.js.map
360
+ //# sourceMappingURL=claude-pair-runtime-VXN4NVGR.js.map
@@ -22,7 +22,7 @@ import {
22
22
  resolveChannels,
23
23
  resolveDmTarget,
24
24
  wrapScheduledTaskPrompt
25
- } from "../chunk-SUUTWC6M.js";
25
+ } from "../chunk-KLNFWXOI.js";
26
26
  import {
27
27
  findTaskByTemplate,
28
28
  getProjectDir,
@@ -41,13 +41,14 @@ import {
41
41
  isStaleForToday,
42
42
  peekCurrentSession,
43
43
  prepareForRespawn,
44
+ reapOrphanChannelMcps,
44
45
  resetRestartCount,
45
46
  resolveClaudeBinary,
46
47
  sanitizeMcpJson,
47
48
  startPersistentSession,
48
49
  stopAllSessionsAndWait,
49
50
  stopPersistentSession
50
- } from "../chunk-QFWR2NV5.js";
51
+ } from "../chunk-HA4IUBVC.js";
51
52
 
52
53
  // src/lib/manager-worker.ts
53
54
  import { createHash } from "crypto";
@@ -58,6 +59,34 @@ import { join as join4, dirname } from "path";
58
59
  import { homedir as homedir3 } from "os";
59
60
  import { fileURLToPath } from "url";
60
61
 
62
+ // src/lib/channel-restart-decision.ts
63
+ function launchableChannelIds(channelConfigs) {
64
+ if (!channelConfigs) return /* @__PURE__ */ new Set();
65
+ const result = /* @__PURE__ */ new Set();
66
+ for (const [channelId, entry] of Object.entries(channelConfigs)) {
67
+ if (!entry) continue;
68
+ if (entry.config == null) continue;
69
+ if (entry.status !== "active" && entry.status !== "pending") continue;
70
+ result.add(channelId);
71
+ }
72
+ return result;
73
+ }
74
+ function decideChannelRestart(input) {
75
+ const { previousChannelIds, currentChannelIds, sessionMode, framework, sessionHealthy } = input;
76
+ if (previousChannelIds === void 0) {
77
+ return { restart: false, added: [], removed: [] };
78
+ }
79
+ const added = [...currentChannelIds].filter((c) => !previousChannelIds.has(c));
80
+ const removed = [...previousChannelIds].filter((c) => !currentChannelIds.has(c));
81
+ if (added.length === 0 && removed.length === 0) {
82
+ return { restart: false, added, removed };
83
+ }
84
+ if (sessionMode !== "persistent") return { restart: false, added, removed };
85
+ if (framework !== "claude-code") return { restart: false, added, removed };
86
+ if (!sessionHealthy) return { restart: false, added, removed };
87
+ return { restart: true, added, removed };
88
+ }
89
+
61
90
  // src/lib/integration-context-render.ts
62
91
  var PLUGIN_CONTEXT_PLACEHOLDER_RE = /\{\{\s*context\.([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
63
92
  var TEAM_OVERRIDES_HEADER = "## Team Overrides\n\n> **The following overrides anything you've read above.** If any rule here conflicts with earlier instructions in this skill, follow what is written here. These are user-supplied directives that take precedence over the plugin's default guidance.\n";
@@ -1554,6 +1583,28 @@ var KNOWN_SAFE_TAIL_SIGNATURES = /* @__PURE__ */ new Set(["session_id_in_use"]);
1554
1583
  var knownVersions = /* @__PURE__ */ new Map();
1555
1584
  var knownStatuses = /* @__PURE__ */ new Map();
1556
1585
  var knownChannels = /* @__PURE__ */ new Map();
1586
+ var pendingSessionRestarts = /* @__PURE__ */ new Map();
1587
+ function scheduleSessionRestart(codeName, delayMs, reason) {
1588
+ const existing = pendingSessionRestarts.get(codeName);
1589
+ if (existing) {
1590
+ clearTimeout(existing);
1591
+ log(`[hot-reload] Coalesced restart for '${codeName}': replacing pending timer with ${reason}`);
1592
+ }
1593
+ const timer = setTimeout(() => {
1594
+ pendingSessionRestarts.delete(codeName);
1595
+ stopPersistentSession(codeName, log);
1596
+ log(`[hot-reload] Session stopped for '${codeName}' \u2014 will respawn with ${reason}`);
1597
+ }, delayMs);
1598
+ timer.unref?.();
1599
+ pendingSessionRestarts.set(codeName, timer);
1600
+ }
1601
+ function cancelPendingSessionRestart(codeName) {
1602
+ const existing = pendingSessionRestarts.get(codeName);
1603
+ if (!existing) return;
1604
+ clearTimeout(existing);
1605
+ pendingSessionRestarts.delete(codeName);
1606
+ log(`[hot-reload] Cancelled pending restart timer for '${codeName}' (another teardown path is handling it)`);
1607
+ }
1557
1608
  var writtenHashes = /* @__PURE__ */ new Map();
1558
1609
  var knownSecretsHashes = /* @__PURE__ */ new Map();
1559
1610
  var knownChannelConfigHashes = /* @__PURE__ */ new Map();
@@ -1630,7 +1681,7 @@ function clearAgentCaches(agentId, codeName) {
1630
1681
  var cachedFrameworkVersion = null;
1631
1682
  var lastVersionCheckAt = 0;
1632
1683
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
1633
- var agtCliVersion = true ? "0.19.13" : "dev";
1684
+ var agtCliVersion = true ? "0.19.14" : "dev";
1634
1685
  function resolveBrewPath(execFileSync2) {
1635
1686
  try {
1636
1687
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -2473,6 +2524,7 @@ async function pollCycle() {
2473
2524
  log,
2474
2525
  resolveFramework: (codeName) => agentFrameworkCache.get(codeName) ?? null,
2475
2526
  stopSession: (codeName) => {
2527
+ cancelPendingSessionRestart(codeName);
2476
2528
  stopPersistentSession(codeName, log);
2477
2529
  persistentSessionAgents.delete(codeName);
2478
2530
  claudeAuthTupleBySession.delete(codeName);
@@ -2533,7 +2585,7 @@ async function pollCycle() {
2533
2585
  }
2534
2586
  try {
2535
2587
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
2536
- const { collectDiagnostics } = await import("../persistent-session-XBRQN7XE.js");
2588
+ const { collectDiagnostics } = await import("../persistent-session-CFDGW7QE.js");
2537
2589
  const diagCodeNames = [...persistentSessionAgents];
2538
2590
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
2539
2591
  let tailscaleHostname;
@@ -2633,6 +2685,7 @@ async function pollCycle() {
2633
2685
  log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
2634
2686
  const adapter = resolveAgentFramework(prev.codeName);
2635
2687
  await stopGatewayIfRunning(prev.codeName, adapter);
2688
+ cancelPendingSessionRestart(prev.codeName);
2636
2689
  stopPersistentSession(prev.codeName, log);
2637
2690
  try {
2638
2691
  const { execSync: es } = await import("child_process");
@@ -2770,6 +2823,7 @@ async function processAgent(agent, agentStates) {
2770
2823
  if (agent.status === "draft" || agent.status === "paused") {
2771
2824
  log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
2772
2825
  await stopGatewayIfRunning(agent.code_name, adapter);
2826
+ cancelPendingSessionRestart(agent.code_name);
2773
2827
  stopPersistentSession(agent.code_name, log);
2774
2828
  try {
2775
2829
  const { execSync: es } = await import("child_process");
@@ -2798,6 +2852,7 @@ async function processAgent(agent, agentStates) {
2798
2852
  if (agent.status === "revoked") {
2799
2853
  log(`Agent '${agent.code_name}' is revoked, cleaning up`);
2800
2854
  await stopGatewayIfRunning(agent.code_name, adapter);
2855
+ cancelPendingSessionRestart(agent.code_name);
2801
2856
  stopPersistentSession(agent.code_name, log);
2802
2857
  try {
2803
2858
  const { execSync: es } = await import("child_process");
@@ -2905,9 +2960,10 @@ async function processAgent(agent, agentStates) {
2905
2960
  const toolsVersion = refreshData.tools.version;
2906
2961
  const known = knownVersions.get(agent.agent_id);
2907
2962
  let lastProvisionAt = state.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
2908
- const currentChannelIds = new Set(Object.keys(refreshData.channel_configs ?? {}));
2963
+ const currentChannelIds = launchableChannelIds(refreshData.channel_configs);
2909
2964
  const previousChannelIds = knownChannels.get(agent.agent_id);
2910
2965
  const channelsChanged = !previousChannelIds || currentChannelIds.size !== previousChannelIds.size || [...currentChannelIds].some((ch) => !previousChannelIds.has(ch)) || [...previousChannelIds].some((ch) => !currentChannelIds.has(ch));
2966
+ let channelConfigConverged = true;
2911
2967
  if (previousChannelIds && channelsChanged && frameworkAdapter.removeChannelCredentials) {
2912
2968
  for (const ch of previousChannelIds) {
2913
2969
  if (!currentChannelIds.has(ch)) {
@@ -2915,12 +2971,12 @@ async function processAgent(agent, agentStates) {
2915
2971
  frameworkAdapter.removeChannelCredentials(agent.code_name, ch);
2916
2972
  log(`Removed ${ch} credentials for '${agent.code_name}'`);
2917
2973
  } catch (err) {
2974
+ channelConfigConverged = false;
2918
2975
  log(`Failed to remove ${ch} credentials for '${agent.code_name}': ${err.message}`);
2919
2976
  }
2920
2977
  }
2921
2978
  }
2922
2979
  }
2923
- knownChannels.set(agent.agent_id, currentChannelIds);
2924
2980
  try {
2925
2981
  const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
2926
2982
  const changedFiles = [];
@@ -3133,6 +3189,7 @@ async function processAgent(agent, agentStates) {
3133
3189
  saveChannelHashCache2();
3134
3190
  log(`Channel credentials written for '${agent.code_name}/${channelId}' (reason=${reason}, hash=${configHash.slice(0, 8)}${prevHash ? `, prev=${prevHash.slice(0, 8)}` : ""})`);
3135
3191
  } catch (err) {
3192
+ channelConfigConverged = false;
3136
3193
  log(`Failed to write channel credentials for '${agent.code_name}/${channelId}': ${err.message}`);
3137
3194
  }
3138
3195
  }
@@ -3145,6 +3202,32 @@ async function processAgent(agent, agentStates) {
3145
3202
  }
3146
3203
  }
3147
3204
  }
3205
+ if (channelConfigConverged) {
3206
+ knownChannels.set(agent.agent_id, currentChannelIds);
3207
+ } else {
3208
+ log(`[channels] Credential sync did not converge for '${agent.code_name}' \u2014 leaving diff live for next tick retry`);
3209
+ }
3210
+ const restartDecision = channelConfigConverged ? decideChannelRestart({
3211
+ previousChannelIds,
3212
+ currentChannelIds,
3213
+ sessionMode: refreshData.agent.session_mode,
3214
+ framework: agentFrameworkCache.get(agent.code_name) ?? "openclaw",
3215
+ sessionHealthy: isSessionHealthy(agent.code_name)
3216
+ }) : { restart: false, added: [], removed: [] };
3217
+ if (restartDecision.restart) {
3218
+ const reasonParts = [];
3219
+ if (restartDecision.added.length > 0) reasonParts.push(`added=${restartDecision.added.join(",")}`);
3220
+ if (restartDecision.removed.length > 0) reasonParts.push(`removed=${restartDecision.removed.join(",")}`);
3221
+ const reason = reasonParts.join(" ");
3222
+ log(`[hot-reload] Channel set changed for '${agent.code_name}' (${reason}) \u2014 restarting session`);
3223
+ const restartNotice = restartDecision.added.length > 0 ? `New channels have been wired up (${restartDecision.added.join(", ")}). Note: channels require a session restart to attach their MCP servers as channel listeners. Your manager will restart your session shortly.` : `Channels were removed (${restartDecision.removed.join(", ")}). Your manager will restart your session shortly so the launch flags drop those channels.`;
3224
+ const delivered = await injectMessage(agent.code_name, "system", restartNotice, { task_name: "channel-update" }, log).catch(() => false);
3225
+ const delay = delivered ? 8e3 : 3e3;
3226
+ if (!delivered) {
3227
+ log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' \u2014 proceeding with shorter delay`);
3228
+ }
3229
+ scheduleSessionRestart(agent.code_name, delay, "new channel set");
3230
+ }
3148
3231
  const agentSessionMode = refreshData.agent.session_mode;
3149
3232
  if (agentSessionMode === "persistent" && (agentFrameworkCache.get(agent.code_name) ?? "openclaw") === "claude-code") {
3150
3233
  try {
@@ -3334,10 +3417,7 @@ async function processAgent(agent, agentStates) {
3334
3417
  if (!delivered) {
3335
3418
  log(`[hot-reload] Inject notification unconfirmed for '${agent.code_name}' \u2014 proceeding with shorter delay`);
3336
3419
  }
3337
- setTimeout(() => {
3338
- stopPersistentSession(agent.code_name, log);
3339
- log(`[hot-reload] Session stopped for '${agent.code_name}' \u2014 will respawn with new MCP servers`);
3340
- }, delay);
3420
+ scheduleSessionRestart(agent.code_name, delay, "new MCP servers");
3341
3421
  }
3342
3422
  }
3343
3423
  } catch (err) {
@@ -4327,6 +4407,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
4327
4407
  const msg = err.message;
4328
4408
  log(`[persistent-session] Failed to resolve auth for '${codeName}': ${msg} \u2014 refusing to spawn`);
4329
4409
  if (isSessionHealthy(codeName)) {
4410
+ cancelPendingSessionRestart(codeName);
4330
4411
  stopPersistentSession(codeName, log);
4331
4412
  persistentSessionAgents.delete(codeName);
4332
4413
  claudeAuthTupleBySession.delete(codeName);
@@ -4349,6 +4430,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
4349
4430
  const recordedAuthTuple = claudeAuthTupleBySession.get(codeName);
4350
4431
  if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {
4351
4432
  log(`[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} \u2192 ${currentAuthTuple}) \u2014 restarting session`);
4433
+ cancelPendingSessionRestart(codeName);
4352
4434
  stopPersistentSession(codeName, log);
4353
4435
  persistentSessionAgents.delete(codeName);
4354
4436
  }
@@ -4360,6 +4442,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
4360
4442
  log(
4361
4443
  `[persistent-session] Day rollover for '${codeName}' (yesterday=${current.date}) \u2014 agent idle, restarting to mint fresh session`
4362
4444
  );
4445
+ cancelPendingSessionRestart(codeName);
4363
4446
  stopPersistentSession(codeName, log);
4364
4447
  persistentSessionAgents.delete(codeName);
4365
4448
  } else {
@@ -5739,7 +5822,7 @@ async function processClaudePairSessions(agents) {
5739
5822
  killPairSession,
5740
5823
  pairTmuxSession,
5741
5824
  finalizeClaudePairOnboarding
5742
- } = await import("../claude-pair-runtime-SJDLJNYF.js");
5825
+ } = await import("../claude-pair-runtime-VXN4NVGR.js");
5743
5826
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
5744
5827
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
5745
5828
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -6301,6 +6384,7 @@ function startManager(opts) {
6301
6384
  `[startup] worker pid=${process.pid} ppid=${process.ppid} node=${process.version} log=${join4(homedir3(), ".augmented", "manager.log")}`
6302
6385
  );
6303
6386
  deployMcpAssets();
6387
+ reapOrphanChannelMcps({ log });
6304
6388
  void ensureHostFrameworkBinaries();
6305
6389
  startPolling();
6306
6390
  }