@integrity-labs/agt-cli 0.27.128 → 0.27.129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/agt.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  success,
29
29
  table,
30
30
  warn
31
- } from "../chunk-TXZLQK3S.js";
31
+ } from "../chunk-A64MR5QP.js";
32
32
  import {
33
33
  CHANNEL_REGISTRY,
34
34
  DEPLOYMENT_TEMPLATES,
@@ -4934,7 +4934,7 @@ import { execFileSync, execSync } from "child_process";
4934
4934
  import { existsSync as existsSync10, realpathSync as realpathSync2 } from "fs";
4935
4935
  import chalk18 from "chalk";
4936
4936
  import ora16 from "ora";
4937
- var cliVersion = true ? "0.27.128" : "dev";
4937
+ var cliVersion = true ? "0.27.129" : "dev";
4938
4938
  async function fetchLatestVersion() {
4939
4939
  const host2 = getHost();
4940
4940
  if (!host2) return null;
@@ -5857,7 +5857,7 @@ function handleError(err) {
5857
5857
  }
5858
5858
 
5859
5859
  // src/bin/agt.ts
5860
- var cliVersion2 = true ? "0.27.128" : "dev";
5860
+ var cliVersion2 = true ? "0.27.129" : "dev";
5861
5861
  var program = new Command();
5862
5862
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
5863
5863
  program.hook("preAction", async (thisCommand, actionCommand) => {
@@ -7653,4 +7653,4 @@ export {
7653
7653
  managerInstallSystemUnitCommand,
7654
7654
  managerUninstallSystemUnitCommand
7655
7655
  };
7656
- //# sourceMappingURL=chunk-TXZLQK3S.js.map
7656
+ //# sourceMappingURL=chunk-A64MR5QP.js.map
@@ -17,7 +17,7 @@ import {
17
17
  provisionStopHook,
18
18
  requireHost,
19
19
  safeWriteJsonAtomic
20
- } from "../chunk-TXZLQK3S.js";
20
+ } from "../chunk-A64MR5QP.js";
21
21
  import {
22
22
  getProjectDir as getProjectDir2,
23
23
  getReadyTasks,
@@ -502,14 +502,14 @@ function reapMissingMcpSessions(args) {
502
502
  if (!missingRaw.includes(key)) liveKeys.add(key);
503
503
  }
504
504
  for (const key of liveKeys) {
505
- const state6 = presenceReaperState.get(stateKey(codeName, key));
506
- if (state6) {
507
- state6.attempts = 0;
508
- state6.lastSeenLiveAt = now();
509
- state6.lastAttemptedSessionStartedAt = null;
510
- state6.gaveUpLogged = false;
511
- state6.firstMissingAt = null;
512
- state6.quarantineLogged = false;
505
+ const state7 = presenceReaperState.get(stateKey(codeName, key));
506
+ if (state7) {
507
+ state7.attempts = 0;
508
+ state7.lastSeenLiveAt = now();
509
+ state7.lastAttemptedSessionStartedAt = null;
510
+ state7.gaveUpLogged = false;
511
+ state7.firstMissingAt = null;
512
+ state7.quarantineLogged = false;
513
513
  }
514
514
  }
515
515
  if (missing.length === 0) {
@@ -527,7 +527,7 @@ function reapMissingMcpSessions(args) {
527
527
  const wouldQuarantine = [];
528
528
  for (const key of missing) {
529
529
  const sk = stateKey(codeName, key);
530
- const state6 = presenceReaperState.get(sk) ?? {
530
+ const state7 = presenceReaperState.get(sk) ?? {
531
531
  attempts: 0,
532
532
  lastSeenLiveAt: null,
533
533
  lastAttemptedSessionStartedAt: null,
@@ -535,19 +535,19 @@ function reapMissingMcpSessions(args) {
535
535
  firstMissingAt: null,
536
536
  quarantineLogged: false
537
537
  };
538
- if (state6.firstMissingAt === null) state6.firstMissingAt = nowMs;
539
- if (state6.lastAttemptedSessionStartedAt !== sessionStartedAt) {
540
- state6.attempts += 1;
541
- state6.lastAttemptedSessionStartedAt = sessionStartedAt;
538
+ if (state7.firstMissingAt === null) state7.firstMissingAt = nowMs;
539
+ if (state7.lastAttemptedSessionStartedAt !== sessionStartedAt) {
540
+ state7.attempts += 1;
541
+ state7.lastAttemptedSessionStartedAt = sessionStartedAt;
542
542
  }
543
- presenceReaperState.set(sk, state6);
544
- if (state6.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {
543
+ presenceReaperState.set(sk, state7);
544
+ if (state7.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {
545
545
  givenUp.push(key);
546
- if (!state6.gaveUpLogged) {
546
+ if (!state7.gaveUpLogged) {
547
547
  log2(
548
548
  `[mcp-presence-reaper] giving up on '${codeName}:${key}' after ${MAX_PRESENCE_RESTART_ATTEMPTS} consecutive failed restarts \u2014 declared but never recovers (likely orphaned config; see ENG-5279)`
549
549
  );
550
- state6.gaveUpLogged = true;
550
+ state7.gaveUpLogged = true;
551
551
  if (onGiveUp) {
552
552
  try {
553
553
  onGiveUp(codeName, key);
@@ -558,10 +558,10 @@ function reapMissingMcpSessions(args) {
558
558
  }
559
559
  }
560
560
  }
561
- if (quarantineMode !== "off" && !state6.quarantineLogged && classifyKey(key) === "optional") {
562
- const dwellElapsed = quarantineDwellMs <= 0 || state6.firstMissingAt !== null && nowMs - state6.firstMissingAt >= quarantineDwellMs;
561
+ if (quarantineMode !== "off" && !state7.quarantineLogged && classifyKey(key) === "optional") {
562
+ const dwellElapsed = quarantineDwellMs <= 0 || state7.firstMissingAt !== null && nowMs - state7.firstMissingAt >= quarantineDwellMs;
563
563
  if (dwellElapsed) {
564
- state6.quarantineLogged = true;
564
+ state7.quarantineLogged = true;
565
565
  if (quarantineMode === "enforce") {
566
566
  quarantined.push(key);
567
567
  log2(
@@ -2101,14 +2101,173 @@ function readRecentTurns(dir, nowMs) {
2101
2101
  return turns;
2102
2102
  }
2103
2103
 
2104
+ // src/lib/memory-extractor.ts
2105
+ var MIN_CHECK_INTERVAL_MS4 = 10 * 6e4;
2106
+ var WINDOW_PAD_MS2 = 5 * 6e4;
2107
+ var VALID_MEMORY_TYPES = /* @__PURE__ */ new Set(["user", "feedback", "project", "reference"]);
2108
+ var SECRET_PATTERNS = [
2109
+ // labelled secrets: `api_key=...`, `password: ...`, `bearer ...`
2110
+ /\b[\w-]*(?:secret|password|passwd|token|api[_-]?key|bearer)[\w-]*\s*[=:]\s*\S+/gi,
2111
+ // common credential prefixes (stripe, github, slack, augmented host keys, …)
2112
+ /\b(?:sk|pk|rk|tlk|ghp|gho|ghs|xox[baprs])[-_][A-Za-z0-9]{12,}\b/g,
2113
+ // long hex blobs (hashes / hex-encoded keys)
2114
+ /\b[A-Fa-f0-9]{32,}\b/g,
2115
+ // long base64 blobs (tokens / encoded secrets)
2116
+ /\b[A-Za-z0-9+/]{40,}={0,2}\b/g,
2117
+ // email addresses
2118
+ /\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g
2119
+ ];
2120
+ function scrubSensitive(text) {
2121
+ let out = text;
2122
+ for (const re of SECRET_PATTERNS) out = out.replace(re, "[redacted]");
2123
+ return out;
2124
+ }
2125
+ var state4 = /* @__PURE__ */ new Map();
2126
+ function buildExtractionPrompt(channel, transcript) {
2127
+ return `You are extracting durable, reusable memories from one completed ${channel} conversation between an AI agent and an end-user. A good memory is a stable preference, fact, correction, or recurring work context that will help the agent in FUTURE conversations \u2014 not a one-off task detail or pleasantry.
2128
+
2129
+ Conversation transcript:
2130
+ """
2131
+ ${transcript}
2132
+ """
2133
+
2134
+ Extract 0-5 candidate memories. For each:
2135
+ - name: short descriptive title (max 50 chars)
2136
+ - content: the memory itself, self-contained (max 200 chars). Do NOT include secrets or sensitive personal data.
2137
+ - type: "user" (a person's stable preference), "feedback" (a correction to how the agent should work), "project" (durable work context), or "reference" (a pointer to an external resource)
2138
+ - confidence: 0.0-1.0 \u2014 how sure you are this is worth remembering long-term
2139
+ - rationale: why this should persist (max 100 chars)
2140
+
2141
+ If there is nothing durable worth remembering, return an empty array. Do NOT invent memories to fill the list.
2142
+
2143
+ Respond with ONLY a JSON array, no other text:
2144
+ [{"name":"...","content":"...","type":"...","confidence":0.8,"rationale":"..."}]`;
2145
+ }
2146
+ function parseCandidates(raw) {
2147
+ const match = raw.match(/\[[\s\S]*\]/);
2148
+ if (!match) return null;
2149
+ let arr;
2150
+ try {
2151
+ arr = JSON.parse(match[0]);
2152
+ } catch {
2153
+ return null;
2154
+ }
2155
+ if (!Array.isArray(arr)) return null;
2156
+ const out = [];
2157
+ for (const entry of arr) {
2158
+ if (!entry || typeof entry !== "object") continue;
2159
+ const c = entry;
2160
+ const name = typeof c.name === "string" ? c.name.trim() : "";
2161
+ if (!name) continue;
2162
+ if (typeof c.type !== "string" || !VALID_MEMORY_TYPES.has(c.type)) continue;
2163
+ const confidence = typeof c.confidence === "number" && Number.isFinite(c.confidence) ? Math.max(0, Math.min(1, c.confidence)) : 0;
2164
+ if (confidence === 0) continue;
2165
+ out.push({
2166
+ name: name.slice(0, 80),
2167
+ content: (typeof c.content === "string" ? c.content : "").trim().slice(0, 500),
2168
+ type: c.type,
2169
+ confidence,
2170
+ rationale: (typeof c.rationale === "string" ? c.rationale : "").trim().slice(0, 200)
2171
+ });
2172
+ }
2173
+ return out;
2174
+ }
2175
+ async function maybeExtractMemories(args) {
2176
+ const { api: api2, backend, codeName, agentId, log: log2 } = args;
2177
+ const now = args.now ?? /* @__PURE__ */ new Date();
2178
+ const nowMs = now.getTime();
2179
+ const existing = state4.get(codeName);
2180
+ if (existing && nowMs - existing.lastCheckedAt < MIN_CHECK_INTERVAL_MS4) {
2181
+ return;
2182
+ }
2183
+ state4.set(codeName, { lastCheckedAt: nowMs });
2184
+ let pending;
2185
+ try {
2186
+ const resp = await api2.get(
2187
+ `/host/memories/pending-extraction?agent_id=${encodeURIComponent(agentId)}`
2188
+ );
2189
+ pending = resp?.conversations ?? [];
2190
+ } catch (err) {
2191
+ log2(`[memory-extract] ${codeName}: pending fetch failed: ${err.message}`);
2192
+ return;
2193
+ }
2194
+ if (pending.length === 0) return;
2195
+ const dir = args.transcriptDir ?? sessionTranscriptDir(getProjectDir(codeName));
2196
+ const allTurns = readRecentTurns(dir, nowMs);
2197
+ if (allTurns.length === 0) {
2198
+ for (const conv of pending) {
2199
+ await reportSkip2(api2, agentId, conv.conversation_id, log2, codeName);
2200
+ }
2201
+ return;
2202
+ }
2203
+ for (const conv of pending) {
2204
+ const tokens = channelRefTokens(conv.channel_ref);
2205
+ const windowStart = Date.parse(conv.started_at) - WINDOW_PAD_MS2;
2206
+ const windowEnd = Date.parse(conv.last_message_at) + WINDOW_PAD_MS2;
2207
+ const turns = reconstructConversation(allTurns, tokens, windowStart, windowEnd);
2208
+ if (turns.length === 0) {
2209
+ await reportSkip2(api2, agentId, conv.conversation_id, log2, codeName);
2210
+ continue;
2211
+ }
2212
+ const transcript = renderTranscript(turns);
2213
+ if (!transcript.trim()) {
2214
+ await reportSkip2(api2, agentId, conv.conversation_id, log2, codeName);
2215
+ continue;
2216
+ }
2217
+ let candidates;
2218
+ try {
2219
+ const out = await backend.run(buildExtractionPrompt(conv.channel, transcript));
2220
+ candidates = parseCandidates(out);
2221
+ } catch (err) {
2222
+ log2(`[memory-extract] ${codeName}: extraction failed: ${err.message}`);
2223
+ continue;
2224
+ }
2225
+ if (candidates === null) {
2226
+ log2(`[memory-extract] ${codeName}: unparseable output for ${conv.conversation_id.slice(0, 8)} \u2014 will retry`);
2227
+ continue;
2228
+ }
2229
+ const scrubbed = candidates.map((c) => ({
2230
+ ...c,
2231
+ content: scrubSensitive(c.content),
2232
+ rationale: scrubSensitive(c.rationale)
2233
+ }));
2234
+ try {
2235
+ const res = await api2.post(
2236
+ "/host/memories/candidates",
2237
+ {
2238
+ agent_id: agentId,
2239
+ conversation_id: conv.conversation_id,
2240
+ candidates: scrubbed
2241
+ }
2242
+ );
2243
+ log2(
2244
+ `[memory-extract] ${codeName}: ${conv.conversation_id.slice(0, 8)} \u2192 ${candidates.length} candidate(s), ${res?.promoted ?? 0} promoted`
2245
+ );
2246
+ } catch (err) {
2247
+ log2(`[memory-extract] ${codeName}: report failed: ${err.message}`);
2248
+ }
2249
+ }
2250
+ }
2251
+ async function reportSkip2(api2, agentId, conversationId, log2, codeName) {
2252
+ try {
2253
+ await api2.post("/host/memories/candidates", {
2254
+ agent_id: agentId,
2255
+ conversation_id: conversationId,
2256
+ skipped: true
2257
+ });
2258
+ } catch (err) {
2259
+ log2(`[memory-extract] ${codeName}: skip report failed: ${err.message}`);
2260
+ }
2261
+ }
2262
+
2104
2263
  // src/lib/activity-cache-monitor.ts
2105
2264
  import { existsSync as existsSync2, readFileSync as readFileSync6 } from "fs";
2106
2265
  import { homedir } from "os";
2107
2266
  import { join as join4 } from "path";
2108
- var MIN_CHECK_INTERVAL_MS4 = 6e4;
2267
+ var MIN_CHECK_INTERVAL_MS5 = 6e4;
2109
2268
  var STATS_CACHE_PATH = join4(homedir(), ".claude", "stats-cache.json");
2110
2269
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
2111
- var state4 = { lastObservedDate: null, lastCheckedAt: 0 };
2270
+ var state5 = { lastObservedDate: null, lastCheckedAt: 0 };
2112
2271
  function selectNewDailyRows(raw, lastObservedDate) {
2113
2272
  let parsed;
2114
2273
  try {
@@ -2147,8 +2306,8 @@ async function maybeReportActivityCache(args) {
2147
2306
  const { api: api2, log: log2 } = args;
2148
2307
  const now = args.now ?? /* @__PURE__ */ new Date();
2149
2308
  const nowMs = now.getTime();
2150
- if (nowMs - state4.lastCheckedAt < MIN_CHECK_INTERVAL_MS4) return;
2151
- state4.lastCheckedAt = nowMs;
2309
+ if (nowMs - state5.lastCheckedAt < MIN_CHECK_INTERVAL_MS5) return;
2310
+ state5.lastCheckedAt = nowMs;
2152
2311
  if (!existsSync2(STATS_CACHE_PATH)) {
2153
2312
  return;
2154
2313
  }
@@ -2159,12 +2318,12 @@ async function maybeReportActivityCache(args) {
2159
2318
  log2(`[activity-cache] readFileSync failed: ${err.message}`);
2160
2319
  return;
2161
2320
  }
2162
- const rows = selectNewDailyRows(raw, state4.lastObservedDate);
2321
+ const rows = selectNewDailyRows(raw, state5.lastObservedDate);
2163
2322
  if (rows.length === 0) return;
2164
2323
  for (const row of rows) {
2165
2324
  try {
2166
2325
  await api2.post("/host/activity-observations", row);
2167
- state4.lastObservedDate = row.date;
2326
+ state5.lastObservedDate = row.date;
2168
2327
  } catch (err) {
2169
2328
  log2(
2170
2329
  `[activity-cache] POST /host/activity-observations failed for date=${row.date}: ${err.message}`
@@ -3278,10 +3437,10 @@ function recordWedgeForCards(states, inProgressCardIds, nowMs, config2) {
3278
3437
  function pruneCardStates(states, liveInProgressCardIds, nowMs, config2) {
3279
3438
  const live = liveInProgressCardIds instanceof Set ? liveInProgressCardIds : new Set(liveInProgressCardIds);
3280
3439
  const next = /* @__PURE__ */ new Map();
3281
- for (const [id, state6] of states) {
3440
+ for (const [id, state7] of states) {
3282
3441
  if (!live.has(id)) continue;
3283
- if (nowMs - state6.lastWedgeAtMs > config2.cooldownMs) continue;
3284
- next.set(id, state6);
3442
+ if (nowMs - state7.lastWedgeAtMs > config2.cooldownMs) continue;
3443
+ next.set(id, state7);
3285
3444
  }
3286
3445
  return next;
3287
3446
  }
@@ -3971,8 +4130,8 @@ var KNOWN_SAFE_TAIL_SIGNATURES = /* @__PURE__ */ new Set(["session_id_in_use"]);
3971
4130
  function shouldSkipRevokedCleanup(previousKnownStatus) {
3972
4131
  return previousKnownStatus === "revoked";
3973
4132
  }
3974
- function hasRevokedResiduals(state6) {
3975
- return state6.gatewayRunning || state6.portAllocated || state6.provisionDirExists;
4133
+ function hasRevokedResiduals(state7) {
4134
+ return state7.gatewayRunning || state7.portAllocated || state7.provisionDirExists;
3976
4135
  }
3977
4136
  var pendingSessionRestarts = /* @__PURE__ */ new Map();
3978
4137
  var pendingRestartVerifications = /* @__PURE__ */ new Map();
@@ -4077,12 +4236,12 @@ function maybeAutoResume(agent) {
4077
4236
  autoResumeMarkers.set(codeName, { trippedAt, autoResumedAt: Date.now() });
4078
4237
  restartBreaker.clear(codeName);
4079
4238
  reportedTrips.delete(codeName);
4080
- state5 = {
4081
- ...state5,
4239
+ state6 = {
4240
+ ...state6,
4082
4241
  circuitBreakerTrips: restartBreaker.serialize(),
4083
4242
  circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
4084
4243
  };
4085
- send({ type: "state-update", state: state5 });
4244
+ send({ type: "state-update", state: state6 });
4086
4245
  log(`[auto-resume] agent=${codeName} resumed \u2014 re-trip within backoff window will stay paused (ENG-6088)`);
4087
4246
  } else {
4088
4247
  autoResumeStandDowns.add(`${codeName}:${trippedAt}`);
@@ -4452,7 +4611,7 @@ function __setAgentChannelTokensForTest(codeName, tokens) {
4452
4611
  }
4453
4612
  var activeChannels = /* @__PURE__ */ new Map();
4454
4613
  var gatewaysStartedThisCycle = /* @__PURE__ */ new Set();
4455
- var state5 = {
4614
+ var state6 = {
4456
4615
  pid: process.pid,
4457
4616
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4458
4617
  lastPollAt: null,
@@ -4494,7 +4653,7 @@ var cachedMaintenanceWindow = null;
4494
4653
  var lastVersionCheckAt = 0;
4495
4654
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
4496
4655
  var lastResponsivenessProbeAt = 0;
4497
- var agtCliVersion = true ? "0.27.128" : "dev";
4656
+ var agtCliVersion = true ? "0.27.129" : "dev";
4498
4657
  function resolveBrewPath(execFileSync4) {
4499
4658
  try {
4500
4659
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -5146,6 +5305,10 @@ async function runEvalClaude(prompt, model) {
5146
5305
  });
5147
5306
  return stdout;
5148
5307
  }
5308
+ function memoryExtractionEnabled() {
5309
+ const v = (process.env["AGT_MEMORY_EXTRACTION_ENABLED"] ?? "").trim().toLowerCase();
5310
+ return v === "true" || v === "1" || v === "yes";
5311
+ }
5149
5312
  var conversationEvalBackend = null;
5150
5313
  function resolveConversationEvalBackend() {
5151
5314
  if (conversationEvalBackend) return conversationEvalBackend;
@@ -5472,10 +5635,10 @@ function isGatewayHung(codeName) {
5472
5635
  if (!Array.isArray(jobs)) return false;
5473
5636
  const now = Date.now();
5474
5637
  for (const job of jobs) {
5475
- const state6 = job.state;
5476
- if (!state6) continue;
5477
- const runStartedAt = state6.runStartedAtMs;
5478
- const isRunning = state6.status === "running" || state6.running === true;
5638
+ const state7 = job.state;
5639
+ if (!state7) continue;
5640
+ const runStartedAt = state7.runStartedAtMs;
5641
+ const isRunning = state7.status === "running" || state7.running === true;
5479
5642
  if (isRunning && runStartedAt && now - runStartedAt > GATEWAY_HUNG_TIMEOUT_MS) {
5480
5643
  return true;
5481
5644
  }
@@ -5676,7 +5839,7 @@ async function pollCycle() {
5676
5839
  const now = Date.now();
5677
5840
  if (now - lastVersionCheckAt > VERSION_CHECK_INTERVAL_MS) {
5678
5841
  try {
5679
- const firstAgent = state5.agents[0];
5842
+ const firstAgent = state6.agents[0];
5680
5843
  const versionAdapter = firstAgent ? resolveAgentFramework(firstAgent.codeName) : getFramework("openclaw");
5681
5844
  if (versionAdapter.getVersion) {
5682
5845
  cachedFrameworkVersion = await versionAdapter.getVersion();
@@ -5720,7 +5883,7 @@ async function pollCycle() {
5720
5883
  const errId = createHash3("sha256").update(errText).digest("hex").slice(0, 12);
5721
5884
  log(`Claude auth detection failed (error_id=${errId})`);
5722
5885
  }
5723
- const hostHasClaudeCode = state5.agents.some(
5886
+ const hostHasClaudeCode = state6.agents.some(
5724
5887
  (a) => agentFrameworkCache.get(a.codeName) === "claude-code"
5725
5888
  );
5726
5889
  if (hostHasClaudeCode) {
@@ -5933,7 +6096,7 @@ async function pollCycle() {
5933
6096
  for (const agent of agents) {
5934
6097
  const requested = agent.restart_requested_at ?? null;
5935
6098
  if (!requested) continue;
5936
- const prev = state5.agents.find((a) => a.agentId === agent.agent_id);
6099
+ const prev = state6.agents.find((a) => a.agentId === agent.agent_id);
5937
6100
  const lastProcessed = prev?.lastRestartProcessedAt ?? null;
5938
6101
  if (lastProcessed && Date.parse(lastProcessed) >= Date.parse(requested)) continue;
5939
6102
  log(`[restart] Dashboard requested restart for '${agent.code_name}' at ${requested}`);
@@ -5956,7 +6119,7 @@ async function pollCycle() {
5956
6119
  await processAgent(agent, agentStates);
5957
6120
  } catch (err) {
5958
6121
  log(`Error processing agent '${agent.code_name}': ${err.message}`);
5959
- const existing = state5.agents.find((a) => a.agentId === agent.agent_id);
6122
+ const existing = state6.agents.find((a) => a.agentId === agent.agent_id);
5960
6123
  if (existing) {
5961
6124
  agentStates.push(existing);
5962
6125
  } else {
@@ -5982,12 +6145,12 @@ async function pollCycle() {
5982
6145
  void maybeReportActivityCache({ api, log });
5983
6146
  const restartAckStateChanged = applyRestartAcks({
5984
6147
  agentStates,
5985
- priorAgents: state5.agents,
6148
+ priorAgents: state6.agents,
5986
6149
  restartAcks
5987
6150
  });
5988
6151
  if (restartAckStateChanged) {
5989
6152
  try {
5990
- const ackedState = { ...state5, agents: agentStates };
6153
+ const ackedState = { ...state6, agents: agentStates };
5991
6154
  atomicWriteFileSync(getStateFile(), JSON.stringify(ackedState, null, 2));
5992
6155
  } catch (err) {
5993
6156
  log(`[restart] failed to persist ack immediately: ${err.message}`);
@@ -6005,7 +6168,7 @@ async function pollCycle() {
6005
6168
  } catch {
6006
6169
  }
6007
6170
  const currentIds = new Set(agents.map((a) => a.agent_id));
6008
- for (const prev of state5.agents) {
6171
+ for (const prev of state6.agents) {
6009
6172
  if (!currentIds.has(prev.agentId)) {
6010
6173
  log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
6011
6174
  const adapter = resolveAgentFramework(prev.codeName);
@@ -6146,10 +6309,10 @@ async function pollCycle() {
6146
6309
  }
6147
6310
  } catch {
6148
6311
  }
6149
- state5 = {
6150
- ...state5,
6312
+ state6 = {
6313
+ ...state6,
6151
6314
  lastPollAt: (/* @__PURE__ */ new Date()).toISOString(),
6152
- pollCount: state5.pollCount + 1,
6315
+ pollCount: state6.pollCount + 1,
6153
6316
  agents: agentStates,
6154
6317
  // ENG-5441: serialise trip state on every poll so manager restarts
6155
6318
  // never silently clear a tripped breaker. Cheap — only tripped
@@ -6163,9 +6326,9 @@ async function pollCycle() {
6163
6326
  consecutivePollFailures = 0;
6164
6327
  }
6165
6328
  verifyPendingRestarts(Date.now());
6166
- send({ type: "state-update", state: state5 });
6329
+ send({ type: "state-update", state: state6 });
6167
6330
  } catch (err) {
6168
- state5.errorCount++;
6331
+ state6.errorCount++;
6169
6332
  const message = err.message;
6170
6333
  log(`Poll error: ${message}`);
6171
6334
  send({ type: "error", message });
@@ -6216,6 +6379,15 @@ async function processAgent(agent, agentStates) {
6216
6379
  agentId: agent.agent_id,
6217
6380
  log
6218
6381
  });
6382
+ if (memoryExtractionEnabled()) {
6383
+ void maybeExtractMemories({
6384
+ api,
6385
+ backend: resolveConversationEvalBackend(),
6386
+ codeName: agent.code_name,
6387
+ agentId: agent.agent_id,
6388
+ log
6389
+ });
6390
+ }
6219
6391
  }
6220
6392
  const now = (/* @__PURE__ */ new Date()).toISOString();
6221
6393
  const adapter = resolveAgentFramework(agent.code_name);
@@ -6340,11 +6512,11 @@ async function processAgent(agent, agentStates) {
6340
6512
  const marker = autoResumeMarkers.get(agent.code_name);
6341
6513
  const isSelfResume = marker !== void 0 && Date.now() - marker.autoResumedAt < AUTO_RESUME_SELF_WINDOW_MS;
6342
6514
  if (!isSelfResume && autoResumeMarkers.delete(agent.code_name)) {
6343
- state5 = {
6344
- ...state5,
6515
+ state6 = {
6516
+ ...state6,
6345
6517
  circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
6346
6518
  };
6347
- send({ type: "state-update", state: state5 });
6519
+ send({ type: "state-update", state: state6 });
6348
6520
  log(`[auto-resume] Cleared auto-resume marker for '${agent.code_name}' on operator resume \u2014 credit re-armed (ENG-6088)`);
6349
6521
  }
6350
6522
  }
@@ -6375,7 +6547,7 @@ async function processAgent(agent, agentStates) {
6375
6547
  });
6376
6548
  } catch (err) {
6377
6549
  log(`Refresh failed for '${agent.code_name}': ${err.message}`);
6378
- const existing = state5.agents.find((a) => a.agentId === agent.agent_id);
6550
+ const existing = state6.agents.find((a) => a.agentId === agent.agent_id);
6379
6551
  agentStates.push(existing ?? {
6380
6552
  agentId: agent.agent_id,
6381
6553
  codeName: agent.code_name,
@@ -6439,7 +6611,7 @@ async function processAgent(agent, agentStates) {
6439
6611
  const charterVersion = refreshData.charter.version;
6440
6612
  const toolsVersion = refreshData.tools.version;
6441
6613
  const known = agentState.knownVersions.get(agent.agent_id);
6442
- let lastProvisionAt = state5.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
6614
+ let lastProvisionAt = state6.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
6443
6615
  const quarantinedChannels = channelQuarantineStore().getQuarantinedKeys(agent.code_name);
6444
6616
  const currentChannelIds = setWithout(
6445
6617
  launchableChannelIds(refreshData.channel_configs),
@@ -7003,7 +7175,7 @@ async function processAgent(agent, agentStates) {
7003
7175
  log(`Failed to provision direct-chat channel for '${agent.code_name}': ${err.message}`);
7004
7176
  }
7005
7177
  }
7006
- let lastSecretsProvisionAt = state5.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;
7178
+ let lastSecretsProvisionAt = state6.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;
7007
7179
  let secretsHash = agentState.knownSecretsHashes.get(agent.agent_id) ?? null;
7008
7180
  try {
7009
7181
  const secretsData = await api.post("/host/secrets", { agent_id: agent.agent_id });
@@ -7996,19 +8168,19 @@ function clearStaleCronRunState(jobsPath) {
7996
8168
  let changed = false;
7997
8169
  const now = Date.now();
7998
8170
  for (const job of jobs) {
7999
- const state6 = job.state;
8000
- if (!state6) continue;
8001
- const runStartedAt = state6.runningAtMs ?? state6.runStartedAtMs;
8002
- const isRunning = state6.running === true || state6.status === "running";
8171
+ const state7 = job.state;
8172
+ if (!state7) continue;
8173
+ const runStartedAt = state7.runningAtMs ?? state7.runStartedAtMs;
8174
+ const isRunning = state7.running === true || state7.status === "running";
8003
8175
  if (isRunning && runStartedAt && now - runStartedAt > STALE_RUN_TIMEOUT_MS) {
8004
- state6.running = false;
8005
- delete state6.status;
8006
- delete state6.runStartedAtMs;
8007
- delete state6.currentRunId;
8176
+ state7.running = false;
8177
+ delete state7.status;
8178
+ delete state7.runStartedAtMs;
8179
+ delete state7.currentRunId;
8008
8180
  changed = true;
8009
8181
  log(`Cleared stale running state for cron job '${job.name}' (stuck for ${Math.round((now - runStartedAt) / 6e4)}min)`);
8010
8182
  } else if (isRunning && !runStartedAt) {
8011
- state6.running = false;
8183
+ state7.running = false;
8012
8184
  changed = true;
8013
8185
  log(`Cleared stale running state for cron job '${job.name}' (no start timestamp)`);
8014
8186
  }
@@ -8095,16 +8267,16 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
8095
8267
  const prevHash = agentState.knownTasksHashes.get(agent.agent_id);
8096
8268
  if (combinedHash !== prevHash) {
8097
8269
  const taskInputs = tasks.map((t) => buildSchedulerTaskInput(t));
8098
- const state7 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
8099
- claudeSchedulerStates.set(codeName, state7);
8270
+ const state8 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
8271
+ claudeSchedulerStates.set(codeName, state8);
8100
8272
  agentState.knownTasksHashes.set(agent.agent_id, combinedHash);
8101
8273
  log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
8102
8274
  }
8103
8275
  if (!claudeSchedulerStates.has(codeName)) {
8104
8276
  claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
8105
8277
  }
8106
- const state6 = claudeSchedulerStates.get(codeName);
8107
- const ready = getReadyTasks(state6, inFlightClaudeTasks);
8278
+ const state7 = claudeSchedulerStates.get(codeName);
8279
+ const ready = getReadyTasks(state7, inFlightClaudeTasks);
8108
8280
  if (ready.length === 0) return;
8109
8281
  for (const task of ready) {
8110
8282
  if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) break;
@@ -8264,8 +8436,8 @@ async function deliverScheduledCardResult(codeName, agentId, cardId) {
8264
8436
  markScheduledCardDeliveryComplete(cardId);
8265
8437
  return "terminal";
8266
8438
  }
8267
- const state6 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
8268
- const task = state6.tasks[card.source_ref];
8439
+ const state7 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
8440
+ const task = state7.tasks[card.source_ref];
8269
8441
  if (!task) {
8270
8442
  log(`[scheduled-kanban] delivery: no scheduler task for source_ref=${card.source_ref} on '${codeName}' \u2014 skipping`);
8271
8443
  markScheduledCardDeliveryComplete(cardId);
@@ -8828,9 +9000,9 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash3("sha256").
8828
9000
  } else if (!claudeSchedulerStates.has(codeName)) {
8829
9001
  claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
8830
9002
  }
8831
- const state6 = claudeSchedulerStates.get(codeName);
8832
- if (state6) {
8833
- const ready = getReadyTasks(state6, inFlightClaudeTasks);
9003
+ const state7 = claudeSchedulerStates.get(codeName);
9004
+ if (state7) {
9005
+ const ready = getReadyTasks(state7, inFlightClaudeTasks);
8834
9006
  if (ready.length > 0) {
8835
9007
  log(`[persistent-session] ${ready.length} ready task(s) for '${codeName}': ${ready.map((t) => `${t.name}(next=${t.nextFireAt ? new Date(t.nextFireAt).toISOString() : "null"})`).join(", ")}`);
8836
9008
  }
@@ -10928,8 +11100,8 @@ function startManager(opts) {
10928
11100
  const raw = readFileSync9(stateFile, "utf-8");
10929
11101
  const parsed = JSON.parse(raw);
10930
11102
  if (Array.isArray(parsed.agents)) {
10931
- state5.agents = parsed.agents;
10932
- log(`[startup] rehydrated ${state5.agents.length} agent state(s) from ${stateFile}`);
11103
+ state6.agents = parsed.agents;
11104
+ log(`[startup] rehydrated ${state6.agents.length} agent state(s) from ${stateFile}`);
10933
11105
  }
10934
11106
  if (parsed.circuitBreakerTrips && typeof parsed.circuitBreakerTrips === "object") {
10935
11107
  restartBreaker.hydrate(parsed.circuitBreakerTrips);