@integrity-labs/agt-cli 0.8.5 → 0.8.7

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 CHANGED
@@ -3411,7 +3411,7 @@ async function acpxCloseCommand(agent2, _opts, cmd) {
3411
3411
  import { execSync } from "child_process";
3412
3412
  import chalk19 from "chalk";
3413
3413
  import ora15 from "ora";
3414
- var cliVersion = true ? "0.8.5" : "dev";
3414
+ var cliVersion = true ? "0.8.7" : "dev";
3415
3415
  async function fetchLatestVersion() {
3416
3416
  const host2 = AGT_HOST;
3417
3417
  if (!host2) return null;
@@ -3527,7 +3527,7 @@ async function checkForUpdateOnStartup() {
3527
3527
  }
3528
3528
 
3529
3529
  // src/bin/agt.ts
3530
- var cliVersion2 = true ? "0.8.5" : "dev";
3530
+ var cliVersion2 = true ? "0.8.7" : "dev";
3531
3531
  var program = new Command();
3532
3532
  program.name("agt").description("Augmented CLI \u2014 agent provisioning and management").version(cliVersion2).option("--json", "Emit machine-readable JSON output (suppress spinners and colors)").option("--skip-update-check", "Skip the automatic update check on startup");
3533
3533
  program.hook("preAction", (thisCommand) => {
@@ -84,6 +84,7 @@ function syncTasksToScheduler(codeName, agentId, tasks) {
84
84
  existing.deliveryChannel = t.delivery_channel;
85
85
  existing.deliveryTo = t.delivery_to;
86
86
  existing.enabled = t.enabled;
87
+ if (t.triggered_at) existing.triggeredAt = new Date(t.triggered_at).getTime();
87
88
  if (scheduleChanged) {
88
89
  existing.nextFireAt = computeNextFire(
89
90
  t.schedule_kind,
@@ -112,6 +113,7 @@ function syncTasksToScheduler(codeName, agentId, tasks) {
112
113
  deliveryChannel: t.delivery_channel,
113
114
  deliveryTo: t.delivery_to,
114
115
  enabled: t.enabled,
116
+ triggeredAt: t.triggered_at ? new Date(t.triggered_at).getTime() : null,
115
117
  nextFireAt: computeNextFire(
116
118
  t.schedule_kind,
117
119
  t.schedule_expr,
@@ -130,9 +132,16 @@ function syncTasksToScheduler(codeName, agentId, tasks) {
130
132
  }
131
133
  function getReadyTasks(state) {
132
134
  const now = Date.now();
133
- const ready = Object.values(state.tasks).filter(
134
- (t) => t.enabled && t.nextFireAt !== null && t.nextFireAt <= now
135
- );
135
+ const ready = Object.values(state.tasks).filter((t) => {
136
+ if (!t.enabled) return false;
137
+ if (t.triggeredAt) {
138
+ const triggerReady = !t.lastFireAt || t.triggeredAt > t.lastFireAt;
139
+ if (!triggerReady) {
140
+ }
141
+ if (triggerReady) return true;
142
+ }
143
+ return t.nextFireAt !== null && t.nextFireAt <= now;
144
+ });
136
145
  const seen = /* @__PURE__ */ new Set();
137
146
  return ready.filter((t) => {
138
147
  if (seen.has(t.templateId)) return false;
@@ -181,4 +190,4 @@ export {
181
190
  findTaskByTemplate,
182
191
  getProjectDir
183
192
  };
184
- //# sourceMappingURL=chunk-5UGOY3IV.js.map
193
+ //# sourceMappingURL=chunk-2TSCVXHE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/claude-scheduler.ts"],"sourcesContent":["/**\n * In-process scheduler for Claude Code agents.\n *\n * On each manager poll cycle, checks if any tasks are due and fires them\n * via `claude -p`. State persists to disk so nextFireAt/lastFireAt survive\n * manager restarts.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { Cron } from 'croner';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SchedulerTaskState {\n taskId: string;\n templateId: string;\n name: string;\n agentCodeName: string;\n agentId: string;\n scheduleKind: 'cron' | 'every' | 'at';\n scheduleExpr: string | null;\n scheduleEvery: string | null;\n scheduleAt: string | null;\n timezone: string;\n prompt: string;\n sessionTarget: string;\n deliveryMode: string;\n deliveryChannel: string | null;\n deliveryTo: string | null;\n enabled: boolean;\n triggeredAt: number | null; // epoch ms — manual trigger from webapp\n nextFireAt: number | null; // epoch ms, null = completed one-shot\n lastFireAt: number | null;\n lastStatus: 'ok' | 'error' | null;\n firedCount: number;\n}\n\nexport interface SchedulerState {\n version: 1;\n tasks: Record<string, SchedulerTaskState>;\n updatedAt: string;\n}\n\nexport interface SchedulerTaskInput {\n id: string;\n template_id: string;\n name: string;\n schedule_kind: 'cron' | 'every' | 'at';\n schedule_expr: string | null;\n schedule_every: string | null;\n schedule_at: string | null;\n timezone: string;\n prompt: string;\n session_target: string;\n delivery_mode: string;\n delivery_channel: string | null;\n delivery_to: string | null;\n enabled: boolean;\n triggered_at?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Interval parsing (reused from Claude Code adapter)\n// ---------------------------------------------------------------------------\n\nfunction parseIntervalMs(scheduleEvery: string | null): number {\n if (!scheduleEvery) return 60 * 60_000; // 1hr default\n const match = scheduleEvery.match(/^(\\d+)\\s*(m|min|h|hr|d)$/i);\n if (!match) return 60 * 60_000;\n const value = parseInt(match[1]!, 10);\n const unit = match[2]!.toLowerCase();\n if (unit === 'h' || unit === 'hr') return value * 60 * 60_000;\n if (unit === 'd') return value * 24 * 60 * 60_000;\n return value * 60_000; // minutes\n}\n\n// ---------------------------------------------------------------------------\n// Next-fire computation\n// ---------------------------------------------------------------------------\n\nexport function computeNextFire(\n kind: 'cron' | 'every' | 'at',\n expr: string | null,\n every: string | null,\n at: string | null,\n timezone: string,\n afterMs?: number,\n): number | null {\n const now = afterMs ?? Date.now();\n\n if (kind === 'cron' && expr) {\n try {\n const cron = new Cron(expr, { timezone: timezone || undefined });\n const next = cron.nextRun(new Date(now));\n return next ? next.getTime() : null;\n } catch {\n return null;\n }\n }\n\n if (kind === 'every') {\n const intervalMs = parseIntervalMs(every);\n return now + intervalMs;\n }\n\n if (kind === 'at' && at) {\n const ts = new Date(at).getTime();\n return isNaN(ts) ? null : ts;\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// State persistence\n// ---------------------------------------------------------------------------\n\nfunction getStateDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName, 'claudecode');\n}\n\nfunction getStatePath(codeName: string): string {\n return join(getStateDir(codeName), 'scheduler-state.json');\n}\n\nexport function loadSchedulerState(codeName: string): SchedulerState {\n const path = getStatePath(codeName);\n if (existsSync(path)) {\n try {\n return JSON.parse(readFileSync(path, 'utf-8'));\n } catch { /* corrupted — start fresh */ }\n }\n return { version: 1, tasks: {}, updatedAt: new Date().toISOString() };\n}\n\nexport function saveSchedulerState(codeName: string, state: SchedulerState): void {\n const dir = getStateDir(codeName);\n mkdirSync(dir, { recursive: true });\n state.updatedAt = new Date().toISOString();\n const path = getStatePath(codeName);\n const tmpPath = path + '.tmp';\n writeFileSync(tmpPath, JSON.stringify(state, null, 2));\n renameSync(tmpPath, path);\n}\n\n// ---------------------------------------------------------------------------\n// Sync API tasks → scheduler state\n// ---------------------------------------------------------------------------\n\nexport function syncTasksToScheduler(\n codeName: string,\n agentId: string,\n tasks: SchedulerTaskInput[],\n): SchedulerState {\n const state = loadSchedulerState(codeName);\n const desiredIds = new Set(tasks.map((t) => t.id));\n\n // Remove tasks no longer in API\n for (const id of Object.keys(state.tasks)) {\n if (!desiredIds.has(id)) {\n delete state.tasks[id];\n }\n }\n\n // Add or update tasks\n for (const t of tasks) {\n const existing = state.tasks[t.id];\n if (existing) {\n // Only recompute nextFireAt if the schedule definition actually changed.\n // Without this guard, every sync cycle resets nextFireAt from \"now\",\n // preventing past-due tasks from ever being detected as ready.\n const scheduleChanged =\n existing.scheduleKind !== t.schedule_kind ||\n existing.scheduleExpr !== t.schedule_expr ||\n existing.scheduleEvery !== t.schedule_every ||\n existing.scheduleAt !== t.schedule_at ||\n existing.timezone !== t.timezone;\n\n // Update mutable fields, preserve fire history\n existing.name = t.name;\n existing.templateId = t.template_id;\n existing.scheduleKind = t.schedule_kind;\n existing.scheduleExpr = t.schedule_expr;\n existing.scheduleEvery = t.schedule_every;\n existing.scheduleAt = t.schedule_at;\n existing.timezone = t.timezone;\n existing.prompt = t.prompt;\n existing.sessionTarget = t.session_target;\n existing.deliveryMode = t.delivery_mode;\n existing.deliveryChannel = t.delivery_channel;\n existing.deliveryTo = t.delivery_to;\n existing.enabled = t.enabled;\n if (t.triggered_at) existing.triggeredAt = new Date(t.triggered_at).getTime();\n if (scheduleChanged) {\n existing.nextFireAt = computeNextFire(\n t.schedule_kind, t.schedule_expr, t.schedule_every, t.schedule_at,\n t.timezone, existing.lastFireAt ?? undefined,\n );\n }\n } else {\n // New task\n state.tasks[t.id] = {\n taskId: t.id,\n templateId: t.template_id,\n name: t.name,\n agentCodeName: codeName,\n agentId,\n scheduleKind: t.schedule_kind,\n scheduleExpr: t.schedule_expr,\n scheduleEvery: t.schedule_every,\n scheduleAt: t.schedule_at,\n timezone: t.timezone,\n prompt: t.prompt,\n sessionTarget: t.session_target,\n deliveryMode: t.delivery_mode,\n deliveryChannel: t.delivery_channel,\n deliveryTo: t.delivery_to,\n enabled: t.enabled,\n triggeredAt: t.triggered_at ? new Date(t.triggered_at).getTime() : null,\n nextFireAt: computeNextFire(\n t.schedule_kind, t.schedule_expr, t.schedule_every, t.schedule_at, t.timezone,\n ),\n lastFireAt: null,\n lastStatus: null,\n firedCount: 0,\n };\n }\n }\n\n saveSchedulerState(codeName, state);\n return state;\n}\n\n// ---------------------------------------------------------------------------\n// Ready-task detection\n// ---------------------------------------------------------------------------\n\nexport function getReadyTasks(state: SchedulerState): SchedulerTaskState[] {\n const now = Date.now();\n const ready = Object.values(state.tasks).filter((t) => {\n if (!t.enabled) return false;\n // Manual trigger: triggered_at is set and hasn't been fired yet\n if (t.triggeredAt) {\n const triggerReady = !t.lastFireAt || t.triggeredAt > t.lastFireAt;\n if (!triggerReady) {\n // Debug: trigger was already consumed\n }\n if (triggerReady) return true;\n }\n // Normal schedule\n return t.nextFireAt !== null && t.nextFireAt <= now;\n });\n // Deduplicate by templateId — only fire one task per template per cycle\n const seen = new Set<string>();\n return ready.filter((t) => {\n if (seen.has(t.templateId)) return false;\n seen.add(t.templateId);\n return true;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Post-execution state update\n// ---------------------------------------------------------------------------\n\nexport function markTaskFired(\n codeName: string,\n taskId: string,\n status: 'ok' | 'error',\n): SchedulerState {\n const state = loadSchedulerState(codeName);\n const task = state.tasks[taskId];\n if (!task) return state;\n\n task.lastFireAt = Date.now();\n task.lastStatus = status;\n task.firedCount++;\n\n // Compute next fire\n if (task.scheduleKind === 'at') {\n // One-shot — mark as completed\n task.nextFireAt = null;\n } else {\n task.nextFireAt = computeNextFire(\n task.scheduleKind, task.scheduleExpr, task.scheduleEvery, task.scheduleAt,\n task.timezone, task.lastFireAt,\n );\n }\n\n saveSchedulerState(codeName, state);\n return state;\n}\n\n// ---------------------------------------------------------------------------\n// Find a task by template ID (for work triggers)\n// ---------------------------------------------------------------------------\n\nexport function findTaskByTemplate(state: SchedulerState, templateId: string): SchedulerTaskState | undefined {\n return Object.values(state.tasks).find(\n (t) => t.templateId === templateId && t.enabled,\n );\n}\n\nexport function getProjectDir(codeName: string): string {\n return join(homedir(), '.augmented', codeName, 'project');\n}\n"],"mappings":"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","names":[]}
@@ -7,7 +7,7 @@ import {
7
7
  markTaskFired,
8
8
  saveSchedulerState,
9
9
  syncTasksToScheduler
10
- } from "./chunk-5UGOY3IV.js";
10
+ } from "./chunk-2TSCVXHE.js";
11
11
  export {
12
12
  computeNextFire,
13
13
  findTaskByTemplate,
@@ -18,4 +18,4 @@ export {
18
18
  saveSchedulerState,
19
19
  syncTasksToScheduler
20
20
  };
21
- //# sourceMappingURL=claude-scheduler-V2QNX3Z4.js.map
21
+ //# sourceMappingURL=claude-scheduler-VFBZFE6U.js.map
@@ -17,7 +17,7 @@ import {
17
17
  loadSchedulerState,
18
18
  markTaskFired,
19
19
  syncTasksToScheduler
20
- } from "../chunk-5UGOY3IV.js";
20
+ } from "../chunk-2TSCVXHE.js";
21
21
 
22
22
  // src/lib/manager-worker.ts
23
23
  import { createHash } from "crypto";
@@ -429,7 +429,7 @@ function spawnSession(config2, session) {
429
429
  "Skill"
430
430
  ].join(",");
431
431
  args.push("--allowedTools", allowedTools);
432
- let envPrefix = "CLAUDE_CODE_SIMPLE=1 ";
432
+ let envPrefix = "";
433
433
  const envIntegrationsPath = join(projectDir, ".env.integrations");
434
434
  if (existsSync(envIntegrationsPath)) {
435
435
  try {
@@ -440,7 +440,7 @@ function spawnSession(config2, session) {
440
440
  const value = line.slice(eqIdx + 1);
441
441
  return `${key}=${JSON.stringify(value)}`;
442
442
  }).join(" ");
443
- if (envVars) envPrefix = `CLAUDE_CODE_SIMPLE=1 ${envVars} `;
443
+ if (envVars) envPrefix = `${envVars} `;
444
444
  } catch {
445
445
  }
446
446
  }
@@ -541,30 +541,17 @@ async function injectMessage(codeName, type, content, meta, log2) {
541
541
  mkdirSync(tmpDir, { recursive: true });
542
542
  const tmpFile = join(tmpDir, ".agt-inject-prompt.txt");
543
543
  writeFileSync2(tmpFile, text);
544
- _log(`[inject] acpx exec: cwd=${projectDir}, file=${tmpFile}, bin=${acpx}`);
545
- const ok = await new Promise((resolve) => {
546
- const child = spawn(acpx, ["claude", "exec", "-f", tmpFile], {
547
- cwd: projectDir,
548
- stdio: ["ignore", "pipe", "pipe"]
549
- });
550
- let stderr = "";
551
- child.stderr?.on("data", (d) => {
552
- stderr += d.toString();
553
- });
554
- child.on("error", (err) => {
555
- _log(`[inject] acpx spawn error for '${codeName}': ${err.message}`);
556
- resolve(false);
557
- });
558
- child.on("close", (code) => {
559
- if (code === 0) {
560
- resolve(true);
561
- } else {
562
- _log(`[inject] acpx exec exited ${code} for '${codeName}': ${stderr.trim()}`);
563
- resolve(false);
564
- }
565
- });
544
+ _log(`[inject] acpx exec (fire-and-forget): cwd=${projectDir}, file=${tmpFile}`);
545
+ const child = spawn(acpx, ["claude", "exec", "-f", tmpFile], {
546
+ cwd: projectDir,
547
+ stdio: "ignore",
548
+ detached: true
566
549
  });
567
- if (ok) return true;
550
+ child.on("error", (err) => {
551
+ _log(`[inject] acpx spawn error for '${codeName}': ${err.message}`);
552
+ });
553
+ child.unref();
554
+ return true;
568
555
  } catch (err) {
569
556
  _log(`[inject] acpx exec failed for '${codeName}': ${err.message}`);
570
557
  }
@@ -653,31 +640,27 @@ function writeAcpxConfig(config2) {
653
640
  if (existsSync(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
654
641
  claudeArgs.push("--allow-dangerously-skip-permissions");
655
642
  claudeArgs.push("--dangerously-skip-permissions");
643
+ claudeArgs.push("--strict-mcp-config");
656
644
  const mcpServerNames2 = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
657
645
  const mcpPatterns2 = mcpServerNames2.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
658
646
  const allowedTools2 = [...mcpPatterns2, "Bash", "Read", "Write", "Edit", "Grep", "Glob", "Agent", "Skill"].join(",");
659
647
  claudeArgs.push("--allowedTools", allowedTools2);
660
- let envPrefix = "CLAUDE_CODE_SIMPLE=1 ";
648
+ const acpCmd = `npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
661
649
  const envIntegrationsPath = join(projectDir, ".env.integrations");
650
+ const wrapperPath = join(projectDir, ".claude", "acpx-agent.sh");
651
+ const wrapperLines = ["#!/usr/bin/env bash"];
662
652
  if (existsSync(envIntegrationsPath)) {
663
- try {
664
- const envContent = readFileSync2(envIntegrationsPath, "utf-8");
665
- const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
666
- const eqIdx = line.indexOf("=");
667
- const key = line.slice(0, eqIdx);
668
- const value = line.slice(eqIdx + 1);
669
- return `${key}=${JSON.stringify(value)}`;
670
- }).join(" ");
671
- if (envVars) envPrefix = `CLAUDE_CODE_SIMPLE=1 ${envVars} `;
672
- } catch {
673
- }
653
+ wrapperLines.push(`set -a`, `source ${JSON.stringify(envIntegrationsPath)}`, `set +a`);
674
654
  }
655
+ wrapperLines.push(`exec ${acpCmd}`);
656
+ mkdirSync(join(projectDir, ".claude"), { recursive: true });
657
+ writeFileSync2(wrapperPath, wrapperLines.join("\n") + "\n", { mode: 493 });
675
658
  const acpxConfig = {
676
659
  defaultAgent: "claude",
677
660
  defaultPermissions: "approve-all",
678
661
  agents: {
679
662
  claude: {
680
- command: `${envPrefix}npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`
663
+ command: wrapperPath
681
664
  }
682
665
  }
683
666
  };
@@ -2516,7 +2499,8 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
2516
2499
  delivery_mode: t.delivery_mode ?? "none",
2517
2500
  delivery_channel: t.delivery_channel ?? null,
2518
2501
  delivery_to: t.delivery_to ?? null,
2519
- enabled: t.enabled ?? true
2502
+ enabled: t.enabled ?? true,
2503
+ triggered_at: t.triggered_at ?? null
2520
2504
  }));
2521
2505
  const state3 = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
2522
2506
  claudeSchedulerStates.set(codeName, state3);
@@ -2590,7 +2574,11 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
2590
2574
  }
2591
2575
  const output = stdout.trim();
2592
2576
  log(`[claude-scheduler] Task '${task.name}' completed for '${codeName}' (${output.length} chars): ${output.slice(0, 300)}`);
2593
- await processClaudeTaskResult(codeName, agentId, task.templateId, output);
2577
+ await processClaudeTaskResult(codeName, agentId, task.templateId, output, {
2578
+ mode: task.deliveryMode,
2579
+ channel: task.deliveryChannel,
2580
+ to: task.deliveryTo
2581
+ });
2594
2582
  const updated = markTaskFired(codeName, task.taskId, "ok");
2595
2583
  claudeSchedulerStates.set(codeName, updated);
2596
2584
  } catch (err) {
@@ -2600,19 +2588,19 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
2600
2588
  claudeSchedulerStates.set(codeName, updated);
2601
2589
  }
2602
2590
  }
2603
- async function processClaudeTaskResult(codeName, agentId, templateId, output) {
2591
+ async function processClaudeTaskResult(codeName, agentId, templateId, output, delivery) {
2604
2592
  try {
2605
2593
  if (STANDUP_TEMPLATES.has(templateId)) {
2606
2594
  const standup = parseStandupSummary(output);
2607
2595
  await api.post("/host/agent-status", {
2608
- agent_id: agentId,
2596
+ agent_code_name: codeName,
2609
2597
  standup,
2610
2598
  current_status: "idle"
2611
2599
  });
2612
2600
  log(`[claude-scheduler] Standup posted for '${codeName}'`);
2613
2601
  } else if (TASK_UPDATE_TEMPLATES.has(templateId)) {
2614
2602
  await api.post("/host/agent-status", {
2615
- agent_id: agentId,
2603
+ agent_code_name: codeName,
2616
2604
  current_tasks: output.slice(0, 2e3)
2617
2605
  });
2618
2606
  log(`[claude-scheduler] Task update posted for '${codeName}'`);
@@ -2635,6 +2623,9 @@ async function processClaudeTaskResult(codeName, agentId, templateId, output) {
2635
2623
  log(`[claude-scheduler] Kanban updates posted for '${codeName}' (${kanbanUpdates.length} updates)`);
2636
2624
  }
2637
2625
  }
2626
+ if (delivery?.mode === "announce" && delivery.channel && delivery.to && output) {
2627
+ await sendTaskNotification(codeName, delivery.channel, delivery.to, output.slice(0, 4e3));
2628
+ }
2638
2629
  } catch (err) {
2639
2630
  log(`[claude-scheduler] Failed to post result for '${codeName}': ${err.message}`);
2640
2631
  }
@@ -2732,7 +2723,8 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2732
2723
  delivery_mode: t.delivery_mode ?? "none",
2733
2724
  delivery_channel: t.delivery_channel ?? null,
2734
2725
  delivery_to: t.delivery_to ?? null,
2735
- enabled: t.enabled ?? true
2726
+ enabled: t.enabled ?? true,
2727
+ triggered_at: t.triggered_at ?? null
2736
2728
  }));
2737
2729
  const schedulerState = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
2738
2730
  claudeSchedulerStates.set(codeName, schedulerState);
@@ -2767,34 +2759,20 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2767
2759
  }
2768
2760
  }
2769
2761
  }
2770
- log(`[persistent-session] Injecting task '${task.name}' into '${codeName}'`);
2771
- const projectDir2 = getProjectDir2(codeName);
2772
- const markerDir = join2(projectDir2, ".claude");
2773
- const markerPath = join2(markerDir, ".agt-pending-task.json");
2774
- try {
2775
- mkdirSync2(markerDir, { recursive: true });
2776
- writeFileSync3(markerPath, JSON.stringify({
2777
- agent_id: agent.agent_id,
2778
- task_id: task.taskId,
2779
- template_id: task.templateId,
2780
- task_name: task.name,
2781
- injected_at: (/* @__PURE__ */ new Date()).toISOString()
2782
- }), "utf-8");
2783
- } catch (err) {
2784
- log(`[persistent-session] Failed to write task marker for '${codeName}': ${err.message}`);
2785
- }
2786
- const injected = await injectMessage(codeName, "task", prompt, {
2787
- task_id: task.taskId,
2788
- template_id: task.templateId,
2789
- task_name: task.name
2790
- }, log);
2791
- if (injected) {
2792
- const updated = markTaskFired(codeName, task.taskId, "ok");
2793
- claudeSchedulerStates.set(codeName, updated);
2794
- log(`[persistent-session] Task '${task.name}' injected for '${codeName}', next fire at ${new Date(updated.tasks[task.taskId]?.nextFireAt ?? 0).toISOString()}`);
2795
- } else {
2796
- log(`[persistent-session] Task '${task.name}' injection FAILED for '${codeName}' \u2014 injectMessage returned false`);
2762
+ log(`[persistent-session] Firing task '${task.name}' for '${codeName}' via claude -p`);
2763
+ if (inFlightClaudeTasks.has(task.taskId)) {
2764
+ continue;
2765
+ }
2766
+ if ((claudeTaskConcurrency.get(codeName) ?? 0) >= MAX_CLAUDE_CONCURRENCY) {
2767
+ break;
2797
2768
  }
2769
+ inFlightClaudeTasks.add(task.taskId);
2770
+ claudeTaskConcurrency.set(codeName, (claudeTaskConcurrency.get(codeName) ?? 0) + 1);
2771
+ executeAndProcessClaudeTask(codeName, agent.agent_id, task, prompt).catch(() => {
2772
+ }).finally(() => {
2773
+ inFlightClaudeTasks.delete(task.taskId);
2774
+ claudeTaskConcurrency.set(codeName, Math.max(0, (claudeTaskConcurrency.get(codeName) ?? 1) - 1));
2775
+ });
2798
2776
  break;
2799
2777
  }
2800
2778
  }
@@ -3012,23 +2990,51 @@ async function processDirectChatMessage(agent, msg) {
3012
2990
  try {
3013
2991
  let reply;
3014
2992
  if (fw === "claude-code") {
3015
- const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-V2QNX3Z4.js");
2993
+ const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-VFBZFE6U.js");
3016
2994
  const projDir = ccProjectDir(agent.codeName);
2995
+ const mcpConfigPath = join2(projDir, ".mcp.json");
2996
+ const channelsConfigPath = join2(projDir, ".mcp-channels.json");
2997
+ const serverNames = [];
2998
+ for (const p of [mcpConfigPath, channelsConfigPath]) {
2999
+ if (existsSync2(p)) {
3000
+ try {
3001
+ const d = JSON.parse(readFileSync3(p, "utf-8"));
3002
+ if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));
3003
+ } catch {
3004
+ }
3005
+ }
3006
+ }
3007
+ const mcpPatterns = serverNames.map((n) => `mcp__${n.replace(/-/g, "_")}__*`);
3008
+ const allowedTools = [...mcpPatterns, "Bash", "Read", "Write", "Edit", "Grep", "Glob"].join(",");
3017
3009
  const chatArgs = [
3018
3010
  "-p",
3019
3011
  msg.content,
3020
3012
  "--output-format",
3021
3013
  "text",
3022
3014
  "--mcp-config",
3023
- join2(projDir, ".mcp.json"),
3015
+ mcpConfigPath,
3016
+ "--strict-mcp-config",
3024
3017
  "--allowedTools",
3025
- "mcp__augmented__*,Bash,Read,Write,Edit,Grep,Glob"
3018
+ allowedTools
3026
3019
  ];
3020
+ if (existsSync2(channelsConfigPath)) chatArgs.push("--mcp-config", channelsConfigPath);
3027
3021
  const chatClaudeMd = join2(projDir, "CLAUDE.md");
3028
3022
  if (existsSync2(chatClaudeMd)) {
3029
3023
  chatArgs.push("--system-prompt-file", chatClaudeMd);
3030
3024
  }
3031
- const { stdout } = await execFilePromiseLong("claude", chatArgs, { cwd: projDir, stdin: "ignore" });
3025
+ const envIntPath = join2(projDir, ".env.integrations");
3026
+ const childEnv = { ...process.env };
3027
+ if (existsSync2(envIntPath)) {
3028
+ try {
3029
+ for (const line of readFileSync3(envIntPath, "utf-8").split("\n")) {
3030
+ if (!line || line.startsWith("#") || !line.includes("=")) continue;
3031
+ const eqIdx = line.indexOf("=");
3032
+ childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);
3033
+ }
3034
+ } catch {
3035
+ }
3036
+ }
3037
+ const { stdout } = await execFilePromiseLong("claude", chatArgs, { cwd: projDir, stdin: "ignore", env: childEnv });
3032
3038
  reply = stdout.trim() || "[No response from agent]";
3033
3039
  } else {
3034
3040
  const { stdout } = await execFilePromiseLong("openclaw", [
@@ -3431,7 +3437,8 @@ async function execFilePromiseLong(cmd, args, opts) {
3431
3437
  return new Promise((resolve, reject) => {
3432
3438
  const child = sp(cmd, args, {
3433
3439
  cwd: opts?.cwd,
3434
- stdio: [opts?.stdin === "ignore" ? "ignore" : "pipe", "pipe", "pipe"]
3440
+ stdio: [opts?.stdin === "ignore" ? "ignore" : "pipe", "pipe", "pipe"],
3441
+ ...opts?.env ? { env: opts.env } : {}
3435
3442
  });
3436
3443
  let stdout = "";
3437
3444
  let stderr = "";