@integrity-labs/agt-cli 0.27.127 → 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 +3 -3
- package/dist/{chunk-RKM5IRRT.js → chunk-A64MR5QP.js} +10 -3
- package/dist/chunk-A64MR5QP.js.map +1 -0
- package/dist/lib/manager-worker.js +251 -79
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/mcp/index.js +48 -1
- package/package.json +1 -1
- package/dist/chunk-RKM5IRRT.js.map +0 -1
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
provisionStopHook,
|
|
18
18
|
requireHost,
|
|
19
19
|
safeWriteJsonAtomic
|
|
20
|
-
} from "../chunk-
|
|
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
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
|
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 (
|
|
539
|
-
if (
|
|
540
|
-
|
|
541
|
-
|
|
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,
|
|
544
|
-
if (
|
|
543
|
+
presenceReaperState.set(sk, state7);
|
|
544
|
+
if (state7.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {
|
|
545
545
|
givenUp.push(key);
|
|
546
|
-
if (!
|
|
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
|
-
|
|
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" && !
|
|
562
|
-
const dwellElapsed = quarantineDwellMs <= 0 ||
|
|
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
|
-
|
|
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
|
|
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
|
|
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 -
|
|
2151
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
3440
|
+
for (const [id, state7] of states) {
|
|
3282
3441
|
if (!live.has(id)) continue;
|
|
3283
|
-
if (nowMs -
|
|
3284
|
-
next.set(id,
|
|
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(
|
|
3975
|
-
return
|
|
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
|
-
|
|
4081
|
-
...
|
|
4239
|
+
state6 = {
|
|
4240
|
+
...state6,
|
|
4082
4241
|
circuitBreakerTrips: restartBreaker.serialize(),
|
|
4083
4242
|
circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
|
|
4084
4243
|
};
|
|
4085
|
-
send({ type: "state-update", state:
|
|
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
|
|
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.
|
|
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
|
|
5476
|
-
if (!
|
|
5477
|
-
const runStartedAt =
|
|
5478
|
-
const isRunning =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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:
|
|
6148
|
+
priorAgents: state6.agents,
|
|
5986
6149
|
restartAcks
|
|
5987
6150
|
});
|
|
5988
6151
|
if (restartAckStateChanged) {
|
|
5989
6152
|
try {
|
|
5990
|
-
const ackedState = { ...
|
|
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
|
|
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
|
-
|
|
6150
|
-
...
|
|
6312
|
+
state6 = {
|
|
6313
|
+
...state6,
|
|
6151
6314
|
lastPollAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6152
|
-
pollCount:
|
|
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:
|
|
6329
|
+
send({ type: "state-update", state: state6 });
|
|
6167
6330
|
} catch (err) {
|
|
6168
|
-
|
|
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
|
-
|
|
6344
|
-
...
|
|
6515
|
+
state6 = {
|
|
6516
|
+
...state6,
|
|
6345
6517
|
circuitBreakerAutoResumes: Object.fromEntries(autoResumeMarkers.entries())
|
|
6346
6518
|
};
|
|
6347
|
-
send({ type: "state-update", state:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
8000
|
-
if (!
|
|
8001
|
-
const runStartedAt =
|
|
8002
|
-
const isRunning =
|
|
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
|
-
|
|
8005
|
-
delete
|
|
8006
|
-
delete
|
|
8007
|
-
delete
|
|
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
|
-
|
|
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
|
|
8099
|
-
claudeSchedulerStates.set(codeName,
|
|
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
|
|
8107
|
-
const ready = getReadyTasks(
|
|
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
|
|
8268
|
-
const task =
|
|
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
|
|
8832
|
-
if (
|
|
8833
|
-
const ready = getReadyTasks(
|
|
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
|
-
|
|
10932
|
-
log(`[startup] rehydrated ${
|
|
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);
|