@integrity-labs/agt-cli 0.7.13 → 0.8.1

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,220 @@ 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("--strict-mcp-config");
415
+ args.push("--name", tmuxSession);
416
+ const mcpServerNames = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
417
+ const mcpPatterns = mcpServerNames.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
418
+ const allowedTools = [
419
+ ...mcpPatterns,
420
+ "Bash",
421
+ "Read",
422
+ "Write",
423
+ "Edit",
424
+ "Grep",
425
+ "Glob",
426
+ "Agent",
427
+ "Skill"
428
+ ].join(",");
429
+ args.push("--allowedTools", allowedTools);
430
+ let envPrefix = "CLAUDE_CODE_SIMPLE=1 ";
431
+ const envIntegrationsPath = join(projectDir, ".env.integrations");
432
+ if (existsSync(envIntegrationsPath)) {
433
+ try {
434
+ const envContent = readFileSync2(envIntegrationsPath, "utf-8");
435
+ const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
436
+ const eqIdx = line.indexOf("=");
437
+ const key = line.slice(0, eqIdx);
438
+ const value = line.slice(eqIdx + 1);
439
+ return `${key}=${JSON.stringify(value)}`;
440
+ }).join(" ");
441
+ if (envVars) envPrefix = `env ${envVars} `;
442
+ } catch {
443
+ }
444
+ }
445
+ 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.";
446
+ const claudeCmd = `${envPrefix}claude ${JSON.stringify(initPrompt)} ${args.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
447
+ const child = spawn("tmux", [
448
+ "new-session",
449
+ "-d",
447
450
  "-s",
448
- sessionName,
449
- "--no-wait",
450
- "--",
451
- initPrompt
451
+ tmuxSession,
452
+ "-c",
453
+ projectDir,
454
+ claudeCmd
452
455
  ], {
453
456
  cwd: projectDir,
454
- timeout: 3e4,
455
- stdio: "ignore"
457
+ stdio: ["ignore", "pipe", "pipe"],
458
+ env: process.env
459
+ });
460
+ child.on("close", (code) => {
461
+ if (code !== 0) {
462
+ log2(`[persistent-session] Failed to create tmux session for '${codeName}' (exit ${code})`);
463
+ session.status = "crashed";
464
+ session.startedAt = Date.now();
465
+ session.restartCount++;
466
+ return;
467
+ }
468
+ log2(`[persistent-session] tmux session '${tmuxSession}' created for '${codeName}'`);
469
+ acceptDialogs(tmuxSession, codeName, log2).catch(() => {
470
+ });
471
+ });
472
+ child.on("error", (err) => {
473
+ log2(`[persistent-session] Failed to start tmux for '${codeName}': ${err.message}`);
474
+ session.status = "crashed";
475
+ session.startedAt = Date.now();
476
+ session.restartCount++;
456
477
  });
457
478
  session.startedAt = Date.now();
458
479
  session.status = "running";
459
480
  session.restartCount = 0;
460
- log2(`[persistent-session] Session '${sessionName}' started for '${codeName}'`);
461
481
  } catch (err) {
462
- log2(`[persistent-session] Failed to start acpx session for '${codeName}': ${err.message}`);
482
+ log2(`[persistent-session] Failed to start session for '${codeName}': ${err.message}`);
463
483
  session.status = "crashed";
464
484
  session.startedAt = Date.now();
465
485
  session.restartCount++;
466
486
  }
467
487
  }
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 {
488
+ async function acceptDialogs(tmuxSession, codeName, log2) {
489
+ for (let i = 0; i < 15; i++) {
490
+ await new Promise((r) => setTimeout(r, 2e3));
491
+ try {
492
+ const screen = execSync(`tmux capture-pane -t ${tmuxSession} -p 2>/dev/null`, { encoding: "utf-8" });
493
+ if (screen.includes("Yes, I trust this folder")) {
494
+ execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
495
+ log2(`[persistent-session] Auto-accepted workspace trust for '${codeName}'`);
496
+ continue;
497
+ }
498
+ if (screen.includes("I am using this for local development")) {
499
+ execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
500
+ log2(`[persistent-session] Auto-accepted dev channels for '${codeName}'`);
501
+ continue;
502
+ }
503
+ if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
504
+ execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
505
+ log2(`[persistent-session] Auto-accepted MCP servers for '${codeName}'`);
506
+ continue;
507
+ }
508
+ if (screen.includes("Yes, I accept") && screen.includes("Bypass Permissions")) {
509
+ execSync(`tmux send-keys -t ${tmuxSession} 2`, { stdio: "ignore" });
510
+ await new Promise((r) => setTimeout(r, 300));
511
+ execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
512
+ log2(`[persistent-session] Auto-accepted bypass permissions for '${codeName}'`);
513
+ continue;
514
+ }
515
+ if (screen.includes("\u276F") && !screen.includes("Enter to confirm")) {
516
+ log2(`[persistent-session] Session ready for '${codeName}' \u2014 no more dialogs`);
517
+ break;
518
+ }
519
+ } catch {
520
+ break;
521
+ }
485
522
  }
486
- sessions.delete(codeName);
487
523
  }
488
524
  async function injectMessage(codeName, type, content, meta) {
489
525
  const session = sessions.get(codeName);
490
526
  if (!session || session.status !== "running") {
491
527
  return false;
492
528
  }
493
- const projectDir = getProjectDir2(codeName);
494
- const sessionName = `agt-${codeName}`;
495
529
  const prefix = meta?.task_name ? `[Task: ${meta.task_name}] ` : "";
496
530
  const text = prefix + content;
531
+ const projectDir = getProjectDir2(codeName);
532
+ const acpx = getAcpxBin();
533
+ if (acpx) {
534
+ try {
535
+ const tmpDir = join(projectDir, ".claude");
536
+ mkdirSync(tmpDir, { recursive: true });
537
+ const tmpFile = join(tmpDir, ".agt-inject-prompt.txt");
538
+ writeFileSync2(tmpFile, text);
539
+ execFileSync(acpx, [
540
+ "claude",
541
+ "exec",
542
+ "--cwd",
543
+ projectDir,
544
+ "-f",
545
+ tmpFile
546
+ ], {
547
+ cwd: projectDir,
548
+ timeout: 3e5,
549
+ // 5 min for task execution
550
+ stdio: "ignore"
551
+ });
552
+ return true;
553
+ } catch {
554
+ }
555
+ }
497
556
  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;
557
+ execFileSync("tmux", ["send-keys", "-t", `agt-${codeName}`, text, "Enter"], { stdio: "ignore" });
558
+ return false;
511
559
  } catch {
512
560
  return false;
513
561
  }
514
562
  }
515
- function isSessionHealthy(codeName) {
563
+ function stopPersistentSession(codeName, log2) {
516
564
  const session = sessions.get(codeName);
517
- if (!session || session.status !== "running") return false;
518
- const projectDir = getProjectDir2(codeName);
565
+ if (!session) return;
566
+ log2(`[persistent-session] Stopping session for '${codeName}'`);
567
+ session.status = "stopped";
519
568
  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");
569
+ execSync(`tmux kill-session -t agt-${codeName} 2>/dev/null`, { stdio: "ignore" });
531
570
  } catch {
532
- session.status = "crashed";
533
- session.startedAt = Date.now();
534
- session.restartCount++;
571
+ }
572
+ try {
573
+ const acpx = getAcpxBin();
574
+ if (acpx) {
575
+ execFileSync(acpx, ["claude", "sessions", "close", `agt-${codeName}`], {
576
+ cwd: getProjectDir2(codeName),
577
+ timeout: 5e3,
578
+ stdio: "ignore"
579
+ });
580
+ }
581
+ } catch {
582
+ }
583
+ sessions.delete(codeName);
584
+ }
585
+ function isSessionHealthy(codeName) {
586
+ const tmuxSession = `agt-${codeName}`;
587
+ try {
588
+ execSync(`tmux has-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
589
+ } catch {
590
+ const session2 = sessions.get(codeName);
591
+ if (session2 && session2.status === "running") {
592
+ session2.status = "crashed";
593
+ }
535
594
  return false;
536
595
  }
596
+ if (!sessions.has(codeName)) {
597
+ sessions.set(codeName, {
598
+ codeName,
599
+ startedAt: Date.now(),
600
+ restartCount: 0,
601
+ status: "running"
602
+ });
603
+ }
604
+ const session = sessions.get(codeName);
605
+ if (session.status !== "running") {
606
+ session.status = "running";
607
+ }
608
+ return true;
537
609
  }
538
610
  function resetRestartCount(codeName) {
539
611
  const session = sessions.get(codeName);
@@ -550,6 +622,47 @@ async function stopAllSessionsAndWait(log2, opts) {
550
622
  function getProjectDir2(codeName) {
551
623
  return join(homedir(), ".augmented", codeName, "project");
552
624
  }
625
+ function writeAcpxConfig(config2) {
626
+ const { projectDir, mcpConfigPath, claudeMdPath, channels, devChannels } = config2;
627
+ const claudeArgs = [];
628
+ if (channels.length > 0) claudeArgs.push("--channels", ...channels);
629
+ if (devChannels.length > 0) claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
630
+ claudeArgs.push("--mcp-config", mcpConfigPath);
631
+ const channelsConfigPath = join(projectDir, ".mcp-channels.json");
632
+ if (existsSync(channelsConfigPath)) claudeArgs.push("--mcp-config", channelsConfigPath);
633
+ if (existsSync(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
634
+ claudeArgs.push("--allow-dangerously-skip-permissions");
635
+ claudeArgs.push("--dangerously-skip-permissions");
636
+ const mcpServerNames2 = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
637
+ const mcpPatterns2 = mcpServerNames2.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
638
+ const allowedTools2 = [...mcpPatterns2, "Bash", "Read", "Write", "Edit", "Grep", "Glob", "Agent", "Skill"].join(",");
639
+ claudeArgs.push("--allowedTools", allowedTools2);
640
+ let envPrefix = "";
641
+ const envIntegrationsPath = join(projectDir, ".env.integrations");
642
+ if (existsSync(envIntegrationsPath)) {
643
+ try {
644
+ const envContent = readFileSync2(envIntegrationsPath, "utf-8");
645
+ const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
646
+ const eqIdx = line.indexOf("=");
647
+ const key = line.slice(0, eqIdx);
648
+ const value = line.slice(eqIdx + 1);
649
+ return `${key}=${JSON.stringify(value)}`;
650
+ }).join(" ");
651
+ if (envVars) envPrefix = `env ${envVars} `;
652
+ } catch {
653
+ }
654
+ }
655
+ const acpxConfig = {
656
+ defaultAgent: "claude",
657
+ defaultPermissions: "approve-all",
658
+ agents: {
659
+ claude: {
660
+ command: `${envPrefix}npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`
661
+ }
662
+ }
663
+ };
664
+ writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
665
+ }
553
666
 
554
667
  // src/lib/realtime-chat.ts
555
668
  import { createClient } from "@supabase/supabase-js";
@@ -807,7 +920,6 @@ var knownChannelConfigHashes = /* @__PURE__ */ new Map();
807
920
  var knownModels = /* @__PURE__ */ new Map();
808
921
  var knownTasksHashes = /* @__PURE__ */ new Map();
809
922
  var knownIntegrationHashes = /* @__PURE__ */ new Map();
810
- var losslessClawInstalled = /* @__PURE__ */ new Map();
811
923
  var knownSkillHashes = /* @__PURE__ */ new Map();
812
924
  var lastCronRunTs = /* @__PURE__ */ new Map();
813
925
  var lastWorkTriggerAt = /* @__PURE__ */ new Map();
@@ -848,7 +960,6 @@ function clearAgentCaches(agentId, codeName) {
848
960
  knownModels.delete(agentId);
849
961
  knownTasksHashes.delete(agentId);
850
962
  knownIntegrationHashes.delete(agentId);
851
- losslessClawInstalled.delete(codeName);
852
963
  agentDisplayNames.delete(codeName);
853
964
  codeNameToAgentId.delete(codeName);
854
965
  agentFrameworkCache.delete(codeName);
@@ -927,6 +1038,59 @@ async function ensureFrameworkBinary(frameworkId) {
927
1038
  }
928
1039
  agentRuntimeAuthenticated = checkClaudeAuth(execFileSync2);
929
1040
  }
1041
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1042
+ var selfUpdateChecked = false;
1043
+ async function checkAndUpdateCli() {
1044
+ if (selfUpdateChecked) return;
1045
+ selfUpdateChecked = true;
1046
+ const cliPath = process.argv[1] ?? "";
1047
+ const isHomebrew = cliPath.includes("/Cellar/") || cliPath.includes("/homebrew/");
1048
+ const isDevMode = cliPath.includes("/src/") || cliPath.includes("tsx");
1049
+ if (isDevMode) return;
1050
+ if (!isHomebrew && !cliPath.includes("node_modules")) return;
1051
+ const { homedir: homedir2 } = await import("os");
1052
+ const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
1053
+ const markerPath = join2(homedir2(), ".augmented", ".last-update-check");
1054
+ try {
1055
+ const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
1056
+ if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;
1057
+ } catch {
1058
+ }
1059
+ const { execFileSync: execFileSync2 } = await import("child_process");
1060
+ let brewPath;
1061
+ try {
1062
+ brewPath = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
1063
+ } catch {
1064
+ return;
1065
+ }
1066
+ try {
1067
+ const outdated = execFileSync2(brewPath, ["outdated", "--json=v2"], {
1068
+ timeout: 3e4,
1069
+ encoding: "utf-8"
1070
+ });
1071
+ const data = JSON.parse(outdated);
1072
+ const agtOutdated = data.formulae?.find((f) => f.name === "agt" || f.name === "integrity-labs/tap/agt");
1073
+ if (agtOutdated) {
1074
+ const installed = agtOutdated.installed_versions?.[0] ?? "unknown";
1075
+ const latest = agtOutdated.current_version ?? "unknown";
1076
+ log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading...`);
1077
+ try {
1078
+ execFileSync2(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
1079
+ timeout: 12e4,
1080
+ stdio: "pipe"
1081
+ });
1082
+ log(`[self-update] agt CLI upgraded to ${latest}. Restart the manager to use the new version.`);
1083
+ } catch (err) {
1084
+ log(`[self-update] Upgrade failed: ${err.message}`);
1085
+ }
1086
+ }
1087
+ } catch {
1088
+ }
1089
+ try {
1090
+ writeF(markerPath, String(Date.now()));
1091
+ } catch {
1092
+ }
1093
+ }
930
1094
  function checkClaudeAuth(execFileSync2) {
931
1095
  try {
932
1096
  const authOutput = execFileSync2("claude", ["auth", "status"], { timeout: 1e4, stdio: "pipe" }).toString().trim();
@@ -960,7 +1124,7 @@ function loadGatewayPorts() {
960
1124
  }
961
1125
  }
962
1126
  function saveGatewayPorts(ports) {
963
- mkdirSync(AUGMENTED_DIR, { recursive: true });
1127
+ mkdirSync2(AUGMENTED_DIR, { recursive: true });
964
1128
  writeFileSync3(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
965
1129
  }
966
1130
  function allocatePort(codeName) {
@@ -1043,7 +1207,7 @@ async function migrateToProfiles() {
1043
1207
  const profileAuthDir = join2(profileDir, "agents", codeName, "agent");
1044
1208
  const authFile = join2(sharedAuthDir, "auth-profiles.json");
1045
1209
  if (existsSync2(authFile)) {
1046
- mkdirSync(profileAuthDir, { recursive: true });
1210
+ mkdirSync2(profileAuthDir, { recursive: true });
1047
1211
  const authContent = readFileSync3(authFile, "utf-8");
1048
1212
  writeFileSync3(join2(profileAuthDir, "auth-profiles.json"), authContent);
1049
1213
  }
@@ -1550,7 +1714,7 @@ async function processAgent(agent, agentStates) {
1550
1714
  try {
1551
1715
  const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
1552
1716
  const changedFiles = [];
1553
- mkdirSync(agentDir, { recursive: true });
1717
+ mkdirSync2(agentDir, { recursive: true });
1554
1718
  for (const artifact of artifacts) {
1555
1719
  const filePath = join2(agentDir, artifact.relativePath);
1556
1720
  const newHash = sha256(artifact.content);
@@ -1757,6 +1921,14 @@ async function processAgent(agent, agentStates) {
1757
1921
  }
1758
1922
  knownIntegrationHashes.set(agent.agent_id, intHash);
1759
1923
  log(`Integrations provisioned for '${agent.code_name}' (${integrations.length} integration(s))`);
1924
+ const fw = agentFrameworkCache.get(agent.code_name) ?? "openclaw";
1925
+ if (fw === "claude-code" && isSessionHealthy(agent.code_name)) {
1926
+ const names = integrations.map((i) => i.display_name || i.definition_id).join(", ");
1927
+ injectMessage(agent.code_name, "system", `Your integrations have been updated. You now have access to: ${names}. Re-read your CLAUDE.md for details.`, {
1928
+ task_name: "integration-update"
1929
+ }).catch(() => {
1930
+ });
1931
+ }
1760
1932
  const managedIntegrations = integrations.filter((i) => i.auth_type === "managed");
1761
1933
  if (managedIntegrations.length > 0 && frameworkAdapter.writeMcpServer) {
1762
1934
  try {
@@ -1766,11 +1938,12 @@ async function processAgent(agent, agentStates) {
1766
1938
  if (tk.agent_id !== agent.agent_id) continue;
1767
1939
  const serverId = tk.toolkit_id.replace(/\//g, "-");
1768
1940
  expectedServerIds.add(serverId);
1941
+ const mcpUrl = tk.mcp_url;
1942
+ const mcpHeaders = tk.mcp_headers;
1769
1943
  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}`);
1944
+ const url = mcpUrl ?? proxyUrl;
1945
+ frameworkAdapter.writeMcpServer(agent.code_name, serverId, { url, headers: mcpHeaders });
1946
+ log(`[managed-toolkit] ${agent.code_name}: ${tk.toolkit_name} \u2192 ${url}`);
1774
1947
  }
1775
1948
  if (frameworkAdapter.removeMcpServer) {
1776
1949
  try {
@@ -1796,38 +1969,6 @@ async function processAgent(agent, agentStates) {
1796
1969
  }
1797
1970
  }
1798
1971
  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
1972
  }
1832
1973
  const resolvedDefIds = new Set(integrations.map((i) => i.definition_id));
1833
1974
  for (const capId of resolvedDefIds) {
@@ -2611,7 +2752,7 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
2611
2752
  const markerDir = join2(projectDir2, ".claude");
2612
2753
  const markerPath = join2(markerDir, ".agt-pending-task.json");
2613
2754
  try {
2614
- mkdirSync(markerDir, { recursive: true });
2755
+ mkdirSync2(markerDir, { recursive: true });
2615
2756
  writeFileSync3(markerPath, JSON.stringify({
2616
2757
  agent_id: agent.agent_id,
2617
2758
  task_id: task.taskId,
@@ -3610,8 +3751,8 @@ var caffeinateProc = null;
3610
3751
  async function startCaffeinate() {
3611
3752
  if (process.platform !== "darwin") return;
3612
3753
  try {
3613
- const { spawn } = await import("child_process");
3614
- caffeinateProc = spawn("caffeinate", ["-dims"], {
3754
+ const { spawn: spawn2 } = await import("child_process");
3755
+ caffeinateProc = spawn2("caffeinate", ["-dims"], {
3615
3756
  stdio: "ignore",
3616
3757
  detached: false
3617
3758
  });
@@ -3641,6 +3782,7 @@ function startPolling() {
3641
3782
  running = true;
3642
3783
  void startCaffeinate();
3643
3784
  log(`Starting poll loop (interval=${config.intervalMs}ms, configDir=${config.configDir})`);
3785
+ checkAndUpdateCli().catch((err) => log(`[self-update] Check failed: ${err.message}`));
3644
3786
  void migrateToProfiles().then(() => {
3645
3787
  startGatewayPool();
3646
3788
  return pollCycle();