@integrity-labs/agt-cli 0.7.13 → 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,20 +329,31 @@ 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
353
  const moduleDir = dirname(fileURLToPath(import.meta.url));
339
354
  for (const candidate of [
340
355
  join(moduleDir, "..", "..", "node_modules", ".bin", "acpx"),
341
- // dist/lib/ → package root
342
356
  join(moduleDir, "..", "..", "..", "..", "node_modules", ".bin", "acpx")
343
- // hoisted to monorepo root
344
357
  ]) {
345
358
  if (existsSync(candidate)) {
346
359
  _acpxBin = candidate;
@@ -352,55 +365,10 @@ function getAcpxBin() {
352
365
  _acpxBin = "acpx";
353
366
  return _acpxBin;
354
367
  } catch {
355
- throw new Error("acpx not found. Run: npm install -g acpx");
368
+ return "";
356
369
  }
357
370
  }
358
371
  var sessions = /* @__PURE__ */ new Map();
359
- function writeAcpxConfig(config2) {
360
- const { projectDir, mcpConfigPath, claudeMdPath, channels, devChannels } = config2;
361
- const claudeArgs = [];
362
- if (channels.length > 0) {
363
- claudeArgs.push("--channels", ...channels);
364
- }
365
- if (devChannels.length > 0) {
366
- claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
367
- }
368
- claudeArgs.push("--mcp-config", mcpConfigPath);
369
- const channelsConfigPath = join(projectDir, ".mcp-channels.json");
370
- if (existsSync(channelsConfigPath)) {
371
- claudeArgs.push("--mcp-config", channelsConfigPath);
372
- }
373
- if (existsSync(claudeMdPath)) {
374
- claudeArgs.push("--system-prompt-file", claudeMdPath);
375
- }
376
- claudeArgs.push("--allow-dangerously-skip-permissions");
377
- claudeArgs.push("--dangerously-skip-permissions");
378
- const envIntegrationsPath = join(projectDir, ".env.integrations");
379
- let envPrefix = "";
380
- if (existsSync(envIntegrationsPath)) {
381
- try {
382
- const envContent = readFileSync2(envIntegrationsPath, "utf-8");
383
- const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
384
- const eqIdx = line.indexOf("=");
385
- const key = line.slice(0, eqIdx);
386
- const value = line.slice(eqIdx + 1);
387
- return `${key}=${value.includes(" ") ? JSON.stringify(value) : value}`;
388
- }).join(" ");
389
- if (envVars) envPrefix = `env ${envVars} `;
390
- } catch {
391
- }
392
- }
393
- const acpxConfig = {
394
- defaultAgent: "claude",
395
- defaultPermissions: "approve-all",
396
- agents: {
397
- claude: {
398
- command: `${envPrefix}npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.join(" ")}`
399
- }
400
- }
401
- };
402
- writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
403
- }
404
372
  function startPersistentSession(config2) {
405
373
  const existing = sessions.get(config2.codeName);
406
374
  if (existing && existing.status === "running") {
@@ -424,116 +392,219 @@ function startPersistentSession(config2) {
424
392
  return session;
425
393
  }
426
394
  function spawnSession(config2, session) {
427
- const { codeName, projectDir, mcpConfigPath, apiHost, log: log2 } = config2;
428
- const sessionName = `agt-${codeName}`;
429
- 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.";
430
- 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}'`);
431
398
  try {
432
399
  sanitizeMcpJson(mcpConfigPath, apiHost);
433
400
  writeAcpxConfig(config2);
434
- execFileSync(getAcpxBin(), [
435
- "claude",
436
- "sessions",
437
- "ensure",
438
- "--name",
439
- sessionName
440
- ], {
441
- cwd: projectDir,
442
- timeout: 3e4,
443
- stdio: "ignore"
444
- });
445
- execFileSync(getAcpxBin(), [
446
- "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",
447
449
  "-s",
448
- sessionName,
449
- "--no-wait",
450
- "--",
451
- initPrompt
450
+ tmuxSession,
451
+ "-c",
452
+ projectDir,
453
+ claudeCmd
452
454
  ], {
453
455
  cwd: projectDir,
454
- timeout: 3e4,
455
- 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++;
456
476
  });
457
477
  session.startedAt = Date.now();
458
478
  session.status = "running";
459
479
  session.restartCount = 0;
460
- log2(`[persistent-session] Session '${sessionName}' started for '${codeName}'`);
461
480
  } catch (err) {
462
- log2(`[persistent-session] Failed to start acpx session for '${codeName}': ${err.message}`);
481
+ log2(`[persistent-session] Failed to start session for '${codeName}': ${err.message}`);
463
482
  session.status = "crashed";
464
483
  session.startedAt = Date.now();
465
484
  session.restartCount++;
466
485
  }
467
486
  }
468
- function stopPersistentSession(codeName, log2) {
469
- const session = sessions.get(codeName);
470
- if (!session) return;
471
- log2(`[persistent-session] Stopping session for '${codeName}'`);
472
- session.status = "stopped";
473
- const projectDir = getProjectDir2(codeName);
474
- try {
475
- execFileSync(getAcpxBin(), ["claude", "sessions", "close", `agt-${codeName}`], {
476
- cwd: projectDir,
477
- timeout: 1e4,
478
- stdio: "ignore"
479
- });
480
- } catch {
481
- }
482
- try {
483
- execFileSync("tmux", ["kill-session", "-t", `agt-${codeName}`], { stdio: "ignore" });
484
- } 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
+ }
485
521
  }
486
- sessions.delete(codeName);
487
522
  }
488
523
  async function injectMessage(codeName, type, content, meta) {
489
524
  const session = sessions.get(codeName);
490
525
  if (!session || session.status !== "running") {
491
526
  return false;
492
527
  }
493
- const projectDir = getProjectDir2(codeName);
494
- const sessionName = `agt-${codeName}`;
495
528
  const prefix = meta?.task_name ? `[Task: ${meta.task_name}] ` : "";
496
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
+ }
497
555
  try {
498
- execFileSync(getAcpxBin(), [
499
- "claude",
500
- "-s",
501
- sessionName,
502
- "--no-wait",
503
- "--",
504
- text
505
- ], {
506
- cwd: projectDir,
507
- timeout: 15e3,
508
- stdio: "ignore"
509
- });
510
- return true;
556
+ execFileSync("tmux", ["send-keys", "-t", `agt-${codeName}`, text, "Enter"], { stdio: "ignore" });
557
+ return false;
511
558
  } catch {
512
559
  return false;
513
560
  }
514
561
  }
515
- function isSessionHealthy(codeName) {
562
+ function stopPersistentSession(codeName, log2) {
516
563
  const session = sessions.get(codeName);
517
- if (!session || session.status !== "running") return false;
518
- const projectDir = getProjectDir2(codeName);
564
+ if (!session) return;
565
+ log2(`[persistent-session] Stopping session for '${codeName}'`);
566
+ session.status = "stopped";
519
567
  try {
520
- const output = execFileSync(getAcpxBin(), [
521
- "claude",
522
- "-s",
523
- `agt-${codeName}`,
524
- "status"
525
- ], {
526
- cwd: projectDir,
527
- timeout: 1e4,
528
- encoding: "utf-8"
529
- });
530
- return output.includes("status: running") || output.includes("status: idle");
568
+ execSync(`tmux kill-session -t agt-${codeName} 2>/dev/null`, { stdio: "ignore" });
531
569
  } catch {
532
- session.status = "crashed";
533
- session.startedAt = Date.now();
534
- 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
+ }
535
593
  return false;
536
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;
537
608
  }
538
609
  function resetRestartCount(codeName) {
539
610
  const session = sessions.get(codeName);
@@ -550,6 +621,47 @@ async function stopAllSessionsAndWait(log2, opts) {
550
621
  function getProjectDir2(codeName) {
551
622
  return join(homedir(), ".augmented", codeName, "project");
552
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
+ }
553
665
 
554
666
  // src/lib/realtime-chat.ts
555
667
  import { createClient } from "@supabase/supabase-js";
@@ -807,7 +919,6 @@ var knownChannelConfigHashes = /* @__PURE__ */ new Map();
807
919
  var knownModels = /* @__PURE__ */ new Map();
808
920
  var knownTasksHashes = /* @__PURE__ */ new Map();
809
921
  var knownIntegrationHashes = /* @__PURE__ */ new Map();
810
- var losslessClawInstalled = /* @__PURE__ */ new Map();
811
922
  var knownSkillHashes = /* @__PURE__ */ new Map();
812
923
  var lastCronRunTs = /* @__PURE__ */ new Map();
813
924
  var lastWorkTriggerAt = /* @__PURE__ */ new Map();
@@ -848,7 +959,6 @@ function clearAgentCaches(agentId, codeName) {
848
959
  knownModels.delete(agentId);
849
960
  knownTasksHashes.delete(agentId);
850
961
  knownIntegrationHashes.delete(agentId);
851
- losslessClawInstalled.delete(codeName);
852
962
  agentDisplayNames.delete(codeName);
853
963
  codeNameToAgentId.delete(codeName);
854
964
  agentFrameworkCache.delete(codeName);
@@ -927,6 +1037,59 @@ async function ensureFrameworkBinary(frameworkId) {
927
1037
  }
928
1038
  agentRuntimeAuthenticated = checkClaudeAuth(execFileSync2);
929
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
+ }
930
1093
  function checkClaudeAuth(execFileSync2) {
931
1094
  try {
932
1095
  const authOutput = execFileSync2("claude", ["auth", "status"], { timeout: 1e4, stdio: "pipe" }).toString().trim();
@@ -960,7 +1123,7 @@ function loadGatewayPorts() {
960
1123
  }
961
1124
  }
962
1125
  function saveGatewayPorts(ports) {
963
- mkdirSync(AUGMENTED_DIR, { recursive: true });
1126
+ mkdirSync2(AUGMENTED_DIR, { recursive: true });
964
1127
  writeFileSync3(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
965
1128
  }
966
1129
  function allocatePort(codeName) {
@@ -1043,7 +1206,7 @@ async function migrateToProfiles() {
1043
1206
  const profileAuthDir = join2(profileDir, "agents", codeName, "agent");
1044
1207
  const authFile = join2(sharedAuthDir, "auth-profiles.json");
1045
1208
  if (existsSync2(authFile)) {
1046
- mkdirSync(profileAuthDir, { recursive: true });
1209
+ mkdirSync2(profileAuthDir, { recursive: true });
1047
1210
  const authContent = readFileSync3(authFile, "utf-8");
1048
1211
  writeFileSync3(join2(profileAuthDir, "auth-profiles.json"), authContent);
1049
1212
  }
@@ -1550,7 +1713,7 @@ async function processAgent(agent, agentStates) {
1550
1713
  try {
1551
1714
  const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
1552
1715
  const changedFiles = [];
1553
- mkdirSync(agentDir, { recursive: true });
1716
+ mkdirSync2(agentDir, { recursive: true });
1554
1717
  for (const artifact of artifacts) {
1555
1718
  const filePath = join2(agentDir, artifact.relativePath);
1556
1719
  const newHash = sha256(artifact.content);
@@ -1757,6 +1920,14 @@ async function processAgent(agent, agentStates) {
1757
1920
  }
1758
1921
  knownIntegrationHashes.set(agent.agent_id, intHash);
1759
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
+ }
1760
1931
  const managedIntegrations = integrations.filter((i) => i.auth_type === "managed");
1761
1932
  if (managedIntegrations.length > 0 && frameworkAdapter.writeMcpServer) {
1762
1933
  try {
@@ -1766,11 +1937,12 @@ async function processAgent(agent, agentStates) {
1766
1937
  if (tk.agent_id !== agent.agent_id) continue;
1767
1938
  const serverId = tk.toolkit_id.replace(/\//g, "-");
1768
1939
  expectedServerIds.add(serverId);
1940
+ const mcpUrl = tk.mcp_url;
1941
+ const mcpHeaders = tk.mcp_headers;
1769
1942
  const proxyUrl = tk.proxy_url.startsWith("/") ? `${requireHost()}${tk.proxy_url}` : tk.proxy_url;
1770
- frameworkAdapter.writeMcpServer(agent.code_name, serverId, {
1771
- url: proxyUrl
1772
- });
1773
- 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}`);
1774
1946
  }
1775
1947
  if (frameworkAdapter.removeMcpServer) {
1776
1948
  try {
@@ -1796,38 +1968,6 @@ async function processAgent(agent, agentStates) {
1796
1968
  }
1797
1969
  }
1798
1970
  needsGatewayRestart = true;
1799
- const hasLcm = integrations.some((i) => i.definition_id === "lossless-claw");
1800
- if (hasLcm && !losslessClawInstalled.get(agent.code_name)) {
1801
- try {
1802
- const { execFileSync: execFileSync2 } = await import("child_process");
1803
- let pluginList;
1804
- try {
1805
- pluginList = execFileSync2(
1806
- "openclaw",
1807
- ["--profile", agent.code_name, "plugins", "list", "--json"],
1808
- { timeout: 15e3 }
1809
- ).toString();
1810
- } catch {
1811
- pluginList = "[]";
1812
- }
1813
- const installed = JSON.parse(pluginList);
1814
- const alreadyInstalled = installed.some(
1815
- (p) => p.name === "lossless-claw" || p.name === "@martian-engineering/lossless-claw"
1816
- );
1817
- if (!alreadyInstalled) {
1818
- log(`Installing lossless-claw plugin for '${agent.code_name}'...`);
1819
- execFileSync2(
1820
- "openclaw",
1821
- ["--profile", agent.code_name, "plugins", "install", "@martian-engineering/lossless-claw"],
1822
- { stdio: "ignore", timeout: 6e4 }
1823
- );
1824
- log(`lossless-claw plugin installed for '${agent.code_name}'`);
1825
- }
1826
- losslessClawInstalled.set(agent.code_name, true);
1827
- } catch (pluginErr) {
1828
- log(`lossless-claw plugin install failed for '${agent.code_name}': ${pluginErr.message}`);
1829
- }
1830
- }
1831
1971
  }
1832
1972
  const resolvedDefIds = new Set(integrations.map((i) => i.definition_id));
1833
1973
  for (const capId of resolvedDefIds) {
@@ -2611,7 +2751,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2611
2751
  const markerDir = join2(projectDir2, ".claude");
2612
2752
  const markerPath = join2(markerDir, ".agt-pending-task.json");
2613
2753
  try {
2614
- mkdirSync(markerDir, { recursive: true });
2754
+ mkdirSync2(markerDir, { recursive: true });
2615
2755
  writeFileSync3(markerPath, JSON.stringify({
2616
2756
  agent_id: agent.agent_id,
2617
2757
  task_id: task.taskId,
@@ -3610,8 +3750,8 @@ var caffeinateProc = null;
3610
3750
  async function startCaffeinate() {
3611
3751
  if (process.platform !== "darwin") return;
3612
3752
  try {
3613
- const { spawn } = await import("child_process");
3614
- caffeinateProc = spawn("caffeinate", ["-dims"], {
3753
+ const { spawn: spawn2 } = await import("child_process");
3754
+ caffeinateProc = spawn2("caffeinate", ["-dims"], {
3615
3755
  stdio: "ignore",
3616
3756
  detached: false
3617
3757
  });
@@ -3641,6 +3781,7 @@ function startPolling() {
3641
3781
  running = true;
3642
3782
  void startCaffeinate();
3643
3783
  log(`Starting poll loop (interval=${config.intervalMs}ms, configDir=${config.configDir})`);
3784
+ checkAndUpdateCli().catch((err) => log(`[self-update] Check failed: ${err.message}`));
3644
3785
  void migrateToProfiles().then(() => {
3645
3786
  startGatewayPool();
3646
3787
  return pollCycle();