@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.
- package/dist/bin/agt.js +3 -3
- package/dist/bin/agt.js.map +1 -1
- package/dist/{chunk-X3FLX6EO.js → chunk-55TMBRXT.js} +47 -86
- package/dist/chunk-55TMBRXT.js.map +1 -0
- package/dist/lib/manager-worker.js +325 -214
- package/dist/lib/manager-worker.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-X3FLX6EO.js.map +0 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
provisionStopHook,
|
|
10
10
|
requireHost,
|
|
11
11
|
resolveChannels
|
|
12
|
-
} from "../chunk-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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 {
|
|
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
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
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
|
|
422
|
-
|
|
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
|
-
|
|
428
|
-
"
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
442
|
-
"
|
|
443
|
-
|
|
444
|
-
|
|
450
|
+
tmuxSession,
|
|
451
|
+
"-c",
|
|
452
|
+
projectDir,
|
|
453
|
+
claudeCmd
|
|
445
454
|
], {
|
|
446
455
|
cwd: projectDir,
|
|
447
|
-
|
|
448
|
-
|
|
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
|
|
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
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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(
|
|
492
|
-
|
|
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
|
|
562
|
+
function stopPersistentSession(codeName, log2) {
|
|
509
563
|
const session = sessions.get(codeName);
|
|
510
|
-
if (!session
|
|
511
|
-
|
|
564
|
+
if (!session) return;
|
|
565
|
+
log2(`[persistent-session] Stopping session for '${codeName}'`);
|
|
566
|
+
session.status = "stopped";
|
|
512
567
|
try {
|
|
513
|
-
|
|
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
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1801
|
-
|
|
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
|
-
|
|
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 =
|
|
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();
|