@integrity-labs/agt-cli 0.21.8 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/agt.js +29 -27
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-OLVOTGNB.js → chunk-27FZ5QEI.js} +136 -3126
- package/dist/chunk-27FZ5QEI.js.map +1 -0
- package/dist/chunk-BZV2KNHY.js +3173 -0
- package/dist/chunk-BZV2KNHY.js.map +1 -0
- package/dist/{chunk-4XNYSTH5.js → chunk-DBGGT2MC.js} +81 -50
- package/dist/chunk-DBGGT2MC.js.map +1 -0
- package/dist/{claude-pair-runtime-ZMTHMKMT.js → claude-pair-runtime-LZAGWNME.js} +2 -2
- package/dist/lib/manager-worker.js +216 -84
- package/dist/lib/manager-worker.js.map +1 -1
- package/{mcp → dist/mcp}/index.js +0 -0
- package/{mcp → dist/mcp}/slack-channel.js +0 -0
- package/{mcp → dist/mcp}/telegram-channel.js +672 -14
- package/dist/{persistent-session-F44544YP.js → persistent-session-EI7576OO.js} +11 -2
- package/dist/responsiveness-probe-SIMNGYC4.js +32 -0
- package/dist/responsiveness-probe-SIMNGYC4.js.map +1 -0
- package/package.json +3 -4
- package/dist/chunk-4XNYSTH5.js.map +0 -1
- package/dist/chunk-OLVOTGNB.js.map +0 -1
- /package/dist/{claude-pair-runtime-ZMTHMKMT.js.map → claude-pair-runtime-LZAGWNME.js.map} +0 -0
- /package/{mcp → dist/mcp}/direct-chat-channel.js +0 -0
- /package/dist/{persistent-session-F44544YP.js.map → persistent-session-EI7576OO.js.map} +0 -0
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SUPERVISOR_RESTART_EXIT_CODE,
|
|
3
3
|
api,
|
|
4
|
-
appendDmFooter,
|
|
5
|
-
classifyOutput,
|
|
6
4
|
exchangeApiKey,
|
|
7
5
|
extractCommandNotFound,
|
|
8
|
-
extractFrontmatter,
|
|
9
|
-
formatActorId,
|
|
10
6
|
getApiKey,
|
|
11
|
-
getFramework,
|
|
12
7
|
getHostId,
|
|
13
8
|
getIntegration,
|
|
14
|
-
isParseError,
|
|
15
|
-
isResolveError,
|
|
16
|
-
isSelfCompletion,
|
|
17
|
-
parseDeliveryTarget,
|
|
18
9
|
provision,
|
|
19
10
|
provisionIsolationHook,
|
|
11
|
+
provisionOrientHook,
|
|
20
12
|
provisionStopHook,
|
|
21
|
-
requireHost
|
|
22
|
-
|
|
23
|
-
resolveDmTarget,
|
|
24
|
-
wrapScheduledTaskPrompt
|
|
25
|
-
} from "../chunk-OLVOTGNB.js";
|
|
13
|
+
requireHost
|
|
14
|
+
} from "../chunk-27FZ5QEI.js";
|
|
26
15
|
import {
|
|
27
16
|
findTaskByTemplate,
|
|
28
|
-
getProjectDir,
|
|
17
|
+
getProjectDir as getProjectDir2,
|
|
29
18
|
getReadyTasks,
|
|
30
19
|
loadSchedulerState,
|
|
31
20
|
markTaskFired,
|
|
@@ -33,16 +22,19 @@ import {
|
|
|
33
22
|
} from "../chunk-TS7W4YNI.js";
|
|
34
23
|
import {
|
|
35
24
|
buildAllowedTools,
|
|
25
|
+
ensureKanbanLoopArmed,
|
|
36
26
|
getLastFailureContext,
|
|
37
|
-
getProjectDir
|
|
27
|
+
getProjectDir,
|
|
38
28
|
getSessionState,
|
|
39
29
|
injectMessage,
|
|
40
30
|
isAgentIdle,
|
|
31
|
+
isKanbanLoopArmedForCurrentSession,
|
|
41
32
|
isSessionHealthy,
|
|
42
33
|
isStaleForToday,
|
|
43
34
|
parsePsRows,
|
|
44
35
|
peekCurrentSession,
|
|
45
36
|
prepareForRespawn,
|
|
37
|
+
readPaneLogTail,
|
|
46
38
|
reapOrphanChannelMcps,
|
|
47
39
|
resetRestartCount,
|
|
48
40
|
resolveClaudeBinary,
|
|
@@ -51,7 +43,22 @@ import {
|
|
|
51
43
|
stopAllSessionsAndWait,
|
|
52
44
|
stopPersistentSession,
|
|
53
45
|
takeZombieDetection
|
|
54
|
-
} from "../chunk-
|
|
46
|
+
} from "../chunk-DBGGT2MC.js";
|
|
47
|
+
import {
|
|
48
|
+
appendDmFooter,
|
|
49
|
+
classifyOutput,
|
|
50
|
+
extractFrontmatter,
|
|
51
|
+
formatActorId,
|
|
52
|
+
getFramework,
|
|
53
|
+
isParseError,
|
|
54
|
+
isResolveError,
|
|
55
|
+
isSelfCompletion,
|
|
56
|
+
parseDeliveryTarget,
|
|
57
|
+
parseUsageBanner,
|
|
58
|
+
resolveChannels,
|
|
59
|
+
resolveDmTarget,
|
|
60
|
+
wrapScheduledTaskPrompt
|
|
61
|
+
} from "../chunk-BZV2KNHY.js";
|
|
55
62
|
|
|
56
63
|
// src/lib/manager-worker.ts
|
|
57
64
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -423,12 +430,12 @@ function reapMissingMcpSessions(args) {
|
|
|
423
430
|
if (!missingRaw.includes(key)) liveKeys.add(key);
|
|
424
431
|
}
|
|
425
432
|
for (const key of liveKeys) {
|
|
426
|
-
const
|
|
427
|
-
if (
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
433
|
+
const state3 = presenceReaperState.get(stateKey(codeName, key));
|
|
434
|
+
if (state3) {
|
|
435
|
+
state3.attempts = 0;
|
|
436
|
+
state3.lastSeenLiveAt = now();
|
|
437
|
+
state3.lastAttemptedSessionStartedAt = null;
|
|
438
|
+
state3.gaveUpLogged = false;
|
|
432
439
|
}
|
|
433
440
|
}
|
|
434
441
|
if (missing.length === 0) {
|
|
@@ -443,24 +450,24 @@ function reapMissingMcpSessions(args) {
|
|
|
443
450
|
const active = [];
|
|
444
451
|
for (const key of missing) {
|
|
445
452
|
const sk = stateKey(codeName, key);
|
|
446
|
-
const
|
|
453
|
+
const state3 = presenceReaperState.get(sk) ?? {
|
|
447
454
|
attempts: 0,
|
|
448
455
|
lastSeenLiveAt: null,
|
|
449
456
|
lastAttemptedSessionStartedAt: null,
|
|
450
457
|
gaveUpLogged: false
|
|
451
458
|
};
|
|
452
|
-
if (
|
|
453
|
-
|
|
454
|
-
|
|
459
|
+
if (state3.lastAttemptedSessionStartedAt !== sessionStartedAt) {
|
|
460
|
+
state3.attempts += 1;
|
|
461
|
+
state3.lastAttemptedSessionStartedAt = sessionStartedAt;
|
|
455
462
|
}
|
|
456
|
-
presenceReaperState.set(sk,
|
|
457
|
-
if (
|
|
463
|
+
presenceReaperState.set(sk, state3);
|
|
464
|
+
if (state3.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {
|
|
458
465
|
givenUp.push(key);
|
|
459
|
-
if (!
|
|
466
|
+
if (!state3.gaveUpLogged) {
|
|
460
467
|
log2(
|
|
461
468
|
`[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)`
|
|
462
469
|
);
|
|
463
|
-
|
|
470
|
+
state3.gaveUpLogged = true;
|
|
464
471
|
if (onGiveUp) {
|
|
465
472
|
try {
|
|
466
473
|
onGiveUp(codeName, key);
|
|
@@ -541,6 +548,61 @@ function decideChannelRestart(input) {
|
|
|
541
548
|
return { restart: true, added, removed };
|
|
542
549
|
}
|
|
543
550
|
|
|
551
|
+
// src/lib/usage-banner-monitor.ts
|
|
552
|
+
var MIN_CHECK_INTERVAL_MS = 6e4;
|
|
553
|
+
var PANE_TAIL_LINES_FOR_BANNER = 200;
|
|
554
|
+
var state = /* @__PURE__ */ new Map();
|
|
555
|
+
async function maybeReportUsageBanner(args) {
|
|
556
|
+
const { api: api2, codeName, agentId, log: log2 } = args;
|
|
557
|
+
const now = args.now ?? /* @__PURE__ */ new Date();
|
|
558
|
+
const nowMs = now.getTime();
|
|
559
|
+
const existing = state.get(codeName);
|
|
560
|
+
if (existing && nowMs - existing.lastCheckedAt < MIN_CHECK_INTERVAL_MS) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
let tail;
|
|
564
|
+
try {
|
|
565
|
+
tail = readPaneLogTail(codeName, PANE_TAIL_LINES_FOR_BANNER);
|
|
566
|
+
} catch (err) {
|
|
567
|
+
log2(`[usage-banner] readPaneLogTail failed for '${codeName}': ${err.message}`);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const next = {
|
|
571
|
+
lastPct: existing?.lastPct ?? null,
|
|
572
|
+
lastWeekResetsAt: existing?.lastWeekResetsAt ?? null,
|
|
573
|
+
lastCheckedAt: nowMs
|
|
574
|
+
};
|
|
575
|
+
if (!tail) {
|
|
576
|
+
state.set(codeName, next);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const observation = parseUsageBanner(tail, now);
|
|
580
|
+
if (!observation) {
|
|
581
|
+
state.set(codeName, next);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const observedAtIso = next.lastWeekResetsAt;
|
|
585
|
+
const weekResetsAtIso = observation.weekResetsAt.toISOString();
|
|
586
|
+
if (existing && existing.lastPct === observation.pct && observedAtIso === weekResetsAtIso) {
|
|
587
|
+
state.set(codeName, next);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
try {
|
|
591
|
+
await api2.post("/host/usage-observations", {
|
|
592
|
+
agent_id: agentId,
|
|
593
|
+
observed_pct: observation.pct,
|
|
594
|
+
week_resets_at: weekResetsAtIso,
|
|
595
|
+
source: "pane_log"
|
|
596
|
+
});
|
|
597
|
+
next.lastPct = observation.pct;
|
|
598
|
+
next.lastWeekResetsAt = weekResetsAtIso;
|
|
599
|
+
state.set(codeName, next);
|
|
600
|
+
} catch (err) {
|
|
601
|
+
log2(`[usage-banner] POST /host/usage-observations failed for '${codeName}': ${err.message}`);
|
|
602
|
+
state.set(codeName, next);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
544
606
|
// src/lib/poll-backoff.ts
|
|
545
607
|
var POLL_BACKOFF_BASE_MS = 1e3;
|
|
546
608
|
var POLL_BACKOFF_MAX_MS = 6e4;
|
|
@@ -2213,8 +2275,8 @@ var KNOWN_SAFE_TAIL_SIGNATURES = /* @__PURE__ */ new Set(["session_id_in_use"]);
|
|
|
2213
2275
|
function shouldSkipRevokedCleanup(previousKnownStatus) {
|
|
2214
2276
|
return previousKnownStatus === "revoked";
|
|
2215
2277
|
}
|
|
2216
|
-
function hasRevokedResiduals(
|
|
2217
|
-
return
|
|
2278
|
+
function hasRevokedResiduals(state3) {
|
|
2279
|
+
return state3.gatewayRunning || state3.portAllocated || state3.provisionDirExists;
|
|
2218
2280
|
}
|
|
2219
2281
|
var knownVersions = /* @__PURE__ */ new Map();
|
|
2220
2282
|
var knownStatuses = /* @__PURE__ */ new Map();
|
|
@@ -2334,8 +2396,14 @@ function managedToolkitMapKey(agentId, serverId) {
|
|
|
2334
2396
|
var lastCronRunTs = /* @__PURE__ */ new Map();
|
|
2335
2397
|
var lastWorkTriggerAt = /* @__PURE__ */ new Map();
|
|
2336
2398
|
var alertedStaleItems = /* @__PURE__ */ new Set();
|
|
2399
|
+
var stuckKanbanLogged = /* @__PURE__ */ new Set();
|
|
2337
2400
|
var apiKeyStatusCache = /* @__PURE__ */ new Map();
|
|
2338
|
-
var STALE_TASK_THRESHOLD_MS =
|
|
2401
|
+
var STALE_TASK_THRESHOLD_MS = (() => {
|
|
2402
|
+
const env = process.env["AUGMENTED_STUCK_TASK_THRESHOLD_MINUTES"];
|
|
2403
|
+
const minutes = env ? Number.parseInt(env, 10) : NaN;
|
|
2404
|
+
if (Number.isFinite(minutes) && minutes > 0) return minutes * 60 * 1e3;
|
|
2405
|
+
return 30 * 60 * 1e3;
|
|
2406
|
+
})();
|
|
2339
2407
|
var alertSlackWebhook = null;
|
|
2340
2408
|
var alertedJobs = /* @__PURE__ */ new Set();
|
|
2341
2409
|
var taskDisplayInfo = /* @__PURE__ */ new Map();
|
|
@@ -2344,7 +2412,7 @@ var codeNameToAgentId = /* @__PURE__ */ new Map();
|
|
|
2344
2412
|
var agentChannelTokens = /* @__PURE__ */ new Map();
|
|
2345
2413
|
var activeChannels = /* @__PURE__ */ new Map();
|
|
2346
2414
|
var gatewaysStartedThisCycle = /* @__PURE__ */ new Set();
|
|
2347
|
-
var
|
|
2415
|
+
var state2 = {
|
|
2348
2416
|
pid: process.pid,
|
|
2349
2417
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2350
2418
|
lastPollAt: null,
|
|
@@ -2399,7 +2467,8 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
2399
2467
|
var cachedFrameworkVersion = null;
|
|
2400
2468
|
var lastVersionCheckAt = 0;
|
|
2401
2469
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2402
|
-
var
|
|
2470
|
+
var lastResponsivenessProbeAt = 0;
|
|
2471
|
+
var agtCliVersion = true ? "0.22.0" : "dev";
|
|
2403
2472
|
function resolveBrewPath(execFileSync4) {
|
|
2404
2473
|
try {
|
|
2405
2474
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -3141,10 +3210,10 @@ function isGatewayHung(codeName) {
|
|
|
3141
3210
|
if (!Array.isArray(jobs)) return false;
|
|
3142
3211
|
const now = Date.now();
|
|
3143
3212
|
for (const job of jobs) {
|
|
3144
|
-
const
|
|
3145
|
-
if (!
|
|
3146
|
-
const runStartedAt =
|
|
3147
|
-
const isRunning =
|
|
3213
|
+
const state3 = job.state;
|
|
3214
|
+
if (!state3) continue;
|
|
3215
|
+
const runStartedAt = state3.runStartedAtMs;
|
|
3216
|
+
const isRunning = state3.status === "running" || state3.running === true;
|
|
3148
3217
|
if (isRunning && runStartedAt && now - runStartedAt > GATEWAY_HUNG_TIMEOUT_MS) {
|
|
3149
3218
|
return true;
|
|
3150
3219
|
}
|
|
@@ -3345,7 +3414,7 @@ async function pollCycle() {
|
|
|
3345
3414
|
const now = Date.now();
|
|
3346
3415
|
if (now - lastVersionCheckAt > VERSION_CHECK_INTERVAL_MS) {
|
|
3347
3416
|
try {
|
|
3348
|
-
const firstAgent =
|
|
3417
|
+
const firstAgent = state2.agents[0];
|
|
3349
3418
|
const versionAdapter = firstAgent ? resolveAgentFramework(firstAgent.codeName) : getFramework("openclaw");
|
|
3350
3419
|
if (versionAdapter.getVersion) {
|
|
3351
3420
|
cachedFrameworkVersion = await versionAdapter.getVersion();
|
|
@@ -3356,7 +3425,7 @@ async function pollCycle() {
|
|
|
3356
3425
|
}
|
|
3357
3426
|
try {
|
|
3358
3427
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
3359
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
3428
|
+
const { collectDiagnostics } = await import("../persistent-session-EI7576OO.js");
|
|
3360
3429
|
const diagCodeNames = [...persistentSessionAgents];
|
|
3361
3430
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
3362
3431
|
let tailscaleHostname;
|
|
@@ -3403,13 +3472,34 @@ async function pollCycle() {
|
|
|
3403
3472
|
} catch (err) {
|
|
3404
3473
|
log(`Heartbeat failed: ${err.message}`);
|
|
3405
3474
|
}
|
|
3475
|
+
try {
|
|
3476
|
+
const {
|
|
3477
|
+
collectResponsivenessProbes,
|
|
3478
|
+
getResponsivenessIntervalMs
|
|
3479
|
+
} = await import("../responsiveness-probe-SIMNGYC4.js");
|
|
3480
|
+
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
3481
|
+
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
3482
|
+
const probeCodeNames = [...persistentSessionAgents];
|
|
3483
|
+
if (probeCodeNames.length > 0) {
|
|
3484
|
+
const probes = collectResponsivenessProbes(probeCodeNames);
|
|
3485
|
+
if (probes.length > 0) {
|
|
3486
|
+
void api.post("/host/responsiveness-probe", { host_id: hostId, probes }).catch((err) => {
|
|
3487
|
+
log(`[responsiveness-probe] post failed: ${err.message}`);
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
lastResponsivenessProbeAt = now;
|
|
3492
|
+
}
|
|
3493
|
+
} catch (err) {
|
|
3494
|
+
log(`[responsiveness-probe] collection failed: ${err.message}`);
|
|
3495
|
+
}
|
|
3406
3496
|
const data = await api.post("/host/agents", { host_id: hostId });
|
|
3407
3497
|
const agents = data.agents ?? [];
|
|
3408
3498
|
const restartAcks = /* @__PURE__ */ new Map();
|
|
3409
3499
|
for (const agent of agents) {
|
|
3410
3500
|
const requested = agent.restart_requested_at ?? null;
|
|
3411
3501
|
if (!requested) continue;
|
|
3412
|
-
const prev =
|
|
3502
|
+
const prev = state2.agents.find((a) => a.agentId === agent.agent_id);
|
|
3413
3503
|
const lastProcessed = prev?.lastRestartProcessedAt ?? null;
|
|
3414
3504
|
if (lastProcessed && Date.parse(lastProcessed) >= Date.parse(requested)) continue;
|
|
3415
3505
|
log(`[restart] Dashboard requested restart for '${agent.code_name}' at ${requested}`);
|
|
@@ -3432,7 +3522,7 @@ async function pollCycle() {
|
|
|
3432
3522
|
await processAgent(agent, agentStates);
|
|
3433
3523
|
} catch (err) {
|
|
3434
3524
|
log(`Error processing agent '${agent.code_name}': ${err.message}`);
|
|
3435
|
-
const existing =
|
|
3525
|
+
const existing = state2.agents.find((a) => a.agentId === agent.agent_id);
|
|
3436
3526
|
if (existing) {
|
|
3437
3527
|
agentStates.push(existing);
|
|
3438
3528
|
} else {
|
|
@@ -3457,12 +3547,12 @@ async function pollCycle() {
|
|
|
3457
3547
|
}
|
|
3458
3548
|
const restartAckStateChanged = applyRestartAcks({
|
|
3459
3549
|
agentStates,
|
|
3460
|
-
priorAgents:
|
|
3550
|
+
priorAgents: state2.agents,
|
|
3461
3551
|
restartAcks
|
|
3462
3552
|
});
|
|
3463
3553
|
if (restartAckStateChanged) {
|
|
3464
3554
|
try {
|
|
3465
|
-
const ackedState = { ...
|
|
3555
|
+
const ackedState = { ...state2, agents: agentStates };
|
|
3466
3556
|
writeFileSync3(getStateFile(), JSON.stringify(ackedState, null, 2));
|
|
3467
3557
|
} catch (err) {
|
|
3468
3558
|
log(`[restart] failed to persist ack immediately: ${err.message}`);
|
|
@@ -3480,7 +3570,7 @@ async function pollCycle() {
|
|
|
3480
3570
|
} catch {
|
|
3481
3571
|
}
|
|
3482
3572
|
const currentIds = new Set(agents.map((a) => a.agent_id));
|
|
3483
|
-
for (const prev of
|
|
3573
|
+
for (const prev of state2.agents) {
|
|
3484
3574
|
if (!currentIds.has(prev.agentId)) {
|
|
3485
3575
|
log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
|
|
3486
3576
|
const adapter = resolveAgentFramework(prev.codeName);
|
|
@@ -3580,19 +3670,19 @@ async function pollCycle() {
|
|
|
3580
3670
|
}
|
|
3581
3671
|
} catch {
|
|
3582
3672
|
}
|
|
3583
|
-
|
|
3584
|
-
...
|
|
3673
|
+
state2 = {
|
|
3674
|
+
...state2,
|
|
3585
3675
|
lastPollAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3586
|
-
pollCount:
|
|
3676
|
+
pollCount: state2.pollCount + 1,
|
|
3587
3677
|
agents: agentStates
|
|
3588
3678
|
};
|
|
3589
3679
|
if (consecutivePollFailures > 0) {
|
|
3590
3680
|
log(`[poll-backoff] recovered after ${consecutivePollFailures} failure(s), resuming normal interval`);
|
|
3591
3681
|
consecutivePollFailures = 0;
|
|
3592
3682
|
}
|
|
3593
|
-
send({ type: "state-update", state });
|
|
3683
|
+
send({ type: "state-update", state: state2 });
|
|
3594
3684
|
} catch (err) {
|
|
3595
|
-
|
|
3685
|
+
state2.errorCount++;
|
|
3596
3686
|
const message = err.message;
|
|
3597
3687
|
log(`Poll error: ${message}`);
|
|
3598
3688
|
send({ type: "error", message });
|
|
@@ -3622,6 +3712,14 @@ async function processAgent(agent, agentStates) {
|
|
|
3622
3712
|
if (agent.framework) {
|
|
3623
3713
|
agentFrameworkCache.set(agent.code_name, agent.framework);
|
|
3624
3714
|
}
|
|
3715
|
+
if (agent.framework === "claude-code" && agent.status === "active") {
|
|
3716
|
+
void maybeReportUsageBanner({
|
|
3717
|
+
api,
|
|
3718
|
+
codeName: agent.code_name,
|
|
3719
|
+
agentId: agent.agent_id,
|
|
3720
|
+
log
|
|
3721
|
+
});
|
|
3722
|
+
}
|
|
3625
3723
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3626
3724
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
3627
3725
|
let agentDir = join4(adapter.getAgentDir(agent.code_name), "provision");
|
|
@@ -3740,7 +3838,7 @@ async function processAgent(agent, agentStates) {
|
|
|
3740
3838
|
});
|
|
3741
3839
|
} catch (err) {
|
|
3742
3840
|
log(`Refresh failed for '${agent.code_name}': ${err.message}`);
|
|
3743
|
-
const existing =
|
|
3841
|
+
const existing = state2.agents.find((a) => a.agentId === agent.agent_id);
|
|
3744
3842
|
agentStates.push(existing ?? {
|
|
3745
3843
|
agentId: agent.agent_id,
|
|
3746
3844
|
codeName: agent.code_name,
|
|
@@ -3796,7 +3894,7 @@ async function processAgent(agent, agentStates) {
|
|
|
3796
3894
|
const charterVersion = refreshData.charter.version;
|
|
3797
3895
|
const toolsVersion = refreshData.tools.version;
|
|
3798
3896
|
const known = knownVersions.get(agent.agent_id);
|
|
3799
|
-
let lastProvisionAt =
|
|
3897
|
+
let lastProvisionAt = state2.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
|
|
3800
3898
|
const currentChannelIds = launchableChannelIds(refreshData.channel_configs);
|
|
3801
3899
|
const previousChannelIds = knownChannels.get(agent.agent_id);
|
|
3802
3900
|
const channelsChanged = !previousChannelIds || currentChannelIds.size !== previousChannelIds.size || [...currentChannelIds].some((ch) => !previousChannelIds.has(ch)) || [...previousChannelIds].some((ch) => !currentChannelIds.has(ch));
|
|
@@ -4160,7 +4258,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4160
4258
|
log(`Failed to provision direct-chat channel for '${agent.code_name}': ${err.message}`);
|
|
4161
4259
|
}
|
|
4162
4260
|
}
|
|
4163
|
-
let lastSecretsProvisionAt =
|
|
4261
|
+
let lastSecretsProvisionAt = state2.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;
|
|
4164
4262
|
let secretsHash = knownSecretsHashes.get(agent.agent_id) ?? null;
|
|
4165
4263
|
try {
|
|
4166
4264
|
const secretsData = await api.post("/host/secrets", { agent_id: agent.agent_id });
|
|
@@ -4604,6 +4702,10 @@ async function processAgent(agent, agentStates) {
|
|
|
4604
4702
|
sessionHealthy: psResult.sessionHealthyAfter,
|
|
4605
4703
|
spawnAttempted: psResult.spawnAttempted
|
|
4606
4704
|
});
|
|
4705
|
+
if (psResult.sessionHealthyAfter) {
|
|
4706
|
+
ensureKanbanLoopArmed(agent.code_name, log).catch(() => {
|
|
4707
|
+
});
|
|
4708
|
+
}
|
|
4607
4709
|
if (stuck.shouldWarn) {
|
|
4608
4710
|
log(
|
|
4609
4711
|
`[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)`
|
|
@@ -4612,7 +4714,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4612
4714
|
log(`[persistent-session-stuck] agent=${agent.code_name} recovered from stuck state`);
|
|
4613
4715
|
}
|
|
4614
4716
|
try {
|
|
4615
|
-
checkMcpConfigDriftAndScheduleRestart(agent.code_name,
|
|
4717
|
+
checkMcpConfigDriftAndScheduleRestart(agent.code_name, getProjectDir(agent.code_name));
|
|
4616
4718
|
} catch (err) {
|
|
4617
4719
|
log(`[hot-reload] .mcp.json drift check failed for '${agent.code_name}': ${err.message}`);
|
|
4618
4720
|
}
|
|
@@ -4620,7 +4722,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4620
4722
|
const sess = getSessionState(agent.code_name);
|
|
4621
4723
|
let mcpJsonParsed = null;
|
|
4622
4724
|
try {
|
|
4623
|
-
const mcpPath = join4(
|
|
4725
|
+
const mcpPath = join4(getProjectDir(agent.code_name), ".mcp.json");
|
|
4624
4726
|
mcpJsonParsed = JSON.parse(readFileSync3(mcpPath, "utf-8"));
|
|
4625
4727
|
} catch {
|
|
4626
4728
|
}
|
|
@@ -4784,8 +4886,8 @@ async function processAgent(agent, agentStates) {
|
|
|
4784
4886
|
if (sessionMode === "persistent") {
|
|
4785
4887
|
if (isSessionHealthy(agent.code_name)) {
|
|
4786
4888
|
const todayItem = boardItems.find((b) => b.status === "todo");
|
|
4787
|
-
const
|
|
4788
|
-
injectMessage(agent.code_name, "task",
|
|
4889
|
+
const triggerMessage = todayItem ? `New work triggered. Top item: id=${todayItem.id} title=${JSON.stringify(todayItem.title)} priority=${todayItem.priority}. Call kanban_move("${todayItem.id}", "in_progress") and start working \u2014 no need to kanban_list first.` : "New work triggered. Check your kanban board with kanban_list and pick up the next item.";
|
|
4890
|
+
injectMessage(agent.code_name, "task", triggerMessage, {
|
|
4789
4891
|
task_name: "kanban-work-trigger"
|
|
4790
4892
|
}, log);
|
|
4791
4893
|
log(`[persistent-session] Work trigger injected for '${agent.code_name}'`);
|
|
@@ -4845,6 +4947,13 @@ ${item.title}${resultLine}`;
|
|
|
4845
4947
|
const displayName = agentDisplayNames.get(agent.code_name) ?? agent.code_name;
|
|
4846
4948
|
for (const item of staleItems) {
|
|
4847
4949
|
const age = Math.round((Date.now() - new Date(item.updated_at).getTime()) / 6e4);
|
|
4950
|
+
const stuckKey = `${agent.code_name}:${item.id}`;
|
|
4951
|
+
if (!stuckKanbanLogged.has(stuckKey)) {
|
|
4952
|
+
log(
|
|
4953
|
+
`[persistent-session-stuck-kanban] agent=${agent.code_name} item_id=${item.id} title=${JSON.stringify(item.title)} status=in_progress age_minutes=${age} threshold_minutes=${Math.round(STALE_TASK_THRESHOLD_MS / 6e4)}`
|
|
4954
|
+
);
|
|
4955
|
+
stuckKanbanLogged.add(stuckKey);
|
|
4956
|
+
}
|
|
4848
4957
|
log(`Stale task: '${item.title}' (id=${item.id}) in_progress for ${age}m \u2014 auto-failing for '${agent.code_name}'`);
|
|
4849
4958
|
alertedStaleItems.add(`${agent.code_name}:${item.id}`);
|
|
4850
4959
|
try {
|
|
@@ -4881,6 +4990,14 @@ In progress for ${age} minutes \u2014 auto-failed`).catch(() => {
|
|
|
4881
4990
|
}
|
|
4882
4991
|
}
|
|
4883
4992
|
}
|
|
4993
|
+
for (const key of stuckKanbanLogged) {
|
|
4994
|
+
if (key.startsWith(prefix)) {
|
|
4995
|
+
const itemId = key.slice(prefix.length);
|
|
4996
|
+
if (!freshBoard.some((b) => b.id === itemId && b.status === "in_progress")) {
|
|
4997
|
+
stuckKanbanLogged.delete(key);
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
4884
5001
|
} catch (err) {
|
|
4885
5002
|
log(`Board diff failed for '${agent.code_name}': ${err.message}`);
|
|
4886
5003
|
}
|
|
@@ -5006,19 +5123,19 @@ function clearStaleCronRunState(jobsPath) {
|
|
|
5006
5123
|
let changed = false;
|
|
5007
5124
|
const now = Date.now();
|
|
5008
5125
|
for (const job of jobs) {
|
|
5009
|
-
const
|
|
5010
|
-
if (!
|
|
5011
|
-
const runStartedAt =
|
|
5012
|
-
const isRunning =
|
|
5126
|
+
const state3 = job.state;
|
|
5127
|
+
if (!state3) continue;
|
|
5128
|
+
const runStartedAt = state3.runningAtMs ?? state3.runStartedAtMs;
|
|
5129
|
+
const isRunning = state3.running === true || state3.status === "running";
|
|
5013
5130
|
if (isRunning && runStartedAt && now - runStartedAt > STALE_RUN_TIMEOUT_MS) {
|
|
5014
|
-
|
|
5015
|
-
delete
|
|
5016
|
-
delete
|
|
5017
|
-
delete
|
|
5131
|
+
state3.running = false;
|
|
5132
|
+
delete state3.status;
|
|
5133
|
+
delete state3.runStartedAtMs;
|
|
5134
|
+
delete state3.currentRunId;
|
|
5018
5135
|
changed = true;
|
|
5019
5136
|
log(`Cleared stale running state for cron job '${job.name}' (stuck for ${Math.round((now - runStartedAt) / 6e4)}min)`);
|
|
5020
5137
|
} else if (isRunning && !runStartedAt) {
|
|
5021
|
-
|
|
5138
|
+
state3.running = false;
|
|
5022
5139
|
changed = true;
|
|
5023
5140
|
log(`Cleared stale running state for cron job '${job.name}' (no start timestamp)`);
|
|
5024
5141
|
}
|
|
@@ -5082,16 +5199,16 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
|
|
|
5082
5199
|
enabled: t.enabled ?? true,
|
|
5083
5200
|
triggered_at: t.triggered_at ?? null
|
|
5084
5201
|
}));
|
|
5085
|
-
const
|
|
5086
|
-
claudeSchedulerStates.set(codeName,
|
|
5202
|
+
const state4 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
|
|
5203
|
+
claudeSchedulerStates.set(codeName, state4);
|
|
5087
5204
|
knownTasksHashes.set(agent.agent_id, combinedHash);
|
|
5088
5205
|
log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
|
|
5089
5206
|
}
|
|
5090
5207
|
if (!claudeSchedulerStates.has(codeName)) {
|
|
5091
5208
|
claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
5092
5209
|
}
|
|
5093
|
-
const
|
|
5094
|
-
const ready = getReadyTasks(
|
|
5210
|
+
const state3 = claudeSchedulerStates.get(codeName);
|
|
5211
|
+
const ready = getReadyTasks(state3, inFlightClaudeTasks);
|
|
5095
5212
|
if (ready.length === 0) return;
|
|
5096
5213
|
for (const task of ready) {
|
|
5097
5214
|
if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) break;
|
|
@@ -5179,7 +5296,7 @@ async function fetchPriorScheduledRuns(agentId, taskId) {
|
|
|
5179
5296
|
}
|
|
5180
5297
|
}
|
|
5181
5298
|
async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
5182
|
-
const projectDir =
|
|
5299
|
+
const projectDir = getProjectDir2(codeName);
|
|
5183
5300
|
const mcpConfigPath = join4(projectDir, ".mcp.json");
|
|
5184
5301
|
let runId = null;
|
|
5185
5302
|
let kanbanItemId = null;
|
|
@@ -5372,8 +5489,8 @@ async function processClaudeTaskResult(codeName, agentId, templateId, rawOutput,
|
|
|
5372
5489
|
}
|
|
5373
5490
|
}
|
|
5374
5491
|
function fireClaudeWorkTrigger(codeName, agentId, boardItems) {
|
|
5375
|
-
const
|
|
5376
|
-
const kanbanTask = findTaskByTemplate(
|
|
5492
|
+
const state3 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
|
|
5493
|
+
const kanbanTask = findTaskByTemplate(state3, "kanban-work");
|
|
5377
5494
|
if (!kanbanTask) {
|
|
5378
5495
|
log(`[claude-scheduler] Work trigger: no kanban-work task found for '${codeName}'`);
|
|
5379
5496
|
return;
|
|
@@ -5400,7 +5517,7 @@ var persistentSessionStuckTracker = new PersistentSessionStuckTracker();
|
|
|
5400
5517
|
var claudeAuthTupleBySession = /* @__PURE__ */ new Map();
|
|
5401
5518
|
async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
5402
5519
|
const codeName = agent.code_name;
|
|
5403
|
-
const projectDir =
|
|
5520
|
+
const projectDir = getProjectDir(codeName);
|
|
5404
5521
|
const mcpConfigPath = join4(projectDir, ".mcp.json");
|
|
5405
5522
|
const claudeMdPath = join4(projectDir, "CLAUDE.md");
|
|
5406
5523
|
const teamSettingsForTz = refreshData.team?.settings;
|
|
@@ -5556,6 +5673,11 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
|
|
|
5556
5673
|
} catch (err) {
|
|
5557
5674
|
log(`[persistent-session] Failed to provision isolation hook for '${codeName}': ${err.message}`);
|
|
5558
5675
|
}
|
|
5676
|
+
try {
|
|
5677
|
+
provisionOrientHook(codeName);
|
|
5678
|
+
} catch (err) {
|
|
5679
|
+
log(`[persistent-session] Failed to provision orient hook for '${codeName}': ${err.message}`);
|
|
5680
|
+
}
|
|
5559
5681
|
const sessionRunResult = await startRun({
|
|
5560
5682
|
agent_id: agent.agent_id,
|
|
5561
5683
|
source_type: "system",
|
|
@@ -5616,13 +5738,18 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
|
|
|
5616
5738
|
} else if (!claudeSchedulerStates.has(codeName)) {
|
|
5617
5739
|
claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
5618
5740
|
}
|
|
5619
|
-
const
|
|
5620
|
-
if (
|
|
5621
|
-
const ready = getReadyTasks(
|
|
5741
|
+
const state3 = claudeSchedulerStates.get(codeName);
|
|
5742
|
+
if (state3) {
|
|
5743
|
+
const ready = getReadyTasks(state3, inFlightClaudeTasks);
|
|
5622
5744
|
if (ready.length > 0) {
|
|
5623
5745
|
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(", ")}`);
|
|
5624
5746
|
}
|
|
5625
5747
|
for (const task of ready) {
|
|
5748
|
+
if (KANBAN_WORK_TEMPLATES.has(task.templateId) && isKanbanWorkCronDisabled() && isKanbanLoopArmedForCurrentSession(codeName)) {
|
|
5749
|
+
const updated = markTaskFired(codeName, task.taskId, "ok");
|
|
5750
|
+
claudeSchedulerStates.set(codeName, updated);
|
|
5751
|
+
continue;
|
|
5752
|
+
}
|
|
5626
5753
|
if (KANBAN_WORK_TEMPLATES.has(task.templateId) && !hasActionableItems(boardItems)) {
|
|
5627
5754
|
log(`[persistent-session] Skipping '${task.name}' for '${codeName}' \u2014 board is empty`);
|
|
5628
5755
|
const updated = markTaskFired(codeName, task.taskId, "ok");
|
|
@@ -5845,7 +5972,7 @@ function ensureRealtimeKanbanStarted(agentStates) {
|
|
|
5845
5972
|
if (agentFw === "claude-code") {
|
|
5846
5973
|
const boardItems = kanbanBoardCache.get(agent.codeName) ?? [];
|
|
5847
5974
|
if (isSessionHealthy(agent.codeName)) {
|
|
5848
|
-
injectMessage(agent.codeName, "task", `New task added to your board:
|
|
5975
|
+
injectMessage(agent.codeName, "task", `New task added to your board: id=${item.id} title=${JSON.stringify(item.title)} priority=${item.priority}. Call kanban_move("${item.id}", "in_progress") and start working.`, {
|
|
5849
5976
|
task_name: "kanban-work-trigger"
|
|
5850
5977
|
}, log);
|
|
5851
5978
|
log(`[realtime] Injected kanban-work trigger for '${agent.codeName}': "${item.title}"`);
|
|
@@ -6084,6 +6211,10 @@ var STANDUP_TEMPLATES = /* @__PURE__ */ new Set(["daily-standup", "end-of-day-su
|
|
|
6084
6211
|
var TASK_UPDATE_TEMPLATES = /* @__PURE__ */ new Set(["hourly-status", "task-update"]);
|
|
6085
6212
|
var PLAN_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan"]);
|
|
6086
6213
|
var KANBAN_WORK_TEMPLATES = /* @__PURE__ */ new Set(["kanban-work"]);
|
|
6214
|
+
function isKanbanWorkCronDisabled() {
|
|
6215
|
+
const v = process.env["AGT_DISABLE_KANBAN_WORK_CRON"];
|
|
6216
|
+
return v === "1" || v?.toLowerCase() === "true";
|
|
6217
|
+
}
|
|
6087
6218
|
var BOARD_INJECT_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan", "task-update", "hourly-status", "end-of-day-summary", "kanban-work"]);
|
|
6088
6219
|
var ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["backlog", "todo", "in_progress"]);
|
|
6089
6220
|
function hasActionableItems(items) {
|
|
@@ -6922,7 +7053,7 @@ async function processClaudePairSessions(agents) {
|
|
|
6922
7053
|
killPairSession,
|
|
6923
7054
|
pairTmuxSession,
|
|
6924
7055
|
finalizeClaudePairOnboarding
|
|
6925
|
-
} = await import("../claude-pair-runtime-
|
|
7056
|
+
} = await import("../claude-pair-runtime-LZAGWNME.js");
|
|
6926
7057
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
6927
7058
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
6928
7059
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -7497,8 +7628,8 @@ function startManager(opts) {
|
|
|
7497
7628
|
const raw = readFileSync3(stateFile, "utf-8");
|
|
7498
7629
|
const parsed = JSON.parse(raw);
|
|
7499
7630
|
if (Array.isArray(parsed.agents)) {
|
|
7500
|
-
|
|
7501
|
-
log(`[startup] rehydrated ${
|
|
7631
|
+
state2.agents = parsed.agents;
|
|
7632
|
+
log(`[startup] rehydrated ${state2.agents.length} agent state(s) from ${stateFile}`);
|
|
7502
7633
|
}
|
|
7503
7634
|
}
|
|
7504
7635
|
} catch (err) {
|
|
@@ -7573,7 +7704,7 @@ function deployMcpAssets() {
|
|
|
7573
7704
|
let mcpSourceDir = "";
|
|
7574
7705
|
let dir = moduleDir;
|
|
7575
7706
|
for (let i = 0; i < 6; i++) {
|
|
7576
|
-
const candidate = join4(dir, "mcp");
|
|
7707
|
+
const candidate = join4(dir, "dist", "mcp");
|
|
7577
7708
|
if (existsSync3(join4(candidate, "index.js"))) {
|
|
7578
7709
|
mcpSourceDir = candidate;
|
|
7579
7710
|
break;
|
|
@@ -7678,6 +7809,7 @@ export {
|
|
|
7678
7809
|
extractCharterSlackPeers,
|
|
7679
7810
|
extractCharterTelegramPeers,
|
|
7680
7811
|
hasRevokedResiduals,
|
|
7812
|
+
isKanbanWorkCronDisabled,
|
|
7681
7813
|
markAgentForFreshMemorySync,
|
|
7682
7814
|
maybeUpgradeClaudeCode,
|
|
7683
7815
|
shouldSkipRevokedCleanup,
|