@integrity-labs/agt-cli 0.26.0 → 0.26.2-eng5706.0

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.
@@ -13,7 +13,7 @@ import {
13
13
  provisionOrientHook,
14
14
  provisionStopHook,
15
15
  requireHost
16
- } from "../chunk-XG4X7BNT.js";
16
+ } from "../chunk-WERKUMWY.js";
17
17
  import {
18
18
  getProjectDir as getProjectDir2,
19
19
  getReadyTasks,
@@ -32,11 +32,9 @@ import {
32
32
  isAgentPromptReady,
33
33
  isSessionHealthy,
34
34
  isStaleForToday,
35
- parsePsRows,
36
35
  peekCurrentSession,
37
36
  prepareForRespawn,
38
37
  readPaneLogTail,
39
- reapOrphanChannelMcps,
40
38
  resetRestartCount,
41
39
  resolveClaudeBinary,
42
40
  sanitizeMcpJson,
@@ -46,7 +44,7 @@ import {
46
44
  stopAllSessionsAndWait,
47
45
  stopPersistentSession,
48
46
  takeZombieDetection
49
- } from "../chunk-LULDCIUS.js";
47
+ } from "../chunk-QSDDP7JN.js";
50
48
  import {
51
49
  KANBAN_CHECK_COMMAND,
52
50
  appendDmFooter,
@@ -69,7 +67,11 @@ import {
69
67
  resolveConnectivityProbe,
70
68
  resolveDmTarget,
71
69
  wrapScheduledTaskPrompt
72
- } from "../chunk-YMIDZT5T.js";
70
+ } from "../chunk-U3HCB23E.js";
71
+ import {
72
+ parsePsRows,
73
+ reapOrphanChannelMcps
74
+ } from "../chunk-XWVM4KPK.js";
73
75
 
74
76
  // src/lib/manager-worker.ts
75
77
  import { createHash as createHash3 } from "crypto";
@@ -2191,6 +2193,67 @@ function withScheduleLinkFooter(opts) {
2191
2193
  return appendScheduleLinkFooter(opts.body, url, opts.medium);
2192
2194
  }
2193
2195
 
2196
+ // src/lib/manager/agent-state.ts
2197
+ var agentState = {
2198
+ // ---------------------------------------------------------------------------
2199
+ // agentId-keyed
2200
+ // ---------------------------------------------------------------------------
2201
+ knownVersions: /* @__PURE__ */ new Map(),
2202
+ knownStatuses: /* @__PURE__ */ new Map(),
2203
+ knownChannels: /* @__PURE__ */ new Map(),
2204
+ writtenHashes: /* @__PURE__ */ new Map(),
2205
+ knownSecretsHashes: /* @__PURE__ */ new Map(),
2206
+ knownModels: /* @__PURE__ */ new Map(),
2207
+ knownTasksHashes: /* @__PURE__ */ new Map(),
2208
+ knownIntegrationHashes: /* @__PURE__ */ new Map(),
2209
+ knownManagedMcpHashes: /* @__PURE__ */ new Map(),
2210
+ knownManagedMcpStructure: /* @__PURE__ */ new Map(),
2211
+ // ---------------------------------------------------------------------------
2212
+ // codeName-keyed
2213
+ // ---------------------------------------------------------------------------
2214
+ agentDisplayNames: /* @__PURE__ */ new Map(),
2215
+ codeNameToAgentId: /* @__PURE__ */ new Map(),
2216
+ /**
2217
+ * Set of code_names with a live persistent session managed by this worker.
2218
+ * Borderline ownership: written from pollCycle and the persistent-session
2219
+ * lifecycle, read from the kanban-inject gate. Lives in agentState to keep
2220
+ * the kanban subsystem from importing from lifecycle in PR-C/E.
2221
+ */
2222
+ persistentSessionAgents: /* @__PURE__ */ new Set(),
2223
+ // ---------------------------------------------------------------------------
2224
+ // compound-keyed (`agentId:<suffix>`)
2225
+ // ---------------------------------------------------------------------------
2226
+ knownChannelConfigHashes: /* @__PURE__ */ new Map(),
2227
+ knownSkillHashes: /* @__PURE__ */ new Map()
2228
+ };
2229
+ function clearAgentState(agentId, codeName) {
2230
+ agentState.knownVersions.delete(agentId);
2231
+ agentState.knownStatuses.delete(agentId);
2232
+ agentState.knownChannels.delete(agentId);
2233
+ agentState.writtenHashes.delete(agentId);
2234
+ agentState.knownSecretsHashes.delete(agentId);
2235
+ agentState.knownModels.delete(agentId);
2236
+ agentState.knownTasksHashes.delete(agentId);
2237
+ agentState.knownIntegrationHashes.delete(agentId);
2238
+ agentState.knownManagedMcpHashes.delete(agentId);
2239
+ agentState.knownManagedMcpStructure.delete(agentId);
2240
+ agentState.agentDisplayNames.delete(codeName);
2241
+ agentState.codeNameToAgentId.delete(codeName);
2242
+ let channelCacheMutated = false;
2243
+ for (const key of agentState.knownChannelConfigHashes.keys()) {
2244
+ if (key.startsWith(`${agentId}:`)) {
2245
+ agentState.knownChannelConfigHashes.delete(key);
2246
+ channelCacheMutated = true;
2247
+ }
2248
+ }
2249
+ for (const key of agentState.knownSkillHashes.keys()) {
2250
+ if (key.startsWith(`${agentId}:`)) {
2251
+ agentState.knownSkillHashes.delete(key);
2252
+ }
2253
+ }
2254
+ return channelCacheMutated;
2255
+ }
2256
+
2194
2257
  // src/lib/restart-flags.ts
2195
2258
  import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync4, renameSync, rmSync, writeFileSync as writeFileSync2 } from "fs";
2196
2259
  import { homedir as homedir3 } from "os";
@@ -2846,9 +2909,6 @@ function shouldSkipRevokedCleanup(previousKnownStatus) {
2846
2909
  function hasRevokedResiduals(state5) {
2847
2910
  return state5.gatewayRunning || state5.portAllocated || state5.provisionDirExists;
2848
2911
  }
2849
- var knownVersions = /* @__PURE__ */ new Map();
2850
- var knownStatuses = /* @__PURE__ */ new Map();
2851
- var knownChannels = /* @__PURE__ */ new Map();
2852
2912
  var pendingSessionRestarts = /* @__PURE__ */ new Map();
2853
2913
  var restartBreaker = new RestartBreaker();
2854
2914
  var reportedTrips = /* @__PURE__ */ new Map();
@@ -2863,7 +2923,7 @@ function recordRestartForBreaker(codeName, reason) {
2863
2923
  }
2864
2924
  async function reportCircuitTripped(codeName, trip) {
2865
2925
  if (reportedTrips.get(codeName) === trip.trippedAt) return;
2866
- const agentId = codeNameToAgentId.get(codeName);
2926
+ const agentId = agentState.codeNameToAgentId.get(codeName);
2867
2927
  if (!agentId) throw new Error(`no agent_id known yet for '${codeName}' \u2014 will retry next poll`);
2868
2928
  await api.post("/host/circuit-breaker/trip", {
2869
2929
  agent_id: agentId,
@@ -2904,8 +2964,6 @@ function cancelPendingSessionRestart(codeName) {
2904
2964
  pendingSessionRestarts.delete(codeName);
2905
2965
  log(`[hot-reload] Cancelled pending restart timer for '${codeName}' (another teardown path is handling it)`);
2906
2966
  }
2907
- var writtenHashes = /* @__PURE__ */ new Map();
2908
- var knownSecretsHashes = /* @__PURE__ */ new Map();
2909
2967
  var runningMcpHashes = /* @__PURE__ */ new Map();
2910
2968
  var runningMcpServerKeys = /* @__PURE__ */ new Map();
2911
2969
  function projectMcpHash(_codeName, projectDir) {
@@ -2962,7 +3020,14 @@ async function runAgentConnectivityProbes(agent, integrations, projectDir) {
2962
3020
  auth_type: i.auth_type,
2963
3021
  source_type: i.source_type ?? null,
2964
3022
  credentials: i.credentials,
2965
- last_connectivity_check_at: i.last_connectivity_check_at ?? null
3023
+ last_connectivity_check_at: i.last_connectivity_check_at ?? null,
3024
+ // ENG-5665: for managed (Composio) + mcp_server toolkits the connectivity
3025
+ // probe is an MCP `tools/list` against the agent's wired server. Derive the
3026
+ // .mcp.json server key the same way the managed-toolkit writer does
3027
+ // (definition_id === toolkit_id, sanitised: non-alnum → '_', lowercased —
3028
+ // e.g. 'composio_outlook'). The executor looks this up in .mcp.json; an
3029
+ // unresolvable key degrades to transient_error, never a false 'down'.
3030
+ mcp_server_key: i.source_type === "managed" || i.source_type === "mcp_server" ? i.definition_id.replace(/[^a-z0-9]/gi, "_").toLowerCase() : void 0
2966
3031
  })),
2967
3032
  { probeDeps, intervalMs: intervalSec * 1e3 }
2968
3033
  );
@@ -3038,13 +3103,6 @@ function checkMcpConfigDriftAndScheduleRestart(codeName, projectDir) {
3038
3103
  }
3039
3104
  }
3040
3105
  }
3041
- var knownChannelConfigHashes = /* @__PURE__ */ new Map();
3042
- var knownModels = /* @__PURE__ */ new Map();
3043
- var knownTasksHashes = /* @__PURE__ */ new Map();
3044
- var knownIntegrationHashes = /* @__PURE__ */ new Map();
3045
- var knownManagedMcpHashes = /* @__PURE__ */ new Map();
3046
- var knownManagedMcpStructure = /* @__PURE__ */ new Map();
3047
- var knownSkillHashes = /* @__PURE__ */ new Map();
3048
3106
  var managedToolkitIdByAgentAndServerId = /* @__PURE__ */ new Map();
3049
3107
  function managedToolkitMapKey(agentId, serverId) {
3050
3108
  return `${agentId}\0${serverId}`;
@@ -3063,8 +3121,6 @@ var STALE_TASK_THRESHOLD_MS = (() => {
3063
3121
  var alertSlackWebhook = null;
3064
3122
  var alertedJobs = /* @__PURE__ */ new Set();
3065
3123
  var taskDisplayInfo = /* @__PURE__ */ new Map();
3066
- var agentDisplayNames = /* @__PURE__ */ new Map();
3067
- var codeNameToAgentId = /* @__PURE__ */ new Map();
3068
3124
  var agentChannelTokens = /* @__PURE__ */ new Map();
3069
3125
  var activeChannels = /* @__PURE__ */ new Map();
3070
3126
  var gatewaysStartedThisCycle = /* @__PURE__ */ new Set();
@@ -3086,18 +3142,7 @@ function resolveAgentFramework(codeName) {
3086
3142
  }
3087
3143
  var gatewayPool = null;
3088
3144
  function clearAgentCaches(agentId, codeName) {
3089
- knownVersions.delete(agentId);
3090
- knownStatuses.delete(agentId);
3091
- knownChannels.delete(agentId);
3092
- writtenHashes.delete(agentId);
3093
- knownSecretsHashes.delete(agentId);
3094
- knownModels.delete(agentId);
3095
- knownTasksHashes.delete(agentId);
3096
- knownIntegrationHashes.delete(agentId);
3097
- knownManagedMcpHashes.delete(agentId);
3098
- knownManagedMcpStructure.delete(agentId);
3099
- agentDisplayNames.delete(codeName);
3100
- codeNameToAgentId.delete(codeName);
3145
+ const channelCacheMutated = clearAgentState(agentId, codeName);
3101
3146
  agentFrameworkCache.delete(codeName);
3102
3147
  kanbanBoardCache.delete(codeName);
3103
3148
  lastHarvestAt.delete(codeName);
@@ -3107,26 +3152,16 @@ function clearAgentCaches(agentId, codeName) {
3107
3152
  memoryFileHashes.delete(agentId);
3108
3153
  lastDownloadHash.delete(agentId);
3109
3154
  lastLocalFileHash.delete(agentId);
3110
- let channelCacheMutated = false;
3111
- for (const key of knownChannelConfigHashes.keys()) {
3112
- if (key.startsWith(`${agentId}:`)) {
3113
- knownChannelConfigHashes.delete(key);
3114
- channelCacheMutated = true;
3115
- }
3116
- }
3117
- if (channelCacheMutated) saveChannelHashCache2();
3118
- for (const key of knownSkillHashes.keys()) {
3119
- if (key.startsWith(`${agentId}:`)) knownSkillHashes.delete(key);
3120
- }
3121
3155
  for (const key of taskDisplayInfo.keys()) {
3122
3156
  if (key.startsWith(`${codeName}:`)) taskDisplayInfo.delete(key);
3123
3157
  }
3158
+ if (channelCacheMutated) saveChannelHashCache2();
3124
3159
  }
3125
3160
  var cachedFrameworkVersion = null;
3126
3161
  var lastVersionCheckAt = 0;
3127
3162
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
3128
3163
  var lastResponsivenessProbeAt = 0;
3129
- var agtCliVersion = true ? "0.26.0" : "dev";
3164
+ var agtCliVersion = true ? "0.26.2-eng5706.0" : "dev";
3130
3165
  function resolveBrewPath(execFileSync4) {
3131
3166
  try {
3132
3167
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -3640,10 +3675,10 @@ function channelHashCacheDir() {
3640
3675
  return config?.configDir ?? join6(process.env["HOME"] ?? "/tmp", ".augmented");
3641
3676
  }
3642
3677
  function loadChannelHashCache2() {
3643
- loadChannelHashCache(knownChannelConfigHashes, channelHashCacheDir());
3678
+ loadChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
3644
3679
  }
3645
3680
  function saveChannelHashCache2() {
3646
- saveChannelHashCache(knownChannelConfigHashes, channelHashCacheDir());
3681
+ saveChannelHashCache(agentState.knownChannelConfigHashes, channelHashCacheDir());
3647
3682
  }
3648
3683
  function send(msg) {
3649
3684
  if (msg.type === "state-update") {
@@ -3974,40 +4009,40 @@ async function stopAllGateways() {
3974
4009
  }
3975
4010
  }
3976
4011
  async function healthCheckGateways(agentStates) {
3977
- for (const agentState of agentStates) {
3978
- if (agentState.status !== "active" || !agentState.gatewayPort) continue;
3979
- const adapter = resolveAgentFramework(agentState.codeName);
4012
+ for (const agent of agentStates) {
4013
+ if (agent.status !== "active" || !agent.gatewayPort) continue;
4014
+ const adapter = resolveAgentFramework(agent.codeName);
3980
4015
  if (!adapter.isGatewayRunning || !adapter.startGateway) continue;
3981
- if (gatewaysStartedThisCycle.has(agentState.codeName)) continue;
3982
- const status = await adapter.isGatewayRunning(agentState.codeName);
3983
- if (!status.running && agentState.gatewayRunning) {
3984
- const displayName = agentDisplayNames.get(agentState.codeName) ?? agentState.codeName;
3985
- log(`Gateway for '${agentState.codeName}' crashed, restarting...`);
4016
+ if (gatewaysStartedThisCycle.has(agent.codeName)) continue;
4017
+ const status = await adapter.isGatewayRunning(agent.codeName);
4018
+ if (!status.running && agent.gatewayRunning) {
4019
+ const displayName = agentState.agentDisplayNames.get(agent.codeName) ?? agent.codeName;
4020
+ log(`Gateway for '${agent.codeName}' crashed, restarting...`);
3986
4021
  sendSlackWebhookMessage(
3987
- `:red_circle: *Host Down* \u2014 *${displayName}* (\`${agentState.codeName}\`)
4022
+ `:red_circle: *Host Down* \u2014 *${displayName}* (\`${agent.codeName}\`)
3988
4023
  OpenClaw gateway crashed. Attempting automatic restart...`
3989
4024
  ).catch(() => {
3990
4025
  });
3991
4026
  try {
3992
- const result = await adapter.startGateway(agentState.codeName, agentState.gatewayPort);
3993
- agentState.gatewayPid = result.pid;
3994
- agentState.gatewayRunning = true;
3995
- log(`Gateway restarted for '${agentState.codeName}' (PID ${result.pid})`);
4027
+ const result = await adapter.startGateway(agent.codeName, agent.gatewayPort);
4028
+ agent.gatewayPid = result.pid;
4029
+ agent.gatewayRunning = true;
4030
+ log(`Gateway restarted for '${agent.codeName}' (PID ${result.pid})`);
3996
4031
  await new Promise((resolve) => setTimeout(resolve, 2e3));
3997
4032
  if (gatewayPool) {
3998
- const token = readGatewayToken(agentState.codeName);
3999
- gatewayPool.addAgent(agentState.codeName, agentState.gatewayPort, token);
4033
+ const token = readGatewayToken(agent.codeName);
4034
+ gatewayPool.addAgent(agent.codeName, agent.gatewayPort, token);
4000
4035
  }
4001
4036
  sendSlackWebhookMessage(
4002
- `:large_green_circle: *Host Recovered* \u2014 *${displayName}* (\`${agentState.codeName}\`)
4037
+ `:large_green_circle: *Host Recovered* \u2014 *${displayName}* (\`${agent.codeName}\`)
4003
4038
  Gateway restarted successfully (PID ${result.pid}).`
4004
4039
  ).catch(() => {
4005
4040
  });
4006
4041
  } catch (err) {
4007
- agentState.gatewayRunning = false;
4008
- log(`Failed to restart gateway for '${agentState.codeName}': ${err.message}`);
4042
+ agent.gatewayRunning = false;
4043
+ log(`Failed to restart gateway for '${agent.codeName}': ${err.message}`);
4009
4044
  sendSlackWebhookMessage(
4010
- `:x: *Host Restart Failed* \u2014 *${displayName}* (\`${agentState.codeName}\`)
4045
+ `:x: *Host Restart Failed* \u2014 *${displayName}* (\`${agent.codeName}\`)
4011
4046
  Automatic restart failed: ${err.message}`
4012
4047
  ).catch(() => {
4013
4048
  });
@@ -4025,7 +4060,7 @@ async function pollCycle() {
4025
4060
  resolveFramework: (codeName) => agentFrameworkCache.get(codeName) ?? null,
4026
4061
  stopSession: (codeName) => {
4027
4062
  stopPersistentSessionAndForgetMcpBaseline(codeName, "channel-set-change");
4028
- persistentSessionAgents.delete(codeName);
4063
+ agentState.persistentSessionAgents.delete(codeName);
4029
4064
  claudeAuthTupleBySession.delete(codeName);
4030
4065
  },
4031
4066
  getTelegramTokens: (codeName) => {
@@ -4085,8 +4120,8 @@ async function pollCycle() {
4085
4120
  }
4086
4121
  try {
4087
4122
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
4088
- const { collectDiagnostics } = await import("../persistent-session-3ITHPO4P.js");
4089
- const diagCodeNames = [...persistentSessionAgents];
4123
+ const { collectDiagnostics } = await import("../persistent-session-K4X76LLB.js");
4124
+ const diagCodeNames = [...agentState.persistentSessionAgents];
4090
4125
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
4091
4126
  let tailscaleHostname;
4092
4127
  try {
@@ -4118,7 +4153,7 @@ async function pollCycle() {
4118
4153
  const errId = createHash3("sha256").update(errText).digest("hex").slice(0, 12);
4119
4154
  log(`Claude auth detection failed (error_id=${errId})`);
4120
4155
  }
4121
- await api.post("/host/heartbeat", {
4156
+ const hbResp = await api.post("/host/heartbeat", {
4122
4157
  host_id: hostId,
4123
4158
  framework_version: cachedFrameworkVersion ?? void 0,
4124
4159
  agt_version: agtCliVersion,
@@ -4136,6 +4171,16 @@ async function pollCycle() {
4136
4171
  // behind a fresh heartbeat.
4137
4172
  realtime_socket_connected: isRealtimeSocketConnected()
4138
4173
  });
4174
+ try {
4175
+ const { maybeUpdateClaudeCode } = await import("../claude-code-updater-7BGQ6JCV.js");
4176
+ await maybeUpdateClaudeCode({
4177
+ desiredVersion: hbResp?.desired_claude_code_version ?? "latest",
4178
+ currentVersion: cachedFrameworkVersion,
4179
+ log
4180
+ });
4181
+ } catch (err) {
4182
+ log(`Claude Code updater error: ${err.message}`);
4183
+ }
4139
4184
  } catch (err) {
4140
4185
  log(`Heartbeat failed: ${err.message}`);
4141
4186
  }
@@ -4143,10 +4188,10 @@ async function pollCycle() {
4143
4188
  const {
4144
4189
  collectResponsivenessProbes,
4145
4190
  getResponsivenessIntervalMs
4146
- } = await import("../responsiveness-probe-7XWORQLO.js");
4191
+ } = await import("../responsiveness-probe-QBV43SJ7.js");
4147
4192
  const probeIntervalMs = getResponsivenessIntervalMs();
4148
4193
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
4149
- const probeCodeNames = [...persistentSessionAgents];
4194
+ const probeCodeNames = [...agentState.persistentSessionAgents];
4150
4195
  if (probeCodeNames.length > 0) {
4151
4196
  const probes = collectResponsivenessProbes(probeCodeNames);
4152
4197
  if (probes.length > 0) {
@@ -4164,8 +4209,8 @@ async function pollCycle() {
4164
4209
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
4165
4210
  const observations = [];
4166
4211
  const pendingCacheCommits = [];
4167
- for (const codeName of persistentSessionAgents) {
4168
- const agentId = codeNameToAgentId.get(codeName);
4212
+ for (const codeName of agentState.persistentSessionAgents) {
4213
+ const agentId = agentState.codeNameToAgentId.get(codeName);
4169
4214
  if (!agentId) continue;
4170
4215
  const tail = readPaneLogTail(codeName, 40);
4171
4216
  if (!tail) continue;
@@ -4185,7 +4230,7 @@ async function pollCycle() {
4185
4230
  (resp?.results ?? []).filter((r) => r.ok).map((r) => r.agent_id)
4186
4231
  );
4187
4232
  for (const { codeName, count } of pendingCacheCommits) {
4188
- const agentId = codeNameToAgentId.get(codeName);
4233
+ const agentId = agentState.codeNameToAgentId.get(codeName);
4189
4234
  if (agentId && accepted.has(agentId)) {
4190
4235
  lastMcpFailedBannerCount.set(codeName, count);
4191
4236
  }
@@ -4416,9 +4461,9 @@ async function getOrCacheRegisteredAgents(adapter, profile) {
4416
4461
  }
4417
4462
  async function processAgent(agent, agentStates) {
4418
4463
  if (!config) return;
4419
- const previousKnownStatus = knownStatuses.get(agent.agent_id);
4420
- agentDisplayNames.set(agent.code_name, agent.display_name);
4421
- codeNameToAgentId.set(agent.code_name, agent.agent_id);
4464
+ const previousKnownStatus = agentState.knownStatuses.get(agent.agent_id);
4465
+ agentState.agentDisplayNames.set(agent.code_name, agent.display_name);
4466
+ agentState.codeNameToAgentId.set(agent.code_name, agent.agent_id);
4422
4467
  maybeReportCurrentTrip(agent.code_name);
4423
4468
  if (agent.framework) {
4424
4469
  agentFrameworkCache.set(agent.code_name, agent.framework);
@@ -4468,7 +4513,7 @@ async function processAgent(agent, agentStates) {
4468
4513
  gatewayRunning: false,
4469
4514
  acpSessions: []
4470
4515
  });
4471
- knownStatuses.set(agent.agent_id, agent.status);
4516
+ agentState.knownStatuses.set(agent.agent_id, agent.status);
4472
4517
  return;
4473
4518
  }
4474
4519
  if (agent.status === "revoked") {
@@ -4515,7 +4560,7 @@ async function processAgent(agent, agentStates) {
4515
4560
  freePort(agent.code_name);
4516
4561
  await cleanupAgentFiles(agent.code_name, agentDir);
4517
4562
  clearAgentCaches(agent.agent_id, agent.code_name);
4518
- knownStatuses.set(agent.agent_id, agent.status);
4563
+ agentState.knownStatuses.set(agent.agent_id, agent.status);
4519
4564
  agentStates.push({
4520
4565
  agentId: agent.agent_id,
4521
4566
  codeName: agent.code_name,
@@ -4534,25 +4579,25 @@ async function processAgent(agent, agentStates) {
4534
4579
  });
4535
4580
  return;
4536
4581
  }
4537
- const previousStatus = knownStatuses.get(agent.agent_id);
4582
+ const previousStatus = agentState.knownStatuses.get(agent.agent_id);
4538
4583
  if (previousStatus && previousStatus !== agent.status) {
4539
4584
  log(`Agent '${agent.code_name}' status changed: ${previousStatus} \u2192 ${agent.status}`);
4540
- knownVersions.delete(agent.agent_id);
4585
+ agentState.knownVersions.delete(agent.agent_id);
4541
4586
  if (previousStatus === "paused" && agent.status === "active" && restartBreaker.isTripped(agent.code_name)) {
4542
4587
  restartBreaker.clear(agent.code_name);
4543
4588
  reportedTrips.delete(agent.code_name);
4544
4589
  log(`[circuit-breaker] Cleared trip for '${agent.code_name}' on operator resume (paused \u2192 active)`);
4545
4590
  }
4546
4591
  let channelCacheMutated = false;
4547
- for (const key of knownChannelConfigHashes.keys()) {
4592
+ for (const key of agentState.knownChannelConfigHashes.keys()) {
4548
4593
  if (key.startsWith(`${agent.agent_id}:`)) {
4549
- knownChannelConfigHashes.delete(key);
4594
+ agentState.knownChannelConfigHashes.delete(key);
4550
4595
  channelCacheMutated = true;
4551
4596
  }
4552
4597
  }
4553
4598
  if (channelCacheMutated) saveChannelHashCache2();
4554
4599
  }
4555
- knownStatuses.set(agent.agent_id, agent.status);
4600
+ agentState.knownStatuses.set(agent.agent_id, agent.status);
4556
4601
  let refreshData;
4557
4602
  try {
4558
4603
  refreshData = await api.post("/host/refresh", {
@@ -4615,10 +4660,10 @@ async function processAgent(agent, agentStates) {
4615
4660
  }
4616
4661
  const charterVersion = refreshData.charter.version;
4617
4662
  const toolsVersion = refreshData.tools.version;
4618
- const known = knownVersions.get(agent.agent_id);
4663
+ const known = agentState.knownVersions.get(agent.agent_id);
4619
4664
  let lastProvisionAt = state4.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
4620
4665
  const currentChannelIds = launchableChannelIds(refreshData.channel_configs);
4621
- const previousChannelIds = knownChannels.get(agent.agent_id);
4666
+ const previousChannelIds = agentState.knownChannels.get(agent.agent_id);
4622
4667
  const channelsChanged = !previousChannelIds || currentChannelIds.size !== previousChannelIds.size || [...currentChannelIds].some((ch) => !previousChannelIds.has(ch)) || [...previousChannelIds].some((ch) => !currentChannelIds.has(ch));
4623
4668
  let channelConfigConverged = true;
4624
4669
  if (previousChannelIds && channelsChanged && frameworkAdapter.removeChannelCredentials) {
@@ -4716,14 +4761,14 @@ async function processAgent(agent, agentStates) {
4716
4761
  } catch {
4717
4762
  }
4718
4763
  lastProvisionAt = (/* @__PURE__ */ new Date()).toISOString();
4719
- knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });
4764
+ agentState.knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });
4720
4765
  const trackedFiles2 = frameworkAdapter.driftTrackedFiles();
4721
4766
  const hashes = /* @__PURE__ */ new Map();
4722
4767
  for (const file of trackedFiles2) {
4723
4768
  const h = hashFile(join6(agentDir, file));
4724
4769
  if (h) hashes.set(file, h);
4725
4770
  }
4726
- writtenHashes.set(agent.agent_id, hashes);
4771
+ agentState.writtenHashes.set(agent.agent_id, hashes);
4727
4772
  const resolvedModelsForRegistration = resolveModelChain(refreshData);
4728
4773
  const primaryModel2 = resolvedModelsForRegistration.primary ?? refreshData.agent.primary_model;
4729
4774
  const registeredAgents = await getOrCacheRegisteredAgents(frameworkAdapter, agent.code_name);
@@ -4745,7 +4790,7 @@ async function processAgent(agent, agentStates) {
4745
4790
  const resolvedForModel = resolveModelChain(refreshData);
4746
4791
  const primaryModel = resolvedForModel.primary ?? refreshData.agent.primary_model;
4747
4792
  if (primaryModel && frameworkAdapter.updateAgentModel) {
4748
- const previousModel = knownModels.get(agent.agent_id);
4793
+ const previousModel = agentState.knownModels.get(agent.agent_id);
4749
4794
  if (previousModel !== primaryModel) {
4750
4795
  try {
4751
4796
  const updated = await frameworkAdapter.updateAgentModel(agent.code_name, primaryModel);
@@ -4781,10 +4826,10 @@ async function processAgent(agent, agentStates) {
4781
4826
  scheduleSessionRestart(agent.code_name, delay, "model change", "model-change");
4782
4827
  }
4783
4828
  }
4784
- knownModels.set(agent.agent_id, primaryModel);
4829
+ agentState.knownModels.set(agent.agent_id, primaryModel);
4785
4830
  }
4786
4831
  let lastDriftCheckAt = now;
4787
- const written = writtenHashes.get(agent.agent_id);
4832
+ const written = agentState.writtenHashes.get(agent.agent_id);
4788
4833
  if (written && existsSync4(agentDir)) {
4789
4834
  const driftedFiles = [];
4790
4835
  for (const [file, expectedHash] of written) {
@@ -4881,14 +4926,14 @@ async function processAgent(agent, agentStates) {
4881
4926
  log(`hasChannelCredentials failed for '${agent.code_name}/${channelId}': ${err.message} \u2014 forcing credential rewrite`);
4882
4927
  onDiskPresent = false;
4883
4928
  }
4884
- if (knownChannelConfigHashes.get(cacheKey) === configHash && onDiskPresent) {
4929
+ if (agentState.knownChannelConfigHashes.get(cacheKey) === configHash && onDiskPresent) {
4885
4930
  continue;
4886
4931
  }
4887
- const prevHash = knownChannelConfigHashes.get(cacheKey);
4932
+ const prevHash = agentState.knownChannelConfigHashes.get(cacheKey);
4888
4933
  const reason = !prevHash ? "first-write" : !onDiskPresent ? "on-disk-missing" : "content-changed";
4889
4934
  if (!onDiskPresent && prevHash) {
4890
4935
  log(`Cached hash for '${agent.code_name}/${channelId}' but on-disk entry missing \u2014 re-writing credentials`);
4891
- knownChannelConfigHashes.delete(cacheKey);
4936
+ agentState.knownChannelConfigHashes.delete(cacheKey);
4892
4937
  }
4893
4938
  try {
4894
4939
  const sessionMode2 = refreshData.agent.session_mode;
@@ -4910,7 +4955,7 @@ async function processAgent(agent, agentStates) {
4910
4955
  agentTimezone
4911
4956
  }
4912
4957
  );
4913
- knownChannelConfigHashes.set(cacheKey, configHash);
4958
+ agentState.knownChannelConfigHashes.set(cacheKey, configHash);
4914
4959
  saveChannelHashCache2();
4915
4960
  log(`Channel credentials written for '${agent.code_name}/${channelId}' (reason=${reason}, hash=${configHash.slice(0, 8)}${prevHash ? `, prev=${prevHash.slice(0, 8)}` : ""})`);
4916
4961
  } catch (err) {
@@ -4928,7 +4973,7 @@ async function processAgent(agent, agentStates) {
4928
4973
  }
4929
4974
  }
4930
4975
  if (channelConfigConverged) {
4931
- knownChannels.set(agent.agent_id, currentChannelIds);
4976
+ agentState.knownChannels.set(agent.agent_id, currentChannelIds);
4932
4977
  } else {
4933
4978
  log(`[channels] Credential sync did not converge for '${agent.code_name}' \u2014 leaving diff live for next tick retry`);
4934
4979
  }
@@ -5002,7 +5047,7 @@ async function processAgent(agent, agentStates) {
5002
5047
  }
5003
5048
  }
5004
5049
  let lastSecretsProvisionAt = state4.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;
5005
- let secretsHash = knownSecretsHashes.get(agent.agent_id) ?? null;
5050
+ let secretsHash = agentState.knownSecretsHashes.get(agent.agent_id) ?? null;
5006
5051
  try {
5007
5052
  const secretsData = await api.post("/host/secrets", { agent_id: agent.agent_id });
5008
5053
  const newHash = secretsData.secrets_hash;
@@ -5013,10 +5058,10 @@ async function processAgent(agent, agentStates) {
5013
5058
  log(`Secrets updated for '${agent.code_name}'`);
5014
5059
  }
5015
5060
  if (newHash) {
5016
- knownSecretsHashes.set(agent.agent_id, newHash);
5061
+ agentState.knownSecretsHashes.set(agent.agent_id, newHash);
5017
5062
  secretsHash = newHash;
5018
5063
  } else {
5019
- knownSecretsHashes.delete(agent.agent_id);
5064
+ agentState.knownSecretsHashes.delete(agent.agent_id);
5020
5065
  secretsHash = null;
5021
5066
  }
5022
5067
  } catch (err) {
@@ -5063,7 +5108,7 @@ async function processAgent(agent, agentStates) {
5063
5108
  }
5064
5109
  if (integrations.length > 0) {
5065
5110
  const intHash = computeIntegrationsHash(integrations);
5066
- const prevIntHash = knownIntegrationHashes.get(agent.agent_id);
5111
+ const prevIntHash = agentState.knownIntegrationHashes.get(agent.agent_id);
5067
5112
  if (intHash !== prevIntHash) {
5068
5113
  const projectDir = join6(homedir4(), ".augmented", agent.code_name, "project");
5069
5114
  const envIntPath = join6(projectDir, ".env.integrations");
@@ -5108,7 +5153,7 @@ async function processAgent(agent, agentStates) {
5108
5153
  }
5109
5154
  }
5110
5155
  if (rotationHandled) {
5111
- knownIntegrationHashes.set(agent.agent_id, intHash);
5156
+ agentState.knownIntegrationHashes.set(agent.agent_id, intHash);
5112
5157
  }
5113
5158
  needsGatewayRestart = true;
5114
5159
  }
@@ -5138,9 +5183,9 @@ async function processAgent(agent, agentStates) {
5138
5183
  return `${e.serverId}|${e.url}|${headersHash}`;
5139
5184
  }).join("\n");
5140
5185
  const mcpHash = createHash3("sha256").update(hashBasis).digest("hex").slice(0, 16);
5141
- const prevMcpHash = knownManagedMcpHashes.get(agent.agent_id);
5186
+ const prevMcpHash = agentState.knownManagedMcpHashes.get(agent.agent_id);
5142
5187
  const structureHash = managedMcpStructureHash(desiredEntries);
5143
- const prevStructureHash = knownManagedMcpStructure.get(agent.agent_id);
5188
+ const prevStructureHash = agentState.knownManagedMcpStructure.get(agent.agent_id);
5144
5189
  if (mcpHash !== prevMcpHash) {
5145
5190
  for (const e of desiredEntries) {
5146
5191
  frameworkAdapter.writeMcpServer(agent.code_name, e.serverId, { url: e.url, headers: e.headers });
@@ -5177,8 +5222,8 @@ async function processAgent(agent, agentStates) {
5177
5222
  }
5178
5223
  }
5179
5224
  }
5180
- knownManagedMcpHashes.set(agent.agent_id, mcpHash);
5181
- knownManagedMcpStructure.set(agent.agent_id, structureHash);
5225
+ agentState.knownManagedMcpHashes.set(agent.agent_id, mcpHash);
5226
+ agentState.knownManagedMcpStructure.set(agent.agent_id, structureHash);
5182
5227
  if (prevStructureHash !== void 0 && structureHash !== prevStructureHash && fwForMcp === "claude-code" && isSessionHealthy(agent.code_name)) {
5183
5228
  const mcpNames = agentToolkits.map((tk) => tk.toolkit_name).join(", ") || "none (all removed)";
5184
5229
  log(`[hot-reload] MCP servers changed for '${agent.code_name}': ${mcpNames} \u2014 restarting session`);
@@ -5304,9 +5349,9 @@ async function processAgent(agent, agentStates) {
5304
5349
  const bundle = buildIntegrationBundle(renderedScopes);
5305
5350
  const contentHash = createHash4("sha256").update(bundleFingerprint(bundle.files)).digest("hex").slice(0, 12);
5306
5351
  const hashKey = `plugin-skill:${agent.agent_id}:${integrationSkillId}`;
5307
- if (knownSkillHashes.get(hashKey) === contentHash) continue;
5352
+ if (agentState.knownSkillHashes.get(hashKey) === contentHash) continue;
5308
5353
  frameworkAdapter.installSkillFiles(agent.code_name, integrationSkillId, bundle.files);
5309
- knownSkillHashes.set(hashKey, contentHash);
5354
+ agentState.knownSkillHashes.set(hashKey, contentHash);
5310
5355
  for (const s of scopes) installedIntegrationSkills.push(s.skill_name);
5311
5356
  log(`Installed integration skill bundle '${integrationSkillId}' for '${agent.code_name}' (${scopes.length} scope(s))`);
5312
5357
  } catch (err) {
@@ -5373,7 +5418,7 @@ async function processAgent(agent, agentStates) {
5373
5418
  try {
5374
5419
  const scriptHash = createHash4("sha256").update(hook.script).digest("hex").slice(0, 12);
5375
5420
  const hookKey = `${agent.agent_id}:${frameworkAdapter.id}:plugin-hook:${slug}:on_install`;
5376
- if (knownSkillHashes.get(hookKey) === scriptHash) continue;
5421
+ if (agentState.knownSkillHashes.get(hookKey) === scriptHash) continue;
5377
5422
  const result = await frameworkAdapter.executePluginHook({
5378
5423
  codeName: agent.code_name,
5379
5424
  pluginSlug: slug,
@@ -5381,7 +5426,7 @@ async function processAgent(agent, agentStates) {
5381
5426
  script: hook.script
5382
5427
  });
5383
5428
  if (result.exitCode === 0) {
5384
- knownSkillHashes.set(hookKey, scriptHash);
5429
+ agentState.knownSkillHashes.set(hookKey, scriptHash);
5385
5430
  log(`Integration hook on_install '${slug}' succeeded for '${agent.code_name}' (${result.durationMs}ms)`);
5386
5431
  } else if (result.timedOut) {
5387
5432
  log(`Integration hook on_install '${slug}' TIMED OUT for '${agent.code_name}' after ${result.durationMs}ms`);
@@ -5589,7 +5634,7 @@ async function processAgent(agent, agentStates) {
5589
5634
  const resolvedModels = resolveModelChain(refreshData);
5590
5635
  const modelsHash = createHash3("sha256").update(JSON.stringify(resolvedModels)).digest("hex").slice(0, 16);
5591
5636
  const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;
5592
- const prevTasksHash = knownTasksHashes.get(agent.agent_id);
5637
+ const prevTasksHash = agentState.knownTasksHashes.get(agent.agent_id);
5593
5638
  if (combinedHash !== prevTasksHash) {
5594
5639
  const enrichedTasks = tasks.map((t) => {
5595
5640
  if (BOARD_INJECT_TEMPLATES.has(t.template_id) && boardItems.length > 0) {
@@ -5626,7 +5671,7 @@ async function processAgent(agent, agentStates) {
5626
5671
  models: resolvedModels
5627
5672
  }
5628
5673
  );
5629
- knownTasksHashes.set(agent.agent_id, combinedHash);
5674
+ agentState.knownTasksHashes.set(agent.agent_id, combinedHash);
5630
5675
  log(`Scheduled tasks synced for '${agent.code_name}' (${enrichedTasks.length} task(s))`);
5631
5676
  } catch (err) {
5632
5677
  log(`Failed to sync scheduled tasks for '${agent.code_name}': ${err.message}`);
@@ -5695,7 +5740,7 @@ async function processAgent(agent, agentStates) {
5695
5740
  cleanupStaleSessions(agent.code_name);
5696
5741
  }
5697
5742
  {
5698
- const agentId = codeNameToAgentId.get(agent.code_name);
5743
+ const agentId = agentState.codeNameToAgentId.get(agent.code_name);
5699
5744
  if (agentId) {
5700
5745
  try {
5701
5746
  const boardData = await api.post("/host/my-kanban", { agent_id: agentId });
@@ -5711,16 +5756,11 @@ async function processAgent(agent, agentStates) {
5711
5756
  );
5712
5757
  log(`[${agent.code_name}] Board diff: ${previousDoneIds.size} prev done \u2192 ${freshDoneIds.size} now, ${newlyDone.length} newly done`);
5713
5758
  if (newlyDone.length > 0) {
5714
- const displayName = agentDisplayNames.get(agent.code_name) ?? agent.code_name;
5759
+ const displayName = agentState.agentDisplayNames.get(agent.code_name) ?? agent.code_name;
5715
5760
  for (const item of newlyDone) {
5716
5761
  log(`Newly done: '${item.title}' notify_channel=${item.notify_channel ?? "none"} notify_to=${item.notify_to ?? "none"}`);
5717
5762
  if (item.notify_channel && item.notify_to) {
5718
- const isFailed = item.status === "failed";
5719
- const resultLine = item.result ? `
5720
- ${isFailed ? "Reason" : "Result"}: ${item.result}` : "";
5721
- const emoji = isFailed ? "\u274C" : "\u2705";
5722
- const message = `${emoji} ${isFailed ? "Task Failed" : "Task Complete"} \u2014 ${displayName}
5723
- ${item.title}${resultLine}`;
5763
+ const message = await buildDoneCardNotification(agentId, agent.code_name, displayName, item);
5724
5764
  sendTaskNotification(agent.code_name, item.notify_channel, item.notify_to, message).catch((err) => {
5725
5765
  log(`sendTaskNotification failed for '${agent.code_name}': ${err.message}`);
5726
5766
  });
@@ -5734,7 +5774,7 @@ ${item.title}${resultLine}`;
5734
5774
  return age > STALE_TASK_THRESHOLD_MS && !alertedStaleItems.has(`${agent.code_name}:${b.id}`);
5735
5775
  });
5736
5776
  if (staleItems.length > 0) {
5737
- const displayName = agentDisplayNames.get(agent.code_name) ?? agent.code_name;
5777
+ const displayName = agentState.agentDisplayNames.get(agent.code_name) ?? agent.code_name;
5738
5778
  for (const item of staleItems) {
5739
5779
  const age = Math.round((Date.now() - new Date(item.updated_at).getTime()) / 6e4);
5740
5780
  const stuckKey = `${agent.code_name}:${item.id}`;
@@ -5807,9 +5847,9 @@ In progress for ${age} minutes \u2014 auto-failed`).catch(() => {
5807
5847
  const h = hashFile(join6(agentDir, file));
5808
5848
  if (h) hashes.set(file, h);
5809
5849
  }
5810
- writtenHashes.set(agent.agent_id, hashes);
5850
+ agentState.writtenHashes.set(agent.agent_id, hashes);
5811
5851
  } else {
5812
- writtenHashes.delete(agent.agent_id);
5852
+ agentState.writtenHashes.delete(agent.agent_id);
5813
5853
  }
5814
5854
  agentStates.push({
5815
5855
  agentId: agent.agent_id,
@@ -5970,7 +6010,7 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
5970
6010
  const resolvedModels = resolveModelChain(refreshData);
5971
6011
  const modelsHash = createHash3("sha256").update(JSON.stringify(resolvedModels)).digest("hex").slice(0, 16);
5972
6012
  const combinedHash = `${stableTasksHash}:${boardHash}:${modelsHash}`;
5973
- const prevHash = knownTasksHashes.get(agent.agent_id);
6013
+ const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
5974
6014
  if (combinedHash !== prevHash) {
5975
6015
  const taskInputs = tasks.map((t) => ({
5976
6016
  id: t.id,
@@ -5991,7 +6031,7 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
5991
6031
  }));
5992
6032
  const state6 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
5993
6033
  claudeSchedulerStates.set(codeName, state6);
5994
- knownTasksHashes.set(agent.agent_id, combinedHash);
6034
+ agentState.knownTasksHashes.set(agent.agent_id, combinedHash);
5995
6035
  log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
5996
6036
  }
5997
6037
  if (!claudeSchedulerStates.has(codeName)) {
@@ -6414,7 +6454,6 @@ async function processClaudeTaskResult(codeName, agentId, templateId, rawOutput,
6414
6454
  log(`[claude-scheduler] Failed to post result for '${codeName}': ${err.message}`);
6415
6455
  }
6416
6456
  }
6417
- var persistentSessionAgents = /* @__PURE__ */ new Set();
6418
6457
  var lastMcpFailedBannerCount = /* @__PURE__ */ new Map();
6419
6458
  var persistentSessionStuckTracker = new PersistentSessionStuckTracker();
6420
6459
  var claudeAuthTupleBySession = /* @__PURE__ */ new Map();
@@ -6478,7 +6517,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
6478
6517
  log(`[persistent-session] Failed to resolve auth for '${codeName}': ${msg} \u2014 refusing to spawn`);
6479
6518
  if (isSessionHealthy(codeName)) {
6480
6519
  stopPersistentSessionAndForgetMcpBaseline(codeName);
6481
- persistentSessionAgents.delete(codeName);
6520
+ agentState.persistentSessionAgents.delete(codeName);
6482
6521
  claudeAuthTupleBySession.delete(codeName);
6483
6522
  }
6484
6523
  return {
@@ -6515,7 +6554,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
6515
6554
  if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {
6516
6555
  log(`[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} \u2192 ${currentAuthTuple}) \u2014 restarting session`);
6517
6556
  stopPersistentSessionAndForgetMcpBaseline(codeName, "auth-tuple-change");
6518
- persistentSessionAgents.delete(codeName);
6557
+ agentState.persistentSessionAgents.delete(codeName);
6519
6558
  restartTrigger = "auth-tuple";
6520
6559
  }
6521
6560
  if (isStaleForToday(codeName, /* @__PURE__ */ new Date(), agentTimezone) && isSessionHealthy(codeName)) {
@@ -6527,7 +6566,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
6527
6566
  `[persistent-session] Day rollover for '${codeName}' (yesterday=${current.date}) \u2014 agent idle, restarting to mint fresh session`
6528
6567
  );
6529
6568
  stopPersistentSessionAndForgetMcpBaseline(codeName, "day-rollover");
6530
- persistentSessionAgents.delete(codeName);
6569
+ agentState.persistentSessionAgents.delete(codeName);
6531
6570
  restartTrigger = "day-rollover";
6532
6571
  } else {
6533
6572
  log(
@@ -6547,7 +6586,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
6547
6586
  };
6548
6587
  }
6549
6588
  if (!isSessionHealthy(codeName)) {
6550
- if (persistentSessionAgents.has(codeName)) {
6589
+ if (agentState.persistentSessionAgents.has(codeName)) {
6551
6590
  const ctx = getLastFailureContext(codeName);
6552
6591
  const recovery = prepareForRespawn(codeName);
6553
6592
  const tailSummary = !ctx.tail ? "" : KNOWN_SAFE_TAIL_SIGNATURES.has(ctx.signature) ? `; last pane output (${PANE_TAIL_PREVIEW_LINES} of ~20 lines):
@@ -6621,7 +6660,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash3("sha256").
6621
6660
  agentTimezone,
6622
6661
  log
6623
6662
  });
6624
- persistentSessionAgents.add(codeName);
6663
+ agentState.persistentSessionAgents.add(codeName);
6625
6664
  claudeAuthTupleBySession.set(codeName, currentAuthTuple);
6626
6665
  return {
6627
6666
  decision: "spawn",
@@ -6635,7 +6674,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash3("sha256").
6635
6674
  claudeAuthTupleBySession.set(codeName, currentAuthTuple);
6636
6675
  }
6637
6676
  const stableTasksHash = createHash3("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
6638
- const prevHash = knownTasksHashes.get(agent.agent_id);
6677
+ const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
6639
6678
  if (stableTasksHash !== prevHash) {
6640
6679
  const taskInputs = tasks.map((t) => ({
6641
6680
  id: t.id,
@@ -6656,7 +6695,7 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash3("sha256").
6656
6695
  }));
6657
6696
  const schedulerState = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
6658
6697
  claudeSchedulerStates.set(codeName, schedulerState);
6659
- knownTasksHashes.set(agent.agent_id, stableTasksHash);
6698
+ agentState.knownTasksHashes.set(agent.agent_id, stableTasksHash);
6660
6699
  log(`[persistent-session] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
6661
6700
  } else if (!claudeSchedulerStates.has(codeName)) {
6662
6701
  claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
@@ -6828,10 +6867,10 @@ function ensureRealtimeDriftStarted(agentStates) {
6828
6867
  token: exchange.token,
6829
6868
  agentIds: activeAgentIds,
6830
6869
  onDrift: (doc) => {
6831
- const agentState = agentStates.find((a) => a.agentId === doc.agent_id);
6832
- if (agentState) {
6833
- knownVersions.delete(doc.agent_id);
6834
- log(`[realtime] Drift invalidated for '${agentState.codeName}' \u2014 will re-provision next cycle`);
6870
+ const agent = agentStates.find((a) => a.agentId === doc.agent_id);
6871
+ if (agent) {
6872
+ agentState.knownVersions.delete(doc.agent_id);
6873
+ log(`[realtime] Drift invalidated for '${agent.codeName}' \u2014 will re-provision next cycle`);
6835
6874
  }
6836
6875
  },
6837
6876
  log
@@ -6883,7 +6922,7 @@ function ensureRealtimeConfigStarted(agentStates) {
6883
6922
  token: exchange.token,
6884
6923
  agentIds: activeAgentIds,
6885
6924
  onConfigChange: (agent) => {
6886
- knownStatuses.delete(agent.agent_id);
6925
+ agentState.knownStatuses.delete(agent.agent_id);
6887
6926
  },
6888
6927
  log
6889
6928
  });
@@ -6983,9 +7022,9 @@ function ensureRealtimeIntegrationContextStarted(agentStates) {
6983
7022
  onContextChange: (payload) => {
6984
7023
  const agent = agentStates.find((a) => a.agentId === payload.agent_id);
6985
7024
  if (agent) {
6986
- for (const key of knownSkillHashes.keys()) {
7025
+ for (const key of agentState.knownSkillHashes.keys()) {
6987
7026
  if (key.startsWith(`plugin-skill:${agent.agentId}:`)) {
6988
- knownSkillHashes.delete(key);
7027
+ agentState.knownSkillHashes.delete(key);
6989
7028
  }
6990
7029
  }
6991
7030
  }
@@ -7242,6 +7281,7 @@ var lastHarvestAt = /* @__PURE__ */ new Map();
7242
7281
  var HARVEST_INTERVAL_MS = 3 * 60 * 1e3;
7243
7282
  var kanbanBoardCache = /* @__PURE__ */ new Map();
7244
7283
  var notifyBoardCache = /* @__PURE__ */ new Map();
7284
+ var NOTIFY_RESULT_MAX_CHARS = 3500;
7245
7285
  async function harvestCronResults(codeName, tasks, gatewayPort) {
7246
7286
  const token = readGatewayToken(codeName);
7247
7287
  const gwArgs = ["--url", `ws://127.0.0.1:${gatewayPort}`, ...token ? ["--token", token] : []];
@@ -7275,7 +7315,7 @@ async function harvestCronResults(codeName, tasks, gatewayPort) {
7275
7315
  }
7276
7316
  const latestRun = runs.filter((r) => r.action === "finished").sort((a, b) => b.ts - a.ts)[0];
7277
7317
  if (latestRun) {
7278
- const agentId = codeNameToAgentId.get(codeName);
7318
+ const agentId = agentState.codeNameToAgentId.get(codeName);
7279
7319
  const summary = latestRun.summary ?? "";
7280
7320
  const isKeyError = summary.includes("Key limit exceeded") || summary.includes("key limit") || summary.includes("rate limit") || summary.includes("insufficient_quota") || summary.includes("billing") || summary.includes("402");
7281
7321
  if (agentId) {
@@ -7322,7 +7362,7 @@ async function harvestCronResults(codeName, tasks, gatewayPort) {
7322
7362
  const planItems = parsePlanItems(summary);
7323
7363
  if (planItems.length > 0) {
7324
7364
  try {
7325
- const agentId = codeNameToAgentId.get(codeName);
7365
+ const agentId = agentState.codeNameToAgentId.get(codeName);
7326
7366
  if (agentId) {
7327
7367
  await api.post("/host/kanban", {
7328
7368
  agent_id: agentId,
@@ -7341,7 +7381,7 @@ async function harvestCronResults(codeName, tasks, gatewayPort) {
7341
7381
  const kanbanUpdates = parseKanbanUpdates(summary);
7342
7382
  if (kanbanUpdates.length > 0) {
7343
7383
  try {
7344
- const agentId = codeNameToAgentId.get(codeName);
7384
+ const agentId = agentState.codeNameToAgentId.get(codeName);
7345
7385
  if (agentId) {
7346
7386
  await api.post("/host/kanban", {
7347
7387
  agent_id: agentId,
@@ -8071,7 +8111,7 @@ async function processClaudePairSessions(agents) {
8071
8111
  killPairSession,
8072
8112
  pairTmuxSession,
8073
8113
  finalizeClaudePairOnboarding
8074
- } = await import("../claude-pair-runtime-7PHOEAUN.js");
8114
+ } = await import("../claude-pair-runtime-2NNAUBP3.js");
8075
8115
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
8076
8116
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
8077
8117
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -8190,6 +8230,24 @@ async function maybePostScheduledTaskRatingPrompt(agentId, taskId, channelId, me
8190
8230
  log(`[rating-prompt] Failed to post rating prompt for ${agentId}/${taskId}: ${err.message}`);
8191
8231
  }
8192
8232
  }
8233
+ async function buildDoneCardNotification(agentId, codeName, displayName, item) {
8234
+ const isFailed = item.status === "failed";
8235
+ let resultBody = item.result ?? "";
8236
+ try {
8237
+ const full = await api.post("/host/kanban-item", {
8238
+ agent_id: agentId,
8239
+ item_id: item.id
8240
+ });
8241
+ if (full.item?.result) resultBody = full.item.result;
8242
+ } catch (err) {
8243
+ log(`[notify] full-result fetch failed for card ${item.id} on '${codeName}': ${err.message} \u2014 using truncated board result`);
8244
+ }
8245
+ const resultLine = resultBody ? `
8246
+ ${isFailed ? "Reason" : "Result"}: ${resultBody.slice(0, NOTIFY_RESULT_MAX_CHARS)}` : "";
8247
+ const emoji = isFailed ? "\u274C" : "\u2705";
8248
+ return `${emoji} ${isFailed ? "Task Failed" : "Task Complete"} \u2014 ${displayName}
8249
+ ${item.title}${resultLine}`;
8250
+ }
8193
8251
  async function sendTaskNotification(agentCodeName, channel, to, text) {
8194
8252
  const tokens = agentChannelTokens.get(agentCodeName);
8195
8253
  if (channel === "slack") {
@@ -8841,6 +8899,7 @@ export {
8841
8899
  ChildProcessError,
8842
8900
  __resetScheduledDeliveryDedupeForTest,
8843
8901
  applyRestartAcks,
8902
+ buildDoneCardNotification,
8844
8903
  claudeCodeUpgradeMarkerPath,
8845
8904
  claudeCodeUpgradeThrottled,
8846
8905
  deliverScheduledCardResult,