@integrity-labs/agt-cli 0.9.0 → 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 +169 -511
- 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/package.json +1 -1
- 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,48 +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
|
-
import { homedir
|
|
28
|
-
import { fileURLToPath
|
|
29
|
-
|
|
30
|
-
// src/lib/mcp-sanitize.ts
|
|
31
|
-
import { readFileSync, writeFileSync } from "fs";
|
|
32
|
-
function sanitizeMcpJson(mcpConfigPath, apiHost) {
|
|
33
|
-
try {
|
|
34
|
-
const mcpRaw = JSON.parse(readFileSync(mcpConfigPath, "utf-8"));
|
|
35
|
-
const servers = mcpRaw.mcpServers;
|
|
36
|
-
if (!servers) return false;
|
|
37
|
-
let changed = false;
|
|
38
|
-
for (const [key, val] of Object.entries(servers)) {
|
|
39
|
-
if (typeof val?.url !== "string") continue;
|
|
40
|
-
if (val.url.startsWith("/")) {
|
|
41
|
-
if (apiHost) {
|
|
42
|
-
val.url = `${apiHost}${val.url}`;
|
|
43
|
-
changed = true;
|
|
44
|
-
} else {
|
|
45
|
-
delete servers[key];
|
|
46
|
-
changed = true;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const url = val.url;
|
|
51
|
-
delete val.url;
|
|
52
|
-
delete val.type;
|
|
53
|
-
val.command = "npx";
|
|
54
|
-
val.args = ["-y", "mcp-remote", url, "--allow-http"];
|
|
55
|
-
changed = true;
|
|
56
|
-
}
|
|
57
|
-
if (changed) writeFileSync(mcpConfigPath, JSON.stringify(mcpRaw, null, 2));
|
|
58
|
-
return changed;
|
|
59
|
-
} catch {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
36
|
+
import { join, dirname } from "path";
|
|
37
|
+
import { homedir } from "os";
|
|
38
|
+
import { fileURLToPath } from "url";
|
|
63
39
|
|
|
64
40
|
// src/lib/gateway-client.ts
|
|
65
41
|
import { EventEmitter } from "events";
|
|
@@ -330,345 +306,6 @@ var GatewayClientPool = class extends EventEmitter {
|
|
|
330
306
|
}
|
|
331
307
|
};
|
|
332
308
|
|
|
333
|
-
// src/lib/persistent-session.ts
|
|
334
|
-
import { spawn, execSync, execFileSync } from "child_process";
|
|
335
|
-
import { join, dirname } from "path";
|
|
336
|
-
import { homedir } from "os";
|
|
337
|
-
import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
338
|
-
import { fileURLToPath } from "url";
|
|
339
|
-
function collectMcpServerNames(mcpConfigPath, channelsConfigPath) {
|
|
340
|
-
const names = [];
|
|
341
|
-
for (const path of [mcpConfigPath, channelsConfigPath]) {
|
|
342
|
-
if (!existsSync(path)) continue;
|
|
343
|
-
try {
|
|
344
|
-
const data = JSON.parse(readFileSync2(path, "utf-8"));
|
|
345
|
-
const servers = data.mcpServers;
|
|
346
|
-
if (servers) names.push(...Object.keys(servers));
|
|
347
|
-
} catch {
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
return names;
|
|
351
|
-
}
|
|
352
|
-
var _acpxBin = null;
|
|
353
|
-
function getAcpxBin() {
|
|
354
|
-
if (_acpxBin) return _acpxBin;
|
|
355
|
-
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
356
|
-
let dir = moduleDir;
|
|
357
|
-
for (let i = 0; i < 6; i++) {
|
|
358
|
-
const candidate = join(dir, "node_modules", ".bin", "acpx");
|
|
359
|
-
if (existsSync(candidate)) {
|
|
360
|
-
_acpxBin = candidate;
|
|
361
|
-
return _acpxBin;
|
|
362
|
-
}
|
|
363
|
-
const parent = dirname(dir);
|
|
364
|
-
if (parent === dir) break;
|
|
365
|
-
dir = parent;
|
|
366
|
-
}
|
|
367
|
-
try {
|
|
368
|
-
execSync("which acpx", { stdio: "ignore" });
|
|
369
|
-
_acpxBin = "acpx";
|
|
370
|
-
return _acpxBin;
|
|
371
|
-
} catch {
|
|
372
|
-
return "";
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
var sessions = /* @__PURE__ */ new Map();
|
|
376
|
-
function startPersistentSession(config2) {
|
|
377
|
-
const existing = sessions.get(config2.codeName);
|
|
378
|
-
if (existing && existing.status === "running") {
|
|
379
|
-
return existing;
|
|
380
|
-
}
|
|
381
|
-
const restartCount = existing?.restartCount ?? 0;
|
|
382
|
-
if (existing?.status === "crashed" && existing.startedAt) {
|
|
383
|
-
const backoffMs = Math.min(5e3 * Math.pow(2, restartCount), 6e4);
|
|
384
|
-
if (Date.now() - existing.startedAt < backoffMs) {
|
|
385
|
-
return existing;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
const session = {
|
|
389
|
-
codeName: config2.codeName,
|
|
390
|
-
startedAt: null,
|
|
391
|
-
restartCount,
|
|
392
|
-
status: "starting"
|
|
393
|
-
};
|
|
394
|
-
sessions.set(config2.codeName, session);
|
|
395
|
-
spawnSession(config2, session);
|
|
396
|
-
return session;
|
|
397
|
-
}
|
|
398
|
-
function spawnSession(config2, session) {
|
|
399
|
-
const { codeName, projectDir, mcpConfigPath, claudeMdPath, channels, devChannels, apiHost, log: log2 } = config2;
|
|
400
|
-
const tmuxSession = `agt-${codeName}`;
|
|
401
|
-
log2(`[persistent-session] Starting tmux session '${tmuxSession}' for '${codeName}'`);
|
|
402
|
-
try {
|
|
403
|
-
sanitizeMcpJson(mcpConfigPath, apiHost);
|
|
404
|
-
writeAcpxConfig(config2);
|
|
405
|
-
try {
|
|
406
|
-
execSync(`tmux kill-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
|
|
407
|
-
} catch {
|
|
408
|
-
}
|
|
409
|
-
const args = [];
|
|
410
|
-
if (channels.length > 0) args.push("--channels", ...channels);
|
|
411
|
-
if (devChannels.length > 0) args.push("--dangerously-load-development-channels", ...devChannels);
|
|
412
|
-
args.push("--mcp-config", mcpConfigPath);
|
|
413
|
-
const channelsConfigPath = join(projectDir, ".mcp-channels.json");
|
|
414
|
-
if (existsSync(channelsConfigPath)) args.push("--mcp-config", channelsConfigPath);
|
|
415
|
-
if (existsSync(claudeMdPath)) args.push("--system-prompt-file", claudeMdPath);
|
|
416
|
-
args.push("--allow-dangerously-skip-permissions");
|
|
417
|
-
args.push("--dangerously-skip-permissions");
|
|
418
|
-
args.push("--strict-mcp-config");
|
|
419
|
-
args.push("--name", tmuxSession);
|
|
420
|
-
const mcpServerNames = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
|
|
421
|
-
const mcpPatterns = mcpServerNames.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
|
|
422
|
-
const allowedTools = [
|
|
423
|
-
...mcpPatterns,
|
|
424
|
-
"Bash",
|
|
425
|
-
"Read",
|
|
426
|
-
"Write",
|
|
427
|
-
"Edit",
|
|
428
|
-
"Grep",
|
|
429
|
-
"Glob",
|
|
430
|
-
"Agent",
|
|
431
|
-
"Skill"
|
|
432
|
-
].join(",");
|
|
433
|
-
args.push("--allowedTools", allowedTools);
|
|
434
|
-
let envPrefix = "";
|
|
435
|
-
const envIntegrationsPath = join(projectDir, ".env.integrations");
|
|
436
|
-
if (existsSync(envIntegrationsPath)) {
|
|
437
|
-
try {
|
|
438
|
-
const envContent = readFileSync2(envIntegrationsPath, "utf-8");
|
|
439
|
-
const envVars = envContent.split("\n").filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
|
|
440
|
-
const eqIdx = line.indexOf("=");
|
|
441
|
-
const key = line.slice(0, eqIdx);
|
|
442
|
-
const value = line.slice(eqIdx + 1);
|
|
443
|
-
return `${key}=${JSON.stringify(value)}`;
|
|
444
|
-
}).join(" ");
|
|
445
|
-
if (envVars) envPrefix = `${envVars} `;
|
|
446
|
-
} catch {
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
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.";
|
|
450
|
-
const claudeCmd = `${envPrefix}claude ${JSON.stringify(initPrompt)} ${args.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
|
|
451
|
-
const child = spawn("tmux", [
|
|
452
|
-
"new-session",
|
|
453
|
-
"-d",
|
|
454
|
-
"-s",
|
|
455
|
-
tmuxSession,
|
|
456
|
-
"-c",
|
|
457
|
-
projectDir,
|
|
458
|
-
claudeCmd
|
|
459
|
-
], {
|
|
460
|
-
cwd: projectDir,
|
|
461
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
462
|
-
env: process.env
|
|
463
|
-
});
|
|
464
|
-
child.on("close", (code) => {
|
|
465
|
-
if (code !== 0) {
|
|
466
|
-
log2(`[persistent-session] Failed to create tmux session for '${codeName}' (exit ${code})`);
|
|
467
|
-
session.status = "crashed";
|
|
468
|
-
session.startedAt = Date.now();
|
|
469
|
-
session.restartCount++;
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
log2(`[persistent-session] tmux session '${tmuxSession}' created for '${codeName}'`);
|
|
473
|
-
acceptDialogs(tmuxSession, codeName, log2).catch(() => {
|
|
474
|
-
});
|
|
475
|
-
});
|
|
476
|
-
child.on("error", (err) => {
|
|
477
|
-
log2(`[persistent-session] Failed to start tmux for '${codeName}': ${err.message}`);
|
|
478
|
-
session.status = "crashed";
|
|
479
|
-
session.startedAt = Date.now();
|
|
480
|
-
session.restartCount++;
|
|
481
|
-
});
|
|
482
|
-
session.startedAt = Date.now();
|
|
483
|
-
session.status = "running";
|
|
484
|
-
session.restartCount = 0;
|
|
485
|
-
} catch (err) {
|
|
486
|
-
log2(`[persistent-session] Failed to start session for '${codeName}': ${err.message}`);
|
|
487
|
-
session.status = "crashed";
|
|
488
|
-
session.startedAt = Date.now();
|
|
489
|
-
session.restartCount++;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
async function acceptDialogs(tmuxSession, codeName, log2) {
|
|
493
|
-
for (let i = 0; i < 15; i++) {
|
|
494
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
495
|
-
try {
|
|
496
|
-
const screen = execSync(`tmux capture-pane -t ${tmuxSession} -p 2>/dev/null`, { encoding: "utf-8" });
|
|
497
|
-
if (screen.includes("Yes, I trust this folder")) {
|
|
498
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
499
|
-
log2(`[persistent-session] Auto-accepted workspace trust for '${codeName}'`);
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
502
|
-
if (screen.includes("I am using this for local development")) {
|
|
503
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
504
|
-
log2(`[persistent-session] Auto-accepted dev channels for '${codeName}'`);
|
|
505
|
-
continue;
|
|
506
|
-
}
|
|
507
|
-
if (screen.includes("Enter to confirm") && screen.includes("MCP")) {
|
|
508
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
509
|
-
log2(`[persistent-session] Auto-accepted MCP servers for '${codeName}'`);
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
if (screen.includes("Yes, I accept") && screen.includes("Bypass Permissions")) {
|
|
513
|
-
execSync(`tmux send-keys -t ${tmuxSession} 2`, { stdio: "ignore" });
|
|
514
|
-
await new Promise((r) => setTimeout(r, 300));
|
|
515
|
-
execSync(`tmux send-keys -t ${tmuxSession} Enter`, { stdio: "ignore" });
|
|
516
|
-
log2(`[persistent-session] Auto-accepted bypass permissions for '${codeName}'`);
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
519
|
-
if (screen.includes("\u276F") && !screen.includes("Enter to confirm")) {
|
|
520
|
-
log2(`[persistent-session] Session ready for '${codeName}' \u2014 no more dialogs`);
|
|
521
|
-
break;
|
|
522
|
-
}
|
|
523
|
-
} catch {
|
|
524
|
-
break;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
async function injectMessage(codeName, type, content, meta, log2) {
|
|
529
|
-
const _log = log2 ?? ((_) => {
|
|
530
|
-
});
|
|
531
|
-
const session = sessions.get(codeName);
|
|
532
|
-
if (!session || session.status !== "running") {
|
|
533
|
-
_log(`[inject] SKIP '${codeName}' \u2014 session ${session ? `status=${session.status}` : "not found in Map"}`);
|
|
534
|
-
return false;
|
|
535
|
-
}
|
|
536
|
-
const prefix = meta?.task_name ? `[Task: ${meta.task_name}] ` : "";
|
|
537
|
-
const text = prefix + content;
|
|
538
|
-
const projectDir = getProjectDir2(codeName);
|
|
539
|
-
const acpx = getAcpxBin();
|
|
540
|
-
if (acpx) {
|
|
541
|
-
try {
|
|
542
|
-
const tmpDir = join(projectDir, ".claude");
|
|
543
|
-
mkdirSync(tmpDir, { recursive: true });
|
|
544
|
-
const tmpFile = join(tmpDir, ".agt-inject-prompt.txt");
|
|
545
|
-
writeFileSync2(tmpFile, text);
|
|
546
|
-
_log(`[inject] acpx exec (fire-and-forget): cwd=${projectDir}, file=${tmpFile}`);
|
|
547
|
-
const child = spawn(acpx, ["claude", "exec", "-f", tmpFile], {
|
|
548
|
-
cwd: projectDir,
|
|
549
|
-
stdio: "ignore",
|
|
550
|
-
detached: true
|
|
551
|
-
});
|
|
552
|
-
child.on("error", (err) => {
|
|
553
|
-
_log(`[inject] acpx spawn error for '${codeName}': ${err.message}`);
|
|
554
|
-
});
|
|
555
|
-
child.unref();
|
|
556
|
-
return true;
|
|
557
|
-
} catch (err) {
|
|
558
|
-
_log(`[inject] acpx exec failed for '${codeName}': ${err.message}`);
|
|
559
|
-
}
|
|
560
|
-
} else {
|
|
561
|
-
_log(`[inject] acpx binary not found \u2014 falling back to tmux send-keys`);
|
|
562
|
-
}
|
|
563
|
-
try {
|
|
564
|
-
execFileSync("tmux", ["send-keys", "-t", `agt-${codeName}`, text, "Enter"], { stdio: "ignore" });
|
|
565
|
-
_log(`[inject] tmux send-keys sent for '${codeName}' \u2014 unverified (returning false)`);
|
|
566
|
-
return false;
|
|
567
|
-
} catch (err) {
|
|
568
|
-
_log(`[inject] tmux send-keys failed for '${codeName}': ${err.message}`);
|
|
569
|
-
return false;
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
function stopPersistentSession(codeName, log2) {
|
|
573
|
-
const session = sessions.get(codeName);
|
|
574
|
-
if (!session) return;
|
|
575
|
-
log2(`[persistent-session] Stopping session for '${codeName}'`);
|
|
576
|
-
session.status = "stopped";
|
|
577
|
-
try {
|
|
578
|
-
execSync(`tmux kill-session -t agt-${codeName} 2>/dev/null`, { stdio: "ignore" });
|
|
579
|
-
} catch {
|
|
580
|
-
}
|
|
581
|
-
try {
|
|
582
|
-
const acpx = getAcpxBin();
|
|
583
|
-
if (acpx) {
|
|
584
|
-
execFileSync(acpx, ["claude", "sessions", "close", `agt-${codeName}`], {
|
|
585
|
-
cwd: getProjectDir2(codeName),
|
|
586
|
-
timeout: 5e3,
|
|
587
|
-
stdio: "ignore"
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
} catch {
|
|
591
|
-
}
|
|
592
|
-
sessions.delete(codeName);
|
|
593
|
-
}
|
|
594
|
-
function isSessionHealthy(codeName) {
|
|
595
|
-
const tmuxSession = `agt-${codeName}`;
|
|
596
|
-
try {
|
|
597
|
-
execSync(`tmux has-session -t ${tmuxSession} 2>/dev/null`, { stdio: "ignore" });
|
|
598
|
-
} catch {
|
|
599
|
-
const session2 = sessions.get(codeName);
|
|
600
|
-
if (session2 && session2.status === "running") {
|
|
601
|
-
session2.status = "crashed";
|
|
602
|
-
}
|
|
603
|
-
return false;
|
|
604
|
-
}
|
|
605
|
-
if (!sessions.has(codeName)) {
|
|
606
|
-
sessions.set(codeName, {
|
|
607
|
-
codeName,
|
|
608
|
-
startedAt: Date.now(),
|
|
609
|
-
restartCount: 0,
|
|
610
|
-
status: "running"
|
|
611
|
-
});
|
|
612
|
-
}
|
|
613
|
-
const session = sessions.get(codeName);
|
|
614
|
-
if (session.status !== "running") {
|
|
615
|
-
session.status = "running";
|
|
616
|
-
}
|
|
617
|
-
return true;
|
|
618
|
-
}
|
|
619
|
-
function resetRestartCount(codeName) {
|
|
620
|
-
const session = sessions.get(codeName);
|
|
621
|
-
if (session) session.restartCount = 0;
|
|
622
|
-
}
|
|
623
|
-
async function stopAllSessionsAndWait(log2, opts) {
|
|
624
|
-
const codeNames = [...sessions.keys()];
|
|
625
|
-
if (codeNames.length === 0) return;
|
|
626
|
-
for (const codeName of codeNames) {
|
|
627
|
-
stopPersistentSession(codeName, log2);
|
|
628
|
-
}
|
|
629
|
-
await new Promise((resolve) => setTimeout(resolve, Math.min(opts.timeoutMs, 2e3)));
|
|
630
|
-
}
|
|
631
|
-
function getProjectDir2(codeName) {
|
|
632
|
-
return join(homedir(), ".augmented", codeName, "project");
|
|
633
|
-
}
|
|
634
|
-
function writeAcpxConfig(config2) {
|
|
635
|
-
const { projectDir, mcpConfigPath, claudeMdPath, channels, devChannels } = config2;
|
|
636
|
-
const claudeArgs = [];
|
|
637
|
-
if (channels.length > 0) claudeArgs.push("--channels", ...channels);
|
|
638
|
-
if (devChannels.length > 0) claudeArgs.push("--dangerously-load-development-channels", ...devChannels);
|
|
639
|
-
claudeArgs.push("--mcp-config", mcpConfigPath);
|
|
640
|
-
const channelsConfigPath = join(projectDir, ".mcp-channels.json");
|
|
641
|
-
if (existsSync(channelsConfigPath)) claudeArgs.push("--mcp-config", channelsConfigPath);
|
|
642
|
-
if (existsSync(claudeMdPath)) claudeArgs.push("--system-prompt-file", claudeMdPath);
|
|
643
|
-
claudeArgs.push("--allow-dangerously-skip-permissions");
|
|
644
|
-
claudeArgs.push("--dangerously-skip-permissions");
|
|
645
|
-
claudeArgs.push("--strict-mcp-config");
|
|
646
|
-
const mcpServerNames2 = collectMcpServerNames(mcpConfigPath, channelsConfigPath);
|
|
647
|
-
const mcpPatterns2 = mcpServerNames2.map((name) => `mcp__${name.replace(/-/g, "_")}__*`);
|
|
648
|
-
const allowedTools2 = [...mcpPatterns2, "Bash", "Read", "Write", "Edit", "Grep", "Glob", "Agent", "Skill"].join(",");
|
|
649
|
-
claudeArgs.push("--allowedTools", allowedTools2);
|
|
650
|
-
const acpCmd = `npx -y @agentclientprotocol/claude-agent-acp ${claudeArgs.map((a) => a.includes(" ") || a.includes("*") ? JSON.stringify(a) : a).join(" ")}`;
|
|
651
|
-
const envIntegrationsPath = join(projectDir, ".env.integrations");
|
|
652
|
-
const wrapperPath = join(projectDir, ".claude", "acpx-agent.sh");
|
|
653
|
-
const wrapperLines = ["#!/usr/bin/env bash"];
|
|
654
|
-
if (existsSync(envIntegrationsPath)) {
|
|
655
|
-
wrapperLines.push(`set -a`, `source ${JSON.stringify(envIntegrationsPath)}`, `set +a`);
|
|
656
|
-
}
|
|
657
|
-
wrapperLines.push(`exec ${acpCmd}`);
|
|
658
|
-
mkdirSync(join(projectDir, ".claude"), { recursive: true });
|
|
659
|
-
writeFileSync2(wrapperPath, wrapperLines.join("\n") + "\n", { mode: 493 });
|
|
660
|
-
const acpxConfig = {
|
|
661
|
-
defaultAgent: "claude",
|
|
662
|
-
defaultPermissions: "approve-all",
|
|
663
|
-
agents: {
|
|
664
|
-
claude: {
|
|
665
|
-
command: wrapperPath
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
writeFileSync2(join(projectDir, ".acpxrc.json"), JSON.stringify(acpxConfig, null, 2));
|
|
670
|
-
}
|
|
671
|
-
|
|
672
309
|
// src/lib/realtime-chat.ts
|
|
673
310
|
import { createClient } from "@supabase/supabase-js";
|
|
674
311
|
var client = null;
|
|
@@ -911,8 +548,8 @@ function stopRealtimeChat() {
|
|
|
911
548
|
var GATEWAY_PORT_BASE = 18800;
|
|
912
549
|
var GATEWAY_PORT_STEP = 10;
|
|
913
550
|
var GATEWAY_PORT_MAX = 18899;
|
|
914
|
-
var AUGMENTED_DIR =
|
|
915
|
-
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");
|
|
916
553
|
var config = null;
|
|
917
554
|
var running = false;
|
|
918
555
|
var pollTimer = null;
|
|
@@ -989,24 +626,24 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
989
626
|
if (frameworkId !== "claude-code") return;
|
|
990
627
|
if (frameworkBinaryChecked.has(frameworkId)) return;
|
|
991
628
|
frameworkBinaryChecked.add(frameworkId);
|
|
992
|
-
const { execFileSync
|
|
629
|
+
const { execFileSync } = await import("child_process");
|
|
993
630
|
let brewPath;
|
|
994
631
|
try {
|
|
995
|
-
brewPath =
|
|
632
|
+
brewPath = execFileSync("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
996
633
|
} catch {
|
|
997
634
|
log("Homebrew not found \u2014 cannot auto-install/upgrade Claude Code. Install manually: https://claude.ai/download");
|
|
998
635
|
return;
|
|
999
636
|
}
|
|
1000
637
|
let claudeExists = false;
|
|
1001
638
|
try {
|
|
1002
|
-
|
|
639
|
+
execFileSync("which", ["claude"], { timeout: 5e3 });
|
|
1003
640
|
claudeExists = true;
|
|
1004
641
|
} catch {
|
|
1005
642
|
}
|
|
1006
643
|
if (!claudeExists) {
|
|
1007
644
|
log("Claude Code binary not found \u2014 installing via Homebrew...");
|
|
1008
645
|
try {
|
|
1009
|
-
|
|
646
|
+
execFileSync(brewPath, ["install", "--cask", "claude-code"], {
|
|
1010
647
|
timeout: 12e4,
|
|
1011
648
|
stdio: "pipe"
|
|
1012
649
|
});
|
|
@@ -1015,7 +652,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1015
652
|
return;
|
|
1016
653
|
}
|
|
1017
654
|
try {
|
|
1018
|
-
|
|
655
|
+
execFileSync("which", ["claude"], { timeout: 5e3 });
|
|
1019
656
|
log("Claude Code installed successfully");
|
|
1020
657
|
} catch {
|
|
1021
658
|
log("Claude Code install completed but binary not found on PATH \u2014 you may need to restart your terminal");
|
|
@@ -1023,7 +660,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1023
660
|
} else {
|
|
1024
661
|
log("Checking for Claude Code updates...");
|
|
1025
662
|
try {
|
|
1026
|
-
const output =
|
|
663
|
+
const output = execFileSync(brewPath, ["upgrade", "--cask", "claude-code"], {
|
|
1027
664
|
timeout: 12e4,
|
|
1028
665
|
stdio: "pipe"
|
|
1029
666
|
}).toString();
|
|
@@ -1041,7 +678,7 @@ async function ensureFrameworkBinary(frameworkId) {
|
|
|
1041
678
|
}
|
|
1042
679
|
}
|
|
1043
680
|
}
|
|
1044
|
-
agentRuntimeAuthenticated = checkClaudeAuth(
|
|
681
|
+
agentRuntimeAuthenticated = checkClaudeAuth(execFileSync);
|
|
1045
682
|
}
|
|
1046
683
|
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1047
684
|
var selfUpdateChecked = false;
|
|
@@ -1053,23 +690,23 @@ async function checkAndUpdateCli() {
|
|
|
1053
690
|
const isDevMode = cliPath.includes("/src/") || cliPath.includes("tsx");
|
|
1054
691
|
if (isDevMode) return;
|
|
1055
692
|
if (!isHomebrew && !cliPath.includes("node_modules")) return;
|
|
1056
|
-
const { homedir:
|
|
693
|
+
const { homedir: homedir2 } = await import("os");
|
|
1057
694
|
const { readFileSync: readF, writeFileSync: writeF } = await import("fs");
|
|
1058
|
-
const markerPath =
|
|
695
|
+
const markerPath = join(homedir2(), ".augmented", ".last-update-check");
|
|
1059
696
|
try {
|
|
1060
697
|
const lastCheck = parseInt(readF(markerPath, "utf-8").trim(), 10);
|
|
1061
698
|
if (Date.now() - lastCheck < UPDATE_CHECK_INTERVAL_MS) return;
|
|
1062
699
|
} catch {
|
|
1063
700
|
}
|
|
1064
|
-
const { execFileSync
|
|
701
|
+
const { execFileSync } = await import("child_process");
|
|
1065
702
|
let brewPath;
|
|
1066
703
|
try {
|
|
1067
|
-
brewPath =
|
|
704
|
+
brewPath = execFileSync("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
1068
705
|
} catch {
|
|
1069
706
|
return;
|
|
1070
707
|
}
|
|
1071
708
|
try {
|
|
1072
|
-
const outdated =
|
|
709
|
+
const outdated = execFileSync(brewPath, ["outdated", "--json=v2"], {
|
|
1073
710
|
timeout: 3e4,
|
|
1074
711
|
encoding: "utf-8"
|
|
1075
712
|
});
|
|
@@ -1080,7 +717,7 @@ async function checkAndUpdateCli() {
|
|
|
1080
717
|
const latest = agtOutdated.current_version ?? "unknown";
|
|
1081
718
|
log(`[self-update] agt CLI update available: ${installed} \u2192 ${latest}. Upgrading...`);
|
|
1082
719
|
try {
|
|
1083
|
-
|
|
720
|
+
execFileSync(brewPath, ["upgrade", "integrity-labs/tap/agt"], {
|
|
1084
721
|
timeout: 12e4,
|
|
1085
722
|
stdio: "pipe"
|
|
1086
723
|
});
|
|
@@ -1096,9 +733,9 @@ async function checkAndUpdateCli() {
|
|
|
1096
733
|
} catch {
|
|
1097
734
|
}
|
|
1098
735
|
}
|
|
1099
|
-
function checkClaudeAuth(
|
|
736
|
+
function checkClaudeAuth(execFileSync) {
|
|
1100
737
|
try {
|
|
1101
|
-
const authOutput =
|
|
738
|
+
const authOutput = execFileSync("claude", ["auth", "status"], { timeout: 1e4, stdio: "pipe" }).toString().trim();
|
|
1102
739
|
let loggedIn = null;
|
|
1103
740
|
try {
|
|
1104
741
|
const parsed = JSON.parse(authOutput);
|
|
@@ -1123,14 +760,14 @@ function checkClaudeAuth(execFileSync2) {
|
|
|
1123
760
|
}
|
|
1124
761
|
function loadGatewayPorts() {
|
|
1125
762
|
try {
|
|
1126
|
-
return JSON.parse(
|
|
763
|
+
return JSON.parse(readFileSync(GATEWAY_PORTS_FILE, "utf-8"));
|
|
1127
764
|
} catch {
|
|
1128
765
|
return {};
|
|
1129
766
|
}
|
|
1130
767
|
}
|
|
1131
768
|
function saveGatewayPorts(ports) {
|
|
1132
|
-
|
|
1133
|
-
|
|
769
|
+
mkdirSync(AUGMENTED_DIR, { recursive: true });
|
|
770
|
+
writeFileSync(GATEWAY_PORTS_FILE, JSON.stringify(ports, null, 2));
|
|
1134
771
|
}
|
|
1135
772
|
function allocatePort(codeName) {
|
|
1136
773
|
const ports = loadGatewayPorts();
|
|
@@ -1152,11 +789,11 @@ function freePort(codeName) {
|
|
|
1152
789
|
saveGatewayPorts(ports);
|
|
1153
790
|
}
|
|
1154
791
|
}
|
|
1155
|
-
var STATE_FILE =
|
|
792
|
+
var STATE_FILE = join(process.env["HOME"] ?? "/tmp", ".augmented", "manager-state.json");
|
|
1156
793
|
function send(msg) {
|
|
1157
794
|
if (msg.type === "state-update") {
|
|
1158
795
|
try {
|
|
1159
|
-
|
|
796
|
+
writeFileSync(STATE_FILE, JSON.stringify(msg.state, null, 2));
|
|
1160
797
|
} catch {
|
|
1161
798
|
}
|
|
1162
799
|
}
|
|
@@ -1178,7 +815,7 @@ function sha256(content) {
|
|
|
1178
815
|
}
|
|
1179
816
|
function hashFile(filePath) {
|
|
1180
817
|
try {
|
|
1181
|
-
const content =
|
|
818
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1182
819
|
return sha256(content);
|
|
1183
820
|
} catch {
|
|
1184
821
|
return null;
|
|
@@ -1186,10 +823,10 @@ function hashFile(filePath) {
|
|
|
1186
823
|
}
|
|
1187
824
|
async function migrateToProfiles() {
|
|
1188
825
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1189
|
-
const sharedConfigPath =
|
|
826
|
+
const sharedConfigPath = join(homeDir, ".openclaw", "openclaw.json");
|
|
1190
827
|
let sharedConfig;
|
|
1191
828
|
try {
|
|
1192
|
-
sharedConfig = JSON.parse(
|
|
829
|
+
sharedConfig = JSON.parse(readFileSync(sharedConfigPath, "utf-8"));
|
|
1193
830
|
} catch {
|
|
1194
831
|
return;
|
|
1195
832
|
}
|
|
@@ -1202,19 +839,19 @@ async function migrateToProfiles() {
|
|
|
1202
839
|
const codeName = agentEntry["id"];
|
|
1203
840
|
if (!codeName) continue;
|
|
1204
841
|
if (codeName === "main") continue;
|
|
1205
|
-
const profileDir =
|
|
1206
|
-
if (
|
|
842
|
+
const profileDir = join(homeDir, `.openclaw-${codeName}`);
|
|
843
|
+
if (existsSync(join(profileDir, "openclaw.json"))) continue;
|
|
1207
844
|
log(`Migrating agent '${codeName}' to per-agent profile`);
|
|
1208
845
|
if (adapter.seedProfileConfig) {
|
|
1209
846
|
adapter.seedProfileConfig(codeName);
|
|
1210
847
|
}
|
|
1211
|
-
const sharedAuthDir =
|
|
1212
|
-
const profileAuthDir =
|
|
1213
|
-
const authFile =
|
|
1214
|
-
if (
|
|
1215
|
-
|
|
1216
|
-
const authContent =
|
|
1217
|
-
|
|
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);
|
|
1218
855
|
}
|
|
1219
856
|
allocatePort(codeName);
|
|
1220
857
|
migrated++;
|
|
@@ -1252,7 +889,7 @@ function readGatewayToken(codeName) {
|
|
|
1252
889
|
}
|
|
1253
890
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1254
891
|
try {
|
|
1255
|
-
const cfg = JSON.parse(
|
|
892
|
+
const cfg = JSON.parse(readFileSync(join(homeDir, `.openclaw-${codeName}`, "openclaw.json"), "utf-8"));
|
|
1256
893
|
return cfg?.gateway?.auth?.token;
|
|
1257
894
|
} catch {
|
|
1258
895
|
return void 0;
|
|
@@ -1261,10 +898,10 @@ function readGatewayToken(codeName) {
|
|
|
1261
898
|
var GATEWAY_HUNG_TIMEOUT_MS = 5 * 6e4;
|
|
1262
899
|
function isGatewayHung(codeName) {
|
|
1263
900
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1264
|
-
const jobsPath =
|
|
1265
|
-
if (!
|
|
901
|
+
const jobsPath = join(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
902
|
+
if (!existsSync(jobsPath)) return false;
|
|
1266
903
|
try {
|
|
1267
|
-
const data = JSON.parse(
|
|
904
|
+
const data = JSON.parse(readFileSync(jobsPath, "utf-8"));
|
|
1268
905
|
const jobs = data.jobs ?? data;
|
|
1269
906
|
if (!Array.isArray(jobs)) return false;
|
|
1270
907
|
const now = Date.now();
|
|
@@ -1297,19 +934,19 @@ async function ensureGatewayRunning(codeName, adapter) {
|
|
|
1297
934
|
}
|
|
1298
935
|
await new Promise((r) => setTimeout(r, 2e3));
|
|
1299
936
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1300
|
-
const cronJobsPath =
|
|
937
|
+
const cronJobsPath = join(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
1301
938
|
clearStaleCronRunState(cronJobsPath);
|
|
1302
939
|
} else {
|
|
1303
940
|
if (status.port) {
|
|
1304
941
|
try {
|
|
1305
942
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1306
|
-
const configPath =
|
|
1307
|
-
if (
|
|
1308
|
-
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"));
|
|
1309
946
|
if (cfg.gateway?.port !== status.port) {
|
|
1310
947
|
if (!cfg.gateway) cfg.gateway = {};
|
|
1311
948
|
cfg.gateway.port = status.port;
|
|
1312
|
-
|
|
949
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
1313
950
|
}
|
|
1314
951
|
}
|
|
1315
952
|
} catch {
|
|
@@ -1329,12 +966,12 @@ async function ensureGatewayRunning(codeName, adapter) {
|
|
|
1329
966
|
gatewaysStartedThisCycle.add(codeName);
|
|
1330
967
|
try {
|
|
1331
968
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
1332
|
-
const configPath =
|
|
1333
|
-
if (
|
|
1334
|
-
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"));
|
|
1335
972
|
if (!cfg.gateway) cfg.gateway = {};
|
|
1336
973
|
cfg.gateway.port = port;
|
|
1337
|
-
|
|
974
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2));
|
|
1338
975
|
}
|
|
1339
976
|
} catch {
|
|
1340
977
|
}
|
|
@@ -1438,11 +1075,32 @@ async function pollCycle() {
|
|
|
1438
1075
|
}
|
|
1439
1076
|
try {
|
|
1440
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
|
+
}
|
|
1441
1097
|
await api.post("/host/heartbeat", {
|
|
1442
1098
|
host_id: hostId,
|
|
1443
1099
|
framework_version: cachedFrameworkVersion ?? void 0,
|
|
1444
1100
|
host_security: detectHostSecurity() ?? void 0,
|
|
1445
|
-
agent_runtime_authenticated: agentRuntimeAuthenticated
|
|
1101
|
+
agent_runtime_authenticated: agentRuntimeAuthenticated,
|
|
1102
|
+
agent_diagnostics: agentDiagnostics,
|
|
1103
|
+
hostname: tailscaleHostname
|
|
1446
1104
|
});
|
|
1447
1105
|
} catch (err) {
|
|
1448
1106
|
log(`Heartbeat failed: ${err.message}`);
|
|
@@ -1501,7 +1159,7 @@ async function pollCycle() {
|
|
|
1501
1159
|
const adapter = resolveAgentFramework(prev.codeName);
|
|
1502
1160
|
await stopGatewayIfRunning(prev.codeName, adapter);
|
|
1503
1161
|
freePort(prev.codeName);
|
|
1504
|
-
const agentDir =
|
|
1162
|
+
const agentDir = join(config.configDir, prev.codeName, "provision");
|
|
1505
1163
|
await cleanupAgentFiles(prev.codeName, agentDir);
|
|
1506
1164
|
clearAgentCaches(prev.agentId, prev.codeName);
|
|
1507
1165
|
}
|
|
@@ -1568,7 +1226,7 @@ async function processAgent(agent, agentStates) {
|
|
|
1568
1226
|
agentFrameworkCache.set(agent.code_name, agent.framework);
|
|
1569
1227
|
}
|
|
1570
1228
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1571
|
-
const agentDir =
|
|
1229
|
+
const agentDir = join(config.configDir, agent.code_name, "provision");
|
|
1572
1230
|
const adapter = resolveAgentFramework(agent.code_name);
|
|
1573
1231
|
if (agent.status === "draft" || agent.status === "paused") {
|
|
1574
1232
|
log(`Agent '${agent.code_name}' is ${agent.status}, skipping provisioning`);
|
|
@@ -1719,9 +1377,9 @@ async function processAgent(agent, agentStates) {
|
|
|
1719
1377
|
try {
|
|
1720
1378
|
const artifacts = generateArtifacts(agent, refreshData, frameworkAdapter);
|
|
1721
1379
|
const changedFiles = [];
|
|
1722
|
-
|
|
1380
|
+
mkdirSync(agentDir, { recursive: true });
|
|
1723
1381
|
for (const artifact of artifacts) {
|
|
1724
|
-
const filePath =
|
|
1382
|
+
const filePath = join(agentDir, artifact.relativePath);
|
|
1725
1383
|
const newHash = sha256(artifact.content);
|
|
1726
1384
|
const existingHash = hashFile(filePath);
|
|
1727
1385
|
if (newHash !== existingHash) {
|
|
@@ -1729,19 +1387,19 @@ async function processAgent(agent, agentStates) {
|
|
|
1729
1387
|
}
|
|
1730
1388
|
}
|
|
1731
1389
|
if (changedFiles.length > 0) {
|
|
1732
|
-
const isFirst = !
|
|
1390
|
+
const isFirst = !existsSync(join(agentDir, "CHARTER.md"));
|
|
1733
1391
|
const verb = isFirst ? "Provisioning" : "Updating";
|
|
1734
1392
|
const fileNames = changedFiles.map((f) => f.relativePath).join(", ");
|
|
1735
1393
|
log(`${verb} '${agent.code_name}': ${fileNames}`);
|
|
1736
1394
|
for (const file of changedFiles) {
|
|
1737
|
-
|
|
1395
|
+
writeFileSync(join(agentDir, file.relativePath), file.content);
|
|
1738
1396
|
}
|
|
1739
1397
|
lastProvisionAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1740
1398
|
knownVersions.set(agent.agent_id, { charterVersion, toolsVersion });
|
|
1741
1399
|
const trackedFiles = frameworkAdapter.driftTrackedFiles();
|
|
1742
1400
|
const hashes = /* @__PURE__ */ new Map();
|
|
1743
1401
|
for (const file of trackedFiles) {
|
|
1744
|
-
const h = hashFile(
|
|
1402
|
+
const h = hashFile(join(agentDir, file));
|
|
1745
1403
|
if (h) hashes.set(file, h);
|
|
1746
1404
|
}
|
|
1747
1405
|
writtenHashes.set(agent.agent_id, hashes);
|
|
@@ -1785,10 +1443,10 @@ async function processAgent(agent, agentStates) {
|
|
|
1785
1443
|
}
|
|
1786
1444
|
let lastDriftCheckAt = now;
|
|
1787
1445
|
const written = writtenHashes.get(agent.agent_id);
|
|
1788
|
-
if (written &&
|
|
1446
|
+
if (written && existsSync(agentDir)) {
|
|
1789
1447
|
const driftedFiles = [];
|
|
1790
1448
|
for (const [file, expectedHash] of written) {
|
|
1791
|
-
const localHash = hashFile(
|
|
1449
|
+
const localHash = hashFile(join(agentDir, file));
|
|
1792
1450
|
if (localHash && localHash !== expectedHash) {
|
|
1793
1451
|
driftedFiles.push(file);
|
|
1794
1452
|
}
|
|
@@ -1799,7 +1457,7 @@ async function processAgent(agent, agentStates) {
|
|
|
1799
1457
|
try {
|
|
1800
1458
|
const localHashes = {};
|
|
1801
1459
|
for (const file of driftedFiles) {
|
|
1802
|
-
localHashes[file] = hashFile(
|
|
1460
|
+
localHashes[file] = hashFile(join(agentDir, file));
|
|
1803
1461
|
}
|
|
1804
1462
|
await api.post("/host/drift", {
|
|
1805
1463
|
agent_id: agent.agent_id,
|
|
@@ -1952,11 +1610,11 @@ async function processAgent(agent, agentStates) {
|
|
|
1952
1610
|
}
|
|
1953
1611
|
if (frameworkAdapter.removeMcpServer) {
|
|
1954
1612
|
try {
|
|
1955
|
-
const { readFileSync:
|
|
1956
|
-
const { join:
|
|
1957
|
-
const { homedir:
|
|
1958
|
-
const mcpPath =
|
|
1959
|
-
const mcpConfig = JSON.parse(
|
|
1613
|
+
const { readFileSync: readFileSync2 } = await import("fs");
|
|
1614
|
+
const { join: join2 } = await import("path");
|
|
1615
|
+
const { homedir: homedir2 } = await import("os");
|
|
1616
|
+
const mcpPath = join2(homedir2(), ".augmented", "agents", agent.code_name, "provision", ".mcp.json");
|
|
1617
|
+
const mcpConfig = JSON.parse(readFileSync2(mcpPath, "utf-8"));
|
|
1960
1618
|
if (mcpConfig.mcpServers) {
|
|
1961
1619
|
const managedPrefixes = ["composio-", "one-", "nango-", "paragon-"];
|
|
1962
1620
|
for (const key of Object.keys(mcpConfig.mcpServers)) {
|
|
@@ -2009,18 +1667,18 @@ async function processAgent(agent, agentStates) {
|
|
|
2009
1667
|
"@tobilu/qmd"
|
|
2010
1668
|
]);
|
|
2011
1669
|
if (intHash !== prevIntHash) {
|
|
2012
|
-
const { execFileSync
|
|
1670
|
+
const { execFileSync } = await import("child_process");
|
|
2013
1671
|
for (const tool of capData.cliTools) {
|
|
2014
1672
|
if (!ALLOWED_CLI_PACKAGES.has(tool.package)) {
|
|
2015
1673
|
log(`Skipping CLI tool '${tool.package}' for '${agent.code_name}' \u2014 not on the allowed packages list`);
|
|
2016
1674
|
continue;
|
|
2017
1675
|
}
|
|
2018
1676
|
try {
|
|
2019
|
-
|
|
1677
|
+
execFileSync("which", [tool.binary], { stdio: "ignore" });
|
|
2020
1678
|
} catch {
|
|
2021
1679
|
log(`Installing CLI tool '${tool.package}' for '${agent.code_name}'...`);
|
|
2022
1680
|
try {
|
|
2023
|
-
|
|
1681
|
+
execFileSync("npm", ["install", "-g", tool.package], { stdio: "ignore", timeout: 6e4 });
|
|
2024
1682
|
log(`CLI tool '${tool.binary}' installed successfully`);
|
|
2025
1683
|
} catch (installErr) {
|
|
2026
1684
|
log(`Failed to install CLI tool '${tool.package}': ${installErr.message}`);
|
|
@@ -2028,8 +1686,8 @@ async function processAgent(agent, agentStates) {
|
|
|
2028
1686
|
}
|
|
2029
1687
|
if (tool.binary === "qmd") {
|
|
2030
1688
|
try {
|
|
2031
|
-
const agentDir2 =
|
|
2032
|
-
|
|
1689
|
+
const agentDir2 = join(process.env["HOME"] ?? "/tmp", ".augmented", agent.code_name);
|
|
1690
|
+
execFileSync("qmd", ["collection", "add", agent.code_name, "project"], {
|
|
2033
1691
|
stdio: "ignore",
|
|
2034
1692
|
timeout: 3e4,
|
|
2035
1693
|
cwd: agentDir2
|
|
@@ -2089,8 +1747,8 @@ async function processAgent(agent, agentStates) {
|
|
|
2089
1747
|
if (agent.status === "active") {
|
|
2090
1748
|
if (frameworkAdapter.installPlugin) {
|
|
2091
1749
|
try {
|
|
2092
|
-
const pluginPath =
|
|
2093
|
-
if (
|
|
1750
|
+
const pluginPath = join(process.cwd(), "packages", "openclaw-plugin-augmented", "src", "index.ts");
|
|
1751
|
+
if (existsSync(pluginPath)) {
|
|
2094
1752
|
frameworkAdapter.installPlugin(agent.code_name, "augmented", pluginPath, {
|
|
2095
1753
|
agtHost: requireHost(),
|
|
2096
1754
|
agtApiKey: getApiKey() ?? void 0,
|
|
@@ -2205,10 +1863,10 @@ async function processAgent(agent, agentStates) {
|
|
|
2205
1863
|
lastWorkTriggerAt.set(agent.code_name, triggerTs);
|
|
2206
1864
|
if (agentFw === "openclaw" && gatewayRunning && gatewayPort) {
|
|
2207
1865
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
2208
|
-
const jobsPath =
|
|
2209
|
-
if (
|
|
1866
|
+
const jobsPath = join(homeDir, `.openclaw-${agent.code_name}`, "cron", "jobs.json");
|
|
1867
|
+
if (existsSync(jobsPath)) {
|
|
2210
1868
|
try {
|
|
2211
|
-
const jobsData = JSON.parse(
|
|
1869
|
+
const jobsData = JSON.parse(readFileSync(jobsPath, "utf-8"));
|
|
2212
1870
|
const kanbanJob = (jobsData.jobs ?? []).find(
|
|
2213
1871
|
(j) => typeof j.name === "string" && j.name.includes("kanban-work")
|
|
2214
1872
|
);
|
|
@@ -2353,19 +2011,19 @@ function cleanupStaleSessions(codeName) {
|
|
|
2353
2011
|
lastCleanupAt.set(codeName, Date.now());
|
|
2354
2012
|
const homeDir = process.env["HOME"] ?? "/tmp";
|
|
2355
2013
|
for (const agentDir of ["main", codeName]) {
|
|
2356
|
-
const sessionsDir =
|
|
2014
|
+
const sessionsDir = join(homeDir, `.openclaw-${codeName}`, "agents", agentDir, "sessions");
|
|
2357
2015
|
cleanupCronSessions(sessionsDir, CRON_SESSION_KEEP_COUNT);
|
|
2358
2016
|
}
|
|
2359
|
-
const cronRunsDir =
|
|
2017
|
+
const cronRunsDir = join(homeDir, `.openclaw-${codeName}`, "cron", "runs");
|
|
2360
2018
|
cleanupOldFiles(cronRunsDir, CRON_RUN_RETENTION_DAYS, ".jsonl");
|
|
2361
|
-
const cronJobsPath =
|
|
2019
|
+
const cronJobsPath = join(homeDir, `.openclaw-${codeName}`, "cron", "jobs.json");
|
|
2362
2020
|
clearStaleCronRunState(cronJobsPath);
|
|
2363
2021
|
}
|
|
2364
2022
|
function cleanupCronSessions(sessionsDir, keepCount) {
|
|
2365
|
-
const indexPath =
|
|
2366
|
-
if (!
|
|
2023
|
+
const indexPath = join(sessionsDir, "sessions.json");
|
|
2024
|
+
if (!existsSync(indexPath)) return;
|
|
2367
2025
|
try {
|
|
2368
|
-
const raw =
|
|
2026
|
+
const raw = readFileSync(indexPath, "utf-8");
|
|
2369
2027
|
const index = JSON.parse(raw);
|
|
2370
2028
|
const cronRunKeys = Object.keys(index).filter((k) => k.includes(":cron:") && k.includes(":run:")).map((k) => ({
|
|
2371
2029
|
key: k,
|
|
@@ -2378,9 +2036,9 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
2378
2036
|
for (const entry of toDelete) {
|
|
2379
2037
|
delete index[entry.key];
|
|
2380
2038
|
if (entry.sessionId) {
|
|
2381
|
-
const sessionFile =
|
|
2039
|
+
const sessionFile = join(sessionsDir, `${entry.sessionId}.jsonl`);
|
|
2382
2040
|
try {
|
|
2383
|
-
if (
|
|
2041
|
+
if (existsSync(sessionFile)) {
|
|
2384
2042
|
unlinkSync(sessionFile);
|
|
2385
2043
|
deletedFiles++;
|
|
2386
2044
|
}
|
|
@@ -2400,8 +2058,8 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
2400
2058
|
delete index[parentKey];
|
|
2401
2059
|
if (parentSessionId) {
|
|
2402
2060
|
try {
|
|
2403
|
-
const f =
|
|
2404
|
-
if (
|
|
2061
|
+
const f = join(sessionsDir, `${parentSessionId}.jsonl`);
|
|
2062
|
+
if (existsSync(f)) {
|
|
2405
2063
|
unlinkSync(f);
|
|
2406
2064
|
deletedFiles++;
|
|
2407
2065
|
}
|
|
@@ -2410,7 +2068,7 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
2410
2068
|
}
|
|
2411
2069
|
}
|
|
2412
2070
|
}
|
|
2413
|
-
|
|
2071
|
+
writeFileSync(indexPath, JSON.stringify(index));
|
|
2414
2072
|
if (toDelete.length > 0) {
|
|
2415
2073
|
log(`Cleaned ${toDelete.length} cron session(s) and ${deletedFiles} file(s) from ${sessionsDir}`);
|
|
2416
2074
|
}
|
|
@@ -2419,9 +2077,9 @@ function cleanupCronSessions(sessionsDir, keepCount) {
|
|
|
2419
2077
|
}
|
|
2420
2078
|
var STALE_RUN_TIMEOUT_MS = 5 * 6e4;
|
|
2421
2079
|
function clearStaleCronRunState(jobsPath) {
|
|
2422
|
-
if (!
|
|
2080
|
+
if (!existsSync(jobsPath)) return;
|
|
2423
2081
|
try {
|
|
2424
|
-
const raw =
|
|
2082
|
+
const raw = readFileSync(jobsPath, "utf-8");
|
|
2425
2083
|
const data = JSON.parse(raw);
|
|
2426
2084
|
const jobs = data.jobs ?? data;
|
|
2427
2085
|
if (!Array.isArray(jobs)) return;
|
|
@@ -2446,19 +2104,19 @@ function clearStaleCronRunState(jobsPath) {
|
|
|
2446
2104
|
}
|
|
2447
2105
|
}
|
|
2448
2106
|
if (changed) {
|
|
2449
|
-
|
|
2107
|
+
writeFileSync(jobsPath, JSON.stringify(data, null, 2));
|
|
2450
2108
|
}
|
|
2451
2109
|
} catch {
|
|
2452
2110
|
}
|
|
2453
2111
|
}
|
|
2454
2112
|
function cleanupOldFiles(dir, maxAgeDays, ext) {
|
|
2455
|
-
if (!
|
|
2113
|
+
if (!existsSync(dir)) return;
|
|
2456
2114
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
2457
2115
|
let removed = 0;
|
|
2458
2116
|
try {
|
|
2459
2117
|
for (const f of readdirSync(dir)) {
|
|
2460
2118
|
if (!f.endsWith(ext)) continue;
|
|
2461
|
-
const fullPath =
|
|
2119
|
+
const fullPath = join(dir, f);
|
|
2462
2120
|
try {
|
|
2463
2121
|
const st = statSync(fullPath);
|
|
2464
2122
|
if (st.mtimeMs < cutoff) {
|
|
@@ -2549,16 +2207,16 @@ async function syncAndCheckClaudeScheduler(agent, tasks, boardItems, refreshData
|
|
|
2549
2207
|
}
|
|
2550
2208
|
async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
2551
2209
|
const projectDir = getProjectDir(codeName);
|
|
2552
|
-
const mcpConfigPath =
|
|
2210
|
+
const mcpConfigPath = join(projectDir, ".mcp.json");
|
|
2553
2211
|
sanitizeMcpJson(mcpConfigPath, requireHost());
|
|
2554
2212
|
try {
|
|
2555
|
-
const claudeMdPath =
|
|
2213
|
+
const claudeMdPath = join(projectDir, "CLAUDE.md");
|
|
2556
2214
|
const serverNames = [];
|
|
2557
|
-
const channelsConfigPath =
|
|
2215
|
+
const channelsConfigPath = join(projectDir, ".mcp-channels.json");
|
|
2558
2216
|
for (const p of [mcpConfigPath, channelsConfigPath]) {
|
|
2559
|
-
if (
|
|
2217
|
+
if (existsSync(p)) {
|
|
2560
2218
|
try {
|
|
2561
|
-
const d = JSON.parse(
|
|
2219
|
+
const d = JSON.parse(readFileSync(p, "utf-8"));
|
|
2562
2220
|
if (d.mcpServers) serverNames.push(...Object.keys(d.mcpServers));
|
|
2563
2221
|
} catch {
|
|
2564
2222
|
}
|
|
@@ -2577,15 +2235,15 @@ async function executeAndProcessClaudeTask(codeName, agentId, task, prompt) {
|
|
|
2577
2235
|
"--allowedTools",
|
|
2578
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
|
}
|
|
@@ -3904,18 +3562,18 @@ function startManager(opts) {
|
|
|
3904
3562
|
startPolling();
|
|
3905
3563
|
}
|
|
3906
3564
|
function deployMcpAssets() {
|
|
3907
|
-
const targetDir =
|
|
3908
|
-
|
|
3909
|
-
const moduleDir =
|
|
3565
|
+
const targetDir = join(homedir(), ".augmented", "_mcp");
|
|
3566
|
+
mkdirSync(targetDir, { recursive: true });
|
|
3567
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
3910
3568
|
let mcpSourceDir = "";
|
|
3911
3569
|
let dir = moduleDir;
|
|
3912
3570
|
for (let i = 0; i < 6; i++) {
|
|
3913
|
-
const candidate =
|
|
3914
|
-
if (
|
|
3571
|
+
const candidate = join(dir, "mcp");
|
|
3572
|
+
if (existsSync(join(candidate, "index.js"))) {
|
|
3915
3573
|
mcpSourceDir = candidate;
|
|
3916
3574
|
break;
|
|
3917
3575
|
}
|
|
3918
|
-
const parent =
|
|
3576
|
+
const parent = dirname(dir);
|
|
3919
3577
|
if (parent === dir) break;
|
|
3920
3578
|
dir = parent;
|
|
3921
3579
|
}
|
|
@@ -3924,9 +3582,9 @@ function deployMcpAssets() {
|
|
|
3924
3582
|
return;
|
|
3925
3583
|
}
|
|
3926
3584
|
for (const file of ["index.js", "slack-channel.js"]) {
|
|
3927
|
-
const src =
|
|
3928
|
-
const dst =
|
|
3929
|
-
if (!
|
|
3585
|
+
const src = join(mcpSourceDir, file);
|
|
3586
|
+
const dst = join(targetDir, file);
|
|
3587
|
+
if (!existsSync(src)) continue;
|
|
3930
3588
|
try {
|
|
3931
3589
|
copyFileSync(src, dst);
|
|
3932
3590
|
} catch (err) {
|