@integrity-labs/agt-cli 0.8.9 → 0.9.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.
- package/dist/bin/agt.js +3 -3
- package/dist/{chunk-55TMBRXT.js → chunk-AMB6FJLZ.js} +12 -1
- package/dist/chunk-AMB6FJLZ.js.map +1 -0
- package/dist/chunk-HTBIKZKS.js +455 -0
- package/dist/chunk-HTBIKZKS.js.map +1 -0
- package/dist/lib/manager-worker.js +192 -501
- package/dist/lib/manager-worker.js.map +1 -1
- package/dist/persistent-session-ZCB562FN.js +25 -0
- package/dist/persistent-session-ZCB562FN.js.map +1 -0
- package/mcp/index.js +587 -0
- package/mcp/slack-channel.js +254 -0
- package/package.json +4 -3
- package/dist/chunk-55TMBRXT.js.map +0 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
provisionStopHook,
|
|
10
10
|
requireHost,
|
|
11
11
|
resolveChannels
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-AMB6FJLZ.js";
|
|
13
13
|
import {
|
|
14
14
|
findTaskByTemplate,
|
|
15
15
|
getProjectDir,
|
|
@@ -18,46 +18,24 @@ import {
|
|
|
18
18
|
markTaskFired,
|
|
19
19
|
syncTasksToScheduler
|
|
20
20
|
} from "../chunk-2TSCVXHE.js";
|
|
21
|
+
import {
|
|
22
|
+
getProjectDir as getProjectDir2,
|
|
23
|
+
injectMessage,
|
|
24
|
+
isSessionHealthy,
|
|
25
|
+
resetRestartCount,
|
|
26
|
+
sanitizeMcpJson,
|
|
27
|
+
startPersistentSession,
|
|
28
|
+
stopAllSessionsAndWait,
|
|
29
|
+
stopPersistentSession
|
|
30
|
+
} from "../chunk-HTBIKZKS.js";
|
|
21
31
|
|
|
22
32
|
// src/lib/manager-worker.ts
|
|
23
33
|
import { createHash } from "crypto";
|
|
24
|
-
import { readFileSync
|
|
34
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync, readdirSync, statSync, unlinkSync, copyFileSync } from "fs";
|
|
25
35
|
import https from "https";
|
|
26
|
-
import { join
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
30
|
-
function sanitizeMcpJson(mcpConfigPath, apiHost) {
|
|
31
|
-
try {
|
|
32
|
-
const mcpRaw = JSON.parse(readFileSync(mcpConfigPath, "utf-8"));
|
|
33
|
-
const servers = mcpRaw.mcpServers;
|
|
34
|
-
if (!servers) return false;
|
|
35
|
-
let changed = false;
|
|
36
|
-
for (const [key, val] of Object.entries(servers)) {
|
|
37
|
-
if (typeof val?.url !== "string") continue;
|
|
38
|
-
if (val.url.startsWith("/")) {
|
|
39
|
-
if (apiHost) {
|
|
40
|
-
val.url = `${apiHost}${val.url}`;
|
|
41
|
-
changed = true;
|
|
42
|
-
} else {
|
|
43
|
-
delete servers[key];
|
|
44
|
-
changed = true;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
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;
|
|
54
|
-
}
|
|
55
|
-
if (changed) writeFileSync(mcpConfigPath, JSON.stringify(mcpRaw, null, 2));
|
|
56
|
-
return changed;
|
|
57
|
-
} catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
36
|
+
import { join, dirname } from "path";
|
|
37
|
+
import { homedir } from "os";
|
|
38
|
+
import { fileURLToPath } from "url";
|
|
61
39
|
|
|
62
40
|
// src/lib/gateway-client.ts
|
|
63
41
|
import { EventEmitter } from "events";
|
|
@@ -328,345 +306,6 @@ var GatewayClientPool = class extends EventEmitter {
|
|
|
328
306
|
}
|
|
329
307
|
};
|
|
330
308
|
|
|
331
|
-
// src/lib/persistent-session.ts
|
|
332
|
-
import { spawn, execSync, execFileSync } from "child_process";
|
|
333
|
-
import { join, dirname } from "path";
|
|
334
|
-
import { homedir } from "os";
|
|
335
|
-
import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
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
|
-
}
|
|
350
|
-
var _acpxBin = null;
|
|
351
|
-
function getAcpxBin() {
|
|
352
|
-
if (_acpxBin) return _acpxBin;
|
|
353
|
-
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
354
|
-
let dir = moduleDir;
|
|
355
|
-
for (let i = 0; i < 6; i++) {
|
|
356
|
-
const candidate = join(dir, "node_modules", ".bin", "acpx");
|
|
357
|
-
if (existsSync(candidate)) {
|
|
358
|
-
_acpxBin = candidate;
|
|
359
|
-
return _acpxBin;
|
|
360
|
-
}
|
|
361
|
-
const parent = dirname(dir);
|
|
362
|
-
if (parent === dir) break;
|
|
363
|
-
dir = parent;
|
|
364
|
-
}
|
|
365
|
-
try {
|
|
366
|
-
execSync("which acpx", { stdio: "ignore" });
|
|
367
|
-
_acpxBin = "acpx";
|
|
368
|
-
return _acpxBin;
|
|
369
|
-
} catch {
|
|
370
|
-
return "";
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
var sessions = /* @__PURE__ */ new Map();
|
|
374
|
-
function startPersistentSession(config2) {
|
|
375
|
-
const existing = sessions.get(config2.codeName);
|
|
376
|
-
if (existing && existing.status === "running") {
|
|
377
|
-
return existing;
|
|
378
|
-
}
|
|
379
|
-
const restartCount = existing?.restartCount ?? 0;
|
|
380
|
-
if (existing?.status === "crashed" && existing.startedAt) {
|
|
381
|
-
const backoffMs = Math.min(5e3 * Math.pow(2, restartCount), 6e4);
|
|
382
|
-
if (Date.now() - existing.startedAt < backoffMs) {
|
|
383
|
-
return existing;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
const session = {
|
|
387
|
-
codeName: config2.codeName,
|
|
388
|
-
startedAt: null,
|
|
389
|
-
restartCount,
|
|
390
|
-
status: "starting"
|
|
391
|
-
};
|
|
392
|
-
sessions.set(config2.codeName, session);
|
|
393
|
-
spawnSession(config2, session);
|
|
394
|
-
return session;
|
|
395
|
-
}
|
|
396
|
-
function spawnSession(config2, session) {
|
|
397
|
-
const { codeName, projectDir, mcpConfigPath, claudeMdPath, channels, devChannels, apiHost, log: log2 } = config2;
|
|
398
|
-
const tmuxSession = `agt-${codeName}`;
|
|
399
|
-
log2(`[persistent-session] Starting tmux session '${tmuxSession}' for '${codeName}'`);
|
|
400
|
-
try {
|
|
401
|
-
sanitizeMcpJson(mcpConfigPath, apiHost);
|
|
402
|
-
writeAcpxConfig(config2);
|
|
403
|
-
try {
|
|
404
|
-
execSync(`tmux kill-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
|
|
405
|
-
} catch {
|
|
406
|
-
}
|
|
407
|
-
const args = [];
|
|
408
|
-
if (channels.length > 0) args.push("--channels", ...channels);
|
|
409
|
-
if (devChannels.length > 0) args.push("--dangerously-load-development-channels", ...devChannels);
|
|
410
|
-
args.push("--mcp-config", mcpConfigPath);
|
|
411
|
-
const channelsConfigPath = join(projectDir, ".mcp-channels.json");
|
|
412
|
-
if (existsSync(channelsConfigPath)) args.push("--mcp-config", channelsConfigPath);
|
|
413
|
-
if (existsSync(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
|
|
414
|
-
args.push("--allow-dangerously-skip-permissions");
|
|
415
|
-
args.push("--dangerously-skip-permissions");
|
|
416
|
-
args.push("--strict-mcp-config");
|
|
417
|
-
args.push("--name", tmuxSession);
|
|
418
|
-
const mcpServerNames = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
|
|
419
|
-
const mcpPatterns = mcpServerNames.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
|
|
420
|
-
const allowedTools = [
|
|
421
|
-
...mcpPatterns,
|
|
422
|
-
"Bash",
|
|
423
|
-
"Read",
|
|
424
|
-
"Write",
|
|
425
|
-
"Edit",
|
|
426
|
-
"Grep",
|
|
427
|
-
"Glob",
|
|
428
|
-
"Agent",
|
|
429
|
-
"Skill"
|
|
430
|
-
].join(",");
|
|
431
|
-
args.push("--allowedTools", allowedTools);
|
|
432
|
-
let envPrefix = "";
|
|
433
|
-
const envIntegrationsPath = join(projectDir, ".env.integrations");
|
|
434
|
-
if (existsSync(envIntegrationsPath)) {
|
|
435
|
-
try {
|
|
436
|
-
const envContent = readFileSync2(envIntegrationsPath, "utf-8");
|
|
437
|
-
const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
|
|
438
|
-
const eqIdx = line.indexOf("=");
|
|
439
|
-
const key = line.slice(0, eqIdx);
|
|
440
|
-
const value = line.slice(eqIdx + 1);
|
|
441
|
-
return `${key}=${JSON.stringify(value)}`;
|
|
442
|
-
}).join(" ");
|
|
443
|
-
if (envVars) envPrefix = `${envVars} `;
|
|
444
|
-
} catch {
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
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.";
|
|
448
|
-
const claudeCmd = `${envPrefix}claude ${JSON.stringify(initPrompt)} ${args.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
|
|
449
|
-
const child = spawn("tmux", [
|
|
450
|
-
"new-session",
|
|
451
|
-
"-d",
|
|
452
|
-
"-s",
|
|
453
|
-
tmuxSession,
|
|
454
|
-
"-c",
|
|
455
|
-
projectDir,
|
|
456
|
-
claudeCmd
|
|
457
|
-
], {
|
|
458
|
-
cwd: projectDir,
|
|
459
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
460
|
-
env: process.env
|
|
461
|
-
});
|
|
462
|
-
child.on("close", (code) => {
|
|
463
|
-
if (code !== 0) {
|
|
464
|
-
log2(`[persistent-session] Failed to create tmux session for '${codeName}' (exit ${code})`);
|
|
465
|
-
session.status = "crashed";
|
|
466
|
-
session.startedAt = Date.now();
|
|
467
|
-
session.restartCount++;
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
log2(`[persistent-session] tmux session '${tmuxSession}' created for '${codeName}'`);
|
|
471
|
-
acceptDialogs(tmuxSession, codeName, log2).catch(() => {
|
|
472
|
-
});
|
|
473
|
-
});
|
|
474
|
-
child.on("error", (err) => {
|
|
475
|
-
log2(`[persistent-session] Failed to start tmux for '${codeName}': ${err.message}`);
|
|
476
|
-
session.status = "crashed";
|
|
477
|
-
session.startedAt = Date.now();
|
|
478
|
-
session.restartCount++;
|
|
479
|
-
});
|
|
480
|
-
session.startedAt = Date.now();
|
|
481
|
-
session.status = "running";
|
|
482
|
-
session.restartCount = 0;
|
|
483
|
-
} catch (err) {
|
|
484
|
-
log2(`[persistent-session] Failed to start session for '${codeName}': ${err.message}`);
|
|
485
|
-
session.status = "crashed";
|
|
486
|
-
session.startedAt = Date.now();
|
|
487
|
-
session.restartCount++;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
async function acceptDialogs(tmuxSession, codeName, log2) {
|
|
491
|
-
for (let i = 0; i < 15; i++) {
|
|
492
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
493
|
-
try {
|
|
494
|
-
const screen = execSync(`tmux capture-pane -t ${tmuxSession} -p 2>/dev/null`, { encoding: "utf-8" });
|
|
495
|
-
if (screen.includes("Yes, I trust this folder")) {
|
|
496
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
497
|
-
log2(`[persistent-session] Auto-accepted workspace trust for '${codeName}'`);
|
|
498
|
-
continue;
|
|
499
|
-
}
|
|
500
|
-
if (screen.includes("I am using this for local development")) {
|
|
501
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
502
|
-
log2(`[persistent-session] Auto-accepted dev channels for '${codeName}'`);
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
|
|
506
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
507
|
-
log2(`[persistent-session] Auto-accepted MCP servers for '${codeName}'`);
|
|
508
|
-
continue;
|
|
509
|
-
}
|
|
510
|
-
if (screen.includes("Yes, I accept") && screen.includes("Bypass Permissions")) {
|
|
511
|
-
execSync(`tmux send-keys -t ${tmuxSession} 2`, { stdio: "ignore" });
|
|
512
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
513
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
514
|
-
log2(`[persistent-session] Auto-accepted bypass permissions for '${codeName}'`);
|
|
515
|
-
continue;
|
|
516
|
-
}
|
|
517
|
-
if (screen.includes("\u276F") && !screen.includes("Enter to confirm")) {
|
|
518
|
-
log2(`[persistent-session] Session ready for '${codeName}' \u2014 no more dialogs`);
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
} catch {
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
async function injectMessage(codeName, type, content, meta, log2) {
|
|
527
|
-
const _log = log2 ?? ((_) => {
|
|
528
|
-
});
|
|
529
|
-
const session = sessions.get(codeName);
|
|
530
|
-
if (!session || session.status !== "running") {
|
|
531
|
-
_log(`[inject] SKIP '${codeName}' \u2014 session ${session ? `status=${session.status}` : "not found in Map"}`);
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
const prefix = meta?.task_name ? `[Task: ${meta.task_name}] ` : "";
|
|
535
|
-
const text = prefix + content;
|
|
536
|
-
const projectDir = getProjectDir2(codeName);
|
|
537
|
-
const acpx = getAcpxBin();
|
|
538
|
-
if (acpx) {
|
|
539
|
-
try {
|
|
540
|
-
const tmpDir = join(projectDir, ".claude");
|
|
541
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
542
|
-
const tmpFile = join(tmpDir, ".agt-inject-prompt.txt");
|
|
543
|
-
writeFileSync2(tmpFile, text);
|
|
544
|
-
_log(`[inject] acpx exec (fire-and-forget): cwd=${projectDir}, file=${tmpFile}`);
|
|
545
|
-
const child = spawn(acpx, ["claude", "exec", "-f", tmpFile], {
|
|
546
|
-
cwd: projectDir,
|
|
547
|
-
stdio: "ignore",
|
|
548
|
-
detached: true
|
|
549
|
-
});
|
|
550
|
-
child.on("error", (err) => {
|
|
551
|
-
_log(`[inject] acpx spawn error for '${codeName}': ${err.message}`);
|
|
552
|
-
});
|
|
553
|
-
child.unref();
|
|
554
|
-
return true;
|
|
555
|
-
} catch (err) {
|
|
556
|
-
_log(`[inject] acpx exec failed for '${codeName}': ${err.message}`);
|
|
557
|
-
}
|
|
558
|
-
} else {
|
|
559
|
-
_log(`[inject] acpx binary not found \u2014 falling back to tmux send-keys`);
|
|
560
|
-
}
|
|
561
|
-
try {
|
|
562
|
-
execFileSync("tmux", ["send-keys", "-t", `agt-${codeName}`, text, "Enter"], { stdio: "ignore" });
|
|
563
|
-
_log(`[inject] tmux send-keys sent for '${codeName}' \u2014 unverified (returning false)`);
|
|
564
|
-
return false;
|
|
565
|
-
} catch (err) {
|
|
566
|
-
_log(`[inject] tmux send-keys failed for '${codeName}': ${err.message}`);
|
|
567
|
-
return false;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
function stopPersistentSession(codeName, log2) {
|
|
571
|
-
const session = sessions.get(codeName);
|
|
572
|
-
if (!session) return;
|
|
573
|
-
log2(`[persistent-session] Stopping session for '${codeName}'`);
|
|
574
|
-
session.status = "stopped";
|
|
575
|
-
try {
|
|
576
|
-
execSync(`tmux kill-session -t agt-${codeName} 2>/dev/null`, { stdio: "ignore" });
|
|
577
|
-
} catch {
|
|
578
|
-
}
|
|
579
|
-
try {
|
|
580
|
-
const acpx = getAcpxBin();
|
|
581
|
-
if (acpx) {
|
|
582
|
-
execFileSync(acpx, ["claude", "sessions", "close", `agt-${codeName}`], {
|
|
583
|
-
cwd: getProjectDir2(codeName),
|
|
584
|
-
timeout: 5e3,
|
|
585
|
-
stdio: "ignore"
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
} catch {
|
|
589
|
-
}
|
|
590
|
-
sessions.delete(codeName);
|
|
591
|
-
}
|
|
592
|
-
function isSessionHealthy(codeName) {
|
|
593
|
-
const tmuxSession = `agt-${codeName}`;
|
|
594
|
-
try {
|
|
595
|
-
execSync(`tmux has-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
|
|
596
|
-
} catch {
|
|
597
|
-
const session2 = sessions.get(codeName);
|
|
598
|
-
if (session2 && session2.status === "running") {
|
|
599
|
-
session2.status = "crashed";
|
|
600
|
-
}
|
|
601
|
-
return false;
|
|
602
|
-
}
|
|
603
|
-
if (!sessions.has(codeName)) {
|
|
604
|
-
sessions.set(codeName, {
|
|
605
|
-
codeName,
|
|
606
|
-
startedAt: Date.now(),
|
|
607
|
-
restartCount: 0,
|
|
608
|
-
status: "running"
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
const session = sessions.get(codeName);
|
|
612
|
-
if (session.status !== "running") {
|
|
613
|
-
session.status = "running";
|
|
614
|
-
}
|
|
615
|
-
return true;
|
|
616
|
-
}
|
|
617
|
-
function resetRestartCount(codeName) {
|
|
618
|
-
const session = sessions.get(codeName);
|
|
619
|
-
if (session) session.restartCount = 0;
|
|
620
|
-
}
|
|
621
|
-
async function stopAllSessionsAndWait(log2, opts) {
|
|
622
|
-
const codeNames = [...sessions.keys()];
|
|
623
|
-
if (codeNames.length === 0) return;
|
|
624
|
-
for (const codeName of codeNames) {
|
|
625
|
-
stopPersistentSession(codeName, log2);
|
|
626
|
-
}
|
|
627
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(opts.timeoutMs, 2e3)));
|
|
628
|
-
}
|
|
629
|
-
function getProjectDir2(codeName) {
|
|
630
|
-
return join(homedir(), ".augmented", codeName, "project");
|
|
631
|
-
}
|
|
632
|
-
function writeAcpxConfig(config2) {
|
|
633
|
-
const { projectDir, mcpConfigPath, claudeMdPath, channels, devChannels } = config2;
|
|
634
|
-
const claudeArgs = [];
|
|
635
|
-
if (channels.length > 0) claudeArgs.push("--channels", ...channels);
|
|
636
|
-
if (devChannels.length > 0) claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
|
|
637
|
-
claudeArgs.push("--mcp-config", mcpConfigPath);
|
|
638
|
-
const channelsConfigPath = join(projectDir, ".mcp-channels.json");
|
|
639
|
-
if (existsSync(channelsConfigPath)) claudeArgs.push("--mcp-config", channelsConfigPath);
|
|
640
|
-
if (existsSync(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
|
|
641
|
-
claudeArgs.push("--allow-dangerously-skip-permissions");
|
|
642
|
-
claudeArgs.push("--dangerously-skip-permissions");
|
|
643
|
-
claudeArgs.push("--strict-mcp-config");
|
|
644
|
-
const mcpServerNames2 = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
|
|
645
|
-
const mcpPatterns2 = mcpServerNames2.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
|
|
646
|
-
const allowedTools2 = [...mcpPatterns2, "Bash", "Read", "Write", "Edit", "Grep", "Glob", "Agent", "Skill"].join(",");
|
|
647
|
-
claudeArgs.push("--allowedTools", allowedTools2);
|
|
648
|
-
const acpCmd = `npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
|
|
649
|
-
const envIntegrationsPath = join(projectDir, ".env.integrations");
|
|
650
|
-
const wrapperPath = join(projectDir, ".claude", "acpx-agent.sh");
|
|
651
|
-
const wrapperLines = ["#!/usr/bin/env bash"];
|
|
652
|
-
if (existsSync(envIntegrationsPath)) {
|
|
653
|
-
wrapperLines.push(`set -a`, `source ${JSON.stringify(envIntegrationsPath)}`, `set +a`);
|
|
654
|
-
}
|
|
655
|
-
wrapperLines.push(`exec ${acpCmd}`);
|
|
656
|
-
mkdirSync(join(projectDir, ".claude"), { recursive: true });
|
|
657
|
-
writeFileSync2(wrapperPath, wrapperLines.join("\n") + "\n", { mode: 493 });
|
|
658
|
-
const acpxConfig = {
|
|
659
|
-
defaultAgent: "claude",
|
|
660
|
-
defaultPermissions: "approve-all",
|
|
661
|
-
agents: {
|
|
662
|
-
claude: {
|
|
663
|
-
command: wrapperPath
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
};
|
|
667
|
-
writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
|
|
668
|
-
}
|
|
669
|
-
|
|
670
309
|
// src/lib/realtime-chat.ts
|
|
671
310
|
import { createClient } from "@supabase/supabase-js";
|
|
672
311
|
var client = null;
|
|
@@ -909,8 +548,8 @@ function stopRealtimeChat() {
|
|
|
909
548
|
var GATEWAY_PORT_BASE = 18800;
|
|
910
549
|
var GATEWAY_PORT_STEP = 10;
|
|
911
550
|
var GATEWAY_PORT_MAX = 18899;
|
|
912
|
-
var AUGMENTED_DIR =
|
|
913
|
-
var GATEWAY_PORTS_FILE =
|
|
551
|
+
var AUGMENTED_DIR = join(process.env["HOME"] ?? "/tmp", ".augmented");
|
|
552
|
+
var GATEWAY_PORTS_FILE = join(AUGMENTED_DIR, "gateway-ports.json");
|
|
914
553
|
var config = null;
|
|
915
554
|
var running = false;
|
|
916
555
|
var pollTimer = null;
|
|
@@ -987,24 +626,24 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
987
626
|
if (frameworkId !== "claude-code") return;
|
|
988
627
|
if (frameworkBinaryChecked.has(frameworkId)) return;
|
|
989
628
|
frameworkBinaryChecked.add(frameworkId);
|
|
990
|
-
const { execFileSync
|
|
629
|
+
const { execFileSync } = await import("child_process");
|
|
991
630
|
let brewPath;
|
|
992
631
|
try {
|
|
993
|
-
brewPath =
|
|
632
|
+
brewPath = execFileSync("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
994
633
|
} catch {
|
|
995
634
|
log("Homebrew not found \u2014 cannot auto-install/upgrade Claude Code. Install manually: https://claude.ai/download");
|
|
996
635
|
return;
|
|
997
636
|
}
|
|
998
637
|
let claudeExists = false;
|
|
999
638
|
try {
|
|
1000
|
-
|
|
639
|
+
execFileSync("which", ["claude"], { timeout: 5e3 });
|
|
1001
640
|
claudeExists = true;
|
|
1002
641
|
} catch {
|
|
1003
642
|
}
|
|
1004
643
|
if (!claudeExists) {
|
|
1005
644
|
log("Claude Code binary not found \u2014 installing via Homebrew...");
|
|
1006
645
|
try {
|
|
1007
|
-
|
|
646
|
+
execFileSync(brewPath, ["install", "--cask", "claude-code"], {
|
|
1008
647
|
timeout: 12e4,
|
|
1009
648
|
stdio: "pipe"
|
|
1010
649
|
});
|
|
@@ -1013,7 +652,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1013
652
|
return;
|
|
1014
653
|
}
|
|
1015
654
|
try {
|
|
1016
|
-
|
|
655
|
+
execFileSync("which", ["claude"], { timeout: 5e3 });
|
|
1017
656
|
log("Claude Code installed successfully");
|
|
1018
657
|
} catch {
|
|
1019
658
|
log("Claude Code install completed but binary not found on PATH \u2014 you may need to restart your terminal");
|
|
@@ -1021,7 +660,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1021
660
|
} else {
|
|
1022
661
|
log("Checking for Claude Code updates...");
|
|
1023
662
|
try {
|
|
1024
|
-
const output =
|
|
663
|
+
const output = execFileSync(brewPath, ["upgrade", "--cask", "claude-code"], {
|
|
1025
664
|
timeout: 12e4,
|
|
1026
665
|
stdio: "pipe"
|
|
1027
666
|
}).toString();
|
|
@@ -1039,7 +678,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1039
678
|
}
|
|
1040
679
|
}
|
|
1041
680
|
}
|
|
1042
|
-
agentRuntimeAuthenticated = checkClaudeAuth(
|
|
681
|
+
agentRuntimeAuthenticated = checkClaudeAuth(execFileSync);
|
|
1043
682
|
}
|
|
1044
683
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1045
684
|
var selfUpdateChecked = false;
|
|
@@ -1053,21 +692,21 @@ async function checkAndUpdateCli() {
|
|
|
1053
692
|
if (!isHomebrew && !cliPath.includes("node_modules")) return;
|
|
1054
693
|
const { homedir: homedir2 } = await import("os");
|
|
1055
694
|
const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
|
|
1056
|
-
const markerPath =
|
|
695
|
+
const markerPath = join(homedir2(), ".augmented", ".last-update-check");
|
|
1057
696
|
try {
|
|
1058
697
|
const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
|
|
1059
698
|
if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;
|
|
1060
699
|
} catch {
|
|
1061
700
|
}
|
|
1062
|
-
const { execFileSync
|
|
701
|
+
const { execFileSync } = await import("child_process");
|
|
1063
702
|
let brewPath;
|
|
1064
703
|
try {
|
|
1065
|
-
brewPath =
|
|
704
|
+
brewPath = execFileSync("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
1066
705
|
} catch {
|
|
1067
706
|
return;
|
|
1068
707
|
}
|
|
1069
708
|
try {
|
|
1070
|
-
const outdated =
|
|
709
|
+
const outdated = execFileSync(brewPath, ["outdated", "--json=v2"], {
|
|
1071
710
|
timeout: 3e4,
|
|
1072
711
|
encoding: "utf-8"
|
|
1073
712
|
});
|
|
@@ -1078,7 +717,7 @@ async function checkAndUpdateCli() {
|
|
|
1078
717
|
const latest = agtOutdated.current_version ?? "unknown";
|
|
1079
718
|
log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading...`);
|
|
1080
719
|
try {
|
|
1081
|
-
|
|
720
|
+
execFileSync(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
|
|
1082
721
|
timeout: 12e4,
|
|
1083
722
|
stdio: "pipe"
|
|
1084
723
|
});
|
|
@@ -1094,9 +733,9 @@ async function checkAndUpdateCli() {
|
|
|
1094
733
|
} catch {
|
|
1095
734
|
}
|
|
1096
735
|
}
|
|
1097
|
-
function checkClaudeAuth(
|
|
736
|
+
function checkClaudeAuth(execFileSync) {
|
|
1098
737
|
try {
|
|
1099
|
-
const authOutput =
|
|
738
|
+
const authOutput = execFileSync("claude", ["auth", "status"], { timeout: 1e4, stdio: "pipe" }).toString().trim();
|
|
1100
739
|
let loggedIn = null;
|
|
1101
740
|
try {
|
|
1102
741
|
const parsed = JSON.parse(authOutput);
|
|
@@ -1121,14 +760,14 @@ function checkClaudeAuth(execFileSync2) {
|
|
|
1121
760
|
}
|
|
1122
761
|
function loadGatewayPorts() {
|
|
1123
762
|
try {
|
|
1124
|
-
return JSON.parse(
|
|
763
|
+
return JSON.parse(readFileSync(GATEWAY_PORTS_FILE, "utf-8"));
|
|
1125
764
|
} catch {
|
|
1126
765
|
return {};
|
|
1127
766
|
}
|
|
1128
767
|
}
|
|
1129
768
|
function saveGatewayPorts(ports) {
|
|
1130
|
-
|
|
1131
|
-
|
|
769
|
+
mkdirSync(AUGMENTED_DIR, { recursive: true });
|
|
770
|
+
writeFileSync(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
|
|
1132
771
|
}
|
|
1133
772
|
function allocatePort(codeName) {
|
|
1134
773
|
const ports = loadGatewayPorts();
|
|
@@ -1150,11 +789,11 @@ function freePort(codeName) {
|
|
|
1150
789
|
saveGatewayPorts(ports);
|
|
1151
790
|
}
|
|
1152
791
|
}
|
|
1153
|
-
var STATE_FILE =
|
|
792
|
+
var STATE_FILE = join(process.env["HOME"] ?? "/tmp", ".augmented", "manager-state.json");
|
|
1154
793
|
function send(msg) {
|
|
1155
794
|
if (msg.type === "state-update") {
|
|
1156
795
|
try {
|
|
1157
|
-
|
|
796
|
+
writeFileSync(STATE_FILE, JSON.stringify(msg.state, null, 2));
|
|
1158
797
|
} catch {
|
|
1159
798
|
}
|
|
1160
799
|
}
|
|
@@ -1176,7 +815,7 @@ function sha256(content) {
|
|
|
1176
815
|
}
|
|
1177
816
|
function hashFile(filePath) {
|
|
1178
817
|
try {
|
|
1179
|
-
const content =
|
|
818
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1180
819
|
return sha256(content);
|
|
1181
820
|
} catch {
|
|
1182
821
|
return null;
|
|
@@ -1184,10 +823,10 @@ function hashFile(filePath) {
|
|
|
1184
823
|
}
|
|
1185
824
|
async function migrateToProfiles() {
|
|
1186
825
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1187
|
-
const sharedConfigPath =
|
|
826
|
+
const sharedConfigPath = join(homeDir, ".openclaw", "openclaw.json");
|
|
1188
827
|
let sharedConfig;
|
|
1189
828
|
try {
|
|
1190
|
-
sharedConfig = JSON.parse(
|
|
829
|
+
sharedConfig = JSON.parse(readFileSync(sharedConfigPath, "utf-8"));
|
|
1191
830
|
} catch {
|
|
1192
831
|
return;
|
|
1193
832
|
}
|
|
@@ -1200,19 +839,19 @@ async function migrateToProfiles() {
|
|
|
1200
839
|
const codeName = agentEntry["id"];
|
|
1201
840
|
if (!codeName) continue;
|
|
1202
841
|
if (codeName === "main") continue;
|
|
1203
|
-
const profileDir =
|
|
1204
|
-
if (
|
|
842
|
+
const profileDir = join(homeDir, `.openclaw-${codeName}`);
|
|
843
|
+
if (existsSync(join(profileDir, "openclaw.json"))) continue;
|
|
1205
844
|
log(`Migrating agent '${codeName}' to per-agent profile`);
|
|
1206
845
|
if (adapter.seedProfileConfig) {
|
|
1207
846
|
adapter.seedProfileConfig(codeName);
|
|
1208
847
|
}
|
|
1209
|
-
const sharedAuthDir =
|
|
1210
|
-
const profileAuthDir =
|
|
1211
|
-
const authFile =
|
|
1212
|
-
if (
|
|
1213
|
-
|
|
1214
|
-
const authContent =
|
|
1215
|
-
|
|
848
|
+
const sharedAuthDir = join(homeDir, ".openclaw", "agents", codeName, "agent");
|
|
849
|
+
const profileAuthDir = join(profileDir, "agents", codeName, "agent");
|
|
850
|
+
const authFile = join(sharedAuthDir, "auth-profiles.json");
|
|
851
|
+
if (existsSync(authFile)) {
|
|
852
|
+
mkdirSync(profileAuthDir, { recursive: true });
|
|
853
|
+
const authContent = readFileSync(authFile, "utf-8");
|
|
854
|
+
writeFileSync(join(profileAuthDir, "auth-profiles.json"), authContent);
|
|
1216
855
|
}
|
|
1217
856
|
allocatePort(codeName);
|
|
1218
857
|
migrated++;
|
|
@@ -1250,7 +889,7 @@ function readGatewayToken(codeName) {
|
|
|
1250
889
|
}
|
|
1251
890
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1252
891
|
try {
|
|
1253
|
-
const cfg = JSON.parse(
|
|
892
|
+
const cfg = JSON.parse(readFileSync(join(homeDir, `.openclaw-${codeName}`, "openclaw.json"), "utf-8"));
|
|
1254
893
|
return cfg?.gateway?.auth?.token;
|
|
1255
894
|
} catch {
|
|
1256
895
|
return void 0;
|
|
@@ -1259,10 +898,10 @@ function readGatewayToken(codeName) {
|
|
|
1259
898
|
var GATEWAY_HUNG_TIMEOUT_MS = 5 * 6e4;
|
|
1260
899
|
function isGatewayHung(codeName) {
|
|
1261
900
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1262
|
-
const jobsPath =
|
|
1263
|
-
if (!
|
|
901
|
+
const jobsPath = join(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
902
|
+
if (!existsSync(jobsPath)) return false;
|
|
1264
903
|
try {
|
|
1265
|
-
const data = JSON.parse(
|
|
904
|
+
const data = JSON.parse(readFileSync(jobsPath, "utf-8"));
|
|
1266
905
|
const jobs = data.jobs ?? data;
|
|
1267
906
|
if (!Array.isArray(jobs)) return false;
|
|
1268
907
|
const now = Date.now();
|
|
@@ -1295,19 +934,19 @@ async function ensureGatewayRunning(codeName, adapter) {
|
|
|
1295
934
|
}
|
|
1296
935
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
1297
936
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1298
|
-
const cronJobsPath =
|
|
937
|
+
const cronJobsPath = join(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
1299
938
|
clearStaleCronRunState(cronJobsPath);
|
|
1300
939
|
} else {
|
|
1301
940
|
if (status.port) {
|
|
1302
941
|
try {
|
|
1303
942
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1304
|
-
const configPath =
|
|
1305
|
-
if (
|
|
1306
|
-
const cfg = JSON.parse(
|
|
943
|
+
const configPath = join(homeDir, `.openclaw-${codeName}`, "openclaw.json");
|
|
944
|
+
if (existsSync(configPath)) {
|
|
945
|
+
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1307
946
|
if (cfg.gateway?.port !== status.port) {
|
|
1308
947
|
if (!cfg.gateway) cfg.gateway = {};
|
|
1309
948
|
cfg.gateway.port = status.port;
|
|
1310
|
-
|
|
949
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
1311
950
|
}
|
|
1312
951
|
}
|
|
1313
952
|
} catch {
|
|
@@ -1327,12 +966,12 @@ async function ensureGatewayRunning(codeName, adapter) {
|
|
|
1327
966
|
gatewaysStartedThisCycle.add(codeName);
|
|
1328
967
|
try {
|
|
1329
968
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1330
|
-
const configPath =
|
|
1331
|
-
if (
|
|
1332
|
-
const cfg = JSON.parse(
|
|
969
|
+
const configPath = join(homeDir, `.openclaw-${codeName}`, "openclaw.json");
|
|
970
|
+
if (existsSync(configPath)) {
|
|
971
|
+
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1333
972
|
if (!cfg.gateway) cfg.gateway = {};
|
|
1334
973
|
cfg.gateway.port = port;
|
|
1335
|
-
|
|
974
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
1336
975
|
}
|
|
1337
976
|
} catch {
|
|
1338
977
|
}
|
|
@@ -1436,11 +1075,32 @@ async function pollCycle() {
|
|
|
1436
1075
|
}
|
|
1437
1076
|
try {
|
|
1438
1077
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
1078
|
+
const { collectDiagnostics } = await import("../persistent-session-ZCB562FN.js");
|
|
1079
|
+
const diagCodeNames = [...persistentSessionAgents];
|
|
1080
|
+
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames) : void 0;
|
|
1081
|
+
let tailscaleHostname;
|
|
1082
|
+
try {
|
|
1083
|
+
const { execSync: es } = await import("child_process");
|
|
1084
|
+
const tsJson = es("tailscale status --self --json 2>/dev/null", {
|
|
1085
|
+
encoding: "utf-8",
|
|
1086
|
+
timeout: 3e3
|
|
1087
|
+
}).trim();
|
|
1088
|
+
const ts = JSON.parse(tsJson);
|
|
1089
|
+
tailscaleHostname = ts.Self?.DNSName?.replace(/\.$/, "") || void 0;
|
|
1090
|
+
} catch {
|
|
1091
|
+
try {
|
|
1092
|
+
const { execSync: es } = await import("child_process");
|
|
1093
|
+
tailscaleHostname = es("hostname", { encoding: "utf-8", timeout: 1e3 }).trim();
|
|
1094
|
+
} catch {
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1439
1097
|
await api.post("/host/heartbeat", {
|
|
1440
1098
|
host_id: hostId,
|
|
1441
1099
|
framework_version: cachedFrameworkVersion ?? void 0,
|
|
1442
1100
|
host_security: detectHostSecurity() ?? void 0,
|
|
1443
|
-
agent_runtime_authenticated: agentRuntimeAuthenticated
|
|
1101
|
+
agent_runtime_authenticated: agentRuntimeAuthenticated,
|
|
1102
|
+
agent_diagnostics: agentDiagnostics,
|
|
1103
|
+
hostname: tailscaleHostname
|
|
1444
1104
|
});
|
|
1445
1105
|
} catch (err) {
|
|
1446
1106
|
log(`Heartbeat failed: ${err.message}`);
|
|
@@ -1499,7 +1159,7 @@ async function pollCycle() {
|
|
|
1499
1159
|
const adapter = resolveAgentFramework(prev.codeName);
|
|
1500
1160
|
await stopGatewayIfRunning(prev.codeName, adapter);
|
|
1501
1161
|
freePort(prev.codeName);
|
|
1502
|
-
const agentDir =
|
|
1162
|
+
const agentDir = join(config.configDir, prev.codeName, "provision");
|
|
1503
1163
|
await cleanupAgentFiles(prev.codeName, agentDir);
|
|
1504
1164
|
clearAgentCaches(prev.agentId, prev.codeName);
|
|
1505
1165
|
}
|
|
@@ -1566,7 +1226,7 @@ async function processAgent(agent, agentStates) {
|
|
|
1566
1226
|
agentFrameworkCache.set(agent.code_name, agent.framework);
|
|
1567
1227
|
}
|
|
1568
1228
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1569
|
-
const agentDir =
|
|
1229
|
+
const agentDir = join(config.configDir, agent.code_name, "provision");
|
|
1570
1230
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
1571
1231
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
1572
1232
|
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
@@ -1717,9 +1377,9 @@ async function processAgent(agent, agentStates) {
|
|
|
1717
1377
|
try {
|
|
1718
1378
|
const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
|
|
1719
1379
|
const changedFiles = [];
|
|
1720
|
-
|
|
1380
|
+
mkdirSync(agentDir, { recursive: true });
|
|
1721
1381
|
for (const artifact of artifacts) {
|
|
1722
|
-
const filePath =
|
|
1382
|
+
const filePath = join(agentDir, artifact.relativePath);
|
|
1723
1383
|
const newHash = sha256(artifact.content);
|
|
1724
1384
|
const existingHash = hashFile(filePath);
|
|
1725
1385
|
if (newHash !== existingHash) {
|
|
@@ -1727,19 +1387,19 @@ async function processAgent(agent, agentStates) {
|
|
|
1727
1387
|
}
|
|
1728
1388
|
}
|
|
1729
1389
|
if (changedFiles.length > 0) {
|
|
1730
|
-
const isFirst = !
|
|
1390
|
+
const isFirst = !existsSync(join(agentDir, "CHARTER.md"));
|
|
1731
1391
|
const verb = isFirst ? "Provisioning" : "Updating";
|
|
1732
1392
|
const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
|
|
1733
1393
|
log(`${verb} '${agent.code_name}': ${fileNames}`);
|
|
1734
1394
|
for (const file of changedFiles) {
|
|
1735
|
-
|
|
1395
|
+
writeFileSync(join(agentDir, file.relativePath), file.content);
|
|
1736
1396
|
}
|
|
1737
1397
|
lastProvisionAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1738
1398
|
knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });
|
|
1739
1399
|
const trackedFiles = frameworkAdapter.driftTrackedFiles();
|
|
1740
1400
|
const hashes = /* @__PURE__ */ new Map();
|
|
1741
1401
|
for (const file of trackedFiles) {
|
|
1742
|
-
const h = hashFile(
|
|
1402
|
+
const h = hashFile(join(agentDir, file));
|
|
1743
1403
|
if (h) hashes.set(file, h);
|
|
1744
1404
|
}
|
|
1745
1405
|
writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -1783,10 +1443,10 @@ async function processAgent(agent, agentStates) {
|
|
|
1783
1443
|
}
|
|
1784
1444
|
let lastDriftCheckAt = now;
|
|
1785
1445
|
const written = writtenHashes.get(agent.agent_id);
|
|
1786
|
-
if (written &&
|
|
1446
|
+
if (written && existsSync(agentDir)) {
|
|
1787
1447
|
const driftedFiles = [];
|
|
1788
1448
|
for (const [file, expectedHash] of written) {
|
|
1789
|
-
const localHash = hashFile(
|
|
1449
|
+
const localHash = hashFile(join(agentDir, file));
|
|
1790
1450
|
if (localHash && localHash !== expectedHash) {
|
|
1791
1451
|
driftedFiles.push(file);
|
|
1792
1452
|
}
|
|
@@ -1797,7 +1457,7 @@ async function processAgent(agent, agentStates) {
|
|
|
1797
1457
|
try {
|
|
1798
1458
|
const localHashes = {};
|
|
1799
1459
|
for (const file of driftedFiles) {
|
|
1800
|
-
localHashes[file] = hashFile(
|
|
1460
|
+
localHashes[file] = hashFile(join(agentDir, file));
|
|
1801
1461
|
}
|
|
1802
1462
|
await api.post("/host/drift", {
|
|
1803
1463
|
agent_id: agent.agent_id,
|
|
@@ -1950,11 +1610,11 @@ async function processAgent(agent, agentStates) {
|
|
|
1950
1610
|
}
|
|
1951
1611
|
if (frameworkAdapter.removeMcpServer) {
|
|
1952
1612
|
try {
|
|
1953
|
-
const { readFileSync:
|
|
1954
|
-
const { join:
|
|
1613
|
+
const { readFileSync: readFileSync2 } = await import("fs");
|
|
1614
|
+
const { join: join2 } = await import("path");
|
|
1955
1615
|
const { homedir: homedir2 } = await import("os");
|
|
1956
|
-
const mcpPath =
|
|
1957
|
-
const mcpConfig = JSON.parse(
|
|
1616
|
+
const mcpPath = join2(homedir2(), ".augmented", "agents", agent.code_name, "provision", ".mcp.json");
|
|
1617
|
+
const mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf-8"));
|
|
1958
1618
|
if (mcpConfig.mcpServers) {
|
|
1959
1619
|
const managedPrefixes = ["composio-", "one-", "nango-", "paragon-"];
|
|
1960
1620
|
for (const key of Object.keys(mcpConfig.mcpServers)) {
|
|
@@ -2007,18 +1667,18 @@ async function processAgent(agent, agentStates) {
|
|
|
2007
1667
|
"@tobilu/qmd"
|
|
2008
1668
|
]);
|
|
2009
1669
|
if (intHash !== prevIntHash) {
|
|
2010
|
-
const { execFileSync
|
|
1670
|
+
const { execFileSync } = await import("child_process");
|
|
2011
1671
|
for (const tool of capData.cliTools) {
|
|
2012
1672
|
if (!ALLOWED_CLI_PACKAGES.has(tool.package)) {
|
|
2013
1673
|
log(`Skipping CLI tool '${tool.package}' for '${agent.code_name}' \u2014 not on the allowed packages list`);
|
|
2014
1674
|
continue;
|
|
2015
1675
|
}
|
|
2016
1676
|
try {
|
|
2017
|
-
|
|
1677
|
+
execFileSync("which", [tool.binary], { stdio: "ignore" });
|
|
2018
1678
|
} catch {
|
|
2019
1679
|
log(`Installing CLI tool '${tool.package}' for '${agent.code_name}'...`);
|
|
2020
1680
|
try {
|
|
2021
|
-
|
|
1681
|
+
execFileSync("npm", ["install", "-g", tool.package], { stdio: "ignore", timeout: 6e4 });
|
|
2022
1682
|
log(`CLI tool '${tool.binary}' installed successfully`);
|
|
2023
1683
|
} catch (installErr) {
|
|
2024
1684
|
log(`Failed to install CLI tool '${tool.package}': ${installErr.message}`);
|
|
@@ -2026,8 +1686,8 @@ async function processAgent(agent, agentStates) {
|
|
|
2026
1686
|
}
|
|
2027
1687
|
if (tool.binary === "qmd") {
|
|
2028
1688
|
try {
|
|
2029
|
-
const agentDir2 =
|
|
2030
|
-
|
|
1689
|
+
const agentDir2 = join(process.env["HOME"] ?? "/tmp", ".augmented", agent.code_name);
|
|
1690
|
+
execFileSync("qmd", ["collection", "add", agent.code_name, "project"], {
|
|
2031
1691
|
stdio: "ignore",
|
|
2032
1692
|
timeout: 3e4,
|
|
2033
1693
|
cwd: agentDir2
|
|
@@ -2087,8 +1747,8 @@ async function processAgent(agent, agentStates) {
|
|
|
2087
1747
|
if (agent.status === "active") {
|
|
2088
1748
|
if (frameworkAdapter.installPlugin) {
|
|
2089
1749
|
try {
|
|
2090
|
-
const pluginPath =
|
|
2091
|
-
if (
|
|
1750
|
+
const pluginPath = join(process.cwd(), "packages", "openclaw-plugin-augmented", "src", "index.ts");
|
|
1751
|
+
if (existsSync(pluginPath)) {
|
|
2092
1752
|
frameworkAdapter.installPlugin(agent.code_name, "augmented", pluginPath, {
|
|
2093
1753
|
agtHost: requireHost(),
|
|
2094
1754
|
agtApiKey: getApiKey() ?? void 0,
|
|
@@ -2203,10 +1863,10 @@ async function processAgent(agent, agentStates) {
|
|
|
2203
1863
|
lastWorkTriggerAt.set(agent.code_name, triggerTs);
|
|
2204
1864
|
if (agentFw === "openclaw" && gatewayRunning && gatewayPort) {
|
|
2205
1865
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
2206
|
-
const jobsPath =
|
|
2207
|
-
if (
|
|
1866
|
+
const jobsPath = join(homeDir, `.openclaw-${agent.code_name}`, "cron", "jobs.json");
|
|
1867
|
+
if (existsSync(jobsPath)) {
|
|
2208
1868
|
try {
|
|
2209
|
-
const jobsData = JSON.parse(
|
|
1869
|
+
const jobsData = JSON.parse(readFileSync(jobsPath, "utf-8"));
|
|
2210
1870
|
const kanbanJob = (jobsData.jobs ?? []).find(
|
|
2211
1871
|
(j) => typeof j.name === "string" && j.name.includes("kanban-work")
|
|
2212
1872
|
);
|
|
@@ -2351,19 +2011,19 @@ function cleanupStaleSessions(codeName) {
|
|
|
2351
2011
|
lastCleanupAt.set(codeName, Date.now());
|
|
2352
2012
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
2353
2013
|
for (const agentDir of ["main", codeName]) {
|
|
2354
|
-
const sessionsDir =
|
|
2014
|
+
const sessionsDir = join(homeDir, `.openclaw-${codeName}`, "agents", agentDir, "sessions");
|
|
2355
2015
|
cleanupCronSessions(sessionsDir, CRON_SESSION_KEEP_COUNT);
|
|
2356
2016
|
}
|
|
2357
|
-
const cronRunsDir =
|
|
2017
|
+
const cronRunsDir = join(homeDir, `.openclaw-${codeName}`, "cron", "runs");
|
|
2358
2018
|
cleanupOldFiles(cronRunsDir, CRON_RUN_RETENTION_DAYS, ".jsonl");
|
|
2359
|
-
const cronJobsPath =
|
|
2019
|
+
const cronJobsPath = join(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
2360
2020
|
clearStaleCronRunState(cronJobsPath);
|
|
2361
2021
|
}
|
|
2362
2022
|
function cleanupCronSessions(sessionsDir, keepCount) {
|
|
2363
|
-
const indexPath =
|
|
2364
|
-
if (!
|
|
2023
|
+
const indexPath = join(sessionsDir, "sessions.json");
|
|
2024
|
+
if (!existsSync(indexPath)) return;
|
|
2365
2025
|
try {
|
|
2366
|
-
const raw =
|
|
2026
|
+
const raw = readFileSync(indexPath, "utf-8");
|
|
2367
2027
|
const index = JSON.parse(raw);
|
|
2368
2028
|
const cronRunKeys = Object.keys(index).filter((k) => k.includes(":cron:") && k.includes(":run:")).map((k) => ({
|
|
2369
2029
|
key: k,
|
|
@@ -2376,9 +2036,9 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
2376
2036
|
for (const entry of toDelete) {
|
|
2377
2037
|
delete index[entry.key];
|
|
2378
2038
|
if (entry.sessionId) {
|
|
2379
|
-
const sessionFile =
|
|
2039
|
+
const sessionFile = join(sessionsDir, `${entry.sessionId}.jsonl`);
|
|
2380
2040
|
try {
|
|
2381
|
-
if (
|
|
2041
|
+
if (existsSync(sessionFile)) {
|
|
2382
2042
|
unlinkSync(sessionFile);
|
|
2383
2043
|
deletedFiles++;
|
|
2384
2044
|
}
|
|
@@ -2398,8 +2058,8 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
2398
2058
|
delete index[parentKey];
|
|
2399
2059
|
if (parentSessionId) {
|
|
2400
2060
|
try {
|
|
2401
|
-
const f =
|
|
2402
|
-
if (
|
|
2061
|
+
const f = join(sessionsDir, `${parentSessionId}.jsonl`);
|
|
2062
|
+
if (existsSync(f)) {
|
|
2403
2063
|
unlinkSync(f);
|
|
2404
2064
|
deletedFiles++;
|
|
2405
2065
|
}
|
|
@@ -2408,7 +2068,7 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
2408
2068
|
}
|
|
2409
2069
|
}
|
|
2410
2070
|
}
|
|
2411
|
-
|
|
2071
|
+
writeFileSync(indexPath, JSON.stringify(index));
|
|
2412
2072
|
if (toDelete.length > 0) {
|
|
2413
2073
|
log(`Cleaned ${toDelete.length} cron session(s) and ${deletedFiles} file(s) from ${sessionsDir}`);
|
|
2414
2074
|
}
|
|
@@ -2417,9 +2077,9 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
2417
2077
|
}
|
|
2418
2078
|
var STALE_RUN_TIMEOUT_MS = 5 * 6e4;
|
|
2419
2079
|
function clearStaleCronRunState(jobsPath) {
|
|
2420
|
-
if (!
|
|
2080
|
+
if (!existsSync(jobsPath)) return;
|
|
2421
2081
|
try {
|
|
2422
|
-
const raw =
|
|
2082
|
+
const raw = readFileSync(jobsPath, "utf-8");
|
|
2423
2083
|
const data = JSON.parse(raw);
|
|
2424
2084
|
const jobs = data.jobs ?? data;
|
|
2425
2085
|
if (!Array.isArray(jobs)) return;
|
|
@@ -2444,19 +2104,19 @@ function clearStaleCronRunState(jobsPath) {
|
|
|
2444
2104
|
}
|
|
2445
2105
|
}
|
|
2446
2106
|
if (changed) {
|
|
2447
|
-
|
|
2107
|
+
writeFileSync(jobsPath, JSON.stringify(data, null, 2));
|
|
2448
2108
|
}
|
|
2449
2109
|
} catch {
|
|
2450
2110
|
}
|
|
2451
2111
|
}
|
|
2452
2112
|
function cleanupOldFiles(dir, maxAgeDays, ext) {
|
|
2453
|
-
if (!
|
|
2113
|
+
if (!existsSync(dir)) return;
|
|
2454
2114
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
2455
2115
|
let removed = 0;
|
|
2456
2116
|
try {
|
|
2457
2117
|
for (const f of readdirSync(dir)) {
|
|
2458
2118
|
if (!f.endsWith(ext)) continue;
|
|
2459
|
-
const fullPath =
|
|
2119
|
+
const fullPath = join(dir, f);
|
|
2460
2120
|
try {
|
|
2461
2121
|
const st = statSync(fullPath);
|
|
2462
2122
|
if (st.mtimeMs < cutoff) {
|
|
@@ -2547,16 +2207,16 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
|
|
|
2547
2207
|
}
|
|
2548
2208
|
async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
2549
2209
|
const projectDir = getProjectDir(codeName);
|
|
2550
|
-
const mcpConfigPath =
|
|
2210
|
+
const mcpConfigPath = join(projectDir, ".mcp.json");
|
|
2551
2211
|
sanitizeMcpJson(mcpConfigPath, requireHost());
|
|
2552
2212
|
try {
|
|
2553
|
-
const claudeMdPath =
|
|
2213
|
+
const claudeMdPath = join(projectDir, "CLAUDE.md");
|
|
2554
2214
|
const serverNames = [];
|
|
2555
|
-
const channelsConfigPath =
|
|
2215
|
+
const channelsConfigPath = join(projectDir, ".mcp-channels.json");
|
|
2556
2216
|
for (const p of [mcpConfigPath, channelsConfigPath]) {
|
|
2557
|
-
if (
|
|
2217
|
+
if (existsSync(p)) {
|
|
2558
2218
|
try {
|
|
2559
|
-
const d = JSON.parse(
|
|
2219
|
+
const d = JSON.parse(readFileSync(p, "utf-8"));
|
|
2560
2220
|
if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));
|
|
2561
2221
|
} catch {
|
|
2562
2222
|
}
|
|
@@ -2573,19 +2233,17 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
2573
2233
|
mcpConfigPath,
|
|
2574
2234
|
"--strict-mcp-config",
|
|
2575
2235
|
"--allowedTools",
|
|
2576
|
-
allowedTools
|
|
2577
|
-
"--allow-dangerously-skip-permissions",
|
|
2578
|
-
"--dangerously-skip-permissions"
|
|
2236
|
+
allowedTools
|
|
2579
2237
|
];
|
|
2580
|
-
if (
|
|
2581
|
-
if (
|
|
2238
|
+
if (existsSync(channelsConfigPath)) claudeArgs.push("--mcp-config", channelsConfigPath);
|
|
2239
|
+
if (existsSync(claudeMdPath)) {
|
|
2582
2240
|
claudeArgs.push("--system-prompt-file", claudeMdPath);
|
|
2583
2241
|
}
|
|
2584
2242
|
const childEnv = { ...process.env };
|
|
2585
|
-
const envIntPath =
|
|
2586
|
-
if (
|
|
2243
|
+
const envIntPath = join(projectDir, ".env.integrations");
|
|
2244
|
+
if (existsSync(envIntPath)) {
|
|
2587
2245
|
try {
|
|
2588
|
-
for (const line of
|
|
2246
|
+
for (const line of readFileSync(envIntPath, "utf-8").split("\n")) {
|
|
2589
2247
|
if (!line || line.startsWith("#") || !line.includes("=")) continue;
|
|
2590
2248
|
const eqIdx = line.indexOf("=");
|
|
2591
2249
|
childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);
|
|
@@ -2688,8 +2346,8 @@ var persistentSessionAgents = /* @__PURE__ */ new Set();
|
|
|
2688
2346
|
async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
2689
2347
|
const codeName = agent.code_name;
|
|
2690
2348
|
const projectDir = getProjectDir2(codeName);
|
|
2691
|
-
const mcpConfigPath =
|
|
2692
|
-
const claudeMdPath =
|
|
2349
|
+
const mcpConfigPath = join(projectDir, ".mcp.json");
|
|
2350
|
+
const claudeMdPath = join(projectDir, "CLAUDE.md");
|
|
2693
2351
|
const channelConfigs = refreshData.channel_configs;
|
|
2694
2352
|
const channels = [];
|
|
2695
2353
|
const devChannels = [];
|
|
@@ -2705,8 +2363,8 @@ async function ensurePersistentSession(agent, tasks, boardItems, refreshData) {
|
|
|
2705
2363
|
}
|
|
2706
2364
|
}
|
|
2707
2365
|
if (!agentRuntimeAuthenticated) {
|
|
2708
|
-
const { execFileSync
|
|
2709
|
-
agentRuntimeAuthenticated = checkClaudeAuth(
|
|
2366
|
+
const { execFileSync } = await import("child_process");
|
|
2367
|
+
agentRuntimeAuthenticated = checkClaudeAuth(execFileSync);
|
|
2710
2368
|
if (!agentRuntimeAuthenticated) {
|
|
2711
2369
|
log(`[persistent-session] Skipping '${codeName}' \u2014 Claude Code not authenticated`);
|
|
2712
2370
|
return;
|
|
@@ -3022,13 +2680,13 @@ async function processDirectChatMessage(agent, msg) {
|
|
|
3022
2680
|
if (fw === "claude-code") {
|
|
3023
2681
|
const { getProjectDir: ccProjectDir } = await import("../claude-scheduler-VFBZFE6U.js");
|
|
3024
2682
|
const projDir = ccProjectDir(agent.codeName);
|
|
3025
|
-
const mcpConfigPath =
|
|
3026
|
-
const channelsConfigPath =
|
|
2683
|
+
const mcpConfigPath = join(projDir, ".mcp.json");
|
|
2684
|
+
const channelsConfigPath = join(projDir, ".mcp-channels.json");
|
|
3027
2685
|
const serverNames = [];
|
|
3028
2686
|
for (const p of [mcpConfigPath, channelsConfigPath]) {
|
|
3029
|
-
if (
|
|
2687
|
+
if (existsSync(p)) {
|
|
3030
2688
|
try {
|
|
3031
|
-
const d = JSON.parse(
|
|
2689
|
+
const d = JSON.parse(readFileSync(p, "utf-8"));
|
|
3032
2690
|
if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));
|
|
3033
2691
|
} catch {
|
|
3034
2692
|
}
|
|
@@ -3047,16 +2705,16 @@ async function processDirectChatMessage(agent, msg) {
|
|
|
3047
2705
|
"--allowedTools",
|
|
3048
2706
|
allowedTools
|
|
3049
2707
|
];
|
|
3050
|
-
if (
|
|
3051
|
-
const chatClaudeMd =
|
|
3052
|
-
if (
|
|
2708
|
+
if (existsSync(channelsConfigPath)) chatArgs.push("--mcp-config", channelsConfigPath);
|
|
2709
|
+
const chatClaudeMd = join(projDir, "CLAUDE.md");
|
|
2710
|
+
if (existsSync(chatClaudeMd)) {
|
|
3053
2711
|
chatArgs.push("--system-prompt-file", chatClaudeMd);
|
|
3054
2712
|
}
|
|
3055
|
-
const envIntPath =
|
|
2713
|
+
const envIntPath = join(projDir, ".env.integrations");
|
|
3056
2714
|
const childEnv = { ...process.env };
|
|
3057
|
-
if (
|
|
2715
|
+
if (existsSync(envIntPath)) {
|
|
3058
2716
|
try {
|
|
3059
|
-
for (const line of
|
|
2717
|
+
for (const line of readFileSync(envIntPath, "utf-8").split("\n")) {
|
|
3060
2718
|
if (!line || line.startsWith("#") || !line.includes("=")) continue;
|
|
3061
2719
|
const eqIdx = line.indexOf("=");
|
|
3062
2720
|
childEnv[line.slice(0, eqIdx)] = line.slice(eqIdx + 1);
|
|
@@ -3328,12 +2986,12 @@ function getBuiltInSkillContent(skillId) {
|
|
|
3328
2986
|
if (builtInSkillCache.has(skillId)) return builtInSkillCache.get(skillId);
|
|
3329
2987
|
try {
|
|
3330
2988
|
const candidates = [
|
|
3331
|
-
|
|
3332
|
-
|
|
2989
|
+
join(process.cwd(), "skills", skillId, "SKILL.md"),
|
|
2990
|
+
join(new URL(".", import.meta.url).pathname, "..", "..", "..", "..", "skills", skillId, "SKILL.md")
|
|
3333
2991
|
];
|
|
3334
2992
|
for (const candidate of candidates) {
|
|
3335
|
-
if (
|
|
3336
|
-
const content =
|
|
2993
|
+
if (existsSync(candidate)) {
|
|
2994
|
+
const content = readFileSync(candidate, "utf-8");
|
|
3337
2995
|
const files = [{ relativePath: "SKILL.md", content }];
|
|
3338
2996
|
builtInSkillCache.set(skillId, files);
|
|
3339
2997
|
return files;
|
|
@@ -3749,7 +3407,7 @@ function generateArtifacts(agent, refreshData, adapter) {
|
|
|
3749
3407
|
return provisionOutput.artifacts;
|
|
3750
3408
|
}
|
|
3751
3409
|
async function cleanupAgentFiles(codeName, agentDir) {
|
|
3752
|
-
if (
|
|
3410
|
+
if (existsSync(agentDir)) {
|
|
3753
3411
|
try {
|
|
3754
3412
|
rmSync(agentDir, { recursive: true, force: true });
|
|
3755
3413
|
log(`Removed provision directory for '${codeName}'`);
|
|
@@ -3808,8 +3466,8 @@ var caffeinateProc = null;
|
|
|
3808
3466
|
async function startCaffeinate() {
|
|
3809
3467
|
if (process.platform !== "darwin") return;
|
|
3810
3468
|
try {
|
|
3811
|
-
const { spawn
|
|
3812
|
-
caffeinateProc =
|
|
3469
|
+
const { spawn } = await import("child_process");
|
|
3470
|
+
caffeinateProc = spawn("caffeinate", ["-dims"], {
|
|
3813
3471
|
stdio: "ignore",
|
|
3814
3472
|
detached: false
|
|
3815
3473
|
});
|
|
@@ -3860,8 +3518,8 @@ async function killAllAgtTmuxSessions() {
|
|
|
3860
3518
|
["list-sessions", "-F", "#{session_name}"],
|
|
3861
3519
|
{ timeout: 2e3, stdin: "ignore" }
|
|
3862
3520
|
);
|
|
3863
|
-
const
|
|
3864
|
-
for (const session of
|
|
3521
|
+
const sessions = output.trim().split("\n").filter((s) => s.startsWith("agt-"));
|
|
3522
|
+
for (const session of sessions) {
|
|
3865
3523
|
try {
|
|
3866
3524
|
await execFilePromiseLong("tmux", ["kill-session", "-t", session], {
|
|
3867
3525
|
timeout: 2e3,
|
|
@@ -3871,8 +3529,8 @@ async function killAllAgtTmuxSessions() {
|
|
|
3871
3529
|
} catch {
|
|
3872
3530
|
}
|
|
3873
3531
|
}
|
|
3874
|
-
if (
|
|
3875
|
-
log(`Cleaned up ${
|
|
3532
|
+
if (sessions.length > 0) {
|
|
3533
|
+
log(`Cleaned up ${sessions.length} agt-* tmux session(s)`);
|
|
3876
3534
|
}
|
|
3877
3535
|
} catch {
|
|
3878
3536
|
}
|
|
@@ -3900,8 +3558,41 @@ async function stopPolling() {
|
|
|
3900
3558
|
}
|
|
3901
3559
|
function startManager(opts) {
|
|
3902
3560
|
config = opts;
|
|
3561
|
+
deployMcpAssets();
|
|
3903
3562
|
startPolling();
|
|
3904
3563
|
}
|
|
3564
|
+
function deployMcpAssets() {
|
|
3565
|
+
const targetDir = join(homedir(), ".augmented", "_mcp");
|
|
3566
|
+
mkdirSync(targetDir, { recursive: true });
|
|
3567
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
3568
|
+
let mcpSourceDir = "";
|
|
3569
|
+
let dir = moduleDir;
|
|
3570
|
+
for (let i = 0; i < 6; i++) {
|
|
3571
|
+
const candidate = join(dir, "mcp");
|
|
3572
|
+
if (existsSync(join(candidate, "index.js"))) {
|
|
3573
|
+
mcpSourceDir = candidate;
|
|
3574
|
+
break;
|
|
3575
|
+
}
|
|
3576
|
+
const parent = dirname(dir);
|
|
3577
|
+
if (parent === dir) break;
|
|
3578
|
+
dir = parent;
|
|
3579
|
+
}
|
|
3580
|
+
if (!mcpSourceDir) {
|
|
3581
|
+
log("[manager] MCP assets not found in CLI package \u2014 skipping deployment");
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
for (const file of ["index.js", "slack-channel.js"]) {
|
|
3585
|
+
const src = join(mcpSourceDir, file);
|
|
3586
|
+
const dst = join(targetDir, file);
|
|
3587
|
+
if (!existsSync(src)) continue;
|
|
3588
|
+
try {
|
|
3589
|
+
copyFileSync(src, dst);
|
|
3590
|
+
} catch (err) {
|
|
3591
|
+
log(`[manager] Failed to deploy ${file}: ${err.message}`);
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
log(`[manager] MCP assets deployed to ${targetDir}`);
|
|
3595
|
+
}
|
|
3905
3596
|
async function stopManager() {
|
|
3906
3597
|
await stopPolling();
|
|
3907
3598
|
}
|