@integrity-labs/agt-cli 0.24.8 → 0.24.10

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.
Files changed (24) hide show
  1. package/dist/bin/agt.js +4 -4
  2. package/dist/bin/agt.js.map +1 -1
  3. package/dist/{chunk-53TCAGCM.js → chunk-7DRLULIO.js} +38 -26
  4. package/dist/{chunk-53TCAGCM.js.map → chunk-7DRLULIO.js.map} +1 -1
  5. package/dist/{chunk-TS7W4YNI.js → chunk-HR5T2RQF.js} +8 -2
  6. package/dist/{chunk-TS7W4YNI.js.map → chunk-HR5T2RQF.js.map} +1 -1
  7. package/dist/{chunk-3A2PMQM4.js → chunk-IEVHEZV4.js} +11 -7
  8. package/dist/chunk-IEVHEZV4.js.map +1 -0
  9. package/dist/{chunk-XW27YIEU.js → chunk-Z25YM6B4.js} +2 -2
  10. package/dist/{claude-pair-runtime-4F53C7RB.js → claude-pair-runtime-SENCKRI4.js} +2 -2
  11. package/dist/{claude-scheduler-XHJS2MZV.js → claude-scheduler-EM24LTGV.js} +2 -2
  12. package/dist/lib/manager-worker.js +255 -20
  13. package/dist/lib/manager-worker.js.map +1 -1
  14. package/dist/mcp/slack-channel.js +94 -4
  15. package/dist/mcp/telegram-channel.js +88 -0
  16. package/dist/{persistent-session-XJYOPFIP.js → persistent-session-3SNFKR2Q.js} +5 -3
  17. package/dist/{responsiveness-probe-EWZNGMTX.js → responsiveness-probe-UBNJFMAU.js} +3 -3
  18. package/package.json +1 -1
  19. package/dist/chunk-3A2PMQM4.js.map +0 -1
  20. /package/dist/{chunk-XW27YIEU.js.map → chunk-Z25YM6B4.js.map} +0 -0
  21. /package/dist/{claude-pair-runtime-4F53C7RB.js.map → claude-pair-runtime-SENCKRI4.js.map} +0 -0
  22. /package/dist/{claude-scheduler-XHJS2MZV.js.map → claude-scheduler-EM24LTGV.js.map} +0 -0
  23. /package/dist/{persistent-session-XJYOPFIP.js.map → persistent-session-3SNFKR2Q.js.map} +0 -0
  24. /package/dist/{responsiveness-probe-EWZNGMTX.js.map → responsiveness-probe-UBNJFMAU.js.map} +0 -0
@@ -1,4 +1,5 @@
1
1
  import {
2
+ ApiError,
2
3
  SUPERVISOR_RESTART_EXIT_CODE,
3
4
  api,
4
5
  estimateActiveTasksTokens,
@@ -12,7 +13,7 @@ import {
12
13
  provisionOrientHook,
13
14
  provisionStopHook,
14
15
  requireHost
15
- } from "../chunk-XW27YIEU.js";
16
+ } from "../chunk-Z25YM6B4.js";
16
17
  import {
17
18
  findTaskByTemplate,
18
19
  getProjectDir as getProjectDir2,
@@ -20,7 +21,7 @@ import {
20
21
  loadSchedulerState,
21
22
  markTaskFired,
22
23
  syncTasksToScheduler
23
- } from "../chunk-TS7W4YNI.js";
24
+ } from "../chunk-HR5T2RQF.js";
24
25
  import {
25
26
  buildAllowedTools,
26
27
  ensureKanbanLoopArmed,
@@ -28,6 +29,7 @@ import {
28
29
  getProjectDir,
29
30
  getSessionState,
30
31
  injectMessage,
32
+ injectMessageWithStatus,
31
33
  isAgentIdle,
32
34
  isAgentPromptReady,
33
35
  isKanbanLoopArmedForCurrentSession,
@@ -48,11 +50,12 @@ import {
48
50
  stopAllSessionsAndWait,
49
51
  stopPersistentSession,
50
52
  takeZombieDetection
51
- } from "../chunk-3A2PMQM4.js";
53
+ } from "../chunk-IEVHEZV4.js";
52
54
  import {
53
55
  KANBAN_CHECK_COMMAND,
54
56
  appendDmFooter,
55
57
  attributeTranscriptUsageByRun,
58
+ classifyActor,
56
59
  classifyOutput,
57
60
  extractFrontmatter,
58
61
  formatActorId,
@@ -68,7 +71,7 @@ import {
68
71
  resolveChannels,
69
72
  resolveDmTarget,
70
73
  wrapScheduledTaskPrompt
71
- } from "../chunk-53TCAGCM.js";
74
+ } from "../chunk-7DRLULIO.js";
72
75
 
73
76
  // src/lib/manager-worker.ts
74
77
  import { createHash as createHash3 } from "crypto";
@@ -2411,6 +2414,16 @@ function stopRealtimeIntegrationContext() {
2411
2414
  function isRealtimeConnected() {
2412
2415
  return connected;
2413
2416
  }
2417
+ function isRealtimeSocketConnected() {
2418
+ try {
2419
+ return client?.realtime?.isConnected() ?? false;
2420
+ } catch {
2421
+ return false;
2422
+ }
2423
+ }
2424
+ function shouldForcePollFallback(channelConnected, socketConnected) {
2425
+ return channelConnected && !socketConnected;
2426
+ }
2414
2427
  function stopRealtimeChat() {
2415
2428
  if (tearingDown) return;
2416
2429
  tearingDown = true;
@@ -2695,6 +2708,7 @@ function stopPersistentSessionAndForgetMcpBaseline(codeName, breakerReason) {
2695
2708
  runningMcpHashes.delete(codeName);
2696
2709
  runningMcpServerKeys.delete(codeName);
2697
2710
  closeInjectedRunIfOpen(codeName, "cancelled", `session stopped (${breakerReason ?? "deprovision"})`);
2711
+ closeScheduledRunsForCode(codeName, "cancelled", `session stopped (${breakerReason ?? "deprovision"})`);
2698
2712
  if (breakerReason) {
2699
2713
  recordRestartForBreaker(codeName, breakerReason);
2700
2714
  }
@@ -2838,7 +2852,7 @@ var cachedFrameworkVersion = null;
2838
2852
  var lastVersionCheckAt = 0;
2839
2853
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
2840
2854
  var lastResponsivenessProbeAt = 0;
2841
- var agtCliVersion = true ? "0.24.8" : "dev";
2855
+ var agtCliVersion = true ? "0.24.10" : "dev";
2842
2856
  function resolveBrewPath(execFileSync4) {
2843
2857
  try {
2844
2858
  const out = execFileSync4("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -3797,7 +3811,7 @@ async function pollCycle() {
3797
3811
  }
3798
3812
  try {
3799
3813
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
3800
- const { collectDiagnostics } = await import("../persistent-session-XJYOPFIP.js");
3814
+ const { collectDiagnostics } = await import("../persistent-session-3SNFKR2Q.js");
3801
3815
  const diagCodeNames = [...persistentSessionAgents];
3802
3816
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
3803
3817
  let tailscaleHostname;
@@ -3839,7 +3853,14 @@ async function pollCycle() {
3839
3853
  agent_diagnostics: agentDiagnostics,
3840
3854
  hostname: tailscaleHostname,
3841
3855
  os_username: osUsername,
3842
- claude_auth: claudeAuth ?? void 0
3856
+ claude_auth: claudeAuth ?? void 0,
3857
+ // ENG-5614: inbound-loop liveness. The actual phoenix socket state
3858
+ // behind the direct-chat Realtime subscription. When this is false
3859
+ // but the manager keeps heartbeating, inbound is being served by the
3860
+ // polling fallback (healRealtimeSocketDivergence forces it). Surfaced
3861
+ // so a sustained socket-down window is observable rather than hidden
3862
+ // behind a fresh heartbeat.
3863
+ realtime_socket_connected: isRealtimeSocketConnected()
3843
3864
  });
3844
3865
  } catch (err) {
3845
3866
  log(`Heartbeat failed: ${err.message}`);
@@ -3848,7 +3869,7 @@ async function pollCycle() {
3848
3869
  const {
3849
3870
  collectResponsivenessProbes,
3850
3871
  getResponsivenessIntervalMs
3851
- } = await import("../responsiveness-probe-EWZNGMTX.js");
3872
+ } = await import("../responsiveness-probe-UBNJFMAU.js");
3852
3873
  const probeIntervalMs = getResponsivenessIntervalMs();
3853
3874
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
3854
3875
  const probeCodeNames = [...persistentSessionAgents];
@@ -4062,6 +4083,7 @@ async function pollCycle() {
4062
4083
  log(`Cron health monitor error: ${err.message}`);
4063
4084
  });
4064
4085
  }
4086
+ healRealtimeSocketDivergence();
4065
4087
  if (!isRealtimeConnected()) {
4066
4088
  pollDirectChatMessages(agentStates).catch((err) => {
4067
4089
  log(`Direct chat poll error: ${err.message}`);
@@ -5106,7 +5128,9 @@ async function processAgent(agent, agentStates) {
5106
5128
  }
5107
5129
  const agentFw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
5108
5130
  const sessionMode = refreshData.agent.session_mode ?? "oneshot";
5109
- if (isKanbanHybridEnabled() && agentFw === "claude-code" && sessionMode === "persistent") {
5131
+ const wantsHybridInject = isKanbanHybridEnabled();
5132
+ const wantsScheduledReconcile = isScheduledViaKanbanEnabled();
5133
+ if ((wantsHybridInject || wantsScheduledReconcile) && agentFw === "claude-code" && sessionMode === "persistent") {
5110
5134
  let hybridBoard = hasBoardTemplates ? boardItems : void 0;
5111
5135
  if (!hybridBoard) {
5112
5136
  try {
@@ -5120,13 +5144,14 @@ async function processAgent(agent, agentStates) {
5120
5144
  hybridBoard = kanbanBoardCache.get(agent.code_name) ?? [];
5121
5145
  }
5122
5146
  }
5123
- if (hasHybridActionableItems(hybridBoard)) {
5124
- const actionableCount = hybridBoard.filter(
5125
- (i) => HYBRID_ACTIONABLE_STATUSES.has(i.status)
5126
- ).length;
5127
- void maybeInjectKanbanCheck(agent.code_name, agent.agent_id, actionableCount);
5128
- } else {
5129
- closeInjectedRunIfOpen(agent.code_name, "completed", "kanban board drained");
5147
+ void reconcileScheduledRuns(agent.code_name, agent.agent_id, hybridBoard);
5148
+ if (wantsHybridInject) {
5149
+ if (hasHybridActionableItems(hybridBoard)) {
5150
+ const actionableCount = hybridBoard.filter(isHybridActionable).length;
5151
+ void maybeInjectKanbanCheck(agent.code_name, agent.agent_id, actionableCount);
5152
+ } else {
5153
+ closeInjectedRunIfOpen(agent.code_name, "completed", "kanban board drained");
5154
+ }
5130
5155
  }
5131
5156
  }
5132
5157
  if (agentFw === "claude-code" && sessionMode === "persistent") {
@@ -5757,6 +5782,157 @@ async function fetchPriorScheduledRuns(agentId, taskId) {
5757
5782
  return [];
5758
5783
  }
5759
5784
  }
5785
+ function isScheduledViaKanbanEnabled() {
5786
+ const v = process.env["AGT_SCHEDULED_VIA_KANBAN"];
5787
+ return v === "1" || v?.toLowerCase() === "true";
5788
+ }
5789
+ var scheduledRunsByCode = /* @__PURE__ */ new Map();
5790
+ var claimedScheduledCards = /* @__PURE__ */ new Set();
5791
+ var completedScheduledCards = /* @__PURE__ */ new Set();
5792
+ function claimScheduledCardDelivery(cardId) {
5793
+ if (claimedScheduledCards.has(cardId)) return false;
5794
+ claimedScheduledCards.add(cardId);
5795
+ return true;
5796
+ }
5797
+ function releaseScheduledCardDelivery(cardId) {
5798
+ claimedScheduledCards.delete(cardId);
5799
+ }
5800
+ function markScheduledCardDeliveryComplete(cardId) {
5801
+ completedScheduledCards.add(cardId);
5802
+ }
5803
+ function __resetScheduledDeliveryDedupeForTest() {
5804
+ claimedScheduledCards.clear();
5805
+ completedScheduledCards.clear();
5806
+ }
5807
+ function trackScheduledRun(codeName, cardId, runId) {
5808
+ let m = scheduledRunsByCode.get(codeName);
5809
+ if (!m) {
5810
+ m = /* @__PURE__ */ new Map();
5811
+ scheduledRunsByCode.set(codeName, m);
5812
+ }
5813
+ m.set(cardId, runId);
5814
+ }
5815
+ function isScheduledCardTracked(cardId) {
5816
+ for (const m of scheduledRunsByCode.values()) {
5817
+ if (m.has(cardId)) return true;
5818
+ }
5819
+ return false;
5820
+ }
5821
+ function closeScheduledRunsForCode(codeName, outcome, reason) {
5822
+ const m = scheduledRunsByCode.get(codeName);
5823
+ if (!m || m.size === 0) return;
5824
+ scheduledRunsByCode.delete(codeName);
5825
+ for (const runId of m.values()) {
5826
+ void finishRun(runId, outcome, { outcomeMessage: reason });
5827
+ }
5828
+ }
5829
+ async function deliverScheduledCardResult(codeName, agentId, cardId) {
5830
+ if (!claimScheduledCardDelivery(cardId)) {
5831
+ return completedScheduledCards.has(cardId) ? "terminal" : "in_flight";
5832
+ }
5833
+ let card;
5834
+ try {
5835
+ const resp = await api.post("/host/kanban-item", {
5836
+ agent_id: agentId,
5837
+ item_id: cardId
5838
+ });
5839
+ card = resp.item;
5840
+ } catch (err) {
5841
+ if (err instanceof ApiError && err.status >= 400 && err.status < 500) {
5842
+ log(`[scheduled-kanban] delivery: card ${cardId} on '${codeName}' fetch returned ${err.status} (${err.message}) \u2014 treating as terminal, not retrying`);
5843
+ markScheduledCardDeliveryComplete(cardId);
5844
+ return "terminal";
5845
+ }
5846
+ releaseScheduledCardDelivery(cardId);
5847
+ log(`[scheduled-kanban] delivery fetch failed for card ${cardId} on '${codeName}': ${err.message} \u2014 will retry`);
5848
+ return "retry";
5849
+ }
5850
+ if (!card || card.status !== "done" || card.source_type !== "scheduled_task" || !card.source_ref) {
5851
+ markScheduledCardDeliveryComplete(cardId);
5852
+ return "terminal";
5853
+ }
5854
+ const state5 = claudeSchedulerStates.get(codeName) ?? loadSchedulerState(codeName);
5855
+ const task = state5.tasks[card.source_ref];
5856
+ if (!task) {
5857
+ log(`[scheduled-kanban] delivery: no scheduler task for source_ref=${card.source_ref} on '${codeName}' \u2014 skipping`);
5858
+ markScheduledCardDeliveryComplete(cardId);
5859
+ return "terminal";
5860
+ }
5861
+ if (!isPlainScheduledTemplate(task.templateId)) {
5862
+ log(`[scheduled-kanban] delivery: card ${cardId} template '${task.templateId}' on '${codeName}' is not a plain scheduled template \u2014 skipping manager delivery`);
5863
+ markScheduledCardDeliveryComplete(cardId);
5864
+ return "terminal";
5865
+ }
5866
+ await processClaudeTaskResult(codeName, agentId, task.templateId, card.result ?? "", {
5867
+ mode: task.deliveryMode,
5868
+ channel: task.deliveryChannel,
5869
+ to: task.deliveryTo,
5870
+ taskId: task.taskId
5871
+ });
5872
+ markScheduledCardDeliveryComplete(cardId);
5873
+ log(`[scheduled-kanban] Delivered result from card ${cardId} (task '${task.name}') for '${codeName}'`);
5874
+ return "delivered";
5875
+ }
5876
+ async function reconcileScheduledRuns(codeName, agentId, board) {
5877
+ const m = scheduledRunsByCode.get(codeName);
5878
+ if (!m || m.size === 0) return;
5879
+ const byId = new Map(board.map((i) => [i.id, i]));
5880
+ for (const [cardId, runId] of [...m.entries()]) {
5881
+ const card = byId.get(cardId);
5882
+ if (!card) continue;
5883
+ if (card.status === "done") {
5884
+ const result = await deliverScheduledCardResult(codeName, agentId, cardId);
5885
+ if (result === "retry" || result === "in_flight") continue;
5886
+ m.delete(cardId);
5887
+ void finishRun(runId, "completed", { outcomeMessage: "scheduled-task card done" });
5888
+ } else if (card.status === "failed") {
5889
+ m.delete(cardId);
5890
+ void finishRun(runId, "failed", { outcomeMessage: "scheduled-task card failed" });
5891
+ }
5892
+ }
5893
+ if (m.size === 0) scheduledRunsByCode.delete(codeName);
5894
+ }
5895
+ async function routeScheduledTaskViaKanban(codeName, agentId, task, prompt) {
5896
+ const { run_id, kanban_item_id } = await startRun({
5897
+ agent_id: agentId,
5898
+ source_type: "scheduled_task",
5899
+ source_ref: task.taskId,
5900
+ metadata: { template_id: task.templateId, name: task.name, via: "kanban-inject" },
5901
+ materialize_kanban: {
5902
+ title: task.name,
5903
+ description: prompt,
5904
+ priority: 2,
5905
+ initial_status: "todo"
5906
+ }
5907
+ });
5908
+ if (!run_id || !kanban_item_id) {
5909
+ log(`[scheduled-kanban] materialise incomplete for task '${task.name}' on '${codeName}' (run_id=${run_id ? "ok" : "null"}, card=${kanban_item_id ? "ok" : "null"}) \u2014 falling back to claude -p`);
5910
+ if (run_id) void finishRun(run_id, "failed", { outcomeMessage: "kanban materialise incomplete", completeKanbanItemId: kanban_item_id });
5911
+ return false;
5912
+ }
5913
+ const nudge = `You have a new scheduled task on your kanban board: id=${kanban_item_id} title=${JSON.stringify(task.name)}. Call kanban_move("${kanban_item_id}", "in_progress"), do the work described on the card, then write the finished result onto the card and mark it done. Do NOT send, post, or message the result to anyone yourself \u2014 recording it on the card is enough; delivery to the recipient is handled for you.
5914
+ ` + formatRunMarker(run_id);
5915
+ let injectStatus;
5916
+ try {
5917
+ injectStatus = await injectMessageWithStatus(codeName, "task", nudge, { task_name: "scheduled-task" }, log);
5918
+ } catch (err) {
5919
+ log(`[scheduled-kanban] nudge injection threw for task '${task.name}' on '${codeName}': ${err.message} \u2014 closing card + falling back to claude -p`);
5920
+ void finishRun(run_id, "failed", { outcomeMessage: "scheduled-task nudge delivery threw", completeKanbanItemId: kanban_item_id });
5921
+ return false;
5922
+ }
5923
+ const { delivered, fallbackUsed } = injectStatus;
5924
+ if (!delivered && !fallbackUsed) {
5925
+ log(`[scheduled-kanban] nudge delivery failed for task '${task.name}' on '${codeName}' \u2014 closing card + falling back to claude -p`);
5926
+ void finishRun(run_id, "failed", { outcomeMessage: "scheduled-task nudge delivery failed", completeKanbanItemId: kanban_item_id });
5927
+ return false;
5928
+ }
5929
+ if (!delivered && fallbackUsed) {
5930
+ log(`[scheduled-kanban] nudge sent via send-keys (unverified) for task '${task.name}' on '${codeName}' \u2014 proceeding without oneshot fallback`);
5931
+ }
5932
+ trackScheduledRun(codeName, kanban_item_id, run_id);
5933
+ log(`[scheduled-kanban] Routed task '${task.name}' for '${codeName}' via todo card ${kanban_item_id} run_id=${run_id}`);
5934
+ return true;
5935
+ }
5760
5936
  async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
5761
5937
  const projectDir = getProjectDir2(codeName);
5762
5938
  const mcpConfigPath = join6(projectDir, ".mcp.json");
@@ -6264,6 +6440,31 @@ ${truncateForLog(ctx.tail)}` : `; pane_tail_hash=sha256:${createHash3("sha256").
6264
6440
  }
6265
6441
  inFlightClaudeTasks.add(task.taskId);
6266
6442
  claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);
6443
+ if (isScheduledViaKanbanEnabled() && isPlainScheduledTemplate(task.templateId)) {
6444
+ let routed = false;
6445
+ try {
6446
+ routed = await routeScheduledTaskViaKanban(codeName, agent.agent_id, task, prompt);
6447
+ } catch (err) {
6448
+ log(`[scheduled-kanban] route threw for '${task.name}' on '${codeName}': ${err.message}`);
6449
+ routed = false;
6450
+ }
6451
+ if (routed) {
6452
+ try {
6453
+ const updated = markTaskFired(codeName, task.taskId, "ok");
6454
+ claudeSchedulerStates.set(codeName, updated);
6455
+ } catch (err) {
6456
+ log(`[scheduled-kanban] markTaskFired failed for '${task.name}' on '${codeName}': ${err.message}`);
6457
+ }
6458
+ if (task.scheduleKind === "at") {
6459
+ api.post("/host/schedules/disable", { agent_id: agent.agent_id, task_id: task.taskId }).catch(
6460
+ (err) => log(`[scheduled-kanban] Failed to disable one-off task '${task.name}': ${err.message}`)
6461
+ );
6462
+ }
6463
+ inFlightClaudeTasks.delete(task.taskId);
6464
+ claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));
6465
+ break;
6466
+ }
6467
+ }
6267
6468
  log(`[persistent-session] Firing task '${task.name}' for '${codeName}' via claude -p`);
6268
6469
  executeAndProcessClaudeTask(codeName, agent.agent_id, task, prompt).catch(() => {
6269
6470
  }).finally(() => {
@@ -6287,6 +6488,18 @@ var subscribedIntegrationContextAgentIds = /* @__PURE__ */ new Set();
6287
6488
  var realtimeAssignStarted = false;
6288
6489
  var realtimeConfigStarted = false;
6289
6490
  var realtimeSubscribedAgentIds = /* @__PURE__ */ new Set();
6491
+ function healRealtimeSocketDivergence() {
6492
+ if (!shouldForcePollFallback(isRealtimeConnected(), isRealtimeSocketConnected())) return false;
6493
+ log("[realtime] Socket divergence \u2014 channel flag connected but socket dead; forcing poll fallback + reconnect");
6494
+ stopRealtimeChat();
6495
+ realtimeStarted = false;
6496
+ realtimeDriftStarted = false;
6497
+ realtimeAssignStarted = false;
6498
+ realtimeConfigStarted = false;
6499
+ realtimeKanbanStarted = false;
6500
+ subscribedIntegrationContextAgentIds = /* @__PURE__ */ new Set();
6501
+ return true;
6502
+ }
6290
6503
  function ensureRealtimeStarted(agentStates) {
6291
6504
  const currentActiveIds = new Set(
6292
6505
  agentStates.filter((a) => a.status === "active").map((a) => a.agentId)
@@ -6447,6 +6660,7 @@ function ensureRealtimeKanbanStarted(agentStates) {
6447
6660
  token: exchange.token,
6448
6661
  agentIds: activeAgentIds,
6449
6662
  onTodayItem: (item) => {
6663
+ if (item.source_type === "scheduled_task") return;
6450
6664
  const agent = agentStates.find((a) => a.agentId === item.agent_id);
6451
6665
  if (!agent) return;
6452
6666
  const agentFw = agentFrameworkCache.get(agent.codeName) ?? "openclaw";
@@ -6474,6 +6688,9 @@ function ensureRealtimeKanbanStarted(agentStates) {
6474
6688
  log(
6475
6689
  `[realtime] Kanban completion forwarded for '${codeName}': item=${event.item_id} status=${event.status} actor=${event.last_actor_id ?? "unknown"}`
6476
6690
  );
6691
+ if (event.status === "done" && (isScheduledViaKanbanEnabled() || isKanbanHybridEnabled()) && classifyActor(event.last_actor_id, event.agent_id) === "user" && isScheduledCardTracked(event.item_id)) {
6692
+ void deliverScheduledCardResult(codeName, event.agent_id, event.item_id);
6693
+ }
6477
6694
  },
6478
6695
  log
6479
6696
  });
@@ -6598,7 +6815,7 @@ ${escapeXml(msg.content)}
6598
6815
  try {
6599
6816
  let reply;
6600
6817
  if (fw === "claude-code") {
6601
- const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-XHJS2MZV.js");
6818
+ const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-EM24LTGV.js");
6602
6819
  const projDir = ccProjectDir(agent.codeName);
6603
6820
  const mcpConfigPath = join6(projDir, ".mcp.json");
6604
6821
  const serverNames = [];
@@ -6692,6 +6909,9 @@ var STANDUP_TEMPLATES = /* @__PURE__ */ new Set(["daily-standup", "end-of-day-su
6692
6909
  var TASK_UPDATE_TEMPLATES = /* @__PURE__ */ new Set(["hourly-status", "task-update"]);
6693
6910
  var PLAN_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan"]);
6694
6911
  var KANBAN_WORK_TEMPLATES = /* @__PURE__ */ new Set(["kanban-work"]);
6912
+ function isPlainScheduledTemplate(templateId) {
6913
+ return !STANDUP_TEMPLATES.has(templateId) && !TASK_UPDATE_TEMPLATES.has(templateId) && !PLAN_TEMPLATES.has(templateId) && !KANBAN_WORK_TEMPLATES.has(templateId);
6914
+ }
6695
6915
  function isKanbanWorkCronDisabled() {
6696
6916
  const v = process.env["AGT_DISABLE_KANBAN_WORK_CRON"];
6697
6917
  return v === "1" || v?.toLowerCase() === "true";
@@ -6705,8 +6925,11 @@ function isKanbanHybridDryRun() {
6705
6925
  return v === "1" || v?.toLowerCase() === "true";
6706
6926
  }
6707
6927
  var HYBRID_ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress"]);
6928
+ function isHybridActionable(item) {
6929
+ return HYBRID_ACTIONABLE_STATUSES.has(item.status) && item.source_type !== "scheduled_task";
6930
+ }
6708
6931
  function hasHybridActionableItems(items) {
6709
- return items.some((item) => HYBRID_ACTIONABLE_STATUSES.has(item.status));
6932
+ return items.some(isHybridActionable);
6710
6933
  }
6711
6934
  var lastKanbanInjectAt = /* @__PURE__ */ new Map();
6712
6935
  var openInjectedRunByCode = /* @__PURE__ */ new Map();
@@ -6782,7 +7005,7 @@ function warnOnConflictingKanbanModes() {
6782
7005
  }
6783
7006
  }
6784
7007
  var BOARD_INJECT_TEMPLATES = /* @__PURE__ */ new Set(["morning-plan", "task-update", "hourly-status", "end-of-day-summary", "kanban-work"]);
6785
- var ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["backlog", "todo", "in_progress"]);
7008
+ var ACTIONABLE_STATUSES = /* @__PURE__ */ new Set(["todo", "in_progress"]);
6786
7009
  function hasActionableItems(items) {
6787
7010
  return items.some((item) => ACTIONABLE_STATUSES.has(item.status));
6788
7011
  }
@@ -7619,7 +7842,7 @@ async function processClaudePairSessions(agents) {
7619
7842
  killPairSession,
7620
7843
  pairTmuxSession,
7621
7844
  finalizeClaudePairOnboarding
7622
- } = await import("../claude-pair-runtime-4F53C7RB.js");
7845
+ } = await import("../claude-pair-runtime-SENCKRI4.js");
7623
7846
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
7624
7847
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
7625
7848
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -8188,6 +8411,9 @@ async function stopPolling(opts = {}) {
8188
8411
  stopCaffeinate();
8189
8412
  stopRealtimeChat();
8190
8413
  stopGatewayPool();
8414
+ for (const codeName of [...scheduledRunsByCode.keys()]) {
8415
+ closeScheduledRunsForCode(codeName, "cancelled", "manager shutdown");
8416
+ }
8191
8417
  log("Killing tmux sessions...");
8192
8418
  await killAllAgtTmuxSessions();
8193
8419
  log("Stopping persistent sessions...");
@@ -8385,19 +8611,28 @@ process.on("disconnect", () => {
8385
8611
  });
8386
8612
  export {
8387
8613
  ChildProcessError,
8614
+ __resetScheduledDeliveryDedupeForTest,
8388
8615
  applyRestartAcks,
8389
8616
  claudeCodeUpgradeMarkerPath,
8390
8617
  claudeCodeUpgradeThrottled,
8618
+ deliverScheduledCardResult,
8391
8619
  extractCharterSlackPeers,
8392
8620
  extractCharterTelegramPeers,
8621
+ hasActionableItems,
8393
8622
  hasHybridActionableItems,
8394
8623
  hasRevokedResiduals,
8624
+ isHybridActionable,
8395
8625
  isKanbanHybridDryRun,
8396
8626
  isKanbanHybridEnabled,
8397
8627
  isKanbanWorkCronDisabled,
8628
+ isPlainScheduledTemplate,
8629
+ isScheduledCardTracked,
8630
+ isScheduledViaKanbanEnabled,
8398
8631
  markAgentForFreshMemorySync,
8399
8632
  maybeInjectKanbanCheck,
8400
8633
  maybeUpgradeClaudeCode,
8634
+ reconcileScheduledRuns,
8635
+ routeScheduledTaskViaKanban,
8401
8636
  shouldSkipRevokedCleanup,
8402
8637
  stampClaudeCodeUpgradeMarker,
8403
8638
  startManager,