@integrity-labs/agt-cli 0.20.3 → 0.20.6
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-INN5PXU3.js → chunk-SOYIZEZG.js} +55 -1
- package/dist/chunk-SOYIZEZG.js.map +1 -0
- package/dist/lib/manager-worker.js +166 -10
- 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-INN5PXU3.js.map +0 -1
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
resolveChannels,
|
|
23
23
|
resolveDmTarget,
|
|
24
24
|
wrapScheduledTaskPrompt
|
|
25
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-SOYIZEZG.js";
|
|
26
26
|
import {
|
|
27
27
|
findTaskByTemplate,
|
|
28
28
|
getProjectDir,
|
|
@@ -1549,6 +1549,56 @@ async function sendError(flag, opts, text) {
|
|
|
1549
1549
|
await sendAck(flag, opts, text);
|
|
1550
1550
|
}
|
|
1551
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
|
+
|
|
1552
1602
|
// src/lib/realtime-chat.ts
|
|
1553
1603
|
import { createClient } from "@supabase/supabase-js";
|
|
1554
1604
|
var client = null;
|
|
@@ -2009,6 +2059,12 @@ function truncateForLog(s) {
|
|
|
2009
2059
|
return lines.slice(-PANE_TAIL_PREVIEW_LINES).map((l) => ` | ${l}`).join("\n");
|
|
2010
2060
|
}
|
|
2011
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
|
+
}
|
|
2012
2068
|
var knownVersions = /* @__PURE__ */ new Map();
|
|
2013
2069
|
var knownStatuses = /* @__PURE__ */ new Map();
|
|
2014
2070
|
var knownChannels = /* @__PURE__ */ new Map();
|
|
@@ -2144,7 +2200,7 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
2144
2200
|
var cachedFrameworkVersion = null;
|
|
2145
2201
|
var lastVersionCheckAt = 0;
|
|
2146
2202
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2147
|
-
var agtCliVersion = true ? "0.20.
|
|
2203
|
+
var agtCliVersion = true ? "0.20.6" : "dev";
|
|
2148
2204
|
function resolveBrewPath(execFileSync4) {
|
|
2149
2205
|
try {
|
|
2150
2206
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -3308,7 +3364,7 @@ async function getOrCacheRegisteredAgents(adapter, profile) {
|
|
|
3308
3364
|
}
|
|
3309
3365
|
async function processAgent(agent, agentStates) {
|
|
3310
3366
|
if (!config) return;
|
|
3311
|
-
|
|
3367
|
+
const previousKnownStatus = knownStatuses.get(agent.agent_id);
|
|
3312
3368
|
agentDisplayNames.set(agent.code_name, agent.display_name);
|
|
3313
3369
|
codeNameToAgentId.set(agent.code_name, agent.agent_id);
|
|
3314
3370
|
if (agent.framework) {
|
|
@@ -3318,7 +3374,9 @@ async function processAgent(agent, agentStates) {
|
|
|
3318
3374
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
3319
3375
|
let agentDir = join4(adapter.getAgentDir(agent.code_name), "provision");
|
|
3320
3376
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
3321
|
-
|
|
3377
|
+
if (previousKnownStatus !== agent.status) {
|
|
3378
|
+
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
3379
|
+
}
|
|
3322
3380
|
await stopGatewayIfRunning(agent.code_name, adapter);
|
|
3323
3381
|
stopPersistentSessionAndForgetMcpBaseline(agent.code_name);
|
|
3324
3382
|
try {
|
|
@@ -3343,9 +3401,41 @@ async function processAgent(agent, agentStates) {
|
|
|
3343
3401
|
gatewayRunning: false,
|
|
3344
3402
|
acpSessions: []
|
|
3345
3403
|
});
|
|
3404
|
+
knownStatuses.set(agent.agent_id, agent.status);
|
|
3346
3405
|
return;
|
|
3347
3406
|
}
|
|
3348
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
|
+
}
|
|
3349
3439
|
log(`Agent '${agent.code_name}' is revoked, cleaning up`);
|
|
3350
3440
|
await stopGatewayIfRunning(agent.code_name, adapter);
|
|
3351
3441
|
stopPersistentSessionAndForgetMcpBaseline(agent.code_name);
|
|
@@ -4221,7 +4311,37 @@ async function processAgent(agent, agentStates) {
|
|
|
4221
4311
|
const agentFw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
|
|
4222
4312
|
const sessionMode = refreshData.agent.session_mode ?? "oneshot";
|
|
4223
4313
|
if (agentFw === "claude-code" && sessionMode === "persistent") {
|
|
4224
|
-
|
|
4314
|
+
const wasSessionHealthy = isSessionHealthy(agent.code_name);
|
|
4315
|
+
let psResult;
|
|
4316
|
+
try {
|
|
4317
|
+
psResult = await ensurePersistentSession(agent, tasks, boardItems, refreshData);
|
|
4318
|
+
} catch (err) {
|
|
4319
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4320
|
+
log(`[persistent-session] EXCEPTION evaluating '${agent.code_name}': ${msg}`);
|
|
4321
|
+
psResult = {
|
|
4322
|
+
decision: "skipped-auth-resolve-failed",
|
|
4323
|
+
spawnAttempted: false,
|
|
4324
|
+
sessionHealthyAfter: wasSessionHealthy,
|
|
4325
|
+
detail: `exception: ${msg}`
|
|
4326
|
+
};
|
|
4327
|
+
}
|
|
4328
|
+
const normalizedDetail = psResult.detail?.replace(/\s+/g, " ").trim();
|
|
4329
|
+
const detailSuffix = normalizedDetail ? ` detail=${JSON.stringify(normalizedDetail)}` : "";
|
|
4330
|
+
log(
|
|
4331
|
+
`[persistent-session-decision] agent=${agent.code_name} decision=${psResult.decision} spawn_attempted=${psResult.spawnAttempted} session_healthy_after=${psResult.sessionHealthyAfter}${detailSuffix}`
|
|
4332
|
+
);
|
|
4333
|
+
const stuck = persistentSessionStuckTracker.record({
|
|
4334
|
+
codeName: agent.code_name,
|
|
4335
|
+
sessionHealthy: psResult.sessionHealthyAfter,
|
|
4336
|
+
spawnAttempted: psResult.spawnAttempted
|
|
4337
|
+
});
|
|
4338
|
+
if (stuck.shouldWarn) {
|
|
4339
|
+
log(
|
|
4340
|
+
`[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)`
|
|
4341
|
+
);
|
|
4342
|
+
} else if (stuck.recovered) {
|
|
4343
|
+
log(`[persistent-session-stuck] agent=${agent.code_name} recovered from stuck state`);
|
|
4344
|
+
}
|
|
4225
4345
|
try {
|
|
4226
4346
|
checkMcpConfigDriftAndScheduleRestart(agent.code_name, getProjectDir2(agent.code_name));
|
|
4227
4347
|
} catch (err) {
|
|
@@ -4954,6 +5074,7 @@ function fireClaudeWorkTrigger(codeName, agentId, boardItems) {
|
|
|
4954
5074
|
});
|
|
4955
5075
|
}
|
|
4956
5076
|
var persistentSessionAgents = /* @__PURE__ */ new Set();
|
|
5077
|
+
var persistentSessionStuckTracker = new PersistentSessionStuckTracker();
|
|
4957
5078
|
var claudeAuthTupleBySession = /* @__PURE__ */ new Map();
|
|
4958
5079
|
async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
4959
5080
|
const codeName = agent.code_name;
|
|
@@ -4986,7 +5107,11 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
4986
5107
|
const apiKey = getApiKey();
|
|
4987
5108
|
if (!apiKey) {
|
|
4988
5109
|
log(`[persistent-session] Skipping '${codeName}' \u2014 AGT_API_KEY not set`);
|
|
4989
|
-
return
|
|
5110
|
+
return {
|
|
5111
|
+
decision: "skipped-no-api-key",
|
|
5112
|
+
spawnAttempted: false,
|
|
5113
|
+
sessionHealthyAfter: isSessionHealthy(codeName)
|
|
5114
|
+
};
|
|
4990
5115
|
}
|
|
4991
5116
|
const exchange = await exchangeApiKey(apiKey, false, { forceRefresh: true });
|
|
4992
5117
|
claudeAuthMode = exchange.claudeAuthMode;
|
|
@@ -5000,26 +5125,42 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
5000
5125
|
persistentSessionAgents.delete(codeName);
|
|
5001
5126
|
claudeAuthTupleBySession.delete(codeName);
|
|
5002
5127
|
}
|
|
5003
|
-
return
|
|
5128
|
+
return {
|
|
5129
|
+
decision: "skipped-auth-resolve-failed",
|
|
5130
|
+
spawnAttempted: false,
|
|
5131
|
+
sessionHealthyAfter: isSessionHealthy(codeName),
|
|
5132
|
+
detail: msg
|
|
5133
|
+
};
|
|
5004
5134
|
}
|
|
5005
5135
|
if (claudeAuthMode === "subscription") {
|
|
5006
5136
|
if (!agentRuntimeAuthenticated) {
|
|
5007
5137
|
agentRuntimeAuthenticated = await checkClaudeAuth();
|
|
5008
5138
|
if (!agentRuntimeAuthenticated) {
|
|
5009
5139
|
log(`[persistent-session] Skipping '${codeName}' \u2014 Claude Code not authenticated (subscription mode)`);
|
|
5010
|
-
return
|
|
5140
|
+
return {
|
|
5141
|
+
decision: "skipped-not-authenticated",
|
|
5142
|
+
spawnAttempted: false,
|
|
5143
|
+
sessionHealthyAfter: isSessionHealthy(codeName)
|
|
5144
|
+
};
|
|
5011
5145
|
}
|
|
5012
5146
|
}
|
|
5013
5147
|
} else if (!anthropicApiKey) {
|
|
5014
5148
|
log(`[persistent-session] Skipping '${codeName}' \u2014 api_key mode but no key returned from exchange`);
|
|
5015
|
-
return
|
|
5149
|
+
return {
|
|
5150
|
+
decision: "skipped-api-key-mode-no-key",
|
|
5151
|
+
spawnAttempted: false,
|
|
5152
|
+
sessionHealthyAfter: isSessionHealthy(codeName)
|
|
5153
|
+
};
|
|
5016
5154
|
}
|
|
5155
|
+
let restartTrigger = null;
|
|
5156
|
+
let dayRolloverDeferred = false;
|
|
5017
5157
|
const currentAuthTuple = `${claudeAuthMode}:${anthropicApiKeyFingerprint ?? "none"}`;
|
|
5018
5158
|
const recordedAuthTuple = claudeAuthTupleBySession.get(codeName);
|
|
5019
5159
|
if (recordedAuthTuple && recordedAuthTuple !== currentAuthTuple && isSessionHealthy(codeName)) {
|
|
5020
5160
|
log(`[persistent-session] Auth config changed for '${codeName}' (${recordedAuthTuple} \u2192 ${currentAuthTuple}) \u2014 restarting session`);
|
|
5021
5161
|
stopPersistentSessionAndForgetMcpBaseline(codeName);
|
|
5022
5162
|
persistentSessionAgents.delete(codeName);
|
|
5163
|
+
restartTrigger = "auth-tuple";
|
|
5023
5164
|
}
|
|
5024
5165
|
if (isStaleForToday(codeName) && isSessionHealthy(codeName)) {
|
|
5025
5166
|
const current = peekCurrentSession(codeName);
|
|
@@ -5031,10 +5172,12 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
5031
5172
|
);
|
|
5032
5173
|
stopPersistentSessionAndForgetMcpBaseline(codeName);
|
|
5033
5174
|
persistentSessionAgents.delete(codeName);
|
|
5175
|
+
restartTrigger = "day-rollover";
|
|
5034
5176
|
} else {
|
|
5035
5177
|
log(
|
|
5036
5178
|
`[persistent-session] Day rollover for '${codeName}' deferred \u2014 agent still active on session ${current.sessionId} (will retry next tick)`
|
|
5037
5179
|
);
|
|
5180
|
+
dayRolloverDeferred = true;
|
|
5038
5181
|
}
|
|
5039
5182
|
}
|
|
5040
5183
|
}
|
|
@@ -5081,7 +5224,12 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
|
|
|
5081
5224
|
});
|
|
5082
5225
|
persistentSessionAgents.add(codeName);
|
|
5083
5226
|
claudeAuthTupleBySession.set(codeName, currentAuthTuple);
|
|
5084
|
-
return
|
|
5227
|
+
return {
|
|
5228
|
+
decision: "spawn",
|
|
5229
|
+
spawnAttempted: true,
|
|
5230
|
+
sessionHealthyAfter: isSessionHealthy(codeName),
|
|
5231
|
+
detail: restartTrigger ? `trigger=${restartTrigger}` : void 0
|
|
5232
|
+
};
|
|
5085
5233
|
}
|
|
5086
5234
|
resetRestartCount(codeName);
|
|
5087
5235
|
if (!claudeAuthTupleBySession.has(codeName)) {
|
|
@@ -5163,6 +5311,12 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
|
|
|
5163
5311
|
break;
|
|
5164
5312
|
}
|
|
5165
5313
|
}
|
|
5314
|
+
const finalDecision = restartTrigger === "auth-tuple" ? "restart-pending" : restartTrigger === "day-rollover" ? "day-rollover-restart" : dayRolloverDeferred ? "day-rollover-deferred" : "healthy";
|
|
5315
|
+
return {
|
|
5316
|
+
decision: finalDecision,
|
|
5317
|
+
spawnAttempted: false,
|
|
5318
|
+
sessionHealthyAfter: isSessionHealthy(codeName)
|
|
5319
|
+
};
|
|
5166
5320
|
}
|
|
5167
5321
|
var realtimeStarted = false;
|
|
5168
5322
|
var realtimeDriftStarted = false;
|
|
@@ -7167,7 +7321,9 @@ export {
|
|
|
7167
7321
|
applyRestartAcks,
|
|
7168
7322
|
extractCharterSlackPeers,
|
|
7169
7323
|
extractCharterTelegramPeers,
|
|
7324
|
+
hasRevokedResiduals,
|
|
7170
7325
|
markAgentForFreshMemorySync,
|
|
7326
|
+
shouldSkipRevokedCleanup,
|
|
7171
7327
|
startManager,
|
|
7172
7328
|
stopManager
|
|
7173
7329
|
};
|