@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.
@@ -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-3LVAPC3T.js");
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-KXWTU5YT.js.map
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
- resolveChannels,
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 as getProjectDir2,
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-QO2FOMWJ.js";
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 state2 = presenceReaperState.get(stateKey(codeName, key));
428
- if (state2) {
429
- state2.attempts = 0;
430
- state2.lastSeenLiveAt = now();
431
- state2.lastAttemptedSessionStartedAt = null;
432
- state2.gaveUpLogged = false;
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 state2 = presenceReaperState.get(sk) ?? {
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 (state2.lastAttemptedSessionStartedAt !== sessionStartedAt) {
454
- state2.attempts += 1;
455
- state2.lastAttemptedSessionStartedAt = sessionStartedAt;
459
+ if (state3.lastAttemptedSessionStartedAt !== sessionStartedAt) {
460
+ state3.attempts += 1;
461
+ state3.lastAttemptedSessionStartedAt = sessionStartedAt;
456
462
  }
457
- presenceReaperState.set(sk, state2);
458
- if (state2.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {
463
+ presenceReaperState.set(sk, state3);
464
+ if (state3.attempts > MAX_PRESENCE_RESTART_ATTEMPTS) {
459
465
  givenUp.push(key);
460
- if (!state2.gaveUpLogged) {
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
- state2.gaveUpLogged = true;
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(state2) {
2218
- return state2.gatewayRunning || state2.portAllocated || state2.provisionDirExists;
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 = 30 * 60 * 1e3;
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 state = {
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 agtCliVersion = true ? "0.21.9" : "dev";
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 state2 = job.state;
3146
- if (!state2) continue;
3147
- const runStartedAt = state2.runStartedAtMs;
3148
- const isRunning = state2.status === "running" || state2.running === true;
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 = state.agents[0];
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-3LVAPC3T.js");
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 = state.agents.find((a) => a.agentId === agent.agent_id);
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 = state.agents.find((a) => a.agentId === agent.agent_id);
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: state.agents,
3550
+ priorAgents: state2.agents,
3462
3551
  restartAcks
3463
3552
  });
3464
3553
  if (restartAckStateChanged) {
3465
3554
  try {
3466
- const ackedState = { ...state, agents: agentStates };
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 state.agents) {
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
- state = {
3585
- ...state,
3673
+ state2 = {
3674
+ ...state2,
3586
3675
  lastPollAt: (/* @__PURE__ */ new Date()).toISOString(),
3587
- pollCount: state.pollCount + 1,
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
- state.errorCount++;
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 = state.agents.find((a) => a.agentId === agent.agent_id);
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 = state.agents.find((a) => a.agentId === agent.agent_id)?.lastProvisionAt ?? null;
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 = state.agents.find((a) => a.agentId === agent.agent_id)?.lastSecretsProvisionAt ?? null;
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, getProjectDir2(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(getProjectDir2(agent.code_name), ".mcp.json");
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 taskHint = todayItem ? ` Top item: "${todayItem.title}" (priority ${todayItem.priority}).` : "";
4789
- injectMessage(agent.code_name, "task", `New work triggered. Check your kanban board with kanban_list and pick up the next item.${taskHint}`, {
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 state2 = job.state;
5011
- if (!state2) continue;
5012
- const runStartedAt = state2.runningAtMs ?? state2.runStartedAtMs;
5013
- const isRunning = state2.running === true || state2.status === "running";
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
- state2.running = false;
5016
- delete state2.status;
5017
- delete state2.runStartedAtMs;
5018
- delete state2.currentRunId;
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
- state2.running = false;
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 state3 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
5087
- claudeSchedulerStates.set(codeName, state3);
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 state2 = claudeSchedulerStates.get(codeName);
5095
- const ready = getReadyTasks(state2, inFlightClaudeTasks);
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 = getProjectDir(codeName);
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 state2 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
5377
- const kanbanTask = findTaskByTemplate(state2, "kanban-work");
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 = getProjectDir2(codeName);
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 state2 = claudeSchedulerStates.get(codeName);
5626
- if (state2) {
5627
- const ready = getReadyTasks(state2, inFlightClaudeTasks);
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: "${item.title}" (priority ${item.priority}). Pick it up \u2014 move to in_progress and start working.`, {
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-KXWTU5YT.js");
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
- state.agents = parsed.agents;
7507
- log(`[startup] rehydrated ${state.agents.length} agent state(s) from ${stateFile}`);
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,