@integrity-labs/agt-cli 0.20.1 → 0.20.5
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-Y3E5EFCM.js → chunk-LE6K3LZ6.js} +95 -13
- package/dist/chunk-LE6K3LZ6.js.map +1 -0
- package/dist/lib/manager-worker.js +169 -11
- package/dist/lib/manager-worker.js.map +1 -1
- package/mcp/slack-channel.js +175 -49
- package/mcp/telegram-channel.js +128 -41
- package/package.json +1 -1
- package/dist/chunk-Y3E5EFCM.js.map +0 -1
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveChannels,
|
|
23
23
|
resolveDmTarget,
|
|
24
24
|
wrapScheduledTaskPrompt
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-LE6K3LZ6.js";
|
|
26
26
|
import {
|
|
27
27
|
findTaskByTemplate,
|
|
28
28
|
getProjectDir,
|
|
@@ -301,10 +301,14 @@ import { execFileSync as execFileSync2 } from "child_process";
|
|
|
301
301
|
var DEFAULT_COLD_START_GRACE_MS = 9e4;
|
|
302
302
|
function findMissingMcpServers(args) {
|
|
303
303
|
const { rows, codeName, mcpJson } = args;
|
|
304
|
-
const
|
|
304
|
+
const servers = mcpJson?.mcpServers ?? {};
|
|
305
|
+
const declared = Object.keys(servers);
|
|
305
306
|
if (declared.length === 0) return [];
|
|
306
307
|
const missing = [];
|
|
307
308
|
for (const key of declared) {
|
|
309
|
+
const entry = servers[key];
|
|
310
|
+
const isStdio = typeof entry?.command === "string";
|
|
311
|
+
if (!isStdio) continue;
|
|
308
312
|
const live = findMcpChildrenForAgent({
|
|
309
313
|
rows,
|
|
310
314
|
codeName,
|
|
@@ -1545,6 +1549,56 @@ async function sendError(flag, opts, text) {
|
|
|
1545
1549
|
await sendAck(flag, opts, text);
|
|
1546
1550
|
}
|
|
1547
1551
|
|
|
1552
|
+
// src/lib/persistent-session-stuck-tracker.ts
|
|
1553
|
+
var DEFAULT_STUCK_WARN_THRESHOLD = 3;
|
|
1554
|
+
var PersistentSessionStuckTracker = class {
|
|
1555
|
+
counts = /* @__PURE__ */ new Map();
|
|
1556
|
+
warned = /* @__PURE__ */ new Set();
|
|
1557
|
+
threshold;
|
|
1558
|
+
constructor(threshold = DEFAULT_STUCK_WARN_THRESHOLD) {
|
|
1559
|
+
if (threshold < 1) throw new Error("stuck-tracker threshold must be >= 1");
|
|
1560
|
+
this.threshold = threshold;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Record one tick for one agent. Returns whether the caller should log
|
|
1564
|
+
* a stuck-warning this tick. Re-warning only fires when the counter
|
|
1565
|
+
* climbs above the threshold *again* after a recovery, so a stuck
|
|
1566
|
+
* agent doesn't spam the log every 30s — operators get one WARN per
|
|
1567
|
+
* stuck streak.
|
|
1568
|
+
*/
|
|
1569
|
+
record(input) {
|
|
1570
|
+
const { codeName, sessionHealthy, spawnAttempted } = input;
|
|
1571
|
+
const wasStuck = (this.counts.get(codeName) ?? 0) >= this.threshold;
|
|
1572
|
+
if (sessionHealthy || spawnAttempted) {
|
|
1573
|
+
this.counts.delete(codeName);
|
|
1574
|
+
const wasWarned = this.warned.delete(codeName);
|
|
1575
|
+
return {
|
|
1576
|
+
consecutiveTicks: 0,
|
|
1577
|
+
shouldWarn: false,
|
|
1578
|
+
recovered: wasStuck && wasWarned
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
const next = (this.counts.get(codeName) ?? 0) + 1;
|
|
1582
|
+
this.counts.set(codeName, next);
|
|
1583
|
+
const crossed = next >= this.threshold && !this.warned.has(codeName);
|
|
1584
|
+
if (crossed) this.warned.add(codeName);
|
|
1585
|
+
return {
|
|
1586
|
+
consecutiveTicks: next,
|
|
1587
|
+
shouldWarn: crossed,
|
|
1588
|
+
recovered: false
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
/** Test/debug helper: peek at an agent's current streak. */
|
|
1592
|
+
peek(codeName) {
|
|
1593
|
+
return this.counts.get(codeName) ?? 0;
|
|
1594
|
+
}
|
|
1595
|
+
/** Test helper: clear all state. */
|
|
1596
|
+
reset() {
|
|
1597
|
+
this.counts.clear();
|
|
1598
|
+
this.warned.clear();
|
|
1599
|
+
}
|
|
1600
|
+
};
|
|
1601
|
+
|
|
1548
1602
|
// src/lib/realtime-chat.ts
|
|
1549
1603
|
import { createClient } from "@supabase/supabase-js";
|
|
1550
1604
|
var client = null;
|
|
@@ -2005,6 +2059,12 @@ function truncateForLog(s) {
|
|
|
2005
2059
|
return lines.slice(-PANE_TAIL_PREVIEW_LINES).map((l) => ` | ${l}`).join("\n");
|
|
2006
2060
|
}
|
|
2007
2061
|
var KNOWN_SAFE_TAIL_SIGNATURES = /* @__PURE__ */ new Set(["session_id_in_use"]);
|
|
2062
|
+
function shouldSkipRevokedCleanup(previousKnownStatus) {
|
|
2063
|
+
return previousKnownStatus === "revoked";
|
|
2064
|
+
}
|
|
2065
|
+
function hasRevokedResiduals(state2) {
|
|
2066
|
+
return state2.gatewayRunning || state2.portAllocated || state2.provisionDirExists;
|
|
2067
|
+
}
|
|
2008
2068
|
var knownVersions = /* @__PURE__ */ new Map();
|
|
2009
2069
|
var knownStatuses = /* @__PURE__ */ new Map();
|
|
2010
2070
|
var knownChannels = /* @__PURE__ */ new Map();
|
|
@@ -2140,7 +2200,7 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
2140
2200
|
var cachedFrameworkVersion = null;
|
|
2141
2201
|
var lastVersionCheckAt = 0;
|
|
2142
2202
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2143
|
-
var agtCliVersion = true ? "0.20.
|
|
2203
|
+
var agtCliVersion = true ? "0.20.5" : "dev";
|
|
2144
2204
|
function resolveBrewPath(execFileSync4) {
|
|
2145
2205
|
try {
|
|
2146
2206
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -3304,7 +3364,7 @@ async function getOrCacheRegisteredAgents(adapter, profile) {
|
|
|
3304
3364
|
}
|
|
3305
3365
|
async function processAgent(agent, agentStates) {
|
|
3306
3366
|
if (!config) return;
|
|
3307
|
-
|
|
3367
|
+
const previousKnownStatus = knownStatuses.get(agent.agent_id);
|
|
3308
3368
|
agentDisplayNames.set(agent.code_name, agent.display_name);
|
|
3309
3369
|
codeNameToAgentId.set(agent.code_name, agent.agent_id);
|
|
3310
3370
|
if (agent.framework) {
|
|
@@ -3314,7 +3374,9 @@ async function processAgent(agent, agentStates) {
|
|
|
3314
3374
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
3315
3375
|
let agentDir = join4(adapter.getAgentDir(agent.code_name), "provision");
|
|
3316
3376
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
3317
|
-
|
|
3377
|
+
if (previousKnownStatus !== agent.status) {
|
|
3378
|
+
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
3379
|
+
}
|
|
3318
3380
|
await stopGatewayIfRunning(agent.code_name, adapter);
|
|
3319
3381
|
stopPersistentSessionAndForgetMcpBaseline(agent.code_name);
|
|
3320
3382
|
try {
|
|
@@ -3339,9 +3401,41 @@ async function processAgent(agent, agentStates) {
|
|
|
3339
3401
|
gatewayRunning: false,
|
|
3340
3402
|
acpSessions: []
|
|
3341
3403
|
});
|
|
3404
|
+
knownStatuses.set(agent.agent_id, agent.status);
|
|
3342
3405
|
return;
|
|
3343
3406
|
}
|
|
3344
3407
|
if (agent.status === "revoked") {
|
|
3408
|
+
if (shouldSkipRevokedCleanup(previousKnownStatus)) {
|
|
3409
|
+
const ports = loadGatewayPorts();
|
|
3410
|
+
const gatewayLiveness = adapter.isGatewayRunning ? await adapter.isGatewayRunning(agent.code_name).catch(() => ({ running: false })) : { running: false };
|
|
3411
|
+
const residuals = {
|
|
3412
|
+
gatewayRunning: gatewayLiveness.running,
|
|
3413
|
+
portAllocated: Object.prototype.hasOwnProperty.call(ports, agent.code_name),
|
|
3414
|
+
provisionDirExists: existsSync3(agentDir)
|
|
3415
|
+
};
|
|
3416
|
+
if (!hasRevokedResiduals(residuals)) {
|
|
3417
|
+
agentStates.push({
|
|
3418
|
+
agentId: agent.agent_id,
|
|
3419
|
+
codeName: agent.code_name,
|
|
3420
|
+
status: agent.status,
|
|
3421
|
+
charterVersion: "",
|
|
3422
|
+
toolsVersion: "",
|
|
3423
|
+
secretsHash: null,
|
|
3424
|
+
lastRefreshAt: now,
|
|
3425
|
+
lastProvisionAt: null,
|
|
3426
|
+
lastDriftCheckAt: null,
|
|
3427
|
+
lastSecretsProvisionAt: null,
|
|
3428
|
+
gatewayPort: null,
|
|
3429
|
+
gatewayPid: null,
|
|
3430
|
+
gatewayRunning: false,
|
|
3431
|
+
acpSessions: []
|
|
3432
|
+
});
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
log(
|
|
3436
|
+
`[revoked] residuals for '${agent.code_name}': gateway=${residuals.gatewayRunning} dir=${residuals.provisionDirExists} port=${residuals.portAllocated ? ports[agent.code_name] : "none"} \u2014 retrying cleanup`
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3345
3439
|
log(`Agent '${agent.code_name}' is revoked, cleaning up`);
|
|
3346
3440
|
await stopGatewayIfRunning(agent.code_name, adapter);
|
|
3347
3441
|
stopPersistentSessionAndForgetMcpBaseline(agent.code_name);
|
|
@@ -4217,7 +4311,35 @@ async function processAgent(agent, agentStates) {
|
|
|
4217
4311
|
const agentFw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
4218
4312
|
const sessionMode = refreshData.agent.session_mode ?? "oneshot";
|
|
4219
4313
|
if (agentFw === "claude-code" && sessionMode === "persistent") {
|
|
4220
|
-
|
|
4314
|
+
let psResult;
|
|
4315
|
+
try {
|
|
4316
|
+
psResult = await ensurePersistentSession(agent, tasks, boardItems, refreshData);
|
|
4317
|
+
} catch (err) {
|
|
4318
|
+
const msg = err.message;
|
|
4319
|
+
log(`[persistent-session] EXCEPTION evaluating '${agent.code_name}': ${msg}`);
|
|
4320
|
+
psResult = {
|
|
4321
|
+
decision: "skipped-auth-resolve-failed",
|
|
4322
|
+
spawnAttempted: false,
|
|
4323
|
+
sessionHealthyAfter: false,
|
|
4324
|
+
detail: `exception: ${msg}`
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
const detailSuffix = psResult.detail ? ` detail="${psResult.detail}"` : "";
|
|
4328
|
+
log(
|
|
4329
|
+
`[persistent-session-decision] agent=${agent.code_name} decision=${psResult.decision} spawn_attempted=${psResult.spawnAttempted} session_healthy_after=${psResult.sessionHealthyAfter}${detailSuffix}`
|
|
4330
|
+
);
|
|
4331
|
+
const stuck = persistentSessionStuckTracker.record({
|
|
4332
|
+
codeName: agent.code_name,
|
|
4333
|
+
sessionHealthy: psResult.sessionHealthyAfter,
|
|
4334
|
+
spawnAttempted: psResult.spawnAttempted
|
|
4335
|
+
});
|
|
4336
|
+
if (stuck.shouldWarn) {
|
|
4337
|
+
log(
|
|
4338
|
+
`[persistent-session-stuck] WARN: agent=${agent.code_name} unhealthy with no spawn attempt for ${stuck.consecutiveTicks} consecutive ticks (last_decision=${psResult.decision}) \u2014 manager-worker may need restart, or upstream guard is mis-firing (see ENG-5116)`
|
|
4339
|
+
);
|
|
4340
|
+
} else if (stuck.recovered) {
|
|
4341
|
+
log(`[persistent-session-stuck] agent=${agent.code_name} recovered from stuck state`);
|
|
4342
|
+
}
|
|
4221
4343
|
try {
|
|
4222
4344
|
checkMcpConfigDriftAndScheduleRestart(agent.code_name, getProjectDir2(agent.code_name));
|
|
4223
4345
|
} catch (err) {
|
|
@@ -4950,6 +5072,7 @@ function fireClaudeWorkTrigger(codeName, agentId, boardItems) {
|
|
|
4950
5072
|
});
|
|
4951
5073
|
}
|
|
4952
5074
|
var persistentSessionAgents = /* @__PURE__ */ new Set();
|
|
5075
|
+
var persistentSessionStuckTracker = new PersistentSessionStuckTracker();
|
|
4953
5076
|
var claudeAuthTupleBySession = /* @__PURE__ */ new Map();
|
|
4954
5077
|
async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
4955
5078
|
const codeName = agent.code_name;
|
|
@@ -4982,7 +5105,11 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4982
5105
|
const apiKey = getApiKey();
|
|
4983
5106
|
if (!apiKey) {
|
|
4984
5107
|
log(`[persistent-session] Skipping '${codeName}' \u2014 AGT_API_KEY not set`);
|
|
4985
|
-
return
|
|
5108
|
+
return {
|
|
5109
|
+
decision: "skipped-no-api-key",
|
|
5110
|
+
spawnAttempted: false,
|
|
5111
|
+
sessionHealthyAfter: isSessionHealthy(codeName)
|
|
5112
|
+
};
|
|
4986
5113
|
}
|
|
4987
5114
|
const exchange = await exchangeApiKey(apiKey, false, { forceRefresh: true });
|
|
4988
5115
|
claudeAuthMode = exchange.claudeAuthMode;
|
|
@@ -4996,26 +5123,42 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4996
5123
|
persistentSessionAgents.delete(codeName);
|
|
4997
5124
|
claudeAuthTupleBySession.delete(codeName);
|
|
4998
5125
|
}
|
|
4999
|
-
return
|
|
5126
|
+
return {
|
|
5127
|
+
decision: "skipped-auth-resolve-failed",
|
|
5128
|
+
spawnAttempted: false,
|
|
5129
|
+
sessionHealthyAfter: isSessionHealthy(codeName),
|
|
5130
|
+
detail: msg
|
|
5131
|
+
};
|
|
5000
5132
|
}
|
|
5001
5133
|
if (claudeAuthMode === "subscription") {
|
|
5002
5134
|
if (!agentRuntimeAuthenticated) {
|
|
5003
5135
|
agentRuntimeAuthenticated = await checkClaudeAuth();
|
|
5004
5136
|
if (!agentRuntimeAuthenticated) {
|
|
5005
5137
|
log(`[persistent-session] Skipping '${codeName}' \u2014 Claude Code not authenticated (subscription mode)`);
|
|
5006
|
-
return
|
|
5138
|
+
return {
|
|
5139
|
+
decision: "skipped-not-authenticated",
|
|
5140
|
+
spawnAttempted: false,
|
|
5141
|
+
sessionHealthyAfter: isSessionHealthy(codeName)
|
|
5142
|
+
};
|
|
5007
5143
|
}
|
|
5008
5144
|
}
|
|
5009
5145
|
} else if (!anthropicApiKey) {
|
|
5010
5146
|
log(`[persistent-session] Skipping '${codeName}' \u2014 api_key mode but no key returned from exchange`);
|
|
5011
|
-
return
|
|
5147
|
+
return {
|
|
5148
|
+
decision: "skipped-api-key-mode-no-key",
|
|
5149
|
+
spawnAttempted: false,
|
|
5150
|
+
sessionHealthyAfter: isSessionHealthy(codeName)
|
|
5151
|
+
};
|
|
5012
5152
|
}
|
|
5153
|
+
let restartTrigger = null;
|
|
5154
|
+
let dayRolloverDeferred = false;
|
|
5013
5155
|
const currentAuthTuple = `${claudeAuthMode}:${anthropicApiKeyFingerprint ?? "none"}`;
|
|
5014
5156
|
const recordedAuthTuple = claudeAuthTupleBySession.get(codeName);
|
|
5015
5157
|
if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {
|
|
5016
5158
|
log(`[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} \u2192 ${currentAuthTuple}) \u2014 restarting session`);
|
|
5017
5159
|
stopPersistentSessionAndForgetMcpBaseline(codeName);
|
|
5018
5160
|
persistentSessionAgents.delete(codeName);
|
|
5161
|
+
restartTrigger = "auth-tuple";
|
|
5019
5162
|
}
|
|
5020
5163
|
if (isStaleForToday(codeName) && isSessionHealthy(codeName)) {
|
|
5021
5164
|
const current = peekCurrentSession(codeName);
|
|
@@ -5027,10 +5170,12 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
5027
5170
|
);
|
|
5028
5171
|
stopPersistentSessionAndForgetMcpBaseline(codeName);
|
|
5029
5172
|
persistentSessionAgents.delete(codeName);
|
|
5173
|
+
restartTrigger = "day-rollover";
|
|
5030
5174
|
} else {
|
|
5031
5175
|
log(
|
|
5032
5176
|
`[persistent-session] Day rollover for '${codeName}' deferred \u2014 agent still active on session ${current.sessionId} (will retry next tick)`
|
|
5033
5177
|
);
|
|
5178
|
+
dayRolloverDeferred = true;
|
|
5034
5179
|
}
|
|
5035
5180
|
}
|
|
5036
5181
|
}
|
|
@@ -5077,7 +5222,12 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
|
|
|
5077
5222
|
});
|
|
5078
5223
|
persistentSessionAgents.add(codeName);
|
|
5079
5224
|
claudeAuthTupleBySession.set(codeName, currentAuthTuple);
|
|
5080
|
-
return
|
|
5225
|
+
return {
|
|
5226
|
+
decision: "spawn",
|
|
5227
|
+
spawnAttempted: true,
|
|
5228
|
+
sessionHealthyAfter: isSessionHealthy(codeName),
|
|
5229
|
+
detail: restartTrigger ? `trigger=${restartTrigger}` : void 0
|
|
5230
|
+
};
|
|
5081
5231
|
}
|
|
5082
5232
|
resetRestartCount(codeName);
|
|
5083
5233
|
if (!claudeAuthTupleBySession.has(codeName)) {
|
|
@@ -5159,6 +5309,12 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
|
|
|
5159
5309
|
break;
|
|
5160
5310
|
}
|
|
5161
5311
|
}
|
|
5312
|
+
const finalDecision = restartTrigger === "auth-tuple" ? "restart-pending" : restartTrigger === "day-rollover" ? "day-rollover-restart" : dayRolloverDeferred ? "day-rollover-deferred" : "healthy";
|
|
5313
|
+
return {
|
|
5314
|
+
decision: finalDecision,
|
|
5315
|
+
spawnAttempted: false,
|
|
5316
|
+
sessionHealthyAfter: isSessionHealthy(codeName)
|
|
5317
|
+
};
|
|
5162
5318
|
}
|
|
5163
5319
|
var realtimeStarted = false;
|
|
5164
5320
|
var realtimeDriftStarted = false;
|
|
@@ -7163,7 +7319,9 @@ export {
|
|
|
7163
7319
|
applyRestartAcks,
|
|
7164
7320
|
extractCharterSlackPeers,
|
|
7165
7321
|
extractCharterTelegramPeers,
|
|
7322
|
+
hasRevokedResiduals,
|
|
7166
7323
|
markAgentForFreshMemorySync,
|
|
7324
|
+
shouldSkipRevokedCleanup,
|
|
7167
7325
|
startManager,
|
|
7168
7326
|
stopManager
|
|
7169
7327
|
};
|