@integrity-labs/agt-cli 0.7.12 → 0.8.0

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.
@@ -9,7 +9,7 @@ import {
9
9
  provisionStopHook,
10
10
  requireHost,
11
11
  resolveChannels
12
- } from "../chunk-X3FLX6EO.js";
12
+ } from "../chunk-55TMBRXT.js";
13
13
  import {
14
14
  findTaskByTemplate,
15
15
  getProjectDir,
@@ -21,7 +21,7 @@ import {
21
21
 
22
22
  // src/lib/manager-worker.ts
23
23
  import { createHash } from "crypto";
24
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync, existsSync as existsSync2, rmSync, readdirSync, statSync, unlinkSync } from "fs";
24
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, 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
 
@@ -45,10 +45,12 @@ function sanitizeMcpJson(mcpConfigPath, apiHost) {
45
45
  continue;
46
46
  }
47
47
  }
48
- if (!val.type) {
49
- val.type = "sse";
50
- changed = true;
51
- }
48
+ const url = val.url;
49
+ delete val.url;
50
+ delete val.type;
51
+ val.command = "npx";
52
+ val.args = ["-y", "mcp-remote", url, "--allow-http"];
53
+ changed = true;
52
54
  }
53
55
  if (changed) writeFileSync(mcpConfigPath, JSON.stringify(mcpRaw, null, 2));
54
56
  return changed;
@@ -327,73 +329,46 @@ var GatewayClientPool = class extends EventEmitter {
327
329
  };
328
330
 
329
331
  // src/lib/persistent-session.ts
330
- import { execFileSync, execSync } from "child_process";
332
+ import { spawn, execSync, execFileSync } from "child_process";
331
333
  import { join, dirname } from "path";
332
334
  import { homedir } from "os";
333
- import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
335
+ import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
334
336
  import { fileURLToPath } from "url";
337
+ function collectMcpServerNames(mcpConfigPath, channelsConfigPath) {
338
+ const names = [];
339
+ for (const path of [mcpConfigPath, channelsConfigPath]) {
340
+ if (!existsSync(path)) continue;
341
+ try {
342
+ const data = JSON.parse(readFileSync2(path, "utf-8"));
343
+ const servers = data.mcpServers;
344
+ if (servers) names.push(...Object.keys(servers));
345
+ } catch {
346
+ }
347
+ }
348
+ return names;
349
+ }
335
350
  var _acpxBin = null;
336
351
  function getAcpxBin() {
337
352
  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;
353
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
354
+ for (const candidate of [
355
+ join(moduleDir, "..", "..", "node_modules", ".bin", "acpx"),
356
+ join(moduleDir, "..", "..", "..", "..", "node_modules", ".bin", "acpx")
357
+ ]) {
358
+ if (existsSync(candidate)) {
359
+ _acpxBin = candidate;
360
+ return _acpxBin;
361
+ }
342
362
  }
343
363
  try {
344
364
  execSync("which acpx", { stdio: "ignore" });
345
365
  _acpxBin = "acpx";
346
366
  return _acpxBin;
347
367
  } catch {
348
- throw new Error("acpx not found. Run: npm install -g acpx");
368
+ return "";
349
369
  }
350
370
  }
351
371
  var sessions = /* @__PURE__ */ new Map();
352
- function writeAcpxConfig(config2) {
353
- const { projectDir, mcpConfigPath, claudeMdPath, channels, devChannels } = config2;
354
- const claudeArgs = [];
355
- if (channels.length > 0) {
356
- claudeArgs.push("--channels", ...channels);
357
- }
358
- if (devChannels.length > 0) {
359
- claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
360
- }
361
- claudeArgs.push("--mcp-config", mcpConfigPath);
362
- const channelsConfigPath = join(projectDir, ".mcp-channels.json");
363
- if (existsSync(channelsConfigPath)) {
364
- claudeArgs.push("--mcp-config", channelsConfigPath);
365
- }
366
- if (existsSync(claudeMdPath)) {
367
- claudeArgs.push("--system-prompt-file", claudeMdPath);
368
- }
369
- claudeArgs.push("--allow-dangerously-skip-permissions");
370
- claudeArgs.push("--dangerously-skip-permissions");
371
- const envIntegrationsPath = join(projectDir, ".env.integrations");
372
- let envPrefix = "";
373
- if (existsSync(envIntegrationsPath)) {
374
- try {
375
- const envContent = readFileSync2(envIntegrationsPath, "utf-8");
376
- const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
377
- const eqIdx = line.indexOf("=");
378
- const key = line.slice(0, eqIdx);
379
- const value = line.slice(eqIdx + 1);
380
- return `${key}=${value.includes(" ") ? JSON.stringify(value) : value}`;
381
- }).join(" ");
382
- if (envVars) envPrefix = `env ${envVars} `;
383
- } catch {
384
- }
385
- }
386
- const acpxConfig = {
387
- defaultAgent: "claude",
388
- defaultPermissions: "approve-all",
389
- agents: {
390
- claude: {
391
- command: `${envPrefix}npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.join(" ")}`
392
- }
393
- }
394
- };
395
- writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
396
- }
397
372
  function startPersistentSession(config2) {
398
373
  const existing = sessions.get(config2.codeName);
399
374
  if (existing && existing.status === "running") {
@@ -417,116 +392,219 @@ function startPersistentSession(config2) {
417
392
  return session;
418
393
  }
419
394
  function spawnSession(config2, session) {
420
- const { codeName, projectDir, mcpConfigPath, apiHost, log: log2 } = config2;
421
- const sessionName = `agt-${codeName}`;
422
- 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.";
423
- log2(`[persistent-session] Starting acpx session '${sessionName}' for '${codeName}'`);
395
+ const { codeName, projectDir, mcpConfigPath, claudeMdPath, channels, devChannels, apiHost, log: log2 } = config2;
396
+ const tmuxSession = `agt-${codeName}`;
397
+ log2(`[persistent-session] Starting tmux session '${tmuxSession}' for '${codeName}'`);
424
398
  try {
425
399
  sanitizeMcpJson(mcpConfigPath, apiHost);
426
400
  writeAcpxConfig(config2);
427
- execFileSync(getAcpxBin(), [
428
- "claude",
429
- "sessions",
430
- "ensure",
431
- "--name",
432
- sessionName
433
- ], {
434
- cwd: projectDir,
435
- timeout: 3e4,
436
- stdio: "ignore"
437
- });
438
- execFileSync(getAcpxBin(), [
439
- "claude",
401
+ try {
402
+ execSync(`tmux kill-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
403
+ } catch {
404
+ }
405
+ const args = [];
406
+ if (channels.length > 0) args.push("--channels", ...channels);
407
+ if (devChannels.length > 0) args.push("--dangerously-load-development-channels", ...devChannels);
408
+ args.push("--mcp-config", mcpConfigPath);
409
+ const channelsConfigPath = join(projectDir, ".mcp-channels.json");
410
+ if (existsSync(channelsConfigPath)) args.push("--mcp-config", channelsConfigPath);
411
+ if (existsSync(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
412
+ args.push("--allow-dangerously-skip-permissions");
413
+ args.push("--dangerously-skip-permissions");
414
+ args.push("--name", tmuxSession);
415
+ const mcpServerNames = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
416
+ const mcpPatterns = mcpServerNames.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
417
+ const allowedTools = [
418
+ ...mcpPatterns,
419
+ "Bash",
420
+ "Read",
421
+ "Write",
422
+ "Edit",
423
+ "Grep",
424
+ "Glob",
425
+ "Agent",
426
+ "Skill"
427
+ ].join(",");
428
+ args.push("--allowedTools", allowedTools);
429
+ let envPrefix = "";
430
+ const envIntegrationsPath = join(projectDir, ".env.integrations");
431
+ if (existsSync(envIntegrationsPath)) {
432
+ try {
433
+ const envContent = readFileSync2(envIntegrationsPath, "utf-8");
434
+ const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
435
+ const eqIdx = line.indexOf("=");
436
+ const key = line.slice(0, eqIdx);
437
+ const value = line.slice(eqIdx + 1);
438
+ return `${key}=${JSON.stringify(value)}`;
439
+ }).join(" ");
440
+ if (envVars) envPrefix = `env ${envVars} `;
441
+ } catch {
442
+ }
443
+ }
444
+ 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.";
445
+ const claudeCmd = `${envPrefix}claude ${JSON.stringify(initPrompt)} ${args.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
446
+ const child = spawn("tmux", [
447
+ "new-session",
448
+ "-d",
440
449
  "-s",
441
- sessionName,
442
- "--no-wait",
443
- "--",
444
- initPrompt
450
+ tmuxSession,
451
+ "-c",
452
+ projectDir,
453
+ claudeCmd
445
454
  ], {
446
455
  cwd: projectDir,
447
- timeout: 3e4,
448
- stdio: "ignore"
456
+ stdio: ["ignore", "pipe", "pipe"],
457
+ env: process.env
458
+ });
459
+ child.on("close", (code) => {
460
+ if (code !== 0) {
461
+ log2(`[persistent-session] Failed to create tmux session for '${codeName}' (exit ${code})`);
462
+ session.status = "crashed";
463
+ session.startedAt = Date.now();
464
+ session.restartCount++;
465
+ return;
466
+ }
467
+ log2(`[persistent-session] tmux session '${tmuxSession}' created for '${codeName}'`);
468
+ acceptDialogs(tmuxSession, codeName, log2).catch(() => {
469
+ });
470
+ });
471
+ child.on("error", (err) => {
472
+ log2(`[persistent-session] Failed to start tmux for '${codeName}': ${err.message}`);
473
+ session.status = "crashed";
474
+ session.startedAt = Date.now();
475
+ session.restartCount++;
449
476
  });
450
477
  session.startedAt = Date.now();
451
478
  session.status = "running";
452
479
  session.restartCount = 0;
453
- log2(`[persistent-session] Session '${sessionName}' started for '${codeName}'`);
454
480
  } catch (err) {
455
- log2(`[persistent-session] Failed to start acpx session for '${codeName}': ${err.message}`);
481
+ log2(`[persistent-session] Failed to start session for '${codeName}': ${err.message}`);
456
482
  session.status = "crashed";
457
483
  session.startedAt = Date.now();
458
484
  session.restartCount++;
459
485
  }
460
486
  }
461
- function stopPersistentSession(codeName, log2) {
462
- const session = sessions.get(codeName);
463
- if (!session) return;
464
- log2(`[persistent-session] Stopping session for '${codeName}'`);
465
- session.status = "stopped";
466
- const projectDir = getProjectDir2(codeName);
467
- try {
468
- execFileSync(getAcpxBin(), ["claude", "sessions", "close", `agt-${codeName}`], {
469
- cwd: projectDir,
470
- timeout: 1e4,
471
- stdio: "ignore"
472
- });
473
- } catch {
474
- }
475
- try {
476
- execFileSync("tmux", ["kill-session", "-t", `agt-${codeName}`], { stdio: "ignore" });
477
- } catch {
487
+ async function acceptDialogs(tmuxSession, codeName, log2) {
488
+ for (let i = 0; i < 15; i++) {
489
+ await new Promise((r) => setTimeout(r, 2e3));
490
+ try {
491
+ const screen = execSync(`tmux capture-pane -t ${tmuxSession} -p 2>/dev/null`, { encoding: "utf-8" });
492
+ if (screen.includes("Yes, I trust this folder")) {
493
+ execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
494
+ log2(`[persistent-session] Auto-accepted workspace trust for '${codeName}'`);
495
+ continue;
496
+ }
497
+ if (screen.includes("I am using this for local development")) {
498
+ execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
499
+ log2(`[persistent-session] Auto-accepted dev channels for '${codeName}'`);
500
+ continue;
501
+ }
502
+ if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
503
+ execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
504
+ log2(`[persistent-session] Auto-accepted MCP servers for '${codeName}'`);
505
+ continue;
506
+ }
507
+ if (screen.includes("Yes, I accept") && screen.includes("Bypass Permissions")) {
508
+ execSync(`tmux send-keys -t ${tmuxSession} 2`, { stdio: "ignore" });
509
+ await new Promise((r) => setTimeout(r, 300));
510
+ execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
511
+ log2(`[persistent-session] Auto-accepted bypass permissions for '${codeName}'`);
512
+ continue;
513
+ }
514
+ if (screen.includes("\u276F") && !screen.includes("Enter to confirm")) {
515
+ log2(`[persistent-session] Session ready for '${codeName}' \u2014 no more dialogs`);
516
+ break;
517
+ }
518
+ } catch {
519
+ break;
520
+ }
478
521
  }
479
- sessions.delete(codeName);
480
522
  }
481
523
  async function injectMessage(codeName, type, content, meta) {
482
524
  const session = sessions.get(codeName);
483
525
  if (!session || session.status !== "running") {
484
526
  return false;
485
527
  }
486
- const projectDir = getProjectDir2(codeName);
487
- const sessionName = `agt-${codeName}`;
488
528
  const prefix = meta?.task_name ? `[Task: ${meta.task_name}] ` : "";
489
529
  const text = prefix + content;
530
+ const projectDir = getProjectDir2(codeName);
531
+ const acpx = getAcpxBin();
532
+ if (acpx) {
533
+ try {
534
+ const tmpDir = join(projectDir, ".claude");
535
+ mkdirSync(tmpDir, { recursive: true });
536
+ const tmpFile = join(tmpDir, ".agt-inject-prompt.txt");
537
+ writeFileSync2(tmpFile, text);
538
+ execFileSync(acpx, [
539
+ "claude",
540
+ "exec",
541
+ "--cwd",
542
+ projectDir,
543
+ "-f",
544
+ tmpFile
545
+ ], {
546
+ cwd: projectDir,
547
+ timeout: 3e5,
548
+ // 5 min for task execution
549
+ stdio: "ignore"
550
+ });
551
+ return true;
552
+ } catch {
553
+ }
554
+ }
490
555
  try {
491
- execFileSync(getAcpxBin(), [
492
- "claude",
493
- "-s",
494
- sessionName,
495
- "--no-wait",
496
- "--",
497
- text
498
- ], {
499
- cwd: projectDir,
500
- timeout: 15e3,
501
- stdio: "ignore"
502
- });
503
- return true;
556
+ execFileSync("tmux", ["send-keys", "-t", `agt-${codeName}`, text, "Enter"], { stdio: "ignore" });
557
+ return false;
504
558
  } catch {
505
559
  return false;
506
560
  }
507
561
  }
508
- function isSessionHealthy(codeName) {
562
+ function stopPersistentSession(codeName, log2) {
509
563
  const session = sessions.get(codeName);
510
- if (!session || session.status !== "running") return false;
511
- const projectDir = getProjectDir2(codeName);
564
+ if (!session) return;
565
+ log2(`[persistent-session] Stopping session for '${codeName}'`);
566
+ session.status = "stopped";
512
567
  try {
513
- const output = execFileSync(getAcpxBin(), [
514
- "claude",
515
- "-s",
516
- `agt-${codeName}`,
517
- "status"
518
- ], {
519
- cwd: projectDir,
520
- timeout: 1e4,
521
- encoding: "utf-8"
522
- });
523
- return output.includes("status: running") || output.includes("status: idle");
568
+ execSync(`tmux kill-session -t agt-${codeName} 2>/dev/null`, { stdio: "ignore" });
524
569
  } catch {
525
- session.status = "crashed";
526
- session.startedAt = Date.now();
527
- session.restartCount++;
570
+ }
571
+ try {
572
+ const acpx = getAcpxBin();
573
+ if (acpx) {
574
+ execFileSync(acpx, ["claude", "sessions", "close", `agt-${codeName}`], {
575
+ cwd: getProjectDir2(codeName),
576
+ timeout: 5e3,
577
+ stdio: "ignore"
578
+ });
579
+ }
580
+ } catch {
581
+ }
582
+ sessions.delete(codeName);
583
+ }
584
+ function isSessionHealthy(codeName) {
585
+ const tmuxSession = `agt-${codeName}`;
586
+ try {
587
+ execSync(`tmux has-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
588
+ } catch {
589
+ const session2 = sessions.get(codeName);
590
+ if (session2 && session2.status === "running") {
591
+ session2.status = "crashed";
592
+ }
528
593
  return false;
529
594
  }
595
+ if (!sessions.has(codeName)) {
596
+ sessions.set(codeName, {
597
+ codeName,
598
+ startedAt: Date.now(),
599
+ restartCount: 0,
600
+ status: "running"
601
+ });
602
+ }
603
+ const session = sessions.get(codeName);
604
+ if (session.status !== "running") {
605
+ session.status = "running";
606
+ }
607
+ return true;
530
608
  }
531
609
  function resetRestartCount(codeName) {
532
610
  const session = sessions.get(codeName);
@@ -543,6 +621,47 @@ async function stopAllSessionsAndWait(log2, opts) {
543
621
  function getProjectDir2(codeName) {
544
622
  return join(homedir(), ".augmented", codeName, "project");
545
623
  }
624
+ function writeAcpxConfig(config2) {
625
+ const { projectDir, mcpConfigPath, claudeMdPath, channels, devChannels } = config2;
626
+ const claudeArgs = [];
627
+ if (channels.length > 0) claudeArgs.push("--channels", ...channels);
628
+ if (devChannels.length > 0) claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
629
+ claudeArgs.push("--mcp-config", mcpConfigPath);
630
+ const channelsConfigPath = join(projectDir, ".mcp-channels.json");
631
+ if (existsSync(channelsConfigPath)) claudeArgs.push("--mcp-config", channelsConfigPath);
632
+ if (existsSync(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
633
+ claudeArgs.push("--allow-dangerously-skip-permissions");
634
+ claudeArgs.push("--dangerously-skip-permissions");
635
+ const mcpServerNames2 = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
636
+ const mcpPatterns2 = mcpServerNames2.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
637
+ const allowedTools2 = [...mcpPatterns2, "Bash", "Read", "Write", "Edit", "Grep", "Glob", "Agent", "Skill"].join(",");
638
+ claudeArgs.push("--allowedTools", allowedTools2);
639
+ let envPrefix = "";
640
+ const envIntegrationsPath = join(projectDir, ".env.integrations");
641
+ if (existsSync(envIntegrationsPath)) {
642
+ try {
643
+ const envContent = readFileSync2(envIntegrationsPath, "utf-8");
644
+ const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
645
+ const eqIdx = line.indexOf("=");
646
+ const key = line.slice(0, eqIdx);
647
+ const value = line.slice(eqIdx + 1);
648
+ return `${key}=${JSON.stringify(value)}`;
649
+ }).join(" ");
650
+ if (envVars) envPrefix = `env ${envVars} `;
651
+ } catch {
652
+ }
653
+ }
654
+ const acpxConfig = {
655
+ defaultAgent: "claude",
656
+ defaultPermissions: "approve-all",
657
+ agents: {
658
+ claude: {
659
+ command: `${envPrefix}npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`
660
+ }
661
+ }
662
+ };
663
+ writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
664
+ }
546
665
 
547
666
  // src/lib/realtime-chat.ts
548
667
  import { createClient } from "@supabase/supabase-js";
@@ -800,7 +919,6 @@ var knownChannelConfigHashes = /* @__PURE__ */ new Map();
800
919
  var knownModels = /* @__PURE__ */ new Map();
801
920
  var knownTasksHashes = /* @__PURE__ */ new Map();
802
921
  var knownIntegrationHashes = /* @__PURE__ */ new Map();
803
- var losslessClawInstalled = /* @__PURE__ */ new Map();
804
922
  var knownSkillHashes = /* @__PURE__ */ new Map();
805
923
  var lastCronRunTs = /* @__PURE__ */ new Map();
806
924
  var lastWorkTriggerAt = /* @__PURE__ */ new Map();
@@ -841,7 +959,6 @@ function clearAgentCaches(agentId, codeName) {
841
959
  knownModels.delete(agentId);
842
960
  knownTasksHashes.delete(agentId);
843
961
  knownIntegrationHashes.delete(agentId);
844
- losslessClawInstalled.delete(codeName);
845
962
  agentDisplayNames.delete(codeName);
846
963
  codeNameToAgentId.delete(codeName);
847
964
  agentFrameworkCache.delete(codeName);
@@ -862,47 +979,10 @@ function clearAgentCaches(agentId, codeName) {
862
979
  var cachedFrameworkVersion = null;
863
980
  var lastVersionCheckAt = 0;
864
981
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
865
- async function ensureBrewDependency(binary, formula) {
866
- if (frameworkBinaryChecked.has(`dep:${binary}`)) return true;
867
- const { execFileSync: execFileSync2 } = await import("child_process");
868
- try {
869
- execFileSync2("which", [binary], { timeout: 5e3 });
870
- frameworkBinaryChecked.add(`dep:${binary}`);
871
- return true;
872
- } catch {
873
- }
874
- let brewPath;
875
- try {
876
- brewPath = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
877
- } catch {
878
- log(`${binary} not found and Homebrew not available \u2014 install manually: brew install ${formula}`);
879
- frameworkBinaryChecked.add(`dep:${binary}`);
880
- return false;
881
- }
882
- log(`${binary} not found \u2014 installing via Homebrew...`);
883
- try {
884
- execFileSync2(brewPath, ["install", formula], { timeout: 12e4, stdio: "pipe" });
885
- } catch (err) {
886
- log(`Failed to install ${formula}: ${err.message}`);
887
- frameworkBinaryChecked.add(`dep:${binary}`);
888
- return false;
889
- }
890
- try {
891
- execFileSync2("which", [binary], { timeout: 5e3 });
892
- log(`${binary} installed successfully`);
893
- frameworkBinaryChecked.add(`dep:${binary}`);
894
- return true;
895
- } catch {
896
- log(`${formula} install completed but ${binary} not found on PATH`);
897
- frameworkBinaryChecked.add(`dep:${binary}`);
898
- return false;
899
- }
900
- }
901
982
  async function ensureFrameworkBinary(frameworkId) {
902
983
  if (frameworkId !== "claude-code") return;
903
984
  if (frameworkBinaryChecked.has(frameworkId)) return;
904
985
  frameworkBinaryChecked.add(frameworkId);
905
- await ensureBrewDependency("tmux", "tmux");
906
986
  const { execFileSync: execFileSync2 } = await import("child_process");
907
987
  let brewPath;
908
988
  try {
@@ -957,6 +1037,59 @@ async function ensureFrameworkBinary(frameworkId) {
957
1037
  }
958
1038
  agentRuntimeAuthenticated = checkClaudeAuth(execFileSync2);
959
1039
  }
1040
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1041
+ var selfUpdateChecked = false;
1042
+ async function checkAndUpdateCli() {
1043
+ if (selfUpdateChecked) return;
1044
+ selfUpdateChecked = true;
1045
+ const cliPath = process.argv[1] ?? "";
1046
+ const isHomebrew = cliPath.includes("/Cellar/") || cliPath.includes("/homebrew/");
1047
+ const isDevMode = cliPath.includes("/src/") || cliPath.includes("tsx");
1048
+ if (isDevMode) return;
1049
+ if (!isHomebrew && !cliPath.includes("node_modules")) return;
1050
+ const { homedir: homedir2 } = await import("os");
1051
+ const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
1052
+ const markerPath = join2(homedir2(), ".augmented", ".last-update-check");
1053
+ try {
1054
+ const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
1055
+ if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;
1056
+ } catch {
1057
+ }
1058
+ const { execFileSync: execFileSync2 } = await import("child_process");
1059
+ let brewPath;
1060
+ try {
1061
+ brewPath = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
1062
+ } catch {
1063
+ return;
1064
+ }
1065
+ try {
1066
+ const outdated = execFileSync2(brewPath, ["outdated", "--json=v2"], {
1067
+ timeout: 3e4,
1068
+ encoding: "utf-8"
1069
+ });
1070
+ const data = JSON.parse(outdated);
1071
+ const agtOutdated = data.formulae?.find((f) => f.name === "agt" || f.name === "integrity-labs/tap/agt");
1072
+ if (agtOutdated) {
1073
+ const installed = agtOutdated.installed_versions?.[0] ?? "unknown";
1074
+ const latest = agtOutdated.current_version ?? "unknown";
1075
+ log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading...`);
1076
+ try {
1077
+ execFileSync2(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
1078
+ timeout: 12e4,
1079
+ stdio: "pipe"
1080
+ });
1081
+ log(`[self-update] agt CLI upgraded to ${latest}. Restart the manager to use the new version.`);
1082
+ } catch (err) {
1083
+ log(`[self-update] Upgrade failed: ${err.message}`);
1084
+ }
1085
+ }
1086
+ } catch {
1087
+ }
1088
+ try {
1089
+ writeF(markerPath, String(Date.now()));
1090
+ } catch {
1091
+ }
1092
+ }
960
1093
  function checkClaudeAuth(execFileSync2) {
961
1094
  try {
962
1095
  const authOutput = execFileSync2("claude", ["auth", "status"], { timeout: 1e4, stdio: "pipe" }).toString().trim();
@@ -990,7 +1123,7 @@ function loadGatewayPorts() {
990
1123
  }
991
1124
  }
992
1125
  function saveGatewayPorts(ports) {
993
- mkdirSync(AUGMENTED_DIR, { recursive: true });
1126
+ mkdirSync2(AUGMENTED_DIR, { recursive: true });
994
1127
  writeFileSync3(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
995
1128
  }
996
1129
  function allocatePort(codeName) {
@@ -1073,7 +1206,7 @@ async function migrateToProfiles() {
1073
1206
  const profileAuthDir = join2(profileDir, "agents", codeName, "agent");
1074
1207
  const authFile = join2(sharedAuthDir, "auth-profiles.json");
1075
1208
  if (existsSync2(authFile)) {
1076
- mkdirSync(profileAuthDir, { recursive: true });
1209
+ mkdirSync2(profileAuthDir, { recursive: true });
1077
1210
  const authContent = readFileSync3(authFile, "utf-8");
1078
1211
  writeFileSync3(join2(profileAuthDir, "auth-profiles.json"), authContent);
1079
1212
  }
@@ -1580,7 +1713,7 @@ async function processAgent(agent, agentStates) {
1580
1713
  try {
1581
1714
  const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
1582
1715
  const changedFiles = [];
1583
- mkdirSync(agentDir, { recursive: true });
1716
+ mkdirSync2(agentDir, { recursive: true });
1584
1717
  for (const artifact of artifacts) {
1585
1718
  const filePath = join2(agentDir, artifact.relativePath);
1586
1719
  const newHash = sha256(artifact.content);
@@ -1787,6 +1920,14 @@ async function processAgent(agent, agentStates) {
1787
1920
  }
1788
1921
  knownIntegrationHashes.set(agent.agent_id, intHash);
1789
1922
  log(`Integrations provisioned for '${agent.code_name}' (${integrations.length} integration(s))`);
1923
+ const fw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
1924
+ if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
1925
+ const names = integrations.map((i) => i.display_name || i.definition_id).join(", ");
1926
+ injectMessage(agent.code_name, "system", `Your integrations have been updated. You now have access to: ${names}. Re-read your CLAUDE.md for details.`, {
1927
+ task_name: "integration-update"
1928
+ }).catch(() => {
1929
+ });
1930
+ }
1790
1931
  const managedIntegrations = integrations.filter((i) => i.auth_type === "managed");
1791
1932
  if (managedIntegrations.length > 0 && frameworkAdapter.writeMcpServer) {
1792
1933
  try {
@@ -1796,11 +1937,12 @@ async function processAgent(agent, agentStates) {
1796
1937
  if (tk.agent_id !== agent.agent_id) continue;
1797
1938
  const serverId = tk.toolkit_id.replace(/\//g, "-");
1798
1939
  expectedServerIds.add(serverId);
1940
+ const mcpUrl = tk.mcp_url;
1941
+ const mcpHeaders = tk.mcp_headers;
1799
1942
  const proxyUrl = tk.proxy_url.startsWith("/") ? `${requireHost()}${tk.proxy_url}` : tk.proxy_url;
1800
- frameworkAdapter.writeMcpServer(agent.code_name, serverId, {
1801
- url: proxyUrl
1802
- });
1803
- log(`[managed-toolkit] ${agent.code_name}: ${tk.toolkit_name} \u2192 ${proxyUrl}`);
1943
+ const url = mcpUrl ?? proxyUrl;
1944
+ frameworkAdapter.writeMcpServer(agent.code_name, serverId, { url, headers: mcpHeaders });
1945
+ log(`[managed-toolkit] ${agent.code_name}: ${tk.toolkit_name} \u2192 ${url}`);
1804
1946
  }
1805
1947
  if (frameworkAdapter.removeMcpServer) {
1806
1948
  try {
@@ -1826,38 +1968,6 @@ async function processAgent(agent, agentStates) {
1826
1968
  }
1827
1969
  }
1828
1970
  needsGatewayRestart = true;
1829
- const hasLcm = integrations.some((i) => i.definition_id === "lossless-claw");
1830
- if (hasLcm && !losslessClawInstalled.get(agent.code_name)) {
1831
- try {
1832
- const { execFileSync: execFileSync2 } = await import("child_process");
1833
- let pluginList;
1834
- try {
1835
- pluginList = execFileSync2(
1836
- "openclaw",
1837
- ["--profile", agent.code_name, "plugins", "list", "--json"],
1838
- { timeout: 15e3 }
1839
- ).toString();
1840
- } catch {
1841
- pluginList = "[]";
1842
- }
1843
- const installed = JSON.parse(pluginList);
1844
- const alreadyInstalled = installed.some(
1845
- (p) => p.name === "lossless-claw" || p.name === "@martian-engineering/lossless-claw"
1846
- );
1847
- if (!alreadyInstalled) {
1848
- log(`Installing lossless-claw plugin for '${agent.code_name}'...`);
1849
- execFileSync2(
1850
- "openclaw",
1851
- ["--profile", agent.code_name, "plugins", "install", "@martian-engineering/lossless-claw"],
1852
- { stdio: "ignore", timeout: 6e4 }
1853
- );
1854
- log(`lossless-claw plugin installed for '${agent.code_name}'`);
1855
- }
1856
- losslessClawInstalled.set(agent.code_name, true);
1857
- } catch (pluginErr) {
1858
- log(`lossless-claw plugin install failed for '${agent.code_name}': ${pluginErr.message}`);
1859
- }
1860
- }
1861
1971
  }
1862
1972
  const resolvedDefIds = new Set(integrations.map((i) => i.definition_id));
1863
1973
  for (const capId of resolvedDefIds) {
@@ -2641,7 +2751,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2641
2751
  const markerDir = join2(projectDir2, ".claude");
2642
2752
  const markerPath = join2(markerDir, ".agt-pending-task.json");
2643
2753
  try {
2644
- mkdirSync(markerDir, { recursive: true });
2754
+ mkdirSync2(markerDir, { recursive: true });
2645
2755
  writeFileSync3(markerPath, JSON.stringify({
2646
2756
  agent_id: agent.agent_id,
2647
2757
  task_id: task.taskId,
@@ -3640,8 +3750,8 @@ var caffeinateProc = null;
3640
3750
  async function startCaffeinate() {
3641
3751
  if (process.platform !== "darwin") return;
3642
3752
  try {
3643
- const { spawn } = await import("child_process");
3644
- caffeinateProc = spawn("caffeinate", ["-dims"], {
3753
+ const { spawn: spawn2 } = await import("child_process");
3754
+ caffeinateProc = spawn2("caffeinate", ["-dims"], {
3645
3755
  stdio: "ignore",
3646
3756
  detached: false
3647
3757
  });
@@ -3671,6 +3781,7 @@ function startPolling() {
3671
3781
  running = true;
3672
3782
  void startCaffeinate();
3673
3783
  log(`Starting poll loop (interval=${config.intervalMs}ms, configDir=${config.configDir})`);
3784
+ checkAndUpdateCli().catch((err) => log(`[self-update] Check failed: ${err.message}`));
3674
3785
  void migrateToProfiles().then(() => {
3675
3786
  startGatewayPool();
3676
3787
  return pollCycle();