@integrity-labs/agt-cli 0.21.9 → 0.22.1
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-3ZO3LKA2.js → chunk-7AOVUCPO.js} +68 -3145
- package/dist/chunk-7AOVUCPO.js.map +1 -0
- package/dist/{chunk-QO2FOMWJ.js → chunk-KTPMEVEO.js} +84 -4
- package/dist/chunk-KTPMEVEO.js.map +1 -0
- package/dist/chunk-ZNRQSPPM.js +3151 -0
- package/dist/chunk-ZNRQSPPM.js.map +1 -0
- package/dist/{claude-pair-runtime-KXWTU5YT.js → claude-pair-runtime-L35F6K5E.js} +2 -2
- package/dist/lib/manager-worker.js +210 -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-3LVAPC3T.js → persistent-session-XG4QK2U5.js} +11 -2
- package/dist/responsiveness-probe-LIXEVHUJ.js +32 -0
- package/dist/responsiveness-probe-LIXEVHUJ.js.map +1 -0
- package/package.json +3 -4
- package/dist/chunk-3ZO3LKA2.js.map +0 -1
- package/dist/chunk-QO2FOMWJ.js.map +0 -1
- /package/dist/{claude-pair-runtime-KXWTU5YT.js.map → claude-pair-runtime-L35F6K5E.js.map} +0 -0
- /package/{mcp → dist/mcp}/direct-chat-channel.js +0 -0
- /package/dist/{persistent-session-3LVAPC3T.js.map → persistent-session-XG4QK2U5.js.map} +0 -0
|
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
|
|
|
100
100
|
return { ok: true };
|
|
101
101
|
} catch {
|
|
102
102
|
}
|
|
103
|
-
const { resolveClaudeBinary } = await import("./persistent-session-
|
|
103
|
+
const { resolveClaudeBinary } = await import("./persistent-session-XG4QK2U5.js");
|
|
104
104
|
const claudeBin = resolveClaudeBinary();
|
|
105
105
|
const pairEnv = {
|
|
106
106
|
...process.env,
|
|
@@ -373,4 +373,4 @@ export {
|
|
|
373
373
|
startClaudePair,
|
|
374
374
|
submitClaudePairCode
|
|
375
375
|
};
|
|
376
|
-
//# sourceMappingURL=claude-pair-runtime-
|
|
376
|
+
//# sourceMappingURL=claude-pair-runtime-L35F6K5E.js.map
|
|
@@ -1,32 +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,
|
|
20
11
|
provisionOrientHook,
|
|
21
12
|
provisionStopHook,
|
|
22
|
-
requireHost
|
|
23
|
-
|
|
24
|
-
resolveDmTarget,
|
|
25
|
-
wrapScheduledTaskPrompt
|
|
26
|
-
} from "../chunk-3ZO3LKA2.js";
|
|
13
|
+
requireHost
|
|
14
|
+
} from "../chunk-7AOVUCPO.js";
|
|
27
15
|
import {
|
|
28
16
|
findTaskByTemplate,
|
|
29
|
-
getProjectDir,
|
|
17
|
+
getProjectDir as getProjectDir2,
|
|
30
18
|
getReadyTasks,
|
|
31
19
|
loadSchedulerState,
|
|
32
20
|
markTaskFired,
|
|
@@ -34,16 +22,19 @@ import {
|
|
|
34
22
|
} from "../chunk-TS7W4YNI.js";
|
|
35
23
|
import {
|
|
36
24
|
buildAllowedTools,
|
|
25
|
+
ensureKanbanLoopArmed,
|
|
37
26
|
getLastFailureContext,
|
|
38
|
-
getProjectDir
|
|
27
|
+
getProjectDir,
|
|
39
28
|
getSessionState,
|
|
40
29
|
injectMessage,
|
|
41
30
|
isAgentIdle,
|
|
31
|
+
isKanbanLoopArmedForCurrentSession,
|
|
42
32
|
isSessionHealthy,
|
|
43
33
|
isStaleForToday,
|
|
44
34
|
parsePsRows,
|
|
45
35
|
peekCurrentSession,
|
|
46
36
|
prepareForRespawn,
|
|
37
|
+
readPaneLogTail,
|
|
47
38
|
reapOrphanChannelMcps,
|
|
48
39
|
resetRestartCount,
|
|
49
40
|
resolveClaudeBinary,
|
|
@@ -52,7 +43,22 @@ import {
|
|
|
52
43
|
stopAllSessionsAndWait,
|
|
53
44
|
stopPersistentSession,
|
|
54
45
|
takeZombieDetection
|
|
55
|
-
} from "../chunk-
|
|
46
|
+
} from "../chunk-KTPMEVEO.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-ZNRQSPPM.js";
|
|
56
62
|
|
|
57
63
|
// src/lib/manager-worker.ts
|
|
58
64
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -424,12 +430,12 @@ function reapMissingMcpSessions(args) {
|
|
|
424
430
|
if (!missingRaw.includes(key)) liveKeys.add(key);
|
|
425
431
|
}
|
|
426
432
|
for (const key of liveKeys) {
|
|
427
|
-
const
|
|
428
|
-
if (
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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;
|
|
433
439
|
}
|
|
434
440
|
}
|
|
435
441
|
if (missing.length === 0) {
|
|
@@ -444,24 +450,24 @@ function reapMissingMcpSessions(args) {
|
|
|
444
450
|
const active = [];
|
|
445
451
|
for (const key of missing) {
|
|
446
452
|
const sk = stateKey(codeName, key);
|
|
447
|
-
const
|
|
453
|
+
const state3 = presenceReaperState.get(sk) ?? {
|
|
448
454
|
attempts: 0,
|
|
449
455
|
lastSeenLiveAt: null,
|
|
450
456
|
lastAttemptedSessionStartedAt: null,
|
|
451
457
|
gaveUpLogged: false
|
|
452
458
|
};
|
|
453
|
-
if (
|
|
454
|
-
|
|
455
|
-
|
|
459
|
+
if (state3.lastAttemptedSessionStartedAt !== sessionStartedAt) {
|
|
460
|
+
state3.attempts += 1;
|
|
461
|
+
state3.lastAttemptedSessionStartedAt = sessionStartedAt;
|
|
456
462
|
}
|
|
457
|
-
presenceReaperState.set(sk,
|
|
458
|
-
if (
|
|
463
|
+
presenceReaperState.set(sk, state3);
|
|
464
|
+
if (state3.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {
|
|
459
465
|
givenUp.push(key);
|
|
460
|
-
if (!
|
|
466
|
+
if (!state3.gaveUpLogged) {
|
|
461
467
|
log2(
|
|
462
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)`
|
|
463
469
|
);
|
|
464
|
-
|
|
470
|
+
state3.gaveUpLogged = true;
|
|
465
471
|
if (onGiveUp) {
|
|
466
472
|
try {
|
|
467
473
|
onGiveUp(codeName, key);
|
|
@@ -542,6 +548,61 @@ function decideChannelRestart(input) {
|
|
|
542
548
|
return { restart: true, added, removed };
|
|
543
549
|
}
|
|
544
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
|
+
|
|
545
606
|
// src/lib/poll-backoff.ts
|
|
546
607
|
var POLL_BACKOFF_BASE_MS = 1e3;
|
|
547
608
|
var POLL_BACKOFF_MAX_MS = 6e4;
|
|
@@ -2214,8 +2275,8 @@ var KNOWN_SAFE_TAIL_SIGNATURES = /* @__PURE__ */ new Set(["session_id_in_use"]);
|
|
|
2214
2275
|
function shouldSkipRevokedCleanup(previousKnownStatus) {
|
|
2215
2276
|
return previousKnownStatus === "revoked";
|
|
2216
2277
|
}
|
|
2217
|
-
function hasRevokedResiduals(
|
|
2218
|
-
return
|
|
2278
|
+
function hasRevokedResiduals(state3) {
|
|
2279
|
+
return state3.gatewayRunning || state3.portAllocated || state3.provisionDirExists;
|
|
2219
2280
|
}
|
|
2220
2281
|
var knownVersions = /* @__PURE__ */ new Map();
|
|
2221
2282
|
var knownStatuses = /* @__PURE__ */ new Map();
|
|
@@ -2335,8 +2396,14 @@ function managedToolkitMapKey(agentId, serverId) {
|
|
|
2335
2396
|
var lastCronRunTs = /* @__PURE__ */ new Map();
|
|
2336
2397
|
var lastWorkTriggerAt = /* @__PURE__ */ new Map();
|
|
2337
2398
|
var alertedStaleItems = /* @__PURE__ */ new Set();
|
|
2399
|
+
var stuckKanbanLogged = /* @__PURE__ */ new Set();
|
|
2338
2400
|
var apiKeyStatusCache = /* @__PURE__ */ new Map();
|
|
2339
|
-
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
|
+
})();
|
|
2340
2407
|
var alertSlackWebhook = null;
|
|
2341
2408
|
var alertedJobs = /* @__PURE__ */ new Set();
|
|
2342
2409
|
var taskDisplayInfo = /* @__PURE__ */ new Map();
|
|
@@ -2345,7 +2412,7 @@ var codeNameToAgentId = /* @__PURE__ */ new Map();
|
|
|
2345
2412
|
var agentChannelTokens = /* @__PURE__ */ new Map();
|
|
2346
2413
|
var activeChannels = /* @__PURE__ */ new Map();
|
|
2347
2414
|
var gatewaysStartedThisCycle = /* @__PURE__ */ new Set();
|
|
2348
|
-
var
|
|
2415
|
+
var state2 = {
|
|
2349
2416
|
pid: process.pid,
|
|
2350
2417
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2351
2418
|
lastPollAt: null,
|
|
@@ -2400,7 +2467,8 @@ function clearAgentCaches(agentId, codeName) {
|
|
|
2400
2467
|
var cachedFrameworkVersion = null;
|
|
2401
2468
|
var lastVersionCheckAt = 0;
|
|
2402
2469
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
2403
|
-
var
|
|
2470
|
+
var lastResponsivenessProbeAt = 0;
|
|
2471
|
+
var agtCliVersion = true ? "0.22.1" : "dev";
|
|
2404
2472
|
function resolveBrewPath(execFileSync4) {
|
|
2405
2473
|
try {
|
|
2406
2474
|
const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -3142,10 +3210,10 @@ function isGatewayHung(codeName) {
|
|
|
3142
3210
|
if (!Array.isArray(jobs)) return false;
|
|
3143
3211
|
const now = Date.now();
|
|
3144
3212
|
for (const job of jobs) {
|
|
3145
|
-
const
|
|
3146
|
-
if (!
|
|
3147
|
-
const runStartedAt =
|
|
3148
|
-
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;
|
|
3149
3217
|
if (isRunning && runStartedAt && now - runStartedAt > GATEWAY_HUNG_TIMEOUT_MS) {
|
|
3150
3218
|
return true;
|
|
3151
3219
|
}
|
|
@@ -3346,7 +3414,7 @@ async function pollCycle() {
|
|
|
3346
3414
|
const now = Date.now();
|
|
3347
3415
|
if (now - lastVersionCheckAt > VERSION_CHECK_INTERVAL_MS) {
|
|
3348
3416
|
try {
|
|
3349
|
-
const firstAgent =
|
|
3417
|
+
const firstAgent = state2.agents[0];
|
|
3350
3418
|
const versionAdapter = firstAgent ? resolveAgentFramework(firstAgent.codeName) : getFramework("openclaw");
|
|
3351
3419
|
if (versionAdapter.getVersion) {
|
|
3352
3420
|
cachedFrameworkVersion = await versionAdapter.getVersion();
|
|
@@ -3357,7 +3425,7 @@ async function pollCycle() {
|
|
|
3357
3425
|
}
|
|
3358
3426
|
try {
|
|
3359
3427
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
3360
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
3428
|
+
const { collectDiagnostics } = await import("../persistent-session-XG4QK2U5.js");
|
|
3361
3429
|
const diagCodeNames = [...persistentSessionAgents];
|
|
3362
3430
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
3363
3431
|
let tailscaleHostname;
|
|
@@ -3404,13 +3472,34 @@ async function pollCycle() {
|
|
|
3404
3472
|
} catch (err) {
|
|
3405
3473
|
log(`Heartbeat failed: ${err.message}`);
|
|
3406
3474
|
}
|
|
3475
|
+
try {
|
|
3476
|
+
const {
|
|
3477
|
+
collectResponsivenessProbes,
|
|
3478
|
+
getResponsivenessIntervalMs
|
|
3479
|
+
} = await import("../responsiveness-probe-LIXEVHUJ.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
|
+
}
|
|
3407
3496
|
const data = await api.post("/host/agents", { host_id: hostId });
|
|
3408
3497
|
const agents = data.agents ?? [];
|
|
3409
3498
|
const restartAcks = /* @__PURE__ */ new Map();
|
|
3410
3499
|
for (const agent of agents) {
|
|
3411
3500
|
const requested = agent.restart_requested_at ?? null;
|
|
3412
3501
|
if (!requested) continue;
|
|
3413
|
-
const prev =
|
|
3502
|
+
const prev = state2.agents.find((a) => a.agentId === agent.agent_id);
|
|
3414
3503
|
const lastProcessed = prev?.lastRestartProcessedAt ?? null;
|
|
3415
3504
|
if (lastProcessed && Date.parse(lastProcessed) >= Date.parse(requested)) continue;
|
|
3416
3505
|
log(`[restart] Dashboard requested restart for '${agent.code_name}' at ${requested}`);
|
|
@@ -3433,7 +3522,7 @@ async function pollCycle() {
|
|
|
3433
3522
|
await processAgent(agent, agentStates);
|
|
3434
3523
|
} catch (err) {
|
|
3435
3524
|
log(`Error processing agent '${agent.code_name}': ${err.message}`);
|
|
3436
|
-
const existing =
|
|
3525
|
+
const existing = state2.agents.find((a) => a.agentId === agent.agent_id);
|
|
3437
3526
|
if (existing) {
|
|
3438
3527
|
agentStates.push(existing);
|
|
3439
3528
|
} else {
|
|
@@ -3458,12 +3547,12 @@ async function pollCycle() {
|
|
|
3458
3547
|
}
|
|
3459
3548
|
const restartAckStateChanged = applyRestartAcks({
|
|
3460
3549
|
agentStates,
|
|
3461
|
-
priorAgents:
|
|
3550
|
+
priorAgents: state2.agents,
|
|
3462
3551
|
restartAcks
|
|
3463
3552
|
});
|
|
3464
3553
|
if (restartAckStateChanged) {
|
|
3465
3554
|
try {
|
|
3466
|
-
const ackedState = { ...
|
|
3555
|
+
const ackedState = { ...state2, agents: agentStates };
|
|
3467
3556
|
writeFileSync3(getStateFile(), JSON.stringify(ackedState, null, 2));
|
|
3468
3557
|
} catch (err) {
|
|
3469
3558
|
log(`[restart] failed to persist ack immediately: ${err.message}`);
|
|
@@ -3481,7 +3570,7 @@ async function pollCycle() {
|
|
|
3481
3570
|
} catch {
|
|
3482
3571
|
}
|
|
3483
3572
|
const currentIds = new Set(agents.map((a) => a.agent_id));
|
|
3484
|
-
for (const prev of
|
|
3573
|
+
for (const prev of state2.agents) {
|
|
3485
3574
|
if (!currentIds.has(prev.agentId)) {
|
|
3486
3575
|
log(`Agent '${prev.codeName}' removed from host (deleted or unassigned)`);
|
|
3487
3576
|
const adapter = resolveAgentFramework(prev.codeName);
|
|
@@ -3581,19 +3670,19 @@ async function pollCycle() {
|
|
|
3581
3670
|
}
|
|
3582
3671
|
} catch {
|
|
3583
3672
|
}
|
|
3584
|
-
|
|
3585
|
-
...
|
|
3673
|
+
state2 = {
|
|
3674
|
+
...state2,
|
|
3586
3675
|
lastPollAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3587
|
-
pollCount:
|
|
3676
|
+
pollCount: state2.pollCount + 1,
|
|
3588
3677
|
agents: agentStates
|
|
3589
3678
|
};
|
|
3590
3679
|
if (consecutivePollFailures > 0) {
|
|
3591
3680
|
log(`[poll-backoff] recovered after ${consecutivePollFailures} failure(s), resuming normal interval`);
|
|
3592
3681
|
consecutivePollFailures = 0;
|
|
3593
3682
|
}
|
|
3594
|
-
send({ type: "state-update", state });
|
|
3683
|
+
send({ type: "state-update", state: state2 });
|
|
3595
3684
|
} catch (err) {
|
|
3596
|
-
|
|
3685
|
+
state2.errorCount++;
|
|
3597
3686
|
const message = err.message;
|
|
3598
3687
|
log(`Poll error: ${message}`);
|
|
3599
3688
|
send({ type: "error", message });
|
|
@@ -3623,6 +3712,14 @@ async function processAgent(agent, agentStates) {
|
|
|
3623
3712
|
if (agent.framework) {
|
|
3624
3713
|
agentFrameworkCache.set(agent.code_name, agent.framework);
|
|
3625
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
|
+
}
|
|
3626
3723
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3627
3724
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
3628
3725
|
let agentDir = join4(adapter.getAgentDir(agent.code_name), "provision");
|
|
@@ -3741,7 +3838,7 @@ async function processAgent(agent, agentStates) {
|
|
|
3741
3838
|
});
|
|
3742
3839
|
} catch (err) {
|
|
3743
3840
|
log(`Refresh failed for '${agent.code_name}': ${err.message}`);
|
|
3744
|
-
const existing =
|
|
3841
|
+
const existing = state2.agents.find((a) => a.agentId === agent.agent_id);
|
|
3745
3842
|
agentStates.push(existing ?? {
|
|
3746
3843
|
agentId: agent.agent_id,
|
|
3747
3844
|
codeName: agent.code_name,
|
|
@@ -3797,7 +3894,7 @@ async function processAgent(agent, agentStates) {
|
|
|
3797
3894
|
const charterVersion = refreshData.charter.version;
|
|
3798
3895
|
const toolsVersion = refreshData.tools.version;
|
|
3799
3896
|
const known = knownVersions.get(agent.agent_id);
|
|
3800
|
-
let lastProvisionAt =
|
|
3897
|
+
let lastProvisionAt = state2.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
|
|
3801
3898
|
const currentChannelIds = launchableChannelIds(refreshData.channel_configs);
|
|
3802
3899
|
const previousChannelIds = knownChannels.get(agent.agent_id);
|
|
3803
3900
|
const channelsChanged = !previousChannelIds || currentChannelIds.size !== previousChannelIds.size || [...currentChannelIds].some((ch) => !previousChannelIds.has(ch)) || [...previousChannelIds].some((ch) => !currentChannelIds.has(ch));
|
|
@@ -4161,7 +4258,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4161
4258
|
log(`Failed to provision direct-chat channel for '${agent.code_name}': ${err.message}`);
|
|
4162
4259
|
}
|
|
4163
4260
|
}
|
|
4164
|
-
let lastSecretsProvisionAt =
|
|
4261
|
+
let lastSecretsProvisionAt = state2.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;
|
|
4165
4262
|
let secretsHash = knownSecretsHashes.get(agent.agent_id) ?? null;
|
|
4166
4263
|
try {
|
|
4167
4264
|
const secretsData = await api.post("/host/secrets", { agent_id: agent.agent_id });
|
|
@@ -4605,6 +4702,10 @@ async function processAgent(agent, agentStates) {
|
|
|
4605
4702
|
sessionHealthy: psResult.sessionHealthyAfter,
|
|
4606
4703
|
spawnAttempted: psResult.spawnAttempted
|
|
4607
4704
|
});
|
|
4705
|
+
if (psResult.sessionHealthyAfter) {
|
|
4706
|
+
ensureKanbanLoopArmed(agent.code_name, log).catch(() => {
|
|
4707
|
+
});
|
|
4708
|
+
}
|
|
4608
4709
|
if (stuck.shouldWarn) {
|
|
4609
4710
|
log(
|
|
4610
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)`
|
|
@@ -4613,7 +4714,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4613
4714
|
log(`[persistent-session-stuck] agent=${agent.code_name} recovered from stuck state`);
|
|
4614
4715
|
}
|
|
4615
4716
|
try {
|
|
4616
|
-
checkMcpConfigDriftAndScheduleRestart(agent.code_name,
|
|
4717
|
+
checkMcpConfigDriftAndScheduleRestart(agent.code_name, getProjectDir(agent.code_name));
|
|
4617
4718
|
} catch (err) {
|
|
4618
4719
|
log(`[hot-reload] .mcp.json drift check failed for '${agent.code_name}': ${err.message}`);
|
|
4619
4720
|
}
|
|
@@ -4621,7 +4722,7 @@ async function processAgent(agent, agentStates) {
|
|
|
4621
4722
|
const sess = getSessionState(agent.code_name);
|
|
4622
4723
|
let mcpJsonParsed = null;
|
|
4623
4724
|
try {
|
|
4624
|
-
const mcpPath = join4(
|
|
4725
|
+
const mcpPath = join4(getProjectDir(agent.code_name), ".mcp.json");
|
|
4625
4726
|
mcpJsonParsed = JSON.parse(readFileSync3(mcpPath, "utf-8"));
|
|
4626
4727
|
} catch {
|
|
4627
4728
|
}
|
|
@@ -4785,8 +4886,8 @@ async function processAgent(agent, agentStates) {
|
|
|
4785
4886
|
if (sessionMode === "persistent") {
|
|
4786
4887
|
if (isSessionHealthy(agent.code_name)) {
|
|
4787
4888
|
const todayItem = boardItems.find((b) => b.status === "todo");
|
|
4788
|
-
const
|
|
4789
|
-
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, {
|
|
4790
4891
|
task_name: "kanban-work-trigger"
|
|
4791
4892
|
}, log);
|
|
4792
4893
|
log(`[persistent-session] Work trigger injected for '${agent.code_name}'`);
|
|
@@ -4846,6 +4947,13 @@ ${item.title}${resultLine}`;
|
|
|
4846
4947
|
const displayName = agentDisplayNames.get(agent.code_name) ?? agent.code_name;
|
|
4847
4948
|
for (const item of staleItems) {
|
|
4848
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
|
+
}
|
|
4849
4957
|
log(`Stale task: '${item.title}' (id=${item.id}) in_progress for ${age}m \u2014 auto-failing for '${agent.code_name}'`);
|
|
4850
4958
|
alertedStaleItems.add(`${agent.code_name}:${item.id}`);
|
|
4851
4959
|
try {
|
|
@@ -4882,6 +4990,14 @@ In progress for ${age} minutes \u2014 auto-failed`).catch(() => {
|
|
|
4882
4990
|
}
|
|
4883
4991
|
}
|
|
4884
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
|
+
}
|
|
4885
5001
|
} catch (err) {
|
|
4886
5002
|
log(`Board diff failed for '${agent.code_name}': ${err.message}`);
|
|
4887
5003
|
}
|
|
@@ -5007,19 +5123,19 @@ function clearStaleCronRunState(jobsPath) {
|
|
|
5007
5123
|
let changed = false;
|
|
5008
5124
|
const now = Date.now();
|
|
5009
5125
|
for (const job of jobs) {
|
|
5010
|
-
const
|
|
5011
|
-
if (!
|
|
5012
|
-
const runStartedAt =
|
|
5013
|
-
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";
|
|
5014
5130
|
if (isRunning && runStartedAt && now - runStartedAt > STALE_RUN_TIMEOUT_MS) {
|
|
5015
|
-
|
|
5016
|
-
delete
|
|
5017
|
-
delete
|
|
5018
|
-
delete
|
|
5131
|
+
state3.running = false;
|
|
5132
|
+
delete state3.status;
|
|
5133
|
+
delete state3.runStartedAtMs;
|
|
5134
|
+
delete state3.currentRunId;
|
|
5019
5135
|
changed = true;
|
|
5020
5136
|
log(`Cleared stale running state for cron job '${job.name}' (stuck for ${Math.round((now - runStartedAt) / 6e4)}min)`);
|
|
5021
5137
|
} else if (isRunning && !runStartedAt) {
|
|
5022
|
-
|
|
5138
|
+
state3.running = false;
|
|
5023
5139
|
changed = true;
|
|
5024
5140
|
log(`Cleared stale running state for cron job '${job.name}' (no start timestamp)`);
|
|
5025
5141
|
}
|
|
@@ -5083,16 +5199,16 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
|
|
|
5083
5199
|
enabled: t.enabled ?? true,
|
|
5084
5200
|
triggered_at: t.triggered_at ?? null
|
|
5085
5201
|
}));
|
|
5086
|
-
const
|
|
5087
|
-
claudeSchedulerStates.set(codeName,
|
|
5202
|
+
const state4 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
|
|
5203
|
+
claudeSchedulerStates.set(codeName, state4);
|
|
5088
5204
|
knownTasksHashes.set(agent.agent_id, combinedHash);
|
|
5089
5205
|
log(`[claude-scheduler] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
|
|
5090
5206
|
}
|
|
5091
5207
|
if (!claudeSchedulerStates.has(codeName)) {
|
|
5092
5208
|
claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
5093
5209
|
}
|
|
5094
|
-
const
|
|
5095
|
-
const ready = getReadyTasks(
|
|
5210
|
+
const state3 = claudeSchedulerStates.get(codeName);
|
|
5211
|
+
const ready = getReadyTasks(state3, inFlightClaudeTasks);
|
|
5096
5212
|
if (ready.length === 0) return;
|
|
5097
5213
|
for (const task of ready) {
|
|
5098
5214
|
if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) break;
|
|
@@ -5180,7 +5296,7 @@ async function fetchPriorScheduledRuns(agentId, taskId) {
|
|
|
5180
5296
|
}
|
|
5181
5297
|
}
|
|
5182
5298
|
async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
5183
|
-
const projectDir =
|
|
5299
|
+
const projectDir = getProjectDir2(codeName);
|
|
5184
5300
|
const mcpConfigPath = join4(projectDir, ".mcp.json");
|
|
5185
5301
|
let runId = null;
|
|
5186
5302
|
let kanbanItemId = null;
|
|
@@ -5373,8 +5489,8 @@ async function processClaudeTaskResult(codeName, agentId, templateId, rawOutput,
|
|
|
5373
5489
|
}
|
|
5374
5490
|
}
|
|
5375
5491
|
function fireClaudeWorkTrigger(codeName, agentId, boardItems) {
|
|
5376
|
-
const
|
|
5377
|
-
const kanbanTask = findTaskByTemplate(
|
|
5492
|
+
const state3 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
|
|
5493
|
+
const kanbanTask = findTaskByTemplate(state3, "kanban-work");
|
|
5378
5494
|
if (!kanbanTask) {
|
|
5379
5495
|
log(`[claude-scheduler] Work trigger: no kanban-work task found for '${codeName}'`);
|
|
5380
5496
|
return;
|
|
@@ -5401,7 +5517,7 @@ var persistentSessionStuckTracker = new PersistentSessionStuckTracker();
|
|
|
5401
5517
|
var claudeAuthTupleBySession = /* @__PURE__ */ new Map();
|
|
5402
5518
|
async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
5403
5519
|
const codeName = agent.code_name;
|
|
5404
|
-
const projectDir =
|
|
5520
|
+
const projectDir = getProjectDir(codeName);
|
|
5405
5521
|
const mcpConfigPath = join4(projectDir, ".mcp.json");
|
|
5406
5522
|
const claudeMdPath = join4(projectDir, "CLAUDE.md");
|
|
5407
5523
|
const teamSettingsForTz = refreshData.team?.settings;
|
|
@@ -5622,13 +5738,18 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash2("sha256").
|
|
|
5622
5738
|
} else if (!claudeSchedulerStates.has(codeName)) {
|
|
5623
5739
|
claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
|
|
5624
5740
|
}
|
|
5625
|
-
const
|
|
5626
|
-
if (
|
|
5627
|
-
const ready = getReadyTasks(
|
|
5741
|
+
const state3 = claudeSchedulerStates.get(codeName);
|
|
5742
|
+
if (state3) {
|
|
5743
|
+
const ready = getReadyTasks(state3, inFlightClaudeTasks);
|
|
5628
5744
|
if (ready.length > 0) {
|
|
5629
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(", ")}`);
|
|
5630
5746
|
}
|
|
5631
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
|
+
}
|
|
5632
5753
|
if (KANBAN_WORK_TEMPLATES.has(task.templateId) && !hasActionableItems(boardItems)) {
|
|
5633
5754
|
log(`[persistent-session] Skipping '${task.name}' for '${codeName}' \u2014 board is empty`);
|
|
5634
5755
|
const updated = markTaskFired(codeName, task.taskId, "ok");
|
|
@@ -5851,7 +5972,7 @@ function ensureRealtimeKanbanStarted(agentStates) {
|
|
|
5851
5972
|
if (agentFw === "claude-code") {
|
|
5852
5973
|
const boardItems = kanbanBoardCache.get(agent.codeName) ?? [];
|
|
5853
5974
|
if (isSessionHealthy(agent.codeName)) {
|
|
5854
|
-
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.`, {
|
|
5855
5976
|
task_name: "kanban-work-trigger"
|
|
5856
5977
|
}, log);
|
|
5857
5978
|
log(`[realtime] Injected kanban-work trigger for '${agent.codeName}': "${item.title}"`);
|
|
@@ -6090,6 +6211,10 @@ var STANDUP_TEMPLATES = /* @__PURE__ */ new Set(["daily-standup", "end-of-day-su
|
|
|
6090
6211
|
var TASK_UPDATE_TEMPLATES = /* @__PURE__ */ new Set(["hourly-status", "task-update"]);
|
|
6091
6212
|
var PLAN_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan"]);
|
|
6092
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
|
+
}
|
|
6093
6218
|
var BOARD_INJECT_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan", "task-update", "hourly-status", "end-of-day-summary", "kanban-work"]);
|
|
6094
6219
|
var ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["backlog", "todo", "in_progress"]);
|
|
6095
6220
|
function hasActionableItems(items) {
|
|
@@ -6928,7 +7053,7 @@ async function processClaudePairSessions(agents) {
|
|
|
6928
7053
|
killPairSession,
|
|
6929
7054
|
pairTmuxSession,
|
|
6930
7055
|
finalizeClaudePairOnboarding
|
|
6931
|
-
} = await import("../claude-pair-runtime-
|
|
7056
|
+
} = await import("../claude-pair-runtime-L35F6K5E.js");
|
|
6932
7057
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
6933
7058
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
6934
7059
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -7503,8 +7628,8 @@ function startManager(opts) {
|
|
|
7503
7628
|
const raw = readFileSync3(stateFile, "utf-8");
|
|
7504
7629
|
const parsed = JSON.parse(raw);
|
|
7505
7630
|
if (Array.isArray(parsed.agents)) {
|
|
7506
|
-
|
|
7507
|
-
log(`[startup] rehydrated ${
|
|
7631
|
+
state2.agents = parsed.agents;
|
|
7632
|
+
log(`[startup] rehydrated ${state2.agents.length} agent state(s) from ${stateFile}`);
|
|
7508
7633
|
}
|
|
7509
7634
|
}
|
|
7510
7635
|
} catch (err) {
|
|
@@ -7579,7 +7704,7 @@ function deployMcpAssets() {
|
|
|
7579
7704
|
let mcpSourceDir = "";
|
|
7580
7705
|
let dir = moduleDir;
|
|
7581
7706
|
for (let i = 0; i < 6; i++) {
|
|
7582
|
-
const candidate = join4(dir, "mcp");
|
|
7707
|
+
const candidate = join4(dir, "dist", "mcp");
|
|
7583
7708
|
if (existsSync3(join4(candidate, "index.js"))) {
|
|
7584
7709
|
mcpSourceDir = candidate;
|
|
7585
7710
|
break;
|
|
@@ -7684,6 +7809,7 @@ export {
|
|
|
7684
7809
|
extractCharterSlackPeers,
|
|
7685
7810
|
extractCharterTelegramPeers,
|
|
7686
7811
|
hasRevokedResiduals,
|
|
7812
|
+
isKanbanWorkCronDisabled,
|
|
7687
7813
|
markAgentForFreshMemorySync,
|
|
7688
7814
|
maybeUpgradeClaudeCode,
|
|
7689
7815
|
shouldSkipRevokedCleanup,
|