@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.
- 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 +316 -175
- 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,20 +329,31 @@ 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
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
|
-
|
|
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
|
|
429
|
-
|
|
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
|
-
|
|
435
|
-
"
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
449
|
-
"
|
|
450
|
-
|
|
451
|
-
|
|
450
|
+
tmuxSession,
|
|
451
|
+
"-c",
|
|
452
|
+
projectDir,
|
|
453
|
+
claudeCmd
|
|
452
454
|
], {
|
|
453
455
|
cwd: projectDir,
|
|
454
|
-
|
|
455
|
-
|
|
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
|
|
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
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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(
|
|
499
|
-
|
|
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
|
|
562
|
+
function stopPersistentSession(codeName, log2) {
|
|
516
563
|
const session = sessions.get(codeName);
|
|
517
|
-
if (!session
|
|
518
|
-
|
|
564
|
+
if (!session) return;
|
|
565
|
+
log2(`[persistent-session] Stopping session for '${codeName}'`);
|
|
566
|
+
session.status = "stopped";
|
|
519
567
|
try {
|
|
520
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1771
|
-
|
|
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
|
-
|
|
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 =
|
|
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();
|