@integrity-labs/agt-cli 0.7.9 → 0.7.11

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.
@@ -17,11 +17,11 @@ import {
17
17
  loadSchedulerState,
18
18
  markTaskFired,
19
19
  syncTasksToScheduler
20
- } from "../chunk-4I4QZRBQ.js";
20
+ } from "../chunk-5UGOY3IV.js";
21
21
 
22
22
  // src/lib/manager-worker.ts
23
23
  import { createHash } from "crypto";
24
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2, rmSync, readdirSync, statSync, unlinkSync } from "fs";
24
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync, existsSync as existsSync2, rmSync, readdirSync, statSync, unlinkSync } from "fs";
25
25
  import https from "https";
26
26
  import { join as join2 } from "path";
27
27
 
@@ -327,55 +327,53 @@ var GatewayClientPool = class extends EventEmitter {
327
327
  };
328
328
 
329
329
  // src/lib/persistent-session.ts
330
- import { spawn, execSync } from "child_process";
331
- import { join } from "path";
330
+ import { execFileSync, execSync } from "child_process";
331
+ import { join, dirname } from "path";
332
332
  import { homedir } from "os";
333
- import { existsSync, readFileSync as readFileSync2 } from "fs";
334
- var sessions = /* @__PURE__ */ new Map();
335
- function startPersistentSession(config2) {
336
- const existing = sessions.get(config2.codeName);
337
- if (existing && existing.status === "running" && existing.process && !existing.process.killed) {
338
- return existing;
333
+ import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
334
+ import { fileURLToPath } from "url";
335
+ var _acpxBin = null;
336
+ function getAcpxBin() {
337
+ if (_acpxBin) return _acpxBin;
338
+ const localBin = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "node_modules", ".bin", "acpx");
339
+ if (existsSync(localBin)) {
340
+ _acpxBin = localBin;
341
+ return _acpxBin;
342
+ }
343
+ try {
344
+ execSync("which acpx", { stdio: "ignore" });
345
+ _acpxBin = "acpx";
346
+ return _acpxBin;
347
+ } catch {
348
+ }
349
+ try {
350
+ execSync("npm install -g acpx@latest", { stdio: "ignore", timeout: 6e4 });
351
+ _acpxBin = "acpx";
352
+ return _acpxBin;
353
+ } catch {
354
+ throw new Error("acpx not found and auto-install failed. Run: npm install -g acpx");
339
355
  }
340
- const session = {
341
- codeName: config2.codeName,
342
- process: null,
343
- taskChannelPort: null,
344
- startedAt: null,
345
- restartCount: existing?.restartCount ?? 0,
346
- status: "starting"
347
- };
348
- sessions.set(config2.codeName, session);
349
- spawnSession(config2, session);
350
- return session;
351
356
  }
352
- function spawnSession(config2, session) {
353
- const { codeName, projectDir, mcpConfigPath, claudeMdPath, channels, devChannels, apiHost, log: log2 } = config2;
354
- sanitizeMcpJson(mcpConfigPath, apiHost);
355
- const args = [];
357
+ var sessions = /* @__PURE__ */ new Map();
358
+ function writeAcpxConfig(config2) {
359
+ const { projectDir, mcpConfigPath, claudeMdPath, channels, devChannels } = config2;
360
+ const claudeArgs = [];
356
361
  if (channels.length > 0) {
357
- args.push("--channels", ...channels);
362
+ claudeArgs.push("--channels", ...channels);
358
363
  }
359
364
  if (devChannels.length > 0) {
360
- args.push("--dangerously-load-development-channels", ...devChannels);
365
+ claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
361
366
  }
362
- args.push("--mcp-config", mcpConfigPath);
367
+ claudeArgs.push("--mcp-config", mcpConfigPath);
363
368
  const channelsConfigPath = join(projectDir, ".mcp-channels.json");
364
369
  if (existsSync(channelsConfigPath)) {
365
- args.push("--mcp-config", channelsConfigPath);
370
+ claudeArgs.push("--mcp-config", channelsConfigPath);
366
371
  }
367
372
  if (existsSync(claudeMdPath)) {
368
- args.push("--system-prompt-file", claudeMdPath);
369
- }
370
- args.push("--allow-dangerously-skip-permissions");
371
- args.push("--dangerously-skip-permissions");
372
- args.push("--name", `agt-${codeName}`);
373
- log2(`[persistent-session] Starting session for '${codeName}' with args: ${args.join(" ")}`);
374
- const tmuxSession = `agt-${codeName}`;
375
- try {
376
- execSync(`tmux kill-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
377
- } catch {
373
+ claudeArgs.push("--system-prompt-file", claudeMdPath);
378
374
  }
375
+ claudeArgs.push("--allow-dangerously-skip-permissions");
376
+ claudeArgs.push("--dangerously-skip-permissions");
379
377
  const envIntegrationsPath = join(projectDir, ".env.integrations");
380
378
  let envPrefix = "";
381
379
  if (existsSync(envIntegrationsPath)) {
@@ -385,138 +383,154 @@ function spawnSession(config2, session) {
385
383
  const eqIdx = line.indexOf("=");
386
384
  const key = line.slice(0, eqIdx);
387
385
  const value = line.slice(eqIdx + 1);
388
- return `${key}=${JSON.stringify(value)}`;
386
+ return `${key}=${value.includes(" ") ? JSON.stringify(value) : value}`;
389
387
  }).join(" ");
390
388
  if (envVars) envPrefix = `env ${envVars} `;
391
389
  } catch {
392
390
  }
393
391
  }
394
- const initPrompt = "You are now online. Wait for messages from your channels (Telegram, Slack) and respond to them. Use your kanban tools to track work.";
395
- const claudeCmd = `${envPrefix}claude ${JSON.stringify(initPrompt)} ${args.map((a) => a.includes(" ") ? JSON.stringify(a) : a).join(" ")}`;
396
- const child = spawn("tmux", [
397
- "new-session",
398
- "-d",
399
- "-s",
400
- tmuxSession,
401
- "-c",
402
- projectDir,
403
- claudeCmd
404
- ], {
405
- cwd: projectDir,
406
- stdio: ["ignore", "pipe", "pipe"],
407
- env: process.env
408
- });
409
- session.process = child;
410
- session.startedAt = Date.now();
411
- session.status = "running";
412
- child.on("close", (code) => {
413
- if (code !== 0) {
414
- log2(`[persistent-session] Failed to create tmux session for '${codeName}' (exit ${code})`);
415
- session.status = "crashed";
416
- session.process = null;
417
- return;
418
- }
419
- log2(`[persistent-session] tmux session '${tmuxSession}' created for '${codeName}'`);
420
- const acceptDialogs = async () => {
421
- for (let i = 0; i < 15; i++) {
422
- await new Promise((r) => setTimeout(r, 2e3));
423
- try {
424
- const { execSync: es } = await import("child_process");
425
- const screen = es(`tmux capture-pane -t ${tmuxSession} -p 2>/dev/null`, { encoding: "utf-8" });
426
- if (screen.includes("Yes, I trust this folder")) {
427
- es(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
428
- log2(`[persistent-session] Auto-accepted workspace trust for '${codeName}'`);
429
- continue;
430
- }
431
- if (screen.includes("I am using this for local development")) {
432
- es(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
433
- log2(`[persistent-session] Auto-accepted dev channels for '${codeName}'`);
434
- continue;
435
- }
436
- if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
437
- es(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
438
- log2(`[persistent-session] Auto-accepted MCP servers for '${codeName}'`);
439
- continue;
440
- }
441
- if (screen.includes("Yes, I accept") && screen.includes("Bypass Permissions")) {
442
- es(`tmux send-keys -t ${tmuxSession} 2`, { stdio: "ignore" });
443
- await new Promise((r) => setTimeout(r, 300));
444
- es(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
445
- log2(`[persistent-session] Auto-accepted bypass permissions for '${codeName}'`);
446
- continue;
447
- }
448
- if (screen.includes("\u276F") && !screen.includes("Enter to confirm")) {
449
- log2(`[persistent-session] Session ready for '${codeName}' \u2014 no more dialogs`);
450
- break;
451
- }
452
- } catch {
453
- break;
454
- }
392
+ const acpxConfig = {
393
+ defaultAgent: "claude",
394
+ defaultPermissions: "approve-all",
395
+ agents: {
396
+ claude: {
397
+ command: `${envPrefix}npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.join(" ")}`
455
398
  }
456
- };
457
- acceptDialogs().catch(() => {
399
+ }
400
+ };
401
+ writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
402
+ }
403
+ function startPersistentSession(config2) {
404
+ const existing = sessions.get(config2.codeName);
405
+ if (existing && existing.status === "running") {
406
+ return existing;
407
+ }
408
+ const restartCount = existing?.restartCount ?? 0;
409
+ if (existing?.status === "crashed" && existing.startedAt) {
410
+ const backoffMs = Math.min(5e3 * Math.pow(2, restartCount), 6e4);
411
+ if (Date.now() - existing.startedAt < backoffMs) {
412
+ return existing;
413
+ }
414
+ }
415
+ const session = {
416
+ codeName: config2.codeName,
417
+ startedAt: null,
418
+ restartCount,
419
+ status: "starting"
420
+ };
421
+ sessions.set(config2.codeName, session);
422
+ spawnSession(config2, session);
423
+ return session;
424
+ }
425
+ function spawnSession(config2, session) {
426
+ const { codeName, projectDir, mcpConfigPath, apiHost, log: log2 } = config2;
427
+ const sessionName = `agt-${codeName}`;
428
+ const initPrompt = "You are now online. Wait for messages from your channels (Telegram, Slack) and respond to them. Use your kanban tools to track work.";
429
+ log2(`[persistent-session] Starting acpx session '${sessionName}' for '${codeName}'`);
430
+ try {
431
+ sanitizeMcpJson(mcpConfigPath, apiHost);
432
+ writeAcpxConfig(config2);
433
+ execFileSync(getAcpxBin(), [
434
+ "claude",
435
+ "sessions",
436
+ "ensure",
437
+ "--name",
438
+ sessionName
439
+ ], {
440
+ cwd: projectDir,
441
+ timeout: 3e4,
442
+ stdio: "ignore"
458
443
  });
459
- });
460
- child.on("error", (err) => {
461
- log2(`[persistent-session] Failed to start tmux for '${codeName}': ${err.message}`);
444
+ execFileSync(getAcpxBin(), [
445
+ "claude",
446
+ "-s",
447
+ sessionName,
448
+ "--no-wait",
449
+ "--",
450
+ initPrompt
451
+ ], {
452
+ cwd: projectDir,
453
+ timeout: 3e4,
454
+ stdio: "ignore"
455
+ });
456
+ session.startedAt = Date.now();
457
+ session.status = "running";
458
+ session.restartCount = 0;
459
+ log2(`[persistent-session] Session '${sessionName}' started for '${codeName}'`);
460
+ } catch (err) {
461
+ log2(`[persistent-session] Failed to start acpx session for '${codeName}': ${err.message}`);
462
462
  session.status = "crashed";
463
- });
463
+ session.startedAt = Date.now();
464
+ session.restartCount++;
465
+ }
464
466
  }
465
467
  function stopPersistentSession(codeName, log2) {
466
468
  const session = sessions.get(codeName);
467
469
  if (!session) return;
468
470
  log2(`[persistent-session] Stopping session for '${codeName}'`);
469
471
  session.status = "stopped";
472
+ const projectDir = getProjectDir2(codeName);
470
473
  try {
471
- execSync(`tmux kill-session -t agt-${codeName} 2>/dev/null`, { stdio: "ignore" });
474
+ execFileSync(getAcpxBin(), ["claude", "sessions", "close", `agt-${codeName}`], {
475
+ cwd: projectDir,
476
+ timeout: 1e4,
477
+ stdio: "ignore"
478
+ });
472
479
  } catch {
473
480
  }
474
- if (session.process && !session.process.killed) {
475
- session.process.kill("SIGTERM");
481
+ try {
482
+ execFileSync("tmux", ["kill-session", "-t", `agt-${codeName}`], { stdio: "ignore" });
483
+ } catch {
476
484
  }
477
- setTimeout(() => {
478
- if (session.process && !session.process.killed) {
479
- session.process.kill("SIGKILL");
480
- }
481
- }, 1e4);
482
485
  sessions.delete(codeName);
483
486
  }
484
487
  async function injectMessage(codeName, type, content, meta) {
485
488
  const session = sessions.get(codeName);
486
- if (!session || session.status !== "running" || !session.process) {
489
+ if (!session || session.status !== "running") {
487
490
  return false;
488
491
  }
489
- if (session.taskChannelPort) {
490
- try {
491
- const res = await fetch(`http://127.0.0.1:${session.taskChannelPort}`, {
492
- method: "POST",
493
- headers: { "Content-Type": "application/json" },
494
- body: JSON.stringify({ type, content, meta }),
495
- signal: AbortSignal.timeout(1e4)
496
- });
497
- if (res.ok) return true;
498
- } catch {
499
- }
500
- }
492
+ const projectDir = getProjectDir2(codeName);
493
+ const sessionName = `agt-${codeName}`;
494
+ const prefix = meta?.task_name ? `[Task: ${meta.task_name}] ` : "";
495
+ const text = prefix + content;
501
496
  try {
502
- const prefix = meta?.task_name ? `[Task: ${meta.task_name}] ` : "";
503
- const text = prefix + content;
504
- const escaped = text.replace(/'/g, "'\\''");
505
- execSync(`tmux send-keys -t agt-${codeName} '${escaped}' Enter`, { stdio: "ignore" });
497
+ execFileSync(getAcpxBin(), [
498
+ "claude",
499
+ "-s",
500
+ sessionName,
501
+ "--no-wait",
502
+ "--",
503
+ text
504
+ ], {
505
+ cwd: projectDir,
506
+ timeout: 15e3,
507
+ stdio: "ignore"
508
+ });
506
509
  return true;
507
510
  } catch {
508
511
  return false;
509
512
  }
510
- return false;
511
513
  }
512
514
  function isSessionHealthy(codeName) {
513
515
  const session = sessions.get(codeName);
514
516
  if (!session || session.status !== "running") return false;
517
+ const projectDir = getProjectDir2(codeName);
515
518
  try {
516
- execSync(`tmux has-session -t agt-${codeName} 2>/dev/null`, { stdio: "ignore" });
517
- return true;
519
+ const output = execFileSync(getAcpxBin(), [
520
+ "claude",
521
+ "-s",
522
+ `agt-${codeName}`,
523
+ "status"
524
+ ], {
525
+ cwd: projectDir,
526
+ timeout: 1e4,
527
+ encoding: "utf-8"
528
+ });
529
+ return output.includes("status: running") || output.includes("status: idle");
518
530
  } catch {
519
531
  session.status = "crashed";
532
+ session.startedAt = Date.now();
533
+ session.restartCount++;
520
534
  return false;
521
535
  }
522
536
  }
@@ -527,28 +541,10 @@ function resetRestartCount(codeName) {
527
541
  async function stopAllSessionsAndWait(log2, opts) {
528
542
  const codeNames = [...sessions.keys()];
529
543
  if (codeNames.length === 0) return;
530
- const exitPromises = [];
531
544
  for (const codeName of codeNames) {
532
- const session = sessions.get(codeName);
533
- const proc = session?.process;
534
545
  stopPersistentSession(codeName, log2);
535
- if (proc && !proc.killed && proc.exitCode === null) {
536
- exitPromises.push(
537
- new Promise((resolve) => {
538
- proc.on("close", resolve);
539
- proc.on("error", resolve);
540
- })
541
- );
542
- }
543
546
  }
544
- if (exitPromises.length === 0) return;
545
- await Promise.race([
546
- Promise.all(exitPromises),
547
- new Promise((resolve) => setTimeout(() => {
548
- log2(`[persistent-session] Shutdown timeout (${opts.timeoutMs}ms), force-killing remaining sessions`);
549
- resolve();
550
- }, opts.timeoutMs))
551
- ]);
547
+ await new Promise((resolve) => setTimeout(resolve, Math.min(opts.timeoutMs, 2e3)));
552
548
  }
553
549
  function getProjectDir2(codeName) {
554
550
  return join(homedir(), ".augmented", codeName, "project");
@@ -874,16 +870,16 @@ var lastVersionCheckAt = 0;
874
870
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
875
871
  async function ensureBrewDependency(binary, formula) {
876
872
  if (frameworkBinaryChecked.has(`dep:${binary}`)) return true;
877
- const { execFileSync } = await import("child_process");
873
+ const { execFileSync: execFileSync2 } = await import("child_process");
878
874
  try {
879
- execFileSync("which", [binary], { timeout: 5e3 });
875
+ execFileSync2("which", [binary], { timeout: 5e3 });
880
876
  frameworkBinaryChecked.add(`dep:${binary}`);
881
877
  return true;
882
878
  } catch {
883
879
  }
884
880
  let brewPath;
885
881
  try {
886
- brewPath = execFileSync("which", ["brew"], { timeout: 5e3 }).toString().trim();
882
+ brewPath = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
887
883
  } catch {
888
884
  log(`${binary} not found and Homebrew not available \u2014 install manually: brew install ${formula}`);
889
885
  frameworkBinaryChecked.add(`dep:${binary}`);
@@ -891,14 +887,14 @@ async function ensureBrewDependency(binary, formula) {
891
887
  }
892
888
  log(`${binary} not found \u2014 installing via Homebrew...`);
893
889
  try {
894
- execFileSync(brewPath, ["install", formula], { timeout: 12e4, stdio: "pipe" });
890
+ execFileSync2(brewPath, ["install", formula], { timeout: 12e4, stdio: "pipe" });
895
891
  } catch (err) {
896
892
  log(`Failed to install ${formula}: ${err.message}`);
897
893
  frameworkBinaryChecked.add(`dep:${binary}`);
898
894
  return false;
899
895
  }
900
896
  try {
901
- execFileSync("which", [binary], { timeout: 5e3 });
897
+ execFileSync2("which", [binary], { timeout: 5e3 });
902
898
  log(`${binary} installed successfully`);
903
899
  frameworkBinaryChecked.add(`dep:${binary}`);
904
900
  return true;
@@ -913,24 +909,24 @@ async function ensureFrameworkBinary(frameworkId) {
913
909
  if (frameworkBinaryChecked.has(frameworkId)) return;
914
910
  frameworkBinaryChecked.add(frameworkId);
915
911
  await ensureBrewDependency("tmux", "tmux");
916
- const { execFileSync } = await import("child_process");
912
+ const { execFileSync: execFileSync2 } = await import("child_process");
917
913
  let brewPath;
918
914
  try {
919
- brewPath = execFileSync("which", ["brew"], { timeout: 5e3 }).toString().trim();
915
+ brewPath = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
920
916
  } catch {
921
917
  log("Homebrew not found \u2014 cannot auto-install/upgrade Claude Code. Install manually: https://claude.ai/download");
922
918
  return;
923
919
  }
924
920
  let claudeExists = false;
925
921
  try {
926
- execFileSync("which", ["claude"], { timeout: 5e3 });
922
+ execFileSync2("which", ["claude"], { timeout: 5e3 });
927
923
  claudeExists = true;
928
924
  } catch {
929
925
  }
930
926
  if (!claudeExists) {
931
927
  log("Claude Code binary not found \u2014 installing via Homebrew...");
932
928
  try {
933
- execFileSync(brewPath, ["install", "--cask", "claude-code"], {
929
+ execFileSync2(brewPath, ["install", "--cask", "claude-code"], {
934
930
  timeout: 12e4,
935
931
  stdio: "pipe"
936
932
  });
@@ -939,7 +935,7 @@ async function ensureFrameworkBinary(frameworkId) {
939
935
  return;
940
936
  }
941
937
  try {
942
- execFileSync("which", ["claude"], { timeout: 5e3 });
938
+ execFileSync2("which", ["claude"], { timeout: 5e3 });
943
939
  log("Claude Code installed successfully");
944
940
  } catch {
945
941
  log("Claude Code install completed but binary not found on PATH \u2014 you may need to restart your terminal");
@@ -947,7 +943,7 @@ async function ensureFrameworkBinary(frameworkId) {
947
943
  } else {
948
944
  log("Checking for Claude Code updates...");
949
945
  try {
950
- const output = execFileSync(brewPath, ["upgrade", "--cask", "claude-code"], {
946
+ const output = execFileSync2(brewPath, ["upgrade", "--cask", "claude-code"], {
951
947
  timeout: 12e4,
952
948
  stdio: "pipe"
953
949
  }).toString();
@@ -965,11 +961,11 @@ async function ensureFrameworkBinary(frameworkId) {
965
961
  }
966
962
  }
967
963
  }
968
- agentRuntimeAuthenticated = checkClaudeAuth(execFileSync);
964
+ agentRuntimeAuthenticated = checkClaudeAuth(execFileSync2);
969
965
  }
970
- function checkClaudeAuth(execFileSync) {
966
+ function checkClaudeAuth(execFileSync2) {
971
967
  try {
972
- const authOutput = execFileSync("claude", ["auth", "status"], { timeout: 1e4, stdio: "pipe" }).toString().trim();
968
+ const authOutput = execFileSync2("claude", ["auth", "status"], { timeout: 1e4, stdio: "pipe" }).toString().trim();
973
969
  let loggedIn = null;
974
970
  try {
975
971
  const parsed = JSON.parse(authOutput);
@@ -1001,7 +997,7 @@ function loadGatewayPorts() {
1001
997
  }
1002
998
  function saveGatewayPorts(ports) {
1003
999
  mkdirSync(AUGMENTED_DIR, { recursive: true });
1004
- writeFileSync2(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
1000
+ writeFileSync3(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
1005
1001
  }
1006
1002
  function allocatePort(codeName) {
1007
1003
  const ports = loadGatewayPorts();
@@ -1027,7 +1023,7 @@ var STATE_FILE = join2(process.env["HOME"] ?? "/tmp", ".augmented", "manager-sta
1027
1023
  function send(msg) {
1028
1024
  if (msg.type === "state-update") {
1029
1025
  try {
1030
- writeFileSync2(STATE_FILE, JSON.stringify(msg.state, null, 2));
1026
+ writeFileSync3(STATE_FILE, JSON.stringify(msg.state, null, 2));
1031
1027
  } catch {
1032
1028
  }
1033
1029
  }
@@ -1085,7 +1081,7 @@ async function migrateToProfiles() {
1085
1081
  if (existsSync2(authFile)) {
1086
1082
  mkdirSync(profileAuthDir, { recursive: true });
1087
1083
  const authContent = readFileSync3(authFile, "utf-8");
1088
- writeFileSync2(join2(profileAuthDir, "auth-profiles.json"), authContent);
1084
+ writeFileSync3(join2(profileAuthDir, "auth-profiles.json"), authContent);
1089
1085
  }
1090
1086
  allocatePort(codeName);
1091
1087
  migrated++;
@@ -1180,7 +1176,7 @@ async function ensureGatewayRunning(codeName, adapter) {
1180
1176
  if (cfg.gateway?.port !== status.port) {
1181
1177
  if (!cfg.gateway) cfg.gateway = {};
1182
1178
  cfg.gateway.port = status.port;
1183
- writeFileSync2(configPath, JSON.stringify(cfg, null, 2));
1179
+ writeFileSync3(configPath, JSON.stringify(cfg, null, 2));
1184
1180
  }
1185
1181
  }
1186
1182
  } catch {
@@ -1205,7 +1201,7 @@ async function ensureGatewayRunning(codeName, adapter) {
1205
1201
  const cfg = JSON.parse(readFileSync3(configPath, "utf-8"));
1206
1202
  if (!cfg.gateway) cfg.gateway = {};
1207
1203
  cfg.gateway.port = port;
1208
- writeFileSync2(configPath, JSON.stringify(cfg, null, 2));
1204
+ writeFileSync3(configPath, JSON.stringify(cfg, null, 2));
1209
1205
  }
1210
1206
  } catch {
1211
1207
  }
@@ -1605,7 +1601,7 @@ async function processAgent(agent, agentStates) {
1605
1601
  const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
1606
1602
  log(`${verb} '${agent.code_name}': ${fileNames}`);
1607
1603
  for (const file of changedFiles) {
1608
- writeFileSync2(join2(agentDir, file.relativePath), file.content);
1604
+ writeFileSync3(join2(agentDir, file.relativePath), file.content);
1609
1605
  }
1610
1606
  lastProvisionAt = (/* @__PURE__ */ new Date()).toISOString();
1611
1607
  knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });
@@ -1839,10 +1835,10 @@ async function processAgent(agent, agentStates) {
1839
1835
  const hasLcm = integrations.some((i) => i.definition_id === "lossless-claw");
1840
1836
  if (hasLcm && !losslessClawInstalled.get(agent.code_name)) {
1841
1837
  try {
1842
- const { execFileSync } = await import("child_process");
1838
+ const { execFileSync: execFileSync2 } = await import("child_process");
1843
1839
  let pluginList;
1844
1840
  try {
1845
- pluginList = execFileSync(
1841
+ pluginList = execFileSync2(
1846
1842
  "openclaw",
1847
1843
  ["--profile", agent.code_name, "plugins", "list", "--json"],
1848
1844
  { timeout: 15e3 }
@@ -1856,7 +1852,7 @@ async function processAgent(agent, agentStates) {
1856
1852
  );
1857
1853
  if (!alreadyInstalled) {
1858
1854
  log(`Installing lossless-claw plugin for '${agent.code_name}'...`);
1859
- execFileSync(
1855
+ execFileSync2(
1860
1856
  "openclaw",
1861
1857
  ["--profile", agent.code_name, "plugins", "install", "@martian-engineering/lossless-claw"],
1862
1858
  { stdio: "ignore", timeout: 6e4 }
@@ -1903,18 +1899,18 @@ async function processAgent(agent, agentStates) {
1903
1899
  "@tobilu/qmd"
1904
1900
  ]);
1905
1901
  if (intHash !== prevIntHash) {
1906
- const { execFileSync } = await import("child_process");
1902
+ const { execFileSync: execFileSync2 } = await import("child_process");
1907
1903
  for (const tool of capData.cliTools) {
1908
1904
  if (!ALLOWED_CLI_PACKAGES.has(tool.package)) {
1909
1905
  log(`Skipping CLI tool '${tool.package}' for '${agent.code_name}' \u2014 not on the allowed packages list`);
1910
1906
  continue;
1911
1907
  }
1912
1908
  try {
1913
- execFileSync("which", [tool.binary], { stdio: "ignore" });
1909
+ execFileSync2("which", [tool.binary], { stdio: "ignore" });
1914
1910
  } catch {
1915
1911
  log(`Installing CLI tool '${tool.package}' for '${agent.code_name}'...`);
1916
1912
  try {
1917
- execFileSync("npm", ["install", "-g", tool.package], { stdio: "ignore", timeout: 6e4 });
1913
+ execFileSync2("npm", ["install", "-g", tool.package], { stdio: "ignore", timeout: 6e4 });
1918
1914
  log(`CLI tool '${tool.binary}' installed successfully`);
1919
1915
  } catch (installErr) {
1920
1916
  log(`Failed to install CLI tool '${tool.package}': ${installErr.message}`);
@@ -1923,7 +1919,7 @@ async function processAgent(agent, agentStates) {
1923
1919
  if (tool.binary === "qmd") {
1924
1920
  try {
1925
1921
  const agentDir2 = join2(process.env["HOME"] ?? "/tmp", ".augmented", agent.code_name);
1926
- execFileSync("qmd", ["collection", "add", agent.code_name, "project"], {
1922
+ execFileSync2("qmd", ["collection", "add", agent.code_name, "project"], {
1927
1923
  stdio: "ignore",
1928
1924
  timeout: 3e4,
1929
1925
  cwd: agentDir2
@@ -2304,7 +2300,7 @@ function cleanupCronSessions(sessionsDir, keepCount) {
2304
2300
  }
2305
2301
  }
2306
2302
  }
2307
- writeFileSync2(indexPath, JSON.stringify(index));
2303
+ writeFileSync3(indexPath, JSON.stringify(index));
2308
2304
  if (toDelete.length > 0) {
2309
2305
  log(`Cleaned ${toDelete.length} cron session(s) and ${deletedFiles} file(s) from ${sessionsDir}`);
2310
2306
  }
@@ -2340,7 +2336,7 @@ function clearStaleCronRunState(jobsPath) {
2340
2336
  }
2341
2337
  }
2342
2338
  if (changed) {
2343
- writeFileSync2(jobsPath, JSON.stringify(data, null, 2));
2339
+ writeFileSync3(jobsPath, JSON.stringify(data, null, 2));
2344
2340
  }
2345
2341
  } catch {
2346
2342
  }
@@ -2563,8 +2559,8 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2563
2559
  }
2564
2560
  }
2565
2561
  if (!agentRuntimeAuthenticated) {
2566
- const { execFileSync } = await import("child_process");
2567
- agentRuntimeAuthenticated = checkClaudeAuth(execFileSync);
2562
+ const { execFileSync: execFileSync2 } = await import("child_process");
2563
+ agentRuntimeAuthenticated = checkClaudeAuth(execFileSync2);
2568
2564
  if (!agentRuntimeAuthenticated) {
2569
2565
  log(`[persistent-session] Skipping '${codeName}' \u2014 Claude Code not authenticated`);
2570
2566
  return;
@@ -2594,6 +2590,32 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2594
2590
  return;
2595
2591
  }
2596
2592
  resetRestartCount(codeName);
2593
+ const stableTasksHash = createHash("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
2594
+ const prevHash = knownTasksHashes.get(agent.agent_id);
2595
+ if (stableTasksHash !== prevHash) {
2596
+ const taskInputs = tasks.map((t) => ({
2597
+ id: t.id,
2598
+ template_id: t.template_id,
2599
+ name: t.name,
2600
+ schedule_kind: t.schedule_kind,
2601
+ schedule_expr: t.schedule_expr ?? null,
2602
+ schedule_every: t.schedule_every ?? null,
2603
+ schedule_at: t.schedule_at ?? null,
2604
+ timezone: t.timezone ?? "UTC",
2605
+ prompt: t.prompt ?? "",
2606
+ session_target: t.session_target ?? "isolated",
2607
+ delivery_mode: t.delivery_mode ?? "none",
2608
+ delivery_channel: t.delivery_channel ?? null,
2609
+ delivery_to: t.delivery_to ?? null,
2610
+ enabled: t.enabled ?? true
2611
+ }));
2612
+ const schedulerState = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
2613
+ claudeSchedulerStates.set(codeName, schedulerState);
2614
+ knownTasksHashes.set(agent.agent_id, stableTasksHash);
2615
+ log(`[persistent-session] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
2616
+ } else if (!claudeSchedulerStates.has(codeName)) {
2617
+ claudeSchedulerStates.set(codeName, loadSchedulerState(codeName));
2618
+ }
2597
2619
  const state2 = claudeSchedulerStates.get(codeName);
2598
2620
  if (state2) {
2599
2621
  const ready = getReadyTasks(state2);
@@ -2626,7 +2648,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2626
2648
  const markerPath = join2(markerDir, ".agt-pending-task.json");
2627
2649
  try {
2628
2650
  mkdirSync(markerDir, { recursive: true });
2629
- writeFileSync2(markerPath, JSON.stringify({
2651
+ writeFileSync3(markerPath, JSON.stringify({
2630
2652
  agent_id: agent.agent_id,
2631
2653
  task_id: task.taskId,
2632
2654
  template_id: task.templateId,
@@ -2644,35 +2666,13 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2644
2666
  if (injected) {
2645
2667
  const updated = markTaskFired(codeName, task.taskId, "ok");
2646
2668
  claudeSchedulerStates.set(codeName, updated);
2647
- log(`[persistent-session] Task '${task.name}' injected, next fire at ${new Date(updated.tasks[task.taskId]?.nextFireAt ?? 0).toISOString()}`);
2669
+ log(`[persistent-session] Task '${task.name}' injected for '${codeName}', next fire at ${new Date(updated.tasks[task.taskId]?.nextFireAt ?? 0).toISOString()}`);
2670
+ } else {
2671
+ log(`[persistent-session] Task '${task.name}' injection FAILED for '${codeName}' \u2014 injectMessage returned false`);
2648
2672
  }
2649
2673
  break;
2650
2674
  }
2651
2675
  }
2652
- const stableTasksHash = createHash("sha256").update(JSON.stringify(tasks)).digest("hex").slice(0, 16);
2653
- const prevHash = knownTasksHashes.get(agent.agent_id);
2654
- if (stableTasksHash !== prevHash) {
2655
- const taskInputs = tasks.map((t) => ({
2656
- id: t.id,
2657
- template_id: t.template_id,
2658
- name: t.name,
2659
- schedule_kind: t.schedule_kind,
2660
- schedule_expr: t.schedule_expr ?? null,
2661
- schedule_every: t.schedule_every ?? null,
2662
- schedule_at: t.schedule_at ?? null,
2663
- timezone: t.timezone ?? "UTC",
2664
- prompt: t.prompt ?? "",
2665
- session_target: t.session_target ?? "isolated",
2666
- delivery_mode: t.delivery_mode ?? "none",
2667
- delivery_channel: t.delivery_channel ?? null,
2668
- delivery_to: t.delivery_to ?? null,
2669
- enabled: t.enabled ?? true
2670
- }));
2671
- const schedulerState = syncTasksToScheduler(codeName, agent.agent_id, taskInputs);
2672
- claudeSchedulerStates.set(codeName, schedulerState);
2673
- knownTasksHashes.set(agent.agent_id, stableTasksHash);
2674
- log(`[persistent-session] Tasks synced for '${codeName}' (${taskInputs.length} task(s))`);
2675
- }
2676
2676
  }
2677
2677
  var realtimeStarted = false;
2678
2678
  var realtimeDriftStarted = false;
@@ -2887,7 +2887,7 @@ async function processDirectChatMessage(agent, msg) {
2887
2887
  try {
2888
2888
  let reply;
2889
2889
  if (fw === "claude-code") {
2890
- const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-APXMZEK4.js");
2890
+ const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-V2QNX3Z4.js");
2891
2891
  const projDir = ccProjectDir(agent.codeName);
2892
2892
  const chatArgs = [
2893
2893
  "-p",
@@ -3646,8 +3646,8 @@ var caffeinateProc = null;
3646
3646
  async function startCaffeinate() {
3647
3647
  if (process.platform !== "darwin") return;
3648
3648
  try {
3649
- const { spawn: spawn2 } = await import("child_process");
3650
- caffeinateProc = spawn2("caffeinate", ["-dims"], {
3649
+ const { spawn } = await import("child_process");
3650
+ caffeinateProc = spawn("caffeinate", ["-dims"], {
3651
3651
  stdio: "ignore",
3652
3652
  detached: false
3653
3653
  });