@openacp/cli 2026.403.3 → 2026.403.5

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/index.js CHANGED
@@ -462,22 +462,22 @@ __export(config_registry_exports, {
462
462
  });
463
463
  import * as fs4 from "fs";
464
464
  import * as path4 from "path";
465
- function getFieldDef(path33) {
466
- return CONFIG_REGISTRY.find((f) => f.path === path33);
465
+ function getFieldDef(path35) {
466
+ return CONFIG_REGISTRY.find((f) => f.path === path35);
467
467
  }
468
468
  function getSafeFields() {
469
469
  return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
470
470
  }
471
- function isHotReloadable(path33) {
472
- const def = getFieldDef(path33);
471
+ function isHotReloadable(path35) {
472
+ const def = getFieldDef(path35);
473
473
  return def?.hotReload ?? false;
474
474
  }
475
475
  function resolveOptions(def, config) {
476
476
  if (!def.options) return void 0;
477
477
  return typeof def.options === "function" ? def.options(config) : def.options;
478
478
  }
479
- function getConfigValue(config, path33) {
480
- const parts = path33.split(".");
479
+ function getConfigValue(config, path35) {
480
+ const parts = path35.split(".");
481
481
  let current = config;
482
482
  for (const part of parts) {
483
483
  if (current && typeof current === "object" && part in current) {
@@ -745,7 +745,11 @@ var init_config = __esm({
745
745
  agents: z.record(z.string(), AgentSchema).optional().default({}),
746
746
  defaultAgent: z.string(),
747
747
  workspace: z.object({
748
- baseDir: z.string().default("~/openacp-workspace")
748
+ baseDir: z.string().default("~/openacp-workspace"),
749
+ security: z.object({
750
+ allowedPaths: z.array(z.string()).default([]),
751
+ envWhitelist: z.array(z.string()).default([])
752
+ }).default({})
749
753
  }).default({}),
750
754
  security: z.object({
751
755
  allowedUserIds: z.array(z.string()).default([]),
@@ -906,11 +910,25 @@ var init_config = __esm({
906
910
  }
907
911
  if (input2.startsWith("/") || input2.startsWith("~")) {
908
912
  const resolved2 = expandHome3(input2);
909
- fs5.mkdirSync(resolved2, { recursive: true });
910
- return resolved2;
913
+ const base = expandHome3(this.config.workspace.baseDir);
914
+ if (resolved2 === base || resolved2.startsWith(base + path5.sep)) {
915
+ fs5.mkdirSync(resolved2, { recursive: true });
916
+ return resolved2;
917
+ }
918
+ throw new Error(
919
+ `Workspace path "${input2}" is outside base directory "${this.config.workspace.baseDir}".`
920
+ );
921
+ }
922
+ const name = input2.replace(/[^a-zA-Z0-9_-]/g, "");
923
+ if (name !== input2) {
924
+ throw new Error(
925
+ `Invalid workspace name: "${input2}". Only alphanumeric characters, hyphens, and underscores are allowed.`
926
+ );
911
927
  }
912
- const name = input2.toLowerCase();
913
- const resolved = path5.join(expandHome3(this.config.workspace.baseDir), name);
928
+ const resolved = path5.join(
929
+ expandHome3(this.config.workspace.baseDir),
930
+ name.toLowerCase()
931
+ );
914
932
  fs5.mkdirSync(resolved, { recursive: true });
915
933
  return resolved;
916
934
  }
@@ -1036,9 +1054,9 @@ var read_text_file_exports = {};
1036
1054
  __export(read_text_file_exports, {
1037
1055
  readTextFileWithRange: () => readTextFileWithRange
1038
1056
  });
1039
- import fs6 from "fs";
1057
+ import fs7 from "fs";
1040
1058
  async function readTextFileWithRange(filePath, options) {
1041
- const content = await fs6.promises.readFile(filePath, "utf-8");
1059
+ const content = await fs7.promises.readFile(filePath, "utf-8");
1042
1060
  if (!options?.line && !options?.limit) return content;
1043
1061
  const lines = content.split("\n");
1044
1062
  const start = Math.max(0, (options.line ?? 1) - 1);
@@ -1078,8 +1096,8 @@ __export(agent_dependencies_exports, {
1078
1096
  listAgentsWithIntegration: () => listAgentsWithIntegration
1079
1097
  });
1080
1098
  import { execFileSync as execFileSync2 } from "child_process";
1081
- import * as fs11 from "fs";
1082
- import * as path9 from "path";
1099
+ import * as fs12 from "fs";
1100
+ import * as path11 from "path";
1083
1101
  function getAgentSetup(registryId) {
1084
1102
  return AGENT_SETUP[registryId];
1085
1103
  }
@@ -1103,9 +1121,9 @@ function commandExists(cmd) {
1103
1121
  }
1104
1122
  let dir = process.cwd();
1105
1123
  while (true) {
1106
- const binPath = path9.join(dir, "node_modules", ".bin", cmd);
1107
- if (fs11.existsSync(binPath)) return true;
1108
- const parent = path9.dirname(dir);
1124
+ const binPath = path11.join(dir, "node_modules", ".bin", cmd);
1125
+ if (fs12.existsSync(binPath)) return true;
1126
+ const parent = path11.dirname(dir);
1109
1127
  if (parent === dir) break;
1110
1128
  dir = parent;
1111
1129
  }
@@ -1360,8 +1378,270 @@ var init_agent_registry = __esm({
1360
1378
  }
1361
1379
  });
1362
1380
 
1381
+ // src/core/agents/agent-installer.ts
1382
+ import * as fs14 from "fs";
1383
+ import * as path13 from "path";
1384
+ import * as os5 from "os";
1385
+ import crypto from "crypto";
1386
+ function validateTarContents(entries, destDir) {
1387
+ for (const entry of entries) {
1388
+ const segments = entry.split("/");
1389
+ if (segments.includes("..")) {
1390
+ throw new Error(`Archive contains unsafe path traversal: ${entry}`);
1391
+ }
1392
+ if (entry.startsWith("/")) {
1393
+ throw new Error(`Archive contains unsafe absolute path: ${entry}`);
1394
+ }
1395
+ }
1396
+ }
1397
+ function validateUninstallPath(binaryPath, agentsDir) {
1398
+ const realPath = path13.resolve(binaryPath);
1399
+ const realAgentsDir = path13.resolve(agentsDir);
1400
+ if (!realPath.startsWith(realAgentsDir + path13.sep) && realPath !== realAgentsDir) {
1401
+ throw new Error(`Refusing to delete path outside agents directory: ${realPath}`);
1402
+ }
1403
+ }
1404
+ function getPlatformKey() {
1405
+ const platform2 = PLATFORM_MAP[process.platform] ?? process.platform;
1406
+ const arch = ARCH_MAP[process.arch] ?? process.arch;
1407
+ return `${platform2}-${arch}`;
1408
+ }
1409
+ function resolveDistribution(agent) {
1410
+ const dist = agent.distribution;
1411
+ if (dist.npx) {
1412
+ return { type: "npx", package: dist.npx.package, args: dist.npx.args ?? [], env: dist.npx.env };
1413
+ }
1414
+ if (dist.uvx) {
1415
+ return { type: "uvx", package: dist.uvx.package, args: dist.uvx.args ?? [], env: dist.uvx.env };
1416
+ }
1417
+ if (dist.binary) {
1418
+ const platformKey = getPlatformKey();
1419
+ const target = dist.binary[platformKey];
1420
+ if (!target) return null;
1421
+ return { type: "binary", archive: target.archive, cmd: target.cmd, args: target.args ?? [], env: target.env };
1422
+ }
1423
+ return null;
1424
+ }
1425
+ function buildInstalledAgent(registryId, name, version, dist, binaryPath) {
1426
+ if (dist.type === "npx") {
1427
+ const npxPackage = stripPackageVersion(dist.package);
1428
+ return {
1429
+ registryId,
1430
+ name,
1431
+ version,
1432
+ distribution: "npx",
1433
+ command: "npx",
1434
+ args: [npxPackage, ...dist.args],
1435
+ env: dist.env ?? {},
1436
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1437
+ binaryPath: null
1438
+ };
1439
+ }
1440
+ if (dist.type === "uvx") {
1441
+ const uvxPackage = stripPythonPackageVersion(dist.package);
1442
+ return {
1443
+ registryId,
1444
+ name,
1445
+ version,
1446
+ distribution: "uvx",
1447
+ command: "uvx",
1448
+ args: [uvxPackage, ...dist.args],
1449
+ env: dist.env ?? {},
1450
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1451
+ binaryPath: null
1452
+ };
1453
+ }
1454
+ const absCmd = path13.resolve(binaryPath, dist.cmd);
1455
+ return {
1456
+ registryId,
1457
+ name,
1458
+ version,
1459
+ distribution: "binary",
1460
+ command: absCmd,
1461
+ args: dist.args,
1462
+ env: dist.env ?? {},
1463
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1464
+ binaryPath
1465
+ };
1466
+ }
1467
+ function stripPackageVersion(pkg) {
1468
+ if (pkg.startsWith("@")) {
1469
+ const afterScope = pkg.indexOf("/");
1470
+ if (afterScope === -1) return pkg;
1471
+ const versionAt = pkg.indexOf("@", afterScope + 1);
1472
+ return versionAt === -1 ? pkg : pkg.slice(0, versionAt);
1473
+ }
1474
+ const at = pkg.indexOf("@");
1475
+ return at === -1 ? pkg : pkg.slice(0, at);
1476
+ }
1477
+ function stripPythonPackageVersion(pkg) {
1478
+ const pyMatch = pkg.match(/^([^=@><!]+)/);
1479
+ if (pyMatch && pkg.includes("==")) return pyMatch[1];
1480
+ const at = pkg.indexOf("@");
1481
+ return at === -1 ? pkg : pkg.slice(0, at);
1482
+ }
1483
+ async function installAgent(agent, store, progress, agentsDir) {
1484
+ const agentKey = getAgentAlias(agent.id);
1485
+ await progress?.onStart(agent.id, agent.name);
1486
+ await progress?.onStep("Checking requirements...");
1487
+ const depResult = checkDependencies(agent.id);
1488
+ if (!depResult.available) {
1489
+ const hints = depResult.missing.map((m) => ` ${m.label}: ${m.installHint}`).join("\n");
1490
+ const msg = `${agent.name} needs some tools installed first:
1491
+ ${hints}`;
1492
+ await progress?.onError(msg);
1493
+ return { ok: false, agentKey, error: msg };
1494
+ }
1495
+ const dist = resolveDistribution(agent);
1496
+ if (!dist) {
1497
+ const platformKey = getPlatformKey();
1498
+ const msg = `${agent.name} is not available for your system (${platformKey}). Check their website for other install options.`;
1499
+ await progress?.onError(msg);
1500
+ return { ok: false, agentKey, error: msg };
1501
+ }
1502
+ if (dist.type === "uvx" && !checkRuntimeAvailable("uvx")) {
1503
+ const msg = `${agent.name} requires Python's uvx tool.
1504
+ Install it with: pip install uv`;
1505
+ await progress?.onError(msg, "pip install uv");
1506
+ return { ok: false, agentKey, error: msg, hint: "pip install uv" };
1507
+ }
1508
+ let binaryPath;
1509
+ if (dist.type === "binary") {
1510
+ try {
1511
+ binaryPath = await downloadAndExtract(agent.id, dist.archive, progress, agentsDir);
1512
+ } catch (err) {
1513
+ const msg = `Failed to download ${agent.name}. Please try again or install manually.`;
1514
+ await progress?.onError(msg);
1515
+ return { ok: false, agentKey, error: msg };
1516
+ }
1517
+ } else {
1518
+ await progress?.onStep("Setting up... (will download on first use)");
1519
+ }
1520
+ const installed = buildInstalledAgent(agent.id, agent.name, agent.version, dist, binaryPath);
1521
+ store.addAgent(agentKey, installed);
1522
+ const setup = getAgentSetup(agent.id);
1523
+ await progress?.onSuccess(agent.name);
1524
+ return { ok: true, agentKey, setupSteps: setup?.setupSteps };
1525
+ }
1526
+ async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir) {
1527
+ const destDir = path13.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
1528
+ fs14.mkdirSync(destDir, { recursive: true });
1529
+ await progress?.onStep("Downloading...");
1530
+ log11.info({ agentId, url: archiveUrl }, "Downloading agent binary");
1531
+ const response = await fetch(archiveUrl);
1532
+ if (!response.ok) {
1533
+ throw new Error(`Download failed: ${response.status} ${response.statusText}`);
1534
+ }
1535
+ const contentLength = Number(response.headers.get("content-length") || 0);
1536
+ const buffer = await readResponseWithProgress(response, contentLength, progress);
1537
+ await progress?.onStep("Extracting...");
1538
+ if (archiveUrl.endsWith(".zip")) {
1539
+ await extractZip(buffer, destDir);
1540
+ } else {
1541
+ await extractTarGz(buffer, destDir);
1542
+ }
1543
+ await progress?.onStep("Ready!");
1544
+ return destDir;
1545
+ }
1546
+ async function readResponseWithProgress(response, contentLength, progress) {
1547
+ if (!response.body) {
1548
+ const arrayBuffer = await response.arrayBuffer();
1549
+ return Buffer.from(arrayBuffer);
1550
+ }
1551
+ const reader = response.body.getReader();
1552
+ const chunks = [];
1553
+ let received = 0;
1554
+ while (true) {
1555
+ const { done, value } = await reader.read();
1556
+ if (done) break;
1557
+ chunks.push(value);
1558
+ received += value.length;
1559
+ if (received > MAX_DOWNLOAD_SIZE) {
1560
+ throw new Error(`Download exceeds size limit of ${MAX_DOWNLOAD_SIZE} bytes`);
1561
+ }
1562
+ if (contentLength > 0) {
1563
+ await progress?.onDownloadProgress(Math.round(received / contentLength * 100));
1564
+ }
1565
+ }
1566
+ return Buffer.concat(chunks);
1567
+ }
1568
+ function validateExtractedPaths(destDir) {
1569
+ const realDest = fs14.realpathSync(destDir);
1570
+ const entries = fs14.readdirSync(destDir, { recursive: true, withFileTypes: true });
1571
+ for (const entry of entries) {
1572
+ const dirent = entry;
1573
+ const parentPath = dirent.parentPath ?? dirent.path ?? destDir;
1574
+ const fullPath = path13.join(parentPath, entry.name);
1575
+ let realPath;
1576
+ try {
1577
+ realPath = fs14.realpathSync(fullPath);
1578
+ } catch {
1579
+ const linkTarget = fs14.readlinkSync(fullPath);
1580
+ realPath = path13.resolve(path13.dirname(fullPath), linkTarget);
1581
+ }
1582
+ if (!realPath.startsWith(realDest + path13.sep) && realPath !== realDest) {
1583
+ fs14.rmSync(destDir, { recursive: true, force: true });
1584
+ throw new Error(`Archive contains unsafe path: ${entry.name}`);
1585
+ }
1586
+ }
1587
+ }
1588
+ async function extractTarGz(buffer, destDir) {
1589
+ const { execFileSync: execFileSync7 } = await import("child_process");
1590
+ const tmpFile = path13.join(destDir, "_archive.tar.gz");
1591
+ fs14.writeFileSync(tmpFile, buffer);
1592
+ try {
1593
+ const listing = execFileSync7("tar", ["tf", tmpFile], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
1594
+ validateTarContents(listing, destDir);
1595
+ execFileSync7("tar", ["xzf", tmpFile, "-C", destDir], { stdio: "pipe" });
1596
+ } finally {
1597
+ fs14.unlinkSync(tmpFile);
1598
+ }
1599
+ validateExtractedPaths(destDir);
1600
+ }
1601
+ async function extractZip(buffer, destDir) {
1602
+ const { execFileSync: execFileSync7 } = await import("child_process");
1603
+ const tmpFile = path13.join(destDir, "_archive.zip");
1604
+ fs14.writeFileSync(tmpFile, buffer);
1605
+ try {
1606
+ execFileSync7("unzip", ["-o", tmpFile, "-d", destDir], { stdio: "pipe" });
1607
+ } finally {
1608
+ fs14.unlinkSync(tmpFile);
1609
+ }
1610
+ validateExtractedPaths(destDir);
1611
+ }
1612
+ async function uninstallAgent(agentKey, store, agentsDir) {
1613
+ const agent = store.getAgent(agentKey);
1614
+ if (!agent) return;
1615
+ if (agent.binaryPath && fs14.existsSync(agent.binaryPath)) {
1616
+ validateUninstallPath(agent.binaryPath, agentsDir ?? DEFAULT_AGENTS_DIR);
1617
+ fs14.rmSync(agent.binaryPath, { recursive: true, force: true });
1618
+ log11.info({ agentKey, binaryPath: agent.binaryPath }, "Deleted agent binary");
1619
+ }
1620
+ store.removeAgent(agentKey);
1621
+ }
1622
+ var log11, DEFAULT_AGENTS_DIR, MAX_DOWNLOAD_SIZE, ARCH_MAP, PLATFORM_MAP;
1623
+ var init_agent_installer = __esm({
1624
+ "src/core/agents/agent-installer.ts"() {
1625
+ "use strict";
1626
+ init_log();
1627
+ init_agent_dependencies();
1628
+ log11 = createChildLogger({ module: "agent-installer" });
1629
+ DEFAULT_AGENTS_DIR = path13.join(os5.homedir(), ".openacp", "agents");
1630
+ MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
1631
+ ARCH_MAP = {
1632
+ arm64: "aarch64",
1633
+ x64: "x86_64"
1634
+ };
1635
+ PLATFORM_MAP = {
1636
+ darwin: "darwin",
1637
+ linux: "linux",
1638
+ win32: "windows"
1639
+ };
1640
+ }
1641
+ });
1642
+
1363
1643
  // src/core/doctor/checks/config.ts
1364
- import * as fs16 from "fs";
1644
+ import * as fs17 from "fs";
1365
1645
  var configCheck;
1366
1646
  var init_config2 = __esm({
1367
1647
  "src/core/doctor/checks/config.ts"() {
@@ -1373,14 +1653,14 @@ var init_config2 = __esm({
1373
1653
  order: 1,
1374
1654
  async run(ctx) {
1375
1655
  const results = [];
1376
- if (!fs16.existsSync(ctx.configPath)) {
1656
+ if (!fs17.existsSync(ctx.configPath)) {
1377
1657
  results.push({ status: "fail", message: "Config file not found" });
1378
1658
  return results;
1379
1659
  }
1380
1660
  results.push({ status: "pass", message: "Config file exists" });
1381
1661
  let raw;
1382
1662
  try {
1383
- raw = JSON.parse(fs16.readFileSync(ctx.configPath, "utf-8"));
1663
+ raw = JSON.parse(fs17.readFileSync(ctx.configPath, "utf-8"));
1384
1664
  } catch (err) {
1385
1665
  results.push({
1386
1666
  status: "fail",
@@ -1399,7 +1679,7 @@ var init_config2 = __esm({
1399
1679
  fixRisk: "safe",
1400
1680
  fix: async () => {
1401
1681
  applyMigrations(raw);
1402
- fs16.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
1682
+ fs17.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
1403
1683
  return { success: true, message: "applied migrations" };
1404
1684
  }
1405
1685
  });
@@ -1423,8 +1703,8 @@ var init_config2 = __esm({
1423
1703
 
1424
1704
  // src/core/doctor/checks/agents.ts
1425
1705
  import { execFileSync as execFileSync3 } from "child_process";
1426
- import * as fs17 from "fs";
1427
- import * as path16 from "path";
1706
+ import * as fs18 from "fs";
1707
+ import * as path18 from "path";
1428
1708
  function commandExists2(cmd) {
1429
1709
  try {
1430
1710
  execFileSync3("which", [cmd], { stdio: "pipe" });
@@ -1433,9 +1713,9 @@ function commandExists2(cmd) {
1433
1713
  }
1434
1714
  let dir = process.cwd();
1435
1715
  while (true) {
1436
- const binPath = path16.join(dir, "node_modules", ".bin", cmd);
1437
- if (fs17.existsSync(binPath)) return true;
1438
- const parent = path16.dirname(dir);
1716
+ const binPath = path18.join(dir, "node_modules", ".bin", cmd);
1717
+ if (fs18.existsSync(binPath)) return true;
1718
+ const parent = path18.dirname(dir);
1439
1719
  if (parent === dir) break;
1440
1720
  dir = parent;
1441
1721
  }
@@ -1487,8 +1767,8 @@ var settings_manager_exports = {};
1487
1767
  __export(settings_manager_exports, {
1488
1768
  SettingsManager: () => SettingsManager
1489
1769
  });
1490
- import fs18 from "fs";
1491
- import path17 from "path";
1770
+ import fs19 from "fs";
1771
+ import path19 from "path";
1492
1772
  var SettingsManager, SettingsAPIImpl;
1493
1773
  var init_settings_manager = __esm({
1494
1774
  "src/core/plugin/settings-manager.ts"() {
@@ -1507,7 +1787,7 @@ var init_settings_manager = __esm({
1507
1787
  async loadSettings(pluginName) {
1508
1788
  const settingsPath = this.getSettingsPath(pluginName);
1509
1789
  try {
1510
- const content = fs18.readFileSync(settingsPath, "utf-8");
1790
+ const content = fs19.readFileSync(settingsPath, "utf-8");
1511
1791
  return JSON.parse(content);
1512
1792
  } catch {
1513
1793
  return {};
@@ -1525,7 +1805,7 @@ var init_settings_manager = __esm({
1525
1805
  };
1526
1806
  }
1527
1807
  getSettingsPath(pluginName) {
1528
- return path17.join(this.basePath, pluginName, "settings.json");
1808
+ return path19.join(this.basePath, pluginName, "settings.json");
1529
1809
  }
1530
1810
  async getPluginSettings(pluginName) {
1531
1811
  return this.loadSettings(pluginName);
@@ -1544,7 +1824,7 @@ var init_settings_manager = __esm({
1544
1824
  readFile() {
1545
1825
  if (this.cache !== null) return this.cache;
1546
1826
  try {
1547
- const content = fs18.readFileSync(this.settingsPath, "utf-8");
1827
+ const content = fs19.readFileSync(this.settingsPath, "utf-8");
1548
1828
  this.cache = JSON.parse(content);
1549
1829
  return this.cache;
1550
1830
  } catch {
@@ -1553,9 +1833,9 @@ var init_settings_manager = __esm({
1553
1833
  }
1554
1834
  }
1555
1835
  writeFile(data) {
1556
- const dir = path17.dirname(this.settingsPath);
1557
- fs18.mkdirSync(dir, { recursive: true });
1558
- fs18.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
1836
+ const dir = path19.dirname(this.settingsPath);
1837
+ fs19.mkdirSync(dir, { recursive: true });
1838
+ fs19.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
1559
1839
  this.cache = data;
1560
1840
  }
1561
1841
  async get(key) {
@@ -1590,7 +1870,7 @@ var init_settings_manager = __esm({
1590
1870
  });
1591
1871
 
1592
1872
  // src/core/doctor/checks/telegram.ts
1593
- import * as path18 from "path";
1873
+ import * as path20 from "path";
1594
1874
  var BOT_TOKEN_REGEX, telegramCheck;
1595
1875
  var init_telegram = __esm({
1596
1876
  "src/core/doctor/checks/telegram.ts"() {
@@ -1606,7 +1886,7 @@ var init_telegram = __esm({
1606
1886
  return results;
1607
1887
  }
1608
1888
  const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
1609
- const sm = new SettingsManager2(path18.join(ctx.pluginsDir, "data"));
1889
+ const sm = new SettingsManager2(path20.join(ctx.pluginsDir, "data"));
1610
1890
  const ps = await sm.loadSettings("@openacp/telegram");
1611
1891
  const legacyCh = ctx.config.channels.telegram;
1612
1892
  const botToken = ps.botToken ?? legacyCh?.botToken;
@@ -1690,7 +1970,7 @@ var init_telegram = __esm({
1690
1970
  });
1691
1971
 
1692
1972
  // src/core/doctor/checks/storage.ts
1693
- import * as fs19 from "fs";
1973
+ import * as fs20 from "fs";
1694
1974
  var storageCheck;
1695
1975
  var init_storage = __esm({
1696
1976
  "src/core/doctor/checks/storage.ts"() {
@@ -1700,28 +1980,28 @@ var init_storage = __esm({
1700
1980
  order: 4,
1701
1981
  async run(ctx) {
1702
1982
  const results = [];
1703
- if (!fs19.existsSync(ctx.dataDir)) {
1983
+ if (!fs20.existsSync(ctx.dataDir)) {
1704
1984
  results.push({
1705
1985
  status: "fail",
1706
1986
  message: "Data directory ~/.openacp does not exist",
1707
1987
  fixable: true,
1708
1988
  fixRisk: "safe",
1709
1989
  fix: async () => {
1710
- fs19.mkdirSync(ctx.dataDir, { recursive: true });
1990
+ fs20.mkdirSync(ctx.dataDir, { recursive: true });
1711
1991
  return { success: true, message: "created directory" };
1712
1992
  }
1713
1993
  });
1714
1994
  } else {
1715
1995
  try {
1716
- fs19.accessSync(ctx.dataDir, fs19.constants.W_OK);
1996
+ fs20.accessSync(ctx.dataDir, fs20.constants.W_OK);
1717
1997
  results.push({ status: "pass", message: "Data directory exists and writable" });
1718
1998
  } catch {
1719
1999
  results.push({ status: "fail", message: "Data directory not writable" });
1720
2000
  }
1721
2001
  }
1722
- if (fs19.existsSync(ctx.sessionsPath)) {
2002
+ if (fs20.existsSync(ctx.sessionsPath)) {
1723
2003
  try {
1724
- const content = fs19.readFileSync(ctx.sessionsPath, "utf-8");
2004
+ const content = fs20.readFileSync(ctx.sessionsPath, "utf-8");
1725
2005
  const data = JSON.parse(content);
1726
2006
  if (typeof data === "object" && data !== null && "sessions" in data) {
1727
2007
  results.push({ status: "pass", message: "Sessions file valid" });
@@ -1732,7 +2012,7 @@ var init_storage = __esm({
1732
2012
  fixable: true,
1733
2013
  fixRisk: "risky",
1734
2014
  fix: async () => {
1735
- fs19.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
2015
+ fs20.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
1736
2016
  return { success: true, message: "reset sessions file" };
1737
2017
  }
1738
2018
  });
@@ -1744,7 +2024,7 @@ var init_storage = __esm({
1744
2024
  fixable: true,
1745
2025
  fixRisk: "risky",
1746
2026
  fix: async () => {
1747
- fs19.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
2027
+ fs20.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
1748
2028
  return { success: true, message: "reset sessions file" };
1749
2029
  }
1750
2030
  });
@@ -1752,20 +2032,20 @@ var init_storage = __esm({
1752
2032
  } else {
1753
2033
  results.push({ status: "pass", message: "Sessions file not present yet (created on first session)" });
1754
2034
  }
1755
- if (!fs19.existsSync(ctx.logsDir)) {
2035
+ if (!fs20.existsSync(ctx.logsDir)) {
1756
2036
  results.push({
1757
2037
  status: "warn",
1758
2038
  message: "Log directory does not exist",
1759
2039
  fixable: true,
1760
2040
  fixRisk: "safe",
1761
2041
  fix: async () => {
1762
- fs19.mkdirSync(ctx.logsDir, { recursive: true });
2042
+ fs20.mkdirSync(ctx.logsDir, { recursive: true });
1763
2043
  return { success: true, message: "created log directory" };
1764
2044
  }
1765
2045
  });
1766
2046
  } else {
1767
2047
  try {
1768
- fs19.accessSync(ctx.logsDir, fs19.constants.W_OK);
2048
+ fs20.accessSync(ctx.logsDir, fs20.constants.W_OK);
1769
2049
  results.push({ status: "pass", message: "Log directory exists and writable" });
1770
2050
  } catch {
1771
2051
  results.push({ status: "fail", message: "Log directory not writable" });
@@ -1778,7 +2058,7 @@ var init_storage = __esm({
1778
2058
  });
1779
2059
 
1780
2060
  // src/core/doctor/checks/workspace.ts
1781
- import * as fs20 from "fs";
2061
+ import * as fs21 from "fs";
1782
2062
  var workspaceCheck;
1783
2063
  var init_workspace = __esm({
1784
2064
  "src/core/doctor/checks/workspace.ts"() {
@@ -1794,20 +2074,20 @@ var init_workspace = __esm({
1794
2074
  return results;
1795
2075
  }
1796
2076
  const baseDir = expandHome3(ctx.config.workspace.baseDir);
1797
- if (!fs20.existsSync(baseDir)) {
2077
+ if (!fs21.existsSync(baseDir)) {
1798
2078
  results.push({
1799
2079
  status: "warn",
1800
2080
  message: `Workspace directory does not exist: ${baseDir}`,
1801
2081
  fixable: true,
1802
2082
  fixRisk: "safe",
1803
2083
  fix: async () => {
1804
- fs20.mkdirSync(baseDir, { recursive: true });
2084
+ fs21.mkdirSync(baseDir, { recursive: true });
1805
2085
  return { success: true, message: "created directory" };
1806
2086
  }
1807
2087
  });
1808
2088
  } else {
1809
2089
  try {
1810
- fs20.accessSync(baseDir, fs20.constants.W_OK);
2090
+ fs21.accessSync(baseDir, fs21.constants.W_OK);
1811
2091
  results.push({ status: "pass", message: `Workspace directory exists: ${baseDir}` });
1812
2092
  } catch {
1813
2093
  results.push({ status: "fail", message: `Workspace directory not writable: ${baseDir}` });
@@ -1820,8 +2100,8 @@ var init_workspace = __esm({
1820
2100
  });
1821
2101
 
1822
2102
  // src/core/doctor/checks/plugins.ts
1823
- import * as fs21 from "fs";
1824
- import * as path19 from "path";
2103
+ import * as fs22 from "fs";
2104
+ import * as path21 from "path";
1825
2105
  var pluginsCheck;
1826
2106
  var init_plugins = __esm({
1827
2107
  "src/core/doctor/checks/plugins.ts"() {
@@ -1831,16 +2111,16 @@ var init_plugins = __esm({
1831
2111
  order: 6,
1832
2112
  async run(ctx) {
1833
2113
  const results = [];
1834
- if (!fs21.existsSync(ctx.pluginsDir)) {
2114
+ if (!fs22.existsSync(ctx.pluginsDir)) {
1835
2115
  results.push({
1836
2116
  status: "warn",
1837
2117
  message: "Plugins directory does not exist",
1838
2118
  fixable: true,
1839
2119
  fixRisk: "safe",
1840
2120
  fix: async () => {
1841
- fs21.mkdirSync(ctx.pluginsDir, { recursive: true });
1842
- fs21.writeFileSync(
1843
- path19.join(ctx.pluginsDir, "package.json"),
2121
+ fs22.mkdirSync(ctx.pluginsDir, { recursive: true });
2122
+ fs22.writeFileSync(
2123
+ path21.join(ctx.pluginsDir, "package.json"),
1844
2124
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
1845
2125
  );
1846
2126
  return { success: true, message: "initialized plugins directory" };
@@ -1849,15 +2129,15 @@ var init_plugins = __esm({
1849
2129
  return results;
1850
2130
  }
1851
2131
  results.push({ status: "pass", message: "Plugins directory exists" });
1852
- const pkgPath = path19.join(ctx.pluginsDir, "package.json");
1853
- if (!fs21.existsSync(pkgPath)) {
2132
+ const pkgPath = path21.join(ctx.pluginsDir, "package.json");
2133
+ if (!fs22.existsSync(pkgPath)) {
1854
2134
  results.push({
1855
2135
  status: "warn",
1856
2136
  message: "Plugins package.json missing",
1857
2137
  fixable: true,
1858
2138
  fixRisk: "safe",
1859
2139
  fix: async () => {
1860
- fs21.writeFileSync(
2140
+ fs22.writeFileSync(
1861
2141
  pkgPath,
1862
2142
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
1863
2143
  );
@@ -1867,7 +2147,7 @@ var init_plugins = __esm({
1867
2147
  return results;
1868
2148
  }
1869
2149
  try {
1870
- const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
2150
+ const pkg = JSON.parse(fs22.readFileSync(pkgPath, "utf-8"));
1871
2151
  const deps = pkg.dependencies || {};
1872
2152
  const count = Object.keys(deps).length;
1873
2153
  results.push({ status: "pass", message: `Plugins package.json valid (${count} plugins)` });
@@ -1878,7 +2158,7 @@ var init_plugins = __esm({
1878
2158
  fixable: true,
1879
2159
  fixRisk: "risky",
1880
2160
  fix: async () => {
1881
- fs21.writeFileSync(
2161
+ fs22.writeFileSync(
1882
2162
  pkgPath,
1883
2163
  JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
1884
2164
  );
@@ -1893,7 +2173,7 @@ var init_plugins = __esm({
1893
2173
  });
1894
2174
 
1895
2175
  // src/core/doctor/checks/daemon.ts
1896
- import * as fs22 from "fs";
2176
+ import * as fs23 from "fs";
1897
2177
  import * as net from "net";
1898
2178
  function isProcessAlive(pid) {
1899
2179
  try {
@@ -1923,8 +2203,8 @@ var init_daemon = __esm({
1923
2203
  order: 7,
1924
2204
  async run(ctx) {
1925
2205
  const results = [];
1926
- if (fs22.existsSync(ctx.pidPath)) {
1927
- const content = fs22.readFileSync(ctx.pidPath, "utf-8").trim();
2206
+ if (fs23.existsSync(ctx.pidPath)) {
2207
+ const content = fs23.readFileSync(ctx.pidPath, "utf-8").trim();
1928
2208
  const pid = parseInt(content, 10);
1929
2209
  if (isNaN(pid)) {
1930
2210
  results.push({
@@ -1933,7 +2213,7 @@ var init_daemon = __esm({
1933
2213
  fixable: true,
1934
2214
  fixRisk: "safe",
1935
2215
  fix: async () => {
1936
- fs22.unlinkSync(ctx.pidPath);
2216
+ fs23.unlinkSync(ctx.pidPath);
1937
2217
  return { success: true, message: "removed invalid PID file" };
1938
2218
  }
1939
2219
  });
@@ -1944,7 +2224,7 @@ var init_daemon = __esm({
1944
2224
  fixable: true,
1945
2225
  fixRisk: "safe",
1946
2226
  fix: async () => {
1947
- fs22.unlinkSync(ctx.pidPath);
2227
+ fs23.unlinkSync(ctx.pidPath);
1948
2228
  return { success: true, message: "removed stale PID file" };
1949
2229
  }
1950
2230
  });
@@ -1952,8 +2232,8 @@ var init_daemon = __esm({
1952
2232
  results.push({ status: "pass", message: `Daemon running (PID ${pid})` });
1953
2233
  }
1954
2234
  }
1955
- if (fs22.existsSync(ctx.portFilePath)) {
1956
- const content = fs22.readFileSync(ctx.portFilePath, "utf-8").trim();
2235
+ if (fs23.existsSync(ctx.portFilePath)) {
2236
+ const content = fs23.readFileSync(ctx.portFilePath, "utf-8").trim();
1957
2237
  const port = parseInt(content, 10);
1958
2238
  if (isNaN(port)) {
1959
2239
  results.push({
@@ -1962,7 +2242,7 @@ var init_daemon = __esm({
1962
2242
  fixable: true,
1963
2243
  fixRisk: "safe",
1964
2244
  fix: async () => {
1965
- fs22.unlinkSync(ctx.portFilePath);
2245
+ fs23.unlinkSync(ctx.portFilePath);
1966
2246
  return { success: true, message: "removed invalid port file" };
1967
2247
  }
1968
2248
  });
@@ -1974,8 +2254,8 @@ var init_daemon = __esm({
1974
2254
  const apiPort = ctx.config.api.port;
1975
2255
  const inUse = await checkPortInUse(apiPort);
1976
2256
  if (inUse) {
1977
- if (fs22.existsSync(ctx.pidPath)) {
1978
- const pid = parseInt(fs22.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
2257
+ if (fs23.existsSync(ctx.pidPath)) {
2258
+ const pid = parseInt(fs23.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
1979
2259
  if (!isNaN(pid) && isProcessAlive(pid)) {
1980
2260
  results.push({ status: "pass", message: `API port ${apiPort} in use by OpenACP daemon` });
1981
2261
  } else {
@@ -1995,17 +2275,17 @@ var init_daemon = __esm({
1995
2275
  });
1996
2276
 
1997
2277
  // src/core/utils/install-binary.ts
1998
- import fs23 from "fs";
1999
- import path20 from "path";
2278
+ import fs24 from "fs";
2279
+ import path22 from "path";
2000
2280
  import https from "https";
2001
2281
  import os9 from "os";
2002
2282
  import { execSync } from "child_process";
2003
2283
  function downloadFile(url, dest) {
2004
2284
  return new Promise((resolve6, reject) => {
2005
- const file = fs23.createWriteStream(dest);
2285
+ const file = fs24.createWriteStream(dest);
2006
2286
  const cleanup = () => {
2007
2287
  try {
2008
- if (fs23.existsSync(dest)) fs23.unlinkSync(dest);
2288
+ if (fs24.existsSync(dest)) fs24.unlinkSync(dest);
2009
2289
  } catch {
2010
2290
  }
2011
2291
  };
@@ -2024,6 +2304,18 @@ function downloadFile(url, dest) {
2024
2304
  });
2025
2305
  return;
2026
2306
  }
2307
+ let totalBytes = 0;
2308
+ const request = response;
2309
+ response.on("data", (chunk) => {
2310
+ totalBytes += chunk.length;
2311
+ if (totalBytes > MAX_DOWNLOAD_SIZE) {
2312
+ request.destroy();
2313
+ file.close(() => {
2314
+ cleanup();
2315
+ reject(new Error(`Download exceeds size limit of ${MAX_DOWNLOAD_SIZE} bytes`));
2316
+ });
2317
+ }
2318
+ });
2027
2319
  response.pipe(file);
2028
2320
  file.on("finish", () => file.close(() => resolve6(dest)));
2029
2321
  file.on("error", (err) => {
@@ -2052,34 +2344,36 @@ function getDownloadUrl(spec) {
2052
2344
  async function ensureBinary(spec, binDir) {
2053
2345
  const resolvedBinDir = binDir ?? DEFAULT_BIN_DIR;
2054
2346
  const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name;
2055
- const binPath = path20.join(resolvedBinDir, binName);
2347
+ const binPath = path22.join(resolvedBinDir, binName);
2056
2348
  if (commandExists(spec.name)) {
2057
2349
  log17.debug({ name: spec.name }, "Found in PATH");
2058
2350
  return spec.name;
2059
2351
  }
2060
- if (fs23.existsSync(binPath)) {
2061
- if (!IS_WINDOWS) fs23.chmodSync(binPath, "755");
2352
+ if (fs24.existsSync(binPath)) {
2353
+ if (!IS_WINDOWS) fs24.chmodSync(binPath, "755");
2062
2354
  log17.debug({ name: spec.name, path: binPath }, "Found in ~/.openacp/bin");
2063
2355
  return binPath;
2064
2356
  }
2065
2357
  log17.info({ name: spec.name }, "Not found, downloading from GitHub...");
2066
- fs23.mkdirSync(resolvedBinDir, { recursive: true });
2358
+ fs24.mkdirSync(resolvedBinDir, { recursive: true });
2067
2359
  const url = getDownloadUrl(spec);
2068
2360
  const isArchive = spec.isArchive?.(url) ?? false;
2069
- const downloadDest = isArchive ? path20.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
2361
+ const downloadDest = isArchive ? path22.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
2070
2362
  await downloadFile(url, downloadDest);
2071
2363
  if (isArchive) {
2364
+ const listing = execSync(`tar -tf "${downloadDest}"`, { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
2365
+ validateTarContents(listing, resolvedBinDir);
2072
2366
  execSync(`tar -xzf "${downloadDest}" -C "${resolvedBinDir}"`, { stdio: "pipe" });
2073
2367
  try {
2074
- fs23.unlinkSync(downloadDest);
2368
+ fs24.unlinkSync(downloadDest);
2075
2369
  } catch {
2076
2370
  }
2077
2371
  }
2078
- if (!fs23.existsSync(binPath)) {
2372
+ if (!fs24.existsSync(binPath)) {
2079
2373
  throw new Error(`${spec.name}: binary not found at ${binPath} after download/extraction. The archive structure may have changed.`);
2080
2374
  }
2081
2375
  if (!IS_WINDOWS) {
2082
- fs23.chmodSync(binPath, "755");
2376
+ fs24.chmodSync(binPath, "755");
2083
2377
  }
2084
2378
  log17.info({ name: spec.name, path: binPath }, "Installed successfully");
2085
2379
  return binPath;
@@ -2090,8 +2384,9 @@ var init_install_binary = __esm({
2090
2384
  "use strict";
2091
2385
  init_log();
2092
2386
  init_agent_dependencies();
2387
+ init_agent_installer();
2093
2388
  log17 = createChildLogger({ module: "binary-installer" });
2094
- DEFAULT_BIN_DIR = path20.join(os9.homedir(), ".openacp", "bin");
2389
+ DEFAULT_BIN_DIR = path22.join(os9.homedir(), ".openacp", "bin");
2095
2390
  IS_WINDOWS = os9.platform() === "win32";
2096
2391
  }
2097
2392
  });
@@ -2132,8 +2427,8 @@ var init_install_cloudflared = __esm({
2132
2427
  });
2133
2428
 
2134
2429
  // src/core/doctor/checks/tunnel.ts
2135
- import * as fs24 from "fs";
2136
- import * as path21 from "path";
2430
+ import * as fs25 from "fs";
2431
+ import * as path23 from "path";
2137
2432
  import * as os10 from "os";
2138
2433
  import { execFileSync as execFileSync4 } from "child_process";
2139
2434
  var tunnelCheck;
@@ -2157,9 +2452,9 @@ var init_tunnel = __esm({
2157
2452
  results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
2158
2453
  if (provider === "cloudflare") {
2159
2454
  const binName = os10.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
2160
- const binPath = path21.join(ctx.dataDir, "bin", binName);
2455
+ const binPath = path23.join(ctx.dataDir, "bin", binName);
2161
2456
  let found = false;
2162
- if (fs24.existsSync(binPath)) {
2457
+ if (fs25.existsSync(binPath)) {
2163
2458
  found = true;
2164
2459
  } else {
2165
2460
  try {
@@ -2201,8 +2496,8 @@ var init_tunnel = __esm({
2201
2496
  });
2202
2497
 
2203
2498
  // src/core/doctor/index.ts
2204
- import * as fs25 from "fs";
2205
- import * as path22 from "path";
2499
+ import * as fs26 from "fs";
2500
+ import * as path24 from "path";
2206
2501
  var ALL_CHECKS, CHECK_TIMEOUT_MS, DoctorEngine;
2207
2502
  var init_doctor = __esm({
2208
2503
  "src/core/doctor/index.ts"() {
@@ -2284,27 +2579,27 @@ var init_doctor = __esm({
2284
2579
  }
2285
2580
  async buildContext() {
2286
2581
  const dataDir = this.dataDir;
2287
- const configPath = process.env.OPENACP_CONFIG_PATH || path22.join(dataDir, "config.json");
2582
+ const configPath = process.env.OPENACP_CONFIG_PATH || path24.join(dataDir, "config.json");
2288
2583
  let config = null;
2289
2584
  let rawConfig = null;
2290
2585
  try {
2291
- const content = fs25.readFileSync(configPath, "utf-8");
2586
+ const content = fs26.readFileSync(configPath, "utf-8");
2292
2587
  rawConfig = JSON.parse(content);
2293
2588
  const cm = new ConfigManager(configPath);
2294
2589
  await cm.load();
2295
2590
  config = cm.get();
2296
2591
  } catch {
2297
2592
  }
2298
- const logsDir = config ? expandHome3(config.logging.logDir) : path22.join(dataDir, "logs");
2593
+ const logsDir = config ? expandHome3(config.logging.logDir) : path24.join(dataDir, "logs");
2299
2594
  return {
2300
2595
  config,
2301
2596
  rawConfig,
2302
2597
  configPath,
2303
2598
  dataDir,
2304
- sessionsPath: path22.join(dataDir, "sessions.json"),
2305
- pidPath: path22.join(dataDir, "openacp.pid"),
2306
- portFilePath: path22.join(dataDir, "api.port"),
2307
- pluginsDir: path22.join(dataDir, "plugins"),
2599
+ sessionsPath: path24.join(dataDir, "sessions.json"),
2600
+ pidPath: path24.join(dataDir, "openacp.pid"),
2601
+ portFilePath: path24.join(dataDir, "api.port"),
2602
+ pluginsDir: path24.join(dataDir, "plugins"),
2308
2603
  logsDir
2309
2604
  };
2310
2605
  }
@@ -2409,16 +2704,16 @@ __export(plugin_installer_exports, {
2409
2704
  });
2410
2705
  import { exec } from "child_process";
2411
2706
  import { promisify } from "util";
2412
- import * as fs27 from "fs/promises";
2707
+ import * as fs28 from "fs/promises";
2413
2708
  import * as os12 from "os";
2414
- import * as path24 from "path";
2709
+ import * as path26 from "path";
2415
2710
  import { pathToFileURL } from "url";
2416
2711
  async function importFromDir(packageName, dir) {
2417
- const pkgDir = path24.join(dir, "node_modules", ...packageName.split("/"));
2418
- const pkgJsonPath = path24.join(pkgDir, "package.json");
2712
+ const pkgDir = path26.join(dir, "node_modules", ...packageName.split("/"));
2713
+ const pkgJsonPath = path26.join(pkgDir, "package.json");
2419
2714
  let pkgJson;
2420
2715
  try {
2421
- pkgJson = JSON.parse(await fs27.readFile(pkgJsonPath, "utf-8"));
2716
+ pkgJson = JSON.parse(await fs28.readFile(pkgJsonPath, "utf-8"));
2422
2717
  } catch (err) {
2423
2718
  throw new Error(`Cannot read package.json for "${packageName}" at ${pkgJsonPath}: ${err.message}`);
2424
2719
  }
@@ -2431,9 +2726,9 @@ async function importFromDir(packageName, dir) {
2431
2726
  } else {
2432
2727
  entry = pkgJson.main ?? "index.js";
2433
2728
  }
2434
- const entryPath = path24.join(pkgDir, entry);
2729
+ const entryPath = path26.join(pkgDir, entry);
2435
2730
  try {
2436
- await fs27.access(entryPath);
2731
+ await fs28.access(entryPath);
2437
2732
  } catch {
2438
2733
  throw new Error(`Entry point "${entry}" not found for "${packageName}" at ${entryPath}`);
2439
2734
  }
@@ -2443,12 +2738,12 @@ async function installNpmPlugin(packageName, pluginsDir) {
2443
2738
  if (!VALID_NPM_NAME.test(packageName)) {
2444
2739
  throw new Error(`Invalid package name: "${packageName}". Must be a valid npm package name.`);
2445
2740
  }
2446
- const dir = pluginsDir ?? path24.join(os12.homedir(), ".openacp", "plugins");
2741
+ const dir = pluginsDir ?? path26.join(os12.homedir(), ".openacp", "plugins");
2447
2742
  try {
2448
2743
  return await importFromDir(packageName, dir);
2449
2744
  } catch {
2450
2745
  }
2451
- await execAsync(`npm install ${packageName} --prefix "${dir}" --save`, {
2746
+ await execAsync(`npm install ${packageName} --prefix "${dir}" --save --ignore-scripts`, {
2452
2747
  timeout: 6e4
2453
2748
  });
2454
2749
  return await importFromDir(packageName, dir);
@@ -2525,10 +2820,10 @@ var install_context_exports = {};
2525
2820
  __export(install_context_exports, {
2526
2821
  createInstallContext: () => createInstallContext
2527
2822
  });
2528
- import path25 from "path";
2823
+ import path27 from "path";
2529
2824
  function createInstallContext(opts) {
2530
2825
  const { pluginName, settingsManager, basePath, legacyConfig, instanceRoot } = opts;
2531
- const dataDir = path25.join(basePath, pluginName, "data");
2826
+ const dataDir = path27.join(basePath, pluginName, "data");
2532
2827
  return {
2533
2828
  pluginName,
2534
2829
  terminal: createTerminalIO(),
@@ -2555,19 +2850,19 @@ __export(api_client_exports, {
2555
2850
  readApiSecret: () => readApiSecret,
2556
2851
  removeStalePortFile: () => removeStalePortFile
2557
2852
  });
2558
- import * as fs28 from "fs";
2559
- import * as path26 from "path";
2853
+ import * as fs29 from "fs";
2854
+ import * as path28 from "path";
2560
2855
  import * as os13 from "os";
2561
2856
  function defaultPortFile(root) {
2562
- return path26.join(root ?? DEFAULT_ROOT, "api.port");
2857
+ return path28.join(root ?? DEFAULT_ROOT, "api.port");
2563
2858
  }
2564
2859
  function defaultSecretFile(root) {
2565
- return path26.join(root ?? DEFAULT_ROOT, "api-secret");
2860
+ return path28.join(root ?? DEFAULT_ROOT, "api-secret");
2566
2861
  }
2567
2862
  function readApiPort(portFilePath, instanceRoot) {
2568
2863
  const filePath = portFilePath ?? defaultPortFile(instanceRoot);
2569
2864
  try {
2570
- const content = fs28.readFileSync(filePath, "utf-8").trim();
2865
+ const content = fs29.readFileSync(filePath, "utf-8").trim();
2571
2866
  const port = parseInt(content, 10);
2572
2867
  return isNaN(port) ? null : port;
2573
2868
  } catch {
@@ -2577,7 +2872,7 @@ function readApiPort(portFilePath, instanceRoot) {
2577
2872
  function readApiSecret(secretFilePath, instanceRoot) {
2578
2873
  const filePath = secretFilePath ?? defaultSecretFile(instanceRoot);
2579
2874
  try {
2580
- const content = fs28.readFileSync(filePath, "utf-8").trim();
2875
+ const content = fs29.readFileSync(filePath, "utf-8").trim();
2581
2876
  return content || null;
2582
2877
  } catch {
2583
2878
  return null;
@@ -2586,7 +2881,7 @@ function readApiSecret(secretFilePath, instanceRoot) {
2586
2881
  function removeStalePortFile(portFilePath, instanceRoot) {
2587
2882
  const filePath = portFilePath ?? defaultPortFile(instanceRoot);
2588
2883
  try {
2589
- fs28.unlinkSync(filePath);
2884
+ fs29.unlinkSync(filePath);
2590
2885
  } catch {
2591
2886
  }
2592
2887
  }
@@ -2602,7 +2897,7 @@ var DEFAULT_ROOT;
2602
2897
  var init_api_client = __esm({
2603
2898
  "src/cli/api-client.ts"() {
2604
2899
  "use strict";
2605
- DEFAULT_ROOT = path26.join(os13.homedir(), ".openacp");
2900
+ DEFAULT_ROOT = path28.join(os13.homedir(), ".openacp");
2606
2901
  }
2607
2902
  });
2608
2903
 
@@ -2636,8 +2931,8 @@ var init_notification = __esm({
2636
2931
  });
2637
2932
 
2638
2933
  // src/plugins/file-service/file-service.ts
2639
- import fs30 from "fs";
2640
- import path29 from "path";
2934
+ import fs31 from "fs";
2935
+ import path31 from "path";
2641
2936
  import { OggOpusDecoder } from "ogg-opus-decoder";
2642
2937
  import wav from "node-wav";
2643
2938
  function classifyMime(mimeType) {
@@ -2693,14 +2988,14 @@ var init_file_service = __esm({
2693
2988
  const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
2694
2989
  let removed = 0;
2695
2990
  try {
2696
- const entries = await fs30.promises.readdir(this.baseDir, { withFileTypes: true });
2991
+ const entries = await fs31.promises.readdir(this.baseDir, { withFileTypes: true });
2697
2992
  for (const entry of entries) {
2698
2993
  if (!entry.isDirectory()) continue;
2699
- const dirPath = path29.join(this.baseDir, entry.name);
2994
+ const dirPath = path31.join(this.baseDir, entry.name);
2700
2995
  try {
2701
- const stat = await fs30.promises.stat(dirPath);
2996
+ const stat = await fs31.promises.stat(dirPath);
2702
2997
  if (stat.mtimeMs < cutoff) {
2703
- await fs30.promises.rm(dirPath, { recursive: true, force: true });
2998
+ await fs31.promises.rm(dirPath, { recursive: true, force: true });
2704
2999
  removed++;
2705
3000
  }
2706
3001
  } catch {
@@ -2711,11 +3006,11 @@ var init_file_service = __esm({
2711
3006
  return removed;
2712
3007
  }
2713
3008
  async saveFile(sessionId, fileName, data, mimeType) {
2714
- const sessionDir = path29.join(this.baseDir, sessionId);
2715
- await fs30.promises.mkdir(sessionDir, { recursive: true });
3009
+ const sessionDir = path31.join(this.baseDir, sessionId);
3010
+ await fs31.promises.mkdir(sessionDir, { recursive: true });
2716
3011
  const safeName = `${Date.now()}-${fileName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
2717
- const filePath = path29.join(sessionDir, safeName);
2718
- await fs30.promises.writeFile(filePath, data);
3012
+ const filePath = path31.join(sessionDir, safeName);
3013
+ await fs31.promises.writeFile(filePath, data);
2719
3014
  return {
2720
3015
  type: classifyMime(mimeType),
2721
3016
  filePath,
@@ -2726,14 +3021,14 @@ var init_file_service = __esm({
2726
3021
  }
2727
3022
  async resolveFile(filePath) {
2728
3023
  try {
2729
- const stat = await fs30.promises.stat(filePath);
3024
+ const stat = await fs31.promises.stat(filePath);
2730
3025
  if (!stat.isFile()) return null;
2731
- const ext = path29.extname(filePath).toLowerCase();
3026
+ const ext = path31.extname(filePath).toLowerCase();
2732
3027
  const mimeType = EXT_TO_MIME[ext] || "application/octet-stream";
2733
3028
  return {
2734
3029
  type: classifyMime(mimeType),
2735
3030
  filePath,
2736
- fileName: path29.basename(filePath),
3031
+ fileName: path31.basename(filePath),
2737
3032
  mimeType,
2738
3033
  size: stat.size
2739
3034
  };
@@ -2966,6 +3261,12 @@ function createAuthPreHandler(getSecret, getJwtSecret, tokenStore) {
2966
3261
  const authHeader = request.headers.authorization;
2967
3262
  const queryToken = request.query?.token;
2968
3263
  const token = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : queryToken;
3264
+ if (queryToken && !authHeader) {
3265
+ log20.warn(
3266
+ { url: request.url.replace(/([?&]token=)[^&]+/, "$1[REDACTED]") },
3267
+ "Token passed via URL query param \u2014 use Authorization: Bearer header to avoid token leakage in tunnel/proxy logs"
3268
+ );
3269
+ }
2969
3270
  if (!token) {
2970
3271
  throw new AuthError("UNAUTHORIZED", "Missing authentication token");
2971
3272
  }
@@ -3015,12 +3316,15 @@ function requireRole(role) {
3015
3316
  }
3016
3317
  };
3017
3318
  }
3319
+ var log20;
3018
3320
  var init_auth = __esm({
3019
3321
  "src/plugins/api-server/middleware/auth.ts"() {
3020
3322
  "use strict";
3021
3323
  init_error_handler();
3022
3324
  init_jwt();
3023
3325
  init_roles();
3326
+ init_log();
3327
+ log20 = createChildLogger({ module: "api-auth" });
3024
3328
  }
3025
3329
  });
3026
3330
 
@@ -3038,7 +3342,6 @@ async function createApiServer(options) {
3038
3342
  const origin = request.headers.origin;
3039
3343
  if (origin) {
3040
3344
  reply.header("Access-Control-Allow-Origin", origin);
3041
- reply.header("Access-Control-Allow-Credentials", "true");
3042
3345
  }
3043
3346
  if (request.method === "OPTIONS") {
3044
3347
  reply.header("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS");
@@ -3046,7 +3349,25 @@ async function createApiServer(options) {
3046
3349
  reply.status(204).send();
3047
3350
  }
3048
3351
  });
3049
- await app.register(fastifyRateLimit, { max: 100, timeWindow: "1 minute" });
3352
+ await app.register(fastifyRateLimit, {
3353
+ max: 100,
3354
+ timeWindow: "1 minute",
3355
+ // When the server is reachable through a tunnel (Cloudflare, ngrok, etc.) all
3356
+ // requests arrive from the same tunnel-proxy IP address. Use the real client IP
3357
+ // from well-known forwarded headers so rate limits are enforced per-caller, not
3358
+ // per-tunnel. Only the first value in X-Forwarded-For is taken (the client); the
3359
+ // rest may be added by intermediate proxies and must not be trusted for limiting.
3360
+ keyGenerator: (request) => {
3361
+ const cfIp = request.headers["cf-connecting-ip"];
3362
+ if (cfIp && typeof cfIp === "string") return cfIp;
3363
+ const xff = request.headers["x-forwarded-for"];
3364
+ if (xff) {
3365
+ const first = (Array.isArray(xff) ? xff[0] : xff).split(",")[0]?.trim();
3366
+ if (first) return first;
3367
+ }
3368
+ return request.ip;
3369
+ }
3370
+ });
3050
3371
  await app.register(fastifySwagger, {
3051
3372
  openapi: {
3052
3373
  info: { title: "OpenACP API", version: "1.0.0" },
@@ -3131,10 +3452,11 @@ var init_service = __esm({
3131
3452
  });
3132
3453
 
3133
3454
  // src/plugins/api-server/sse-manager.ts
3134
- var SSEManager;
3455
+ var MAX_SSE_CONNECTIONS, SSEManager;
3135
3456
  var init_sse_manager = __esm({
3136
3457
  "src/plugins/api-server/sse-manager.ts"() {
3137
3458
  "use strict";
3459
+ MAX_SSE_CONNECTIONS = 50;
3138
3460
  SSEManager = class {
3139
3461
  constructor(eventBus, getSessionStats, startedAt) {
3140
3462
  this.eventBus = eventBus;
@@ -3176,6 +3498,11 @@ var init_sse_manager = __esm({
3176
3498
  }, 15e3);
3177
3499
  }
3178
3500
  handleRequest(req, res) {
3501
+ if (this.sseConnections.size >= MAX_SSE_CONNECTIONS) {
3502
+ res.writeHead(503, { "Content-Type": "application/json" });
3503
+ res.end(JSON.stringify({ error: "Too many SSE connections" }));
3504
+ return;
3505
+ }
3179
3506
  const parsedUrl = new URL(req.url || "", "http://localhost");
3180
3507
  const sessionFilter = parsedUrl.searchParams.get("sessionId");
3181
3508
  console.log(`[sse] +connection total=${this.sseConnections.size + 1}`);
@@ -3189,7 +3516,6 @@ var init_sse_manager = __esm({
3189
3516
  };
3190
3517
  if (origin) {
3191
3518
  corsHeaders["Access-Control-Allow-Origin"] = origin;
3192
- corsHeaders["Access-Control-Allow-Credentials"] = "true";
3193
3519
  }
3194
3520
  res.socket?.setNoDelay(true);
3195
3521
  res.writeHead(200, corsHeaders);
@@ -3256,8 +3582,8 @@ data: ${JSON.stringify(data)}
3256
3582
  });
3257
3583
 
3258
3584
  // src/plugins/api-server/static-server.ts
3259
- import * as fs31 from "fs";
3260
- import * as path30 from "path";
3585
+ import * as fs32 from "fs";
3586
+ import * as path32 from "path";
3261
3587
  import { fileURLToPath } from "url";
3262
3588
  var MIME_TYPES, StaticServer;
3263
3589
  var init_static_server = __esm({
@@ -3281,16 +3607,16 @@ var init_static_server = __esm({
3281
3607
  this.uiDir = uiDir;
3282
3608
  if (!this.uiDir) {
3283
3609
  const __filename = fileURLToPath(import.meta.url);
3284
- const candidate = path30.resolve(path30.dirname(__filename), "../../ui/dist");
3285
- if (fs31.existsSync(path30.join(candidate, "index.html"))) {
3610
+ const candidate = path32.resolve(path32.dirname(__filename), "../../ui/dist");
3611
+ if (fs32.existsSync(path32.join(candidate, "index.html"))) {
3286
3612
  this.uiDir = candidate;
3287
3613
  }
3288
3614
  if (!this.uiDir) {
3289
- const publishCandidate = path30.resolve(
3290
- path30.dirname(__filename),
3615
+ const publishCandidate = path32.resolve(
3616
+ path32.dirname(__filename),
3291
3617
  "../ui"
3292
3618
  );
3293
- if (fs31.existsSync(path30.join(publishCandidate, "index.html"))) {
3619
+ if (fs32.existsSync(path32.join(publishCandidate, "index.html"))) {
3294
3620
  this.uiDir = publishCandidate;
3295
3621
  }
3296
3622
  }
@@ -3302,12 +3628,23 @@ var init_static_server = __esm({
3302
3628
  serve(req, res) {
3303
3629
  if (!this.uiDir) return false;
3304
3630
  const urlPath = (req.url || "/").split("?")[0];
3305
- const safePath = path30.normalize(urlPath);
3306
- const filePath = path30.join(this.uiDir, safePath);
3307
- if (!filePath.startsWith(this.uiDir + path30.sep) && filePath !== this.uiDir)
3631
+ const safePath = path32.normalize(urlPath);
3632
+ const filePath = path32.join(this.uiDir, safePath);
3633
+ if (!filePath.startsWith(this.uiDir + path32.sep) && filePath !== this.uiDir)
3308
3634
  return false;
3309
- if (fs31.existsSync(filePath) && fs31.statSync(filePath).isFile()) {
3310
- const ext = path30.extname(filePath);
3635
+ let realFilePath;
3636
+ try {
3637
+ realFilePath = fs32.realpathSync(filePath);
3638
+ } catch {
3639
+ realFilePath = null;
3640
+ }
3641
+ if (realFilePath !== null) {
3642
+ const realUiDir = fs32.realpathSync(this.uiDir);
3643
+ if (!realFilePath.startsWith(realUiDir + path32.sep) && realFilePath !== realUiDir)
3644
+ return false;
3645
+ }
3646
+ if (realFilePath !== null && fs32.existsSync(realFilePath) && fs32.statSync(realFilePath).isFile()) {
3647
+ const ext = path32.extname(filePath);
3311
3648
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
3312
3649
  const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
3313
3650
  const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
@@ -3315,16 +3652,16 @@ var init_static_server = __esm({
3315
3652
  "Content-Type": contentType,
3316
3653
  "Cache-Control": cacheControl
3317
3654
  });
3318
- fs31.createReadStream(filePath).pipe(res);
3655
+ fs32.createReadStream(realFilePath).pipe(res);
3319
3656
  return true;
3320
3657
  }
3321
- const indexPath = path30.join(this.uiDir, "index.html");
3322
- if (fs31.existsSync(indexPath)) {
3658
+ const indexPath = path32.join(this.uiDir, "index.html");
3659
+ if (fs32.existsSync(indexPath)) {
3323
3660
  res.writeHead(200, {
3324
3661
  "Content-Type": "text/html; charset=utf-8",
3325
3662
  "Cache-Control": "no-cache"
3326
3663
  });
3327
- fs31.createReadStream(indexPath).pipe(res);
3664
+ fs32.createReadStream(indexPath).pipe(res);
3328
3665
  return true;
3329
3666
  }
3330
3667
  return false;
@@ -3479,9 +3816,9 @@ var init_exports = __esm({
3479
3816
  });
3480
3817
 
3481
3818
  // src/plugins/context/context-cache.ts
3482
- import * as fs32 from "fs";
3483
- import * as path31 from "path";
3484
- import * as crypto from "crypto";
3819
+ import * as fs33 from "fs";
3820
+ import * as path33 from "path";
3821
+ import * as crypto2 from "crypto";
3485
3822
  var DEFAULT_TTL_MS, ContextCache;
3486
3823
  var init_context_cache = __esm({
3487
3824
  "src/plugins/context/context-cache.ts"() {
@@ -3491,29 +3828,29 @@ var init_context_cache = __esm({
3491
3828
  constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
3492
3829
  this.cacheDir = cacheDir;
3493
3830
  this.ttlMs = ttlMs;
3494
- fs32.mkdirSync(cacheDir, { recursive: true });
3831
+ fs33.mkdirSync(cacheDir, { recursive: true });
3495
3832
  }
3496
3833
  keyHash(repoPath, queryKey) {
3497
- return crypto.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
3834
+ return crypto2.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
3498
3835
  }
3499
3836
  filePath(repoPath, queryKey) {
3500
- return path31.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
3837
+ return path33.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
3501
3838
  }
3502
3839
  get(repoPath, queryKey) {
3503
3840
  const fp = this.filePath(repoPath, queryKey);
3504
3841
  try {
3505
- const stat = fs32.statSync(fp);
3842
+ const stat = fs33.statSync(fp);
3506
3843
  if (Date.now() - stat.mtimeMs > this.ttlMs) {
3507
- fs32.unlinkSync(fp);
3844
+ fs33.unlinkSync(fp);
3508
3845
  return null;
3509
3846
  }
3510
- return JSON.parse(fs32.readFileSync(fp, "utf-8"));
3847
+ return JSON.parse(fs33.readFileSync(fp, "utf-8"));
3511
3848
  } catch {
3512
3849
  return null;
3513
3850
  }
3514
3851
  }
3515
3852
  set(repoPath, queryKey, result) {
3516
- fs32.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
3853
+ fs33.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
3517
3854
  }
3518
3855
  };
3519
3856
  }
@@ -3521,7 +3858,7 @@ var init_context_cache = __esm({
3521
3858
 
3522
3859
  // src/plugins/context/context-manager.ts
3523
3860
  import * as os15 from "os";
3524
- import * as path32 from "path";
3861
+ import * as path34 from "path";
3525
3862
  var ContextManager;
3526
3863
  var init_context_manager = __esm({
3527
3864
  "src/plugins/context/context-manager.ts"() {
@@ -3532,7 +3869,7 @@ var init_context_manager = __esm({
3532
3869
  cache;
3533
3870
  historyStore;
3534
3871
  constructor(cachePath) {
3535
- this.cache = new ContextCache(cachePath ?? path32.join(os15.homedir(), ".openacp", "cache", "entire"));
3872
+ this.cache = new ContextCache(cachePath ?? path34.join(os15.homedir(), ".openacp", "cache", "entire"));
3536
3873
  }
3537
3874
  setHistoryStore(store) {
3538
3875
  this.historyStore = store;
@@ -4433,8 +4770,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
4433
4770
  }
4434
4771
  if (lowerName === "grep") {
4435
4772
  const pattern = args.pattern ?? "";
4436
- const path33 = args.path ?? "";
4437
- return pattern ? `\u{1F50D} Grep "${pattern}"${path33 ? ` in ${path33}` : ""}` : `\u{1F527} ${name}`;
4773
+ const path35 = args.path ?? "";
4774
+ return pattern ? `\u{1F50D} Grep "${pattern}"${path35 ? ` in ${path35}` : ""}` : `\u{1F527} ${name}`;
4438
4775
  }
4439
4776
  if (lowerName === "glob") {
4440
4777
  const pattern = args.pattern ?? "";
@@ -4470,8 +4807,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
4470
4807
  }
4471
4808
  if (lowerName === "grep") {
4472
4809
  const pattern = args.pattern ?? "";
4473
- const path33 = args.path ?? "";
4474
- return pattern ? `"${pattern}"${path33 ? ` in ${path33}` : ""}` : name;
4810
+ const path35 = args.path ?? "";
4811
+ return pattern ? `"${pattern}"${path35 ? ` in ${path35}` : ""}` : name;
4475
4812
  }
4476
4813
  if (lowerName === "glob") {
4477
4814
  return String(args.pattern ?? name);
@@ -5735,7 +6072,7 @@ function setupDangerousModeCallbacks(bot, core) {
5735
6072
  }).catch(() => {
5736
6073
  });
5737
6074
  }
5738
- log21.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
6075
+ log22.info({ sessionId, wantOn }, "Bypass permissions toggled via button");
5739
6076
  try {
5740
6077
  await ctx.editMessageText(buildSessionStatusText(session), {
5741
6078
  parse_mode: "HTML",
@@ -5758,7 +6095,7 @@ function setupDangerousModeCallbacks(bot, core) {
5758
6095
  const newDangerousMode = !(record.clientOverrides?.bypassPermissions ?? record.dangerousMode ?? false);
5759
6096
  core.sessionManager.patchRecord(sessionId, { clientOverrides: { bypassPermissions: newDangerousMode } }).catch(() => {
5760
6097
  });
5761
- log21.info(
6098
+ log22.info(
5762
6099
  { sessionId, dangerousMode: newDangerousMode },
5763
6100
  "Bypass permissions toggled via button (store-only, session not in memory)"
5764
6101
  );
@@ -5943,14 +6280,14 @@ async function handleRestart(ctx, core) {
5943
6280
  await new Promise((r) => setTimeout(r, 500));
5944
6281
  await core.requestRestart();
5945
6282
  }
5946
- var log21, OUTPUT_MODE_LABELS;
6283
+ var log22, OUTPUT_MODE_LABELS;
5947
6284
  var init_admin = __esm({
5948
6285
  "src/plugins/telegram/commands/admin.ts"() {
5949
6286
  "use strict";
5950
6287
  init_bypass_detection();
5951
6288
  init_formatting();
5952
6289
  init_log();
5953
- log21 = createChildLogger({ module: "telegram-cmd-admin" });
6290
+ log22 = createChildLogger({ module: "telegram-cmd-admin" });
5954
6291
  OUTPUT_MODE_LABELS = {
5955
6292
  low: "\u{1F507} Low",
5956
6293
  medium: "\u{1F4CA} Medium",
@@ -5965,7 +6302,7 @@ function botFromCtx(ctx) {
5965
6302
  return { api: ctx.api };
5966
6303
  }
5967
6304
  async function createSessionDirect(ctx, core, chatId, agentName, workspace, onControlMessage) {
5968
- log22.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
6305
+ log23.info({ userId: ctx.from?.id, agentName, workspace }, "New session command (direct)");
5969
6306
  let threadId;
5970
6307
  try {
5971
6308
  const topicName = `\u{1F504} New Session`;
@@ -5994,7 +6331,7 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onCo
5994
6331
  onControlMessage?.(session.id, controlMsg.message_id);
5995
6332
  return threadId ?? null;
5996
6333
  } catch (err) {
5997
- log22.error({ err }, "Session creation failed");
6334
+ log23.error({ err }, "Session creation failed");
5998
6335
  if (threadId) {
5999
6336
  try {
6000
6337
  await ctx.api.deleteForumTopic(chatId, threadId);
@@ -6184,7 +6521,7 @@ Agent: <code>${escapeHtml(agentKey)}</code>
6184
6521
  }
6185
6522
  });
6186
6523
  }
6187
- var log22, WS_CACHE_MAX, workspaceCache, nextWsId;
6524
+ var log23, WS_CACHE_MAX, workspaceCache, nextWsId;
6188
6525
  var init_new_session = __esm({
6189
6526
  "src/plugins/telegram/commands/new-session.ts"() {
6190
6527
  "use strict";
@@ -6192,7 +6529,7 @@ var init_new_session = __esm({
6192
6529
  init_topics();
6193
6530
  init_log();
6194
6531
  init_admin();
6195
- log22 = createChildLogger({ module: "telegram-cmd-new-session" });
6532
+ log23 = createChildLogger({ module: "telegram-cmd-new-session" });
6196
6533
  WS_CACHE_MAX = 50;
6197
6534
  workspaceCache = /* @__PURE__ */ new Map();
6198
6535
  nextWsId = 0;
@@ -6256,7 +6593,7 @@ ${lines.join("\n")}${truncated}`,
6256
6593
  { parse_mode: "HTML", reply_markup: keyboard }
6257
6594
  );
6258
6595
  } catch (err) {
6259
- log23.error({ err }, "handleTopics error");
6596
+ log24.error({ err }, "handleTopics error");
6260
6597
  await ctx.reply("\u274C Failed to list sessions.", { parse_mode: "HTML" }).catch(() => {
6261
6598
  });
6262
6599
  }
@@ -6277,13 +6614,13 @@ async function handleCleanup(ctx, core, chatId, statuses) {
6277
6614
  try {
6278
6615
  await ctx.api.deleteForumTopic(chatId, topicId);
6279
6616
  } catch (err) {
6280
- log23.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
6617
+ log24.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
6281
6618
  }
6282
6619
  }
6283
6620
  await core.sessionManager.removeRecord(record.sessionId);
6284
6621
  deleted++;
6285
6622
  } catch (err) {
6286
- log23.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
6623
+ log24.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
6287
6624
  failed++;
6288
6625
  }
6289
6626
  }
@@ -6354,7 +6691,7 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
6354
6691
  try {
6355
6692
  await core.sessionManager.cancelSession(record.sessionId);
6356
6693
  } catch (err) {
6357
- log23.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
6694
+ log24.warn({ err, sessionId: record.sessionId }, "Failed to cancel session during cleanup");
6358
6695
  }
6359
6696
  }
6360
6697
  const topicId = record.platform?.topicId;
@@ -6362,13 +6699,13 @@ async function handleCleanupEverythingConfirmed(ctx, core, chatId, systemTopicId
6362
6699
  try {
6363
6700
  await ctx.api.deleteForumTopic(chatId, topicId);
6364
6701
  } catch (err) {
6365
- log23.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
6702
+ log24.warn({ err, sessionId: record.sessionId, topicId }, "Failed to delete forum topic during cleanup");
6366
6703
  }
6367
6704
  }
6368
6705
  await core.sessionManager.removeRecord(record.sessionId);
6369
6706
  deleted++;
6370
6707
  } catch (err) {
6371
- log23.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
6708
+ log24.error({ err, sessionId: record.sessionId }, "Failed to cleanup session");
6372
6709
  failed++;
6373
6710
  }
6374
6711
  }
@@ -6436,13 +6773,13 @@ async function handleArchiveConfirm(ctx, core, chatId) {
6436
6773
  }
6437
6774
  }
6438
6775
  }
6439
- var log23;
6776
+ var log24;
6440
6777
  var init_session = __esm({
6441
6778
  "src/plugins/telegram/commands/session.ts"() {
6442
6779
  "use strict";
6443
6780
  init_formatting();
6444
6781
  init_log();
6445
- log23 = createChildLogger({ module: "telegram-cmd-session" });
6782
+ log24 = createChildLogger({ module: "telegram-cmd-session" });
6446
6783
  }
6447
6784
  });
6448
6785
 
@@ -7085,7 +7422,7 @@ var init_agents2 = __esm({
7085
7422
  // src/plugins/telegram/commands/resume.ts
7086
7423
  function setupResumeCallbacks(_bot, _core, _chatId, _onControlMessage) {
7087
7424
  }
7088
- var log24;
7425
+ var log25;
7089
7426
  var init_resume = __esm({
7090
7427
  "src/plugins/telegram/commands/resume.ts"() {
7091
7428
  "use strict";
@@ -7095,7 +7432,7 @@ var init_resume = __esm({
7095
7432
  init_topics();
7096
7433
  init_admin();
7097
7434
  init_log();
7098
- log24 = createChildLogger({ module: "telegram-cmd-resume" });
7435
+ log25 = createChildLogger({ module: "telegram-cmd-resume" });
7099
7436
  }
7100
7437
  });
7101
7438
 
@@ -7280,7 +7617,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
7280
7617
  } catch {
7281
7618
  }
7282
7619
  } catch (err) {
7283
- log25.error({ err, fieldPath }, "Failed to toggle config");
7620
+ log26.error({ err, fieldPath }, "Failed to toggle config");
7284
7621
  try {
7285
7622
  await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
7286
7623
  } catch {
@@ -7363,7 +7700,7 @@ Tap to change:`, {
7363
7700
  } catch {
7364
7701
  }
7365
7702
  } catch (err) {
7366
- log25.error({ err, fieldPath }, "Failed to set config");
7703
+ log26.error({ err, fieldPath }, "Failed to set config");
7367
7704
  try {
7368
7705
  await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
7369
7706
  } catch {
@@ -7421,13 +7758,13 @@ Tap to change:`, {
7421
7758
  }
7422
7759
  });
7423
7760
  }
7424
- var log25;
7761
+ var log26;
7425
7762
  var init_settings = __esm({
7426
7763
  "src/plugins/telegram/commands/settings.ts"() {
7427
7764
  "use strict";
7428
7765
  init_config_registry();
7429
7766
  init_log();
7430
- log25 = createChildLogger({ module: "telegram-settings" });
7767
+ log26 = createChildLogger({ module: "telegram-settings" });
7431
7768
  }
7432
7769
  });
7433
7770
 
@@ -7474,7 +7811,7 @@ async function handleDoctor(ctx) {
7474
7811
  reply_markup: keyboard
7475
7812
  });
7476
7813
  } catch (err) {
7477
- log26.error({ err }, "Doctor command failed");
7814
+ log27.error({ err }, "Doctor command failed");
7478
7815
  await ctx.api.editMessageText(
7479
7816
  ctx.chat.id,
7480
7817
  statusMsg.message_id,
@@ -7523,7 +7860,7 @@ function setupDoctorCallbacks(bot) {
7523
7860
  }
7524
7861
  }
7525
7862
  } catch (err) {
7526
- log26.error({ err, index }, "Doctor fix callback failed");
7863
+ log27.error({ err, index }, "Doctor fix callback failed");
7527
7864
  }
7528
7865
  });
7529
7866
  bot.callbackQuery("m:doctor", async (ctx) => {
@@ -7534,13 +7871,13 @@ function setupDoctorCallbacks(bot) {
7534
7871
  await handleDoctor(ctx);
7535
7872
  });
7536
7873
  }
7537
- var log26, pendingFixesStore;
7874
+ var log27, pendingFixesStore;
7538
7875
  var init_doctor2 = __esm({
7539
7876
  "src/plugins/telegram/commands/doctor.ts"() {
7540
7877
  "use strict";
7541
7878
  init_doctor();
7542
7879
  init_log();
7543
- log26 = createChildLogger({ module: "telegram-cmd-doctor" });
7880
+ log27 = createChildLogger({ module: "telegram-cmd-doctor" });
7544
7881
  pendingFixesStore = /* @__PURE__ */ new Map();
7545
7882
  }
7546
7883
  });
@@ -7599,13 +7936,13 @@ function setupTunnelCallbacks(bot, core) {
7599
7936
  }
7600
7937
  });
7601
7938
  }
7602
- var log27;
7939
+ var log28;
7603
7940
  var init_tunnel2 = __esm({
7604
7941
  "src/plugins/telegram/commands/tunnel.ts"() {
7605
7942
  "use strict";
7606
7943
  init_formatting();
7607
7944
  init_log();
7608
- log27 = createChildLogger({ module: "telegram-cmd-tunnel" });
7945
+ log28 = createChildLogger({ module: "telegram-cmd-tunnel" });
7609
7946
  }
7610
7947
  });
7611
7948
 
@@ -7619,10 +7956,10 @@ async function executeSwitchAgent(ctx, core, sessionId, agentName) {
7619
7956
  `Switched to <b>${escapeHtml(agentName)}</b> (${status})`,
7620
7957
  { parse_mode: "HTML" }
7621
7958
  );
7622
- log28.info({ sessionId, agentName, resumed }, "Agent switched via /switch");
7959
+ log29.info({ sessionId, agentName, resumed }, "Agent switched via /switch");
7623
7960
  } catch (err) {
7624
7961
  await ctx.reply(`Failed to switch agent: ${escapeHtml(String(err.message || err))}`);
7625
- log28.warn({ sessionId, agentName, err: err.message }, "Agent switch failed");
7962
+ log29.warn({ sessionId, agentName, err: err.message }, "Agent switch failed");
7626
7963
  }
7627
7964
  }
7628
7965
  function setupSwitchCallbacks(bot, core) {
@@ -7667,13 +8004,13 @@ Switch to <b>${escapeHtml(agentName)}</b> anyway?`,
7667
8004
  await executeSwitchAgent(ctx, core, session.id, data);
7668
8005
  });
7669
8006
  }
7670
- var log28;
8007
+ var log29;
7671
8008
  var init_switch = __esm({
7672
8009
  "src/plugins/telegram/commands/switch.ts"() {
7673
8010
  "use strict";
7674
8011
  init_formatting();
7675
8012
  init_log();
7676
- log28 = createChildLogger({ module: "telegram-cmd-switch" });
8013
+ log29 = createChildLogger({ module: "telegram-cmd-switch" });
7677
8014
  }
7678
8015
  });
7679
8016
 
@@ -8028,14 +8365,14 @@ var init_commands = __esm({
8028
8365
  // src/plugins/telegram/permissions.ts
8029
8366
  import { InlineKeyboard as InlineKeyboard11 } from "grammy";
8030
8367
  import { nanoid as nanoid2 } from "nanoid";
8031
- var log29, PermissionHandler;
8368
+ var log30, PermissionHandler;
8032
8369
  var init_permissions = __esm({
8033
8370
  "src/plugins/telegram/permissions.ts"() {
8034
8371
  "use strict";
8035
8372
  init_formatting();
8036
8373
  init_topics();
8037
8374
  init_log();
8038
- log29 = createChildLogger({ module: "telegram-permissions" });
8375
+ log30 = createChildLogger({ module: "telegram-permissions" });
8039
8376
  PermissionHandler = class {
8040
8377
  constructor(bot, chatId, getSession, sendNotification) {
8041
8378
  this.bot = bot;
@@ -8095,7 +8432,7 @@ ${escapeHtml(request.description)}`,
8095
8432
  }
8096
8433
  const session = this.getSession(pending.sessionId);
8097
8434
  const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
8098
- log29.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
8435
+ log30.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
8099
8436
  if (session?.permissionGate.requestId === pending.requestId) {
8100
8437
  session.permissionGate.resolve(optionId);
8101
8438
  }
@@ -8115,7 +8452,7 @@ ${escapeHtml(request.description)}`,
8115
8452
  });
8116
8453
 
8117
8454
  // src/plugins/telegram/activity.ts
8118
- var log30, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker2;
8455
+ var log31, THINKING_REFRESH_MS, THINKING_MAX_MS, ThinkingIndicator, ToolCard, ActivityTracker2;
8119
8456
  var init_activity = __esm({
8120
8457
  "src/plugins/telegram/activity.ts"() {
8121
8458
  "use strict";
@@ -8125,7 +8462,7 @@ var init_activity = __esm({
8125
8462
  init_stream_accumulator();
8126
8463
  init_stream_accumulator();
8127
8464
  init_display_spec_builder();
8128
- log30 = createChildLogger({ module: "telegram:activity" });
8465
+ log31 = createChildLogger({ module: "telegram:activity" });
8129
8466
  THINKING_REFRESH_MS = 15e3;
8130
8467
  THINKING_MAX_MS = 3 * 60 * 1e3;
8131
8468
  ThinkingIndicator = class {
@@ -8166,7 +8503,7 @@ var init_activity = __esm({
8166
8503
  }
8167
8504
  }
8168
8505
  } catch (err) {
8169
- log30.warn({ err }, "ThinkingIndicator.show() failed");
8506
+ log31.warn({ err }, "ThinkingIndicator.show() failed");
8170
8507
  } finally {
8171
8508
  this.sending = false;
8172
8509
  }
@@ -8334,7 +8671,7 @@ var init_activity = __esm({
8334
8671
  this.tracer?.log("telegram", { action: "telegram:delete:overflow", sessionId: this.sessionId, msgId: staleId });
8335
8672
  }
8336
8673
  } catch (err) {
8337
- log30.warn({ err }, "[ToolCard] send/edit failed");
8674
+ log31.warn({ err }, "[ToolCard] send/edit failed");
8338
8675
  }
8339
8676
  }
8340
8677
  };
@@ -8438,7 +8775,7 @@ var init_activity = __esm({
8438
8775
  const entry = this.toolStateMap.merge(id, status, rawInput, content, viewerLinks, diffStats);
8439
8776
  if (!existed || !entry) return;
8440
8777
  if (viewerLinks || entry.viewerLinks) {
8441
- log30.debug({ toolId: id, status, hasIncomingLinks: !!viewerLinks, hasEntryLinks: !!entry.viewerLinks, entryLinks: entry.viewerLinks }, "toolUpdate: viewer links trace");
8778
+ log31.debug({ toolId: id, status, hasIncomingLinks: !!viewerLinks, hasEntryLinks: !!entry.viewerLinks, entryLinks: entry.viewerLinks }, "toolUpdate: viewer links trace");
8442
8779
  }
8443
8780
  const spec = this.specBuilder.buildToolSpec(entry, this._outputMode, this.sessionContext);
8444
8781
  this.toolCard.updateFromSpec(spec);
@@ -8757,13 +9094,13 @@ var init_draft_manager = __esm({
8757
9094
  });
8758
9095
 
8759
9096
  // src/plugins/telegram/skill-command-manager.ts
8760
- var log31, SkillCommandManager;
9097
+ var log32, SkillCommandManager;
8761
9098
  var init_skill_command_manager = __esm({
8762
9099
  "src/plugins/telegram/skill-command-manager.ts"() {
8763
9100
  "use strict";
8764
9101
  init_commands();
8765
9102
  init_log();
8766
- log31 = createChildLogger({ module: "skill-commands" });
9103
+ log32 = createChildLogger({ module: "skill-commands" });
8767
9104
  SkillCommandManager = class {
8768
9105
  // sessionId → pinned msgId
8769
9106
  constructor(bot, chatId, sendQueue, sessionManager) {
@@ -8829,7 +9166,7 @@ var init_skill_command_manager = __esm({
8829
9166
  disable_notification: true
8830
9167
  });
8831
9168
  } catch (err) {
8832
- log31.error({ err, sessionId }, "Failed to send skill commands");
9169
+ log32.error({ err, sessionId }, "Failed to send skill commands");
8833
9170
  }
8834
9171
  }
8835
9172
  async cleanup(sessionId) {
@@ -8950,7 +9287,7 @@ function patchedFetch(input2, init) {
8950
9287
  }
8951
9288
  return fetch(input2, init);
8952
9289
  }
8953
- var log32, TelegramAdapter;
9290
+ var log33, TelegramAdapter;
8954
9291
  var init_adapter = __esm({
8955
9292
  "src/plugins/telegram/adapter.ts"() {
8956
9293
  "use strict";
@@ -8969,7 +9306,7 @@ var init_adapter = __esm({
8969
9306
  init_messaging_adapter();
8970
9307
  init_renderer2();
8971
9308
  init_output_mode_resolver();
8972
- log32 = createChildLogger({ module: "telegram" });
9309
+ log33 = createChildLogger({ module: "telegram" });
8973
9310
  TelegramAdapter = class extends MessagingAdapter {
8974
9311
  name = "telegram";
8975
9312
  renderer = new TelegramRenderer();
@@ -9002,6 +9339,8 @@ var init_adapter = __esm({
9002
9339
  _pendingSkillCommands = /* @__PURE__ */ new Map();
9003
9340
  /** Control message IDs per session (for updating status text/buttons) */
9004
9341
  controlMsgIds = /* @__PURE__ */ new Map();
9342
+ _threadReadyHandler;
9343
+ _configChangedHandler;
9005
9344
  /** Store control message ID in memory + persist to session record */
9006
9345
  storeControlMsgId(sessionId, msgId) {
9007
9346
  this.controlMsgIds.set(sessionId, msgId);
@@ -9088,7 +9427,7 @@ var init_adapter = __esm({
9088
9427
  );
9089
9428
  this.bot.catch((err) => {
9090
9429
  const rootCause = err.error instanceof Error ? err.error : err;
9091
- log32.error({ err: rootCause }, "Telegram bot error");
9430
+ log33.error({ err: rootCause }, "Telegram bot error");
9092
9431
  });
9093
9432
  this.bot.api.config.use(async (prev, method, payload, signal) => {
9094
9433
  const maxRetries = 3;
@@ -9106,7 +9445,7 @@ var init_adapter = __esm({
9106
9445
  if (rateLimitedMethods.includes(method)) {
9107
9446
  this.sendQueue.onRateLimited();
9108
9447
  }
9109
- log32.warn(
9448
+ log33.warn(
9110
9449
  { method, retryAfter, attempt: attempt + 1 },
9111
9450
  "Rate limited by Telegram, retrying"
9112
9451
  );
@@ -9301,7 +9640,7 @@ ${p}` : p;
9301
9640
  }
9302
9641
  );
9303
9642
  this.permissionHandler.setupCallbackHandler();
9304
- this.core.eventBus.on("session:threadReady", ({ sessionId, channelId, threadId }) => {
9643
+ this._threadReadyHandler = ({ sessionId, channelId, threadId }) => {
9305
9644
  if (channelId !== "telegram") return;
9306
9645
  const session = this.core.sessionManager.getSession(sessionId);
9307
9646
  if (!session) return;
@@ -9328,17 +9667,19 @@ ${p}` : p;
9328
9667
  ).then((msg) => {
9329
9668
  if (msg) this.storeControlMsgId(sessionId, msg.message_id);
9330
9669
  }).catch((err) => {
9331
- log32.warn({ err, sessionId }, "Failed to send initial messages for new session");
9670
+ log33.warn({ err, sessionId }, "Failed to send initial messages for new session");
9332
9671
  });
9333
- });
9334
- this.core.eventBus.on("session:configChanged", ({ sessionId }) => {
9672
+ };
9673
+ this.core.eventBus.on("session:threadReady", this._threadReadyHandler);
9674
+ this._configChangedHandler = ({ sessionId }) => {
9335
9675
  this.updateControlMessage(sessionId).catch(() => {
9336
9676
  });
9337
- });
9677
+ };
9678
+ this.core.eventBus.on("session:configChanged", this._configChangedHandler);
9338
9679
  this.setupRoutes();
9339
9680
  this.bot.start({
9340
9681
  allowed_updates: ["message", "callback_query"],
9341
- onStart: () => log32.info(
9682
+ onStart: () => log33.info(
9342
9683
  { chatId: this.telegramConfig.chatId },
9343
9684
  "Telegram bot started"
9344
9685
  )
@@ -9365,12 +9706,12 @@ ${p}` : p;
9365
9706
  )
9366
9707
  });
9367
9708
  } catch (err) {
9368
- log32.warn({ err }, "Failed to send welcome message");
9709
+ log33.warn({ err }, "Failed to send welcome message");
9369
9710
  }
9370
9711
  try {
9371
9712
  await this.core.assistantManager.spawn("telegram", String(this.assistantTopicId));
9372
9713
  } catch (err) {
9373
- log32.error({ err }, "Failed to spawn assistant");
9714
+ log33.error({ err }, "Failed to spawn assistant");
9374
9715
  }
9375
9716
  }
9376
9717
  /**
@@ -9384,7 +9725,7 @@ ${p}` : p;
9384
9725
  } catch (err) {
9385
9726
  if (attempt === maxRetries) throw err;
9386
9727
  const delay = baseDelayMs * Math.pow(2, attempt - 1);
9387
- log32.warn(
9728
+ log33.warn(
9388
9729
  { err, attempt, maxRetries, delayMs: delay, operation: label },
9389
9730
  `${label} failed, retrying in ${delay}ms`
9390
9731
  );
@@ -9404,7 +9745,7 @@ ${p}` : p;
9404
9745
  }),
9405
9746
  "setMyCommands"
9406
9747
  ).catch((err) => {
9407
- log32.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
9748
+ log33.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
9408
9749
  });
9409
9750
  }
9410
9751
  async stop() {
@@ -9412,9 +9753,17 @@ ${p}` : p;
9412
9753
  tracker.destroy();
9413
9754
  }
9414
9755
  this.sessionTrackers.clear();
9756
+ if (this._threadReadyHandler) {
9757
+ this.core.eventBus.off("session:threadReady", this._threadReadyHandler);
9758
+ this._threadReadyHandler = void 0;
9759
+ }
9760
+ if (this._configChangedHandler) {
9761
+ this.core.eventBus.off("session:configChanged", this._configChangedHandler);
9762
+ this._configChangedHandler = void 0;
9763
+ }
9415
9764
  this.sendQueue.clear();
9416
9765
  await this.bot.stop();
9417
- log32.info("Telegram bot stopped");
9766
+ log33.info("Telegram bot stopped");
9418
9767
  }
9419
9768
  // --- CommandRegistry response rendering ---
9420
9769
  async renderCommandResponse(response, chatId, topicId) {
@@ -9531,7 +9880,7 @@ ${lines.join("\n")}`;
9531
9880
  threadId: String(threadId),
9532
9881
  userId: String(ctx.from.id),
9533
9882
  text: forwardText
9534
- }).catch((err) => log32.error({ err }, "handleMessage error"));
9883
+ }).catch((err) => log33.error({ err }, "handleMessage error"));
9535
9884
  });
9536
9885
  this.bot.on("message:photo", async (ctx) => {
9537
9886
  const threadId = ctx.message.message_thread_id;
@@ -9620,7 +9969,7 @@ ${lines.join("\n")}`;
9620
9969
  if (session.archiving) return;
9621
9970
  const threadId = Number(session.threadId);
9622
9971
  if (!threadId || isNaN(threadId)) {
9623
- log32.warn(
9972
+ log33.warn(
9624
9973
  { sessionId, threadId: session.threadId },
9625
9974
  "Session has no valid threadId, skipping message"
9626
9975
  );
@@ -9636,7 +9985,7 @@ ${lines.join("\n")}`;
9636
9985
  this._sessionThreadIds.delete(sessionId);
9637
9986
  }
9638
9987
  }).catch((err) => {
9639
- log32.warn({ err, sessionId }, "Dispatch queue error");
9988
+ log33.warn({ err, sessionId }, "Dispatch queue error");
9640
9989
  });
9641
9990
  this._dispatchQueues.set(sessionId, next);
9642
9991
  await next;
@@ -9758,7 +10107,7 @@ ${lines.join("\n")}`;
9758
10107
  );
9759
10108
  usageMsgId = result?.message_id;
9760
10109
  } catch (err) {
9761
- log32.warn({ err, sessionId }, "Failed to send usage message");
10110
+ log33.warn({ err, sessionId }, "Failed to send usage message");
9762
10111
  }
9763
10112
  if (this.notificationTopicId && sessionId !== this.core.assistantManager?.get("telegram")?.id) {
9764
10113
  const sess = this.core.sessionManager.getSession(sessionId);
@@ -9786,7 +10135,7 @@ Task completed.
9786
10135
  if (!content.attachment) return;
9787
10136
  const { attachment } = content;
9788
10137
  if (attachment.size > 50 * 1024 * 1024) {
9789
- log32.warn(
10138
+ log33.warn(
9790
10139
  {
9791
10140
  sessionId,
9792
10141
  fileName: attachment.fileName,
@@ -9830,7 +10179,7 @@ Task completed.
9830
10179
  );
9831
10180
  }
9832
10181
  } catch (err) {
9833
- log32.error(
10182
+ log33.error(
9834
10183
  { err, sessionId, fileName: attachment.fileName },
9835
10184
  "Failed to send attachment"
9836
10185
  );
@@ -9929,7 +10278,7 @@ Task completed.
9929
10278
  }
9930
10279
  async sendPermissionRequest(sessionId, request) {
9931
10280
  this.getTracer(sessionId)?.log("telegram", { action: "permission:send", sessionId, requestId: request.id, description: request.description });
9932
- log32.info({ sessionId, requestId: request.id }, "Permission request sent");
10281
+ log33.info({ sessionId, requestId: request.id }, "Permission request sent");
9933
10282
  const session = this.core.sessionManager.getSession(sessionId);
9934
10283
  if (!session) return;
9935
10284
  await this.sendQueue.enqueue(
@@ -9939,7 +10288,7 @@ Task completed.
9939
10288
  async sendNotification(notification) {
9940
10289
  this.getTracer(notification.sessionId)?.log("telegram", { action: "notification:send", sessionId: notification.sessionId, type: notification.type });
9941
10290
  if (notification.sessionId === this.core.assistantManager?.get("telegram")?.id) return;
9942
- log32.info(
10291
+ log33.info(
9943
10292
  { sessionId: notification.sessionId, type: notification.type },
9944
10293
  "Notification sent"
9945
10294
  );
@@ -9978,7 +10327,7 @@ Task completed.
9978
10327
  }
9979
10328
  async createSessionThread(sessionId, name) {
9980
10329
  this.getTracer(sessionId)?.log("telegram", { action: "thread:create", sessionId, name });
9981
- log32.info({ sessionId, name }, "Session topic created");
10330
+ log33.info({ sessionId, name }, "Session topic created");
9982
10331
  return String(
9983
10332
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
9984
10333
  );
@@ -9989,7 +10338,7 @@ Task completed.
9989
10338
  if (!session) return;
9990
10339
  const threadId = Number(session.threadId);
9991
10340
  if (!threadId) {
9992
- log32.debug({ sessionId, newName }, "Cannot rename thread \u2014 threadId not set yet");
10341
+ log33.debug({ sessionId, newName }, "Cannot rename thread \u2014 threadId not set yet");
9993
10342
  return;
9994
10343
  }
9995
10344
  await renameSessionTopic(
@@ -10008,7 +10357,7 @@ Task completed.
10008
10357
  try {
10009
10358
  await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
10010
10359
  } catch (err) {
10011
- log32.warn(
10360
+ log33.warn(
10012
10361
  { err, sessionId, topicId },
10013
10362
  "Failed to delete forum topic (may already be deleted)"
10014
10363
  );
@@ -10052,7 +10401,7 @@ Task completed.
10052
10401
  const buffer = Buffer.from(await response.arrayBuffer());
10053
10402
  return { buffer, filePath: file.file_path };
10054
10403
  } catch (err) {
10055
- log32.error({ err }, "Failed to download file from Telegram");
10404
+ log33.error({ err }, "Failed to download file from Telegram");
10056
10405
  return null;
10057
10406
  }
10058
10407
  }
@@ -10073,7 +10422,7 @@ Task completed.
10073
10422
  try {
10074
10423
  buffer = await this.fileService.convertOggToWav(buffer);
10075
10424
  } catch (err) {
10076
- log32.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
10425
+ log33.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
10077
10426
  fileName = "voice.ogg";
10078
10427
  mimeType = "audio/ogg";
10079
10428
  originalFilePath = void 0;
@@ -10105,7 +10454,7 @@ Task completed.
10105
10454
  userId: String(userId),
10106
10455
  text: text3,
10107
10456
  attachments: [att]
10108
- }).catch((err) => log32.error({ err }, "handleMessage error"));
10457
+ }).catch((err) => log33.error({ err }, "handleMessage error"));
10109
10458
  }
10110
10459
  async cleanupSkillCommands(sessionId) {
10111
10460
  this._pendingSkillCommands.delete(sessionId);
@@ -10230,12 +10579,150 @@ var StderrCapture = class {
10230
10579
  // src/core/index.ts
10231
10580
  init_config();
10232
10581
 
10233
- // src/core/agents/agent-instance.ts
10234
- import { spawn as spawn2, execFileSync } from "child_process";
10235
- import { Transform } from "stream";
10236
- import fs8 from "fs";
10237
- import path7 from "path";
10238
- import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
10582
+ // src/core/agents/agent-instance.ts
10583
+ import { spawn as spawn2, execFileSync } from "child_process";
10584
+ import { Transform } from "stream";
10585
+ import fs9 from "fs";
10586
+ import path8 from "path";
10587
+ import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
10588
+
10589
+ // src/core/security/path-guard.ts
10590
+ import fs6 from "fs";
10591
+ import path6 from "path";
10592
+ import ignore from "ignore";
10593
+ var DEFAULT_DENY_PATTERNS = [
10594
+ ".env",
10595
+ ".env.*",
10596
+ "*.key",
10597
+ "*.pem",
10598
+ ".ssh/",
10599
+ ".aws/",
10600
+ ".openacp/",
10601
+ "**/credentials*",
10602
+ "**/secrets*",
10603
+ "**/*.secret"
10604
+ ];
10605
+ var PathGuard = class {
10606
+ cwd;
10607
+ allowedPaths;
10608
+ ig;
10609
+ constructor(options) {
10610
+ try {
10611
+ this.cwd = fs6.realpathSync(path6.resolve(options.cwd));
10612
+ } catch {
10613
+ this.cwd = path6.resolve(options.cwd);
10614
+ }
10615
+ this.allowedPaths = options.allowedPaths.map((p) => {
10616
+ try {
10617
+ return fs6.realpathSync(path6.resolve(p));
10618
+ } catch {
10619
+ return path6.resolve(p);
10620
+ }
10621
+ });
10622
+ this.ig = ignore();
10623
+ this.ig.add(DEFAULT_DENY_PATTERNS);
10624
+ if (options.ignorePatterns.length > 0) {
10625
+ this.ig.add(options.ignorePatterns);
10626
+ }
10627
+ }
10628
+ validatePath(targetPath, operation) {
10629
+ const resolved = path6.resolve(targetPath);
10630
+ let realPath;
10631
+ try {
10632
+ realPath = fs6.realpathSync(resolved);
10633
+ } catch {
10634
+ realPath = resolved;
10635
+ }
10636
+ if (operation === "write" && path6.basename(realPath) === ".openacpignore") {
10637
+ return { allowed: false, reason: "Cannot write to .openacpignore" };
10638
+ }
10639
+ const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd + path6.sep);
10640
+ const isWithinAllowed = this.allowedPaths.some(
10641
+ (ap) => realPath === ap || realPath.startsWith(ap + path6.sep)
10642
+ );
10643
+ if (!isWithinCwd && !isWithinAllowed) {
10644
+ return {
10645
+ allowed: false,
10646
+ reason: `Path is outside workspace boundary: ${realPath}`
10647
+ };
10648
+ }
10649
+ if (isWithinCwd) {
10650
+ const relativePath = path6.relative(this.cwd, realPath);
10651
+ if (relativePath === ".openacpignore") {
10652
+ return { allowed: true, reason: "" };
10653
+ }
10654
+ if (relativePath && this.ig.ignores(relativePath)) {
10655
+ return {
10656
+ allowed: false,
10657
+ reason: `Path matches ignore pattern: ${relativePath}`
10658
+ };
10659
+ }
10660
+ }
10661
+ return { allowed: true, reason: "" };
10662
+ }
10663
+ addAllowedPath(p) {
10664
+ try {
10665
+ this.allowedPaths.push(fs6.realpathSync(path6.resolve(p)));
10666
+ } catch {
10667
+ this.allowedPaths.push(path6.resolve(p));
10668
+ }
10669
+ }
10670
+ static loadIgnoreFile(cwd) {
10671
+ const ignorePath = path6.join(cwd, ".openacpignore");
10672
+ try {
10673
+ const content = fs6.readFileSync(ignorePath, "utf-8");
10674
+ return content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
10675
+ } catch {
10676
+ return [];
10677
+ }
10678
+ }
10679
+ };
10680
+
10681
+ // src/core/security/env-filter.ts
10682
+ var DEFAULT_ENV_WHITELIST = [
10683
+ "PATH",
10684
+ "HOME",
10685
+ "SHELL",
10686
+ "LANG",
10687
+ "LC_*",
10688
+ "TERM",
10689
+ "USER",
10690
+ "LOGNAME",
10691
+ "TMPDIR",
10692
+ "XDG_*",
10693
+ "NODE_ENV",
10694
+ "EDITOR",
10695
+ // Git
10696
+ "GIT_*",
10697
+ "SSH_AUTH_SOCK",
10698
+ "SSH_AGENT_PID",
10699
+ // Terminal rendering
10700
+ "COLORTERM",
10701
+ "FORCE_COLOR",
10702
+ "NO_COLOR",
10703
+ "TERM_PROGRAM",
10704
+ "HOSTNAME"
10705
+ ];
10706
+ function matchesPattern(key, pattern) {
10707
+ if (pattern.endsWith("*")) {
10708
+ return key.startsWith(pattern.slice(0, -1));
10709
+ }
10710
+ return key === pattern;
10711
+ }
10712
+ function filterEnv(processEnv, agentEnv, whitelist) {
10713
+ const patterns = whitelist ?? DEFAULT_ENV_WHITELIST;
10714
+ const result = {};
10715
+ for (const [key, value] of Object.entries(processEnv)) {
10716
+ if (value === void 0) continue;
10717
+ if (patterns.some((p) => matchesPattern(key, p))) {
10718
+ result[key] = value;
10719
+ }
10720
+ }
10721
+ if (agentEnv) {
10722
+ Object.assign(result, agentEnv);
10723
+ }
10724
+ return result;
10725
+ }
10239
10726
 
10240
10727
  // src/core/utils/typed-emitter.ts
10241
10728
  var TypedEmitter = class {
@@ -10357,7 +10844,7 @@ var TerminalManager = class {
10357
10844
  }
10358
10845
  const childProcess = spawn(termCommand, args, {
10359
10846
  cwd: termCwd,
10360
- env: { ...process.env, ...env },
10847
+ env: filterEnv(process.env, env),
10361
10848
  shell: false
10362
10849
  });
10363
10850
  const state = {
@@ -10466,24 +10953,24 @@ var McpManager = class {
10466
10953
  };
10467
10954
 
10468
10955
  // src/core/utils/debug-tracer.ts
10469
- import fs7 from "fs";
10470
- import path6 from "path";
10956
+ import fs8 from "fs";
10957
+ import path7 from "path";
10471
10958
  var DEBUG_ENABLED = process.env.OPENACP_DEBUG === "true" || process.env.OPENACP_DEBUG === "1";
10472
10959
  var DebugTracer = class {
10473
10960
  constructor(sessionId, workingDirectory) {
10474
10961
  this.sessionId = sessionId;
10475
10962
  this.workingDirectory = workingDirectory;
10476
- this.logDir = path6.join(workingDirectory, ".log");
10963
+ this.logDir = path7.join(workingDirectory, ".log");
10477
10964
  }
10478
10965
  dirCreated = false;
10479
10966
  logDir;
10480
10967
  log(layer, data) {
10481
10968
  try {
10482
10969
  if (!this.dirCreated) {
10483
- fs7.mkdirSync(this.logDir, { recursive: true });
10970
+ fs8.mkdirSync(this.logDir, { recursive: true });
10484
10971
  this.dirCreated = true;
10485
10972
  }
10486
- const filePath = path6.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
10973
+ const filePath = path7.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
10487
10974
  const seen = /* @__PURE__ */ new WeakSet();
10488
10975
  const line = JSON.stringify({ ts: Date.now(), ...data }, (_key, value) => {
10489
10976
  if (typeof value === "object" && value !== null) {
@@ -10492,7 +10979,7 @@ var DebugTracer = class {
10492
10979
  }
10493
10980
  return value;
10494
10981
  }) + "\n";
10495
- fs7.appendFileSync(filePath, line);
10982
+ fs8.appendFileSync(filePath, line);
10496
10983
  } catch {
10497
10984
  }
10498
10985
  }
@@ -10510,11 +10997,11 @@ init_log();
10510
10997
  var log4 = createChildLogger({ module: "agent-instance" });
10511
10998
  function findPackageRoot(startDir) {
10512
10999
  let dir = startDir;
10513
- while (dir !== path7.dirname(dir)) {
10514
- if (fs8.existsSync(path7.join(dir, "package.json"))) {
11000
+ while (dir !== path8.dirname(dir)) {
11001
+ if (fs9.existsSync(path8.join(dir, "package.json"))) {
10515
11002
  return dir;
10516
11003
  }
10517
- dir = path7.dirname(dir);
11004
+ dir = path8.dirname(dir);
10518
11005
  }
10519
11006
  return startDir;
10520
11007
  }
@@ -10526,26 +11013,26 @@ function resolveAgentCommand(cmd) {
10526
11013
  }
10527
11014
  for (const root of searchRoots) {
10528
11015
  const packageDirs = [
10529
- path7.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
10530
- path7.resolve(root, "node_modules", cmd, "dist", "index.js")
11016
+ path8.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
11017
+ path8.resolve(root, "node_modules", cmd, "dist", "index.js")
10531
11018
  ];
10532
11019
  for (const jsPath of packageDirs) {
10533
- if (fs8.existsSync(jsPath)) {
11020
+ if (fs9.existsSync(jsPath)) {
10534
11021
  return { command: process.execPath, args: [jsPath] };
10535
11022
  }
10536
11023
  }
10537
11024
  }
10538
11025
  for (const root of searchRoots) {
10539
- const localBin = path7.resolve(root, "node_modules", ".bin", cmd);
10540
- if (fs8.existsSync(localBin)) {
10541
- const content = fs8.readFileSync(localBin, "utf-8");
11026
+ const localBin = path8.resolve(root, "node_modules", ".bin", cmd);
11027
+ if (fs9.existsSync(localBin)) {
11028
+ const content = fs9.readFileSync(localBin, "utf-8");
10542
11029
  if (content.startsWith("#!/usr/bin/env node")) {
10543
11030
  return { command: process.execPath, args: [localBin] };
10544
11031
  }
10545
11032
  const match = content.match(/"([^"]+\.js)"/);
10546
11033
  if (match) {
10547
- const target = path7.resolve(path7.dirname(localBin), match[1]);
10548
- if (fs8.existsSync(target)) {
11034
+ const target = path8.resolve(path8.dirname(localBin), match[1]);
11035
+ if (fs9.existsSync(target)) {
10549
11036
  return { command: process.execPath, args: [target] };
10550
11037
  }
10551
11038
  }
@@ -10554,7 +11041,7 @@ function resolveAgentCommand(cmd) {
10554
11041
  try {
10555
11042
  const fullPath = execFileSync("which", [cmd], { encoding: "utf-8" }).trim();
10556
11043
  if (fullPath) {
10557
- const content = fs8.readFileSync(fullPath, "utf-8");
11044
+ const content = fs9.readFileSync(fullPath, "utf-8");
10558
11045
  if (content.startsWith("#!/usr/bin/env node")) {
10559
11046
  return { command: process.execPath, args: [fullPath] };
10560
11047
  }
@@ -10570,6 +11057,7 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
10570
11057
  terminalManager = new TerminalManager();
10571
11058
  static mcpManager = new McpManager();
10572
11059
  _destroying = false;
11060
+ pathGuard;
10573
11061
  sessionId;
10574
11062
  agentName;
10575
11063
  promptCapabilities;
@@ -10578,6 +11066,10 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
10578
11066
  initialSessionResponse;
10579
11067
  middlewareChain;
10580
11068
  debugTracer = null;
11069
+ /** Allow external callers (e.g. SessionFactory) to whitelist additional read paths */
11070
+ addAllowedPath(p) {
11071
+ this.pathGuard.addAllowedPath(p);
11072
+ }
10581
11073
  // Callback — set by core when wiring events
10582
11074
  onPermissionRequest = async () => "";
10583
11075
  constructor(agentName) {
@@ -10595,13 +11087,24 @@ var AgentInstance = class _AgentInstance extends TypedEmitter {
10595
11087
  },
10596
11088
  "Resolved agent command"
10597
11089
  );
11090
+ const ignorePatterns = PathGuard.loadIgnoreFile(workingDirectory);
11091
+ instance.pathGuard = new PathGuard({
11092
+ cwd: workingDirectory,
11093
+ // allowedPaths is wired from workspace.security.allowedPaths config;
11094
+ // spawnSubprocess would need to receive a SecurityConfig param to use it.
11095
+ // Tracked as follow-up: pass workspace security config through spawn/resume call chain.
11096
+ allowedPaths: [],
11097
+ ignorePatterns
11098
+ });
10598
11099
  instance.child = spawn2(
10599
11100
  resolved.command,
10600
11101
  [...resolved.args, ...agentDef.args],
10601
11102
  {
10602
11103
  stdio: ["pipe", "pipe", "pipe"],
10603
11104
  cwd: workingDirectory,
10604
- env: { ...process.env, ...agentDef.env }
11105
+ // envWhitelist from workspace.security.envWhitelist config would extend DEFAULT_ENV_WHITELIST.
11106
+ // Tracked as follow-up: pass workspace security config through spawn/resume call chain.
11107
+ env: filterEnv(process.env, agentDef.env)
10605
11108
  }
10606
11109
  );
10607
11110
  await new Promise((resolve6, reject) => {
@@ -10912,6 +11415,10 @@ ${stderr}`
10912
11415
  // ── File operations ──────────────────────────────────────────────────
10913
11416
  async readTextFile(params) {
10914
11417
  const p = params;
11418
+ const pathCheck = self.pathGuard.validatePath(p.path, "read");
11419
+ if (!pathCheck.allowed) {
11420
+ throw new Error(`[Access denied] ${pathCheck.reason}`);
11421
+ }
10915
11422
  if (self.middlewareChain) {
10916
11423
  const result = await self.middlewareChain.execute("fs:beforeRead", { sessionId: self.sessionId, path: p.path, line: p.line, limit: p.limit }, async (r) => r);
10917
11424
  if (!result) return { content: "" };
@@ -10926,14 +11433,18 @@ ${stderr}`
10926
11433
  async writeTextFile(params) {
10927
11434
  let writePath = params.path;
10928
11435
  let writeContent = params.content;
11436
+ const pathCheck = self.pathGuard.validatePath(writePath, "write");
11437
+ if (!pathCheck.allowed) {
11438
+ throw new Error(`[Access denied] ${pathCheck.reason}`);
11439
+ }
10929
11440
  if (self.middlewareChain) {
10930
11441
  const result = await self.middlewareChain.execute("fs:beforeWrite", { sessionId: self.sessionId, path: writePath, content: writeContent }, async (r) => r);
10931
11442
  if (!result) return {};
10932
11443
  writePath = result.path;
10933
11444
  writeContent = result.content;
10934
11445
  }
10935
- await fs8.promises.mkdir(path7.dirname(writePath), { recursive: true });
10936
- await fs8.promises.writeFile(writePath, writeContent, "utf-8");
11446
+ await fs9.promises.mkdir(path8.dirname(writePath), { recursive: true });
11447
+ await fs9.promises.writeFile(writePath, writeContent, "utf-8");
10937
11448
  return {};
10938
11449
  },
10939
11450
  // ── Terminal operations (delegated to TerminalManager) ─────────────
@@ -11008,10 +11519,24 @@ ${stderr}`
11008
11519
  for (const att of attachments ?? []) {
11009
11520
  const tooLarge = att.size > 10 * 1024 * 1024;
11010
11521
  if (att.type === "image" && this.promptCapabilities?.image && !tooLarge && SUPPORTED_IMAGE_MIMES.has(att.mimeType)) {
11011
- const data = await fs8.promises.readFile(att.filePath);
11522
+ const attCheck = this.pathGuard.validatePath(att.filePath, "read");
11523
+ if (!attCheck.allowed) {
11524
+ contentBlocks[0].text += `
11525
+
11526
+ [Attachment access denied: ${attCheck.reason}]`;
11527
+ continue;
11528
+ }
11529
+ const data = await fs9.promises.readFile(att.filePath);
11012
11530
  contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
11013
11531
  } else if (att.type === "audio" && this.promptCapabilities?.audio && !tooLarge) {
11014
- const data = await fs8.promises.readFile(att.filePath);
11532
+ const attCheck = this.pathGuard.validatePath(att.filePath, "read");
11533
+ if (!attCheck.allowed) {
11534
+ contentBlocks[0].text += `
11535
+
11536
+ [Attachment access denied: ${attCheck.reason}]`;
11537
+ continue;
11538
+ }
11539
+ const data = await fs9.promises.readFile(att.filePath);
11015
11540
  contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
11016
11541
  } else {
11017
11542
  if ((att.type === "image" || att.type === "audio") && !tooLarge) {
@@ -11217,7 +11742,7 @@ var PermissionGate = class {
11217
11742
 
11218
11743
  // src/core/sessions/session.ts
11219
11744
  init_log();
11220
- import * as fs9 from "fs";
11745
+ import * as fs10 from "fs";
11221
11746
  var moduleLog = createChildLogger({ module: "session" });
11222
11747
  var TTS_PROMPT_INSTRUCTION = `
11223
11748
 
@@ -11440,7 +11965,7 @@ ${text3}`;
11440
11965
  try {
11441
11966
  const audioPath = att.originalFilePath || att.filePath;
11442
11967
  const audioMime = att.originalFilePath ? "audio/ogg" : att.mimeType;
11443
- const audioBuffer = await fs9.promises.readFile(audioPath);
11968
+ const audioBuffer = await fs10.promises.readFile(audioPath);
11444
11969
  const result = await this.speechService.transcribe(audioBuffer, audioMime);
11445
11970
  this.log.info({ provider: "stt", duration: result.duration }, "Voice transcribed");
11446
11971
  this.emit("agent_event", {
@@ -11627,6 +12152,9 @@ ${result.text}` : result.text;
11627
12152
  }
11628
12153
  };
11629
12154
 
12155
+ // src/core/message-transformer.ts
12156
+ import * as path9 from "path";
12157
+
11630
12158
  // src/core/utils/extract-file-info.ts
11631
12159
  function extractFileInfo(name, kind, content, rawInput, meta) {
11632
12160
  if (kind && !["read", "edit", "write"].includes(kind)) return null;
@@ -11752,6 +12280,41 @@ function parseContent(content) {
11752
12280
  // src/core/message-transformer.ts
11753
12281
  init_log();
11754
12282
  var log5 = createChildLogger({ module: "message-transformer" });
12283
+ var BINARY_VIEWER_EXTENSIONS = /* @__PURE__ */ new Set([
12284
+ ".wav",
12285
+ ".ogg",
12286
+ ".mp3",
12287
+ ".m4a",
12288
+ ".aac",
12289
+ ".flac",
12290
+ ".opus",
12291
+ ".weba",
12292
+ ".mp4",
12293
+ ".avi",
12294
+ ".mov",
12295
+ ".webm",
12296
+ ".mkv",
12297
+ ".jpg",
12298
+ ".jpeg",
12299
+ ".png",
12300
+ ".gif",
12301
+ ".webp",
12302
+ ".bmp",
12303
+ ".ico",
12304
+ ".svg",
12305
+ ".pdf",
12306
+ ".zip",
12307
+ ".gz",
12308
+ ".tar",
12309
+ ".7z",
12310
+ ".rar",
12311
+ ".bin",
12312
+ ".exe",
12313
+ ".dll",
12314
+ ".so",
12315
+ ".dylib",
12316
+ ".wasm"
12317
+ ]);
11755
12318
  function computeLineDiff(oldStr, newStr) {
11756
12319
  const oldLines = oldStr ? oldStr.split("\n") : [];
11757
12320
  const newLines = newStr ? newStr.split("\n") : [];
@@ -11948,6 +12511,11 @@ var MessageTransformer = class {
11948
12511
  );
11949
12512
  return;
11950
12513
  }
12514
+ const fileExt = path9.extname(fileInfo.filePath).toLowerCase();
12515
+ if (BINARY_VIEWER_EXTENSIONS.has(fileExt)) {
12516
+ log5.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping binary file");
12517
+ return;
12518
+ }
11951
12519
  const publicUrl = this.tunnelService.getPublicUrl();
11952
12520
  if (publicUrl.startsWith("http://localhost") || publicUrl.startsWith("http://127.0.0.1")) {
11953
12521
  log5.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping (no public tunnel URL)");
@@ -12336,12 +12904,12 @@ var SessionBridge = class {
12336
12904
  break;
12337
12905
  case "image_content": {
12338
12906
  if (this.deps.fileService) {
12339
- const fs33 = this.deps.fileService;
12907
+ const fs34 = this.deps.fileService;
12340
12908
  const sid = this.session.id;
12341
12909
  const { data, mimeType } = event;
12342
12910
  const buffer = Buffer.from(data, "base64");
12343
- const ext = fs33.extensionFromMime(mimeType);
12344
- fs33.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
12911
+ const ext = fs34.extensionFromMime(mimeType);
12912
+ fs34.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
12345
12913
  this.sendMessage(sid, {
12346
12914
  type: "attachment",
12347
12915
  text: "",
@@ -12353,12 +12921,12 @@ var SessionBridge = class {
12353
12921
  }
12354
12922
  case "audio_content": {
12355
12923
  if (this.deps.fileService) {
12356
- const fs33 = this.deps.fileService;
12924
+ const fs34 = this.deps.fileService;
12357
12925
  const sid = this.session.id;
12358
12926
  const { data, mimeType } = event;
12359
12927
  const buffer = Buffer.from(data, "base64");
12360
- const ext = fs33.extensionFromMime(mimeType);
12361
- fs33.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
12928
+ const ext = fs34.extensionFromMime(mimeType);
12929
+ fs34.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
12362
12930
  this.sendMessage(sid, {
12363
12931
  type: "attachment",
12364
12932
  text: "",
@@ -12445,18 +13013,8 @@ var SessionBridge = class {
12445
13013
  this.emitAfterResolve(mw, permReq.id, optionId, "user", startTime);
12446
13014
  return optionId;
12447
13015
  }
12448
- /** Check if a permission request should be auto-approved (openacp commands or bypass mode) */
13016
+ /** Check if a permission request should be auto-approved (bypass mode only) */
12449
13017
  checkAutoApprove(request) {
12450
- if (request.description.toLowerCase().includes("openacp")) {
12451
- const allowOption = request.options.find((o) => o.isAllow);
12452
- if (allowOption) {
12453
- log6.info(
12454
- { sessionId: this.session.id, requestId: request.id },
12455
- "Auto-approving openacp command"
12456
- );
12457
- return allowOption.id;
12458
- }
12459
- }
12460
13018
  const modeOption = this.session.getConfigByCategory("mode");
12461
13019
  const isAgentBypass = modeOption && isPermissionBypass(
12462
13020
  typeof modeOption.currentValue === "string" ? modeOption.currentValue : ""
@@ -12514,6 +13072,10 @@ var SessionFactory = class {
12514
13072
  agentCatalog;
12515
13073
  /** Injected by Core — needed for context-aware session creation */
12516
13074
  getContextManager;
13075
+ /** Injected by Core — returns extra filesystem paths the agent is allowed to read.
13076
+ * Used to whitelist the file-service upload directory so agents can read attachments
13077
+ * saved outside the workspace (e.g. ~/.openacp/instances/main/files/). */
13078
+ getAgentAllowedPaths;
12517
13079
  get speechService() {
12518
13080
  return typeof this.speechServiceAccessor === "function" ? this.speechServiceAccessor() : this.speechServiceAccessor;
12519
13081
  }
@@ -12605,6 +13167,10 @@ var SessionFactory = class {
12605
13167
  });
12606
13168
  throw err;
12607
13169
  }
13170
+ const extraPaths = this.getAgentAllowedPaths?.() ?? [];
13171
+ for (const p of extraPaths) {
13172
+ agentInstance.addAllowedPath(p);
13173
+ }
12608
13174
  agentInstance.middlewareChain = this.middlewareChain;
12609
13175
  const session = new Session({
12610
13176
  id: createParams.existingSessionId,
@@ -12821,13 +13387,13 @@ var SessionFactory = class {
12821
13387
  };
12822
13388
 
12823
13389
  // src/core/core.ts
12824
- import path15 from "path";
13390
+ import path17 from "path";
12825
13391
  import os8 from "os";
12826
13392
 
12827
13393
  // src/core/sessions/session-store.ts
12828
13394
  init_log();
12829
- import fs10 from "fs";
12830
- import path8 from "path";
13395
+ import fs11 from "fs";
13396
+ import path10 from "path";
12831
13397
  var log8 = createChildLogger({ module: "session-store" });
12832
13398
  var DEBOUNCE_MS = 2e3;
12833
13399
  var JsonFileSessionStore = class {
@@ -12898,9 +13464,9 @@ var JsonFileSessionStore = class {
12898
13464
  version: 1,
12899
13465
  sessions: Object.fromEntries(this.records)
12900
13466
  };
12901
- const dir = path8.dirname(this.filePath);
12902
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
12903
- fs10.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
13467
+ const dir = path10.dirname(this.filePath);
13468
+ if (!fs11.existsSync(dir)) fs11.mkdirSync(dir, { recursive: true });
13469
+ fs11.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
12904
13470
  }
12905
13471
  destroy() {
12906
13472
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
@@ -12913,10 +13479,10 @@ var JsonFileSessionStore = class {
12913
13479
  }
12914
13480
  }
12915
13481
  load() {
12916
- if (!fs10.existsSync(this.filePath)) return;
13482
+ if (!fs11.existsSync(this.filePath)) return;
12917
13483
  try {
12918
13484
  const raw = JSON.parse(
12919
- fs10.readFileSync(this.filePath, "utf-8")
13485
+ fs11.readFileSync(this.filePath, "utf-8")
12920
13486
  );
12921
13487
  if (raw.version !== 1) {
12922
13488
  log8.warn(
@@ -12932,7 +13498,7 @@ var JsonFileSessionStore = class {
12932
13498
  } catch (err) {
12933
13499
  log8.error({ err }, "Failed to load session store, backing up corrupt file");
12934
13500
  try {
12935
- fs10.renameSync(this.filePath, `${this.filePath}.bak`);
13501
+ fs11.renameSync(this.filePath, `${this.filePath}.bak`);
12936
13502
  } catch {
12937
13503
  }
12938
13504
  }
@@ -13025,12 +13591,16 @@ var AgentSwitchHandler = class {
13025
13591
  await switchAdapter.cleanupSessionState(session.id);
13026
13592
  }
13027
13593
  const fromAgentSessionId = session.agentSessionId;
13594
+ const fileService = this.deps.getService("file-service");
13028
13595
  try {
13029
13596
  await session.switchAgent(toAgent, async () => {
13030
13597
  if (canResume) {
13031
- return agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
13598
+ const instance = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
13599
+ if (fileService) instance.addAllowedPath(fileService.baseDir);
13600
+ return instance;
13032
13601
  } else {
13033
13602
  const instance = await agentManager.spawn(toAgent, session.workingDirectory);
13603
+ if (fileService) instance.addAllowedPath(fileService.baseDir);
13034
13604
  try {
13035
13605
  const contextService = this.deps.getService("context");
13036
13606
  if (contextService) {
@@ -13125,14 +13695,14 @@ var AgentSwitchHandler = class {
13125
13695
  };
13126
13696
 
13127
13697
  // src/core/agents/agent-catalog.ts
13128
- import * as fs14 from "fs";
13129
- import * as path12 from "path";
13698
+ import * as fs15 from "fs";
13699
+ import * as path14 from "path";
13130
13700
  import * as os6 from "os";
13131
13701
 
13132
13702
  // src/core/agents/agent-store.ts
13133
13703
  init_log();
13134
- import * as fs12 from "fs";
13135
- import * as path10 from "path";
13704
+ import * as fs13 from "fs";
13705
+ import * as path12 from "path";
13136
13706
  import * as os4 from "os";
13137
13707
  import { z as z2 } from "zod";
13138
13708
  var log10 = createChildLogger({ module: "agent-store" });
@@ -13156,15 +13726,15 @@ var AgentStore = class {
13156
13726
  data = { version: 1, installed: {} };
13157
13727
  filePath;
13158
13728
  constructor(filePath) {
13159
- this.filePath = filePath ?? path10.join(os4.homedir(), ".openacp", "agents.json");
13729
+ this.filePath = filePath ?? path12.join(os4.homedir(), ".openacp", "agents.json");
13160
13730
  }
13161
13731
  load() {
13162
- if (!fs12.existsSync(this.filePath)) {
13732
+ if (!fs13.existsSync(this.filePath)) {
13163
13733
  this.data = { version: 1, installed: {} };
13164
13734
  return;
13165
13735
  }
13166
13736
  try {
13167
- const raw = JSON.parse(fs12.readFileSync(this.filePath, "utf-8"));
13737
+ const raw = JSON.parse(fs13.readFileSync(this.filePath, "utf-8"));
13168
13738
  const result = AgentStoreSchema.safeParse(raw);
13169
13739
  if (result.success) {
13170
13740
  this.data = result.data;
@@ -13178,7 +13748,7 @@ var AgentStore = class {
13178
13748
  }
13179
13749
  }
13180
13750
  exists() {
13181
- return fs12.existsSync(this.filePath);
13751
+ return fs13.existsSync(this.filePath);
13182
13752
  }
13183
13753
  getInstalled() {
13184
13754
  return this.data.installed;
@@ -13198,244 +13768,15 @@ var AgentStore = class {
13198
13768
  return key in this.data.installed;
13199
13769
  }
13200
13770
  save() {
13201
- fs12.mkdirSync(path10.dirname(this.filePath), { recursive: true });
13771
+ fs13.mkdirSync(path12.dirname(this.filePath), { recursive: true });
13202
13772
  const tmpPath = this.filePath + ".tmp";
13203
- fs12.writeFileSync(tmpPath, JSON.stringify(this.data, null, 2));
13204
- fs12.renameSync(tmpPath, this.filePath);
13773
+ fs13.writeFileSync(tmpPath, JSON.stringify(this.data, null, 2), { mode: 384 });
13774
+ fs13.renameSync(tmpPath, this.filePath);
13205
13775
  }
13206
13776
  };
13207
13777
 
13208
- // src/core/agents/agent-installer.ts
13209
- init_log();
13210
- init_agent_dependencies();
13211
- import * as fs13 from "fs";
13212
- import * as path11 from "path";
13213
- import * as os5 from "os";
13214
- var log11 = createChildLogger({ module: "agent-installer" });
13215
- var DEFAULT_AGENTS_DIR = path11.join(os5.homedir(), ".openacp", "agents");
13216
- var ARCH_MAP = {
13217
- arm64: "aarch64",
13218
- x64: "x86_64"
13219
- };
13220
- var PLATFORM_MAP = {
13221
- darwin: "darwin",
13222
- linux: "linux",
13223
- win32: "windows"
13224
- };
13225
- function getPlatformKey() {
13226
- const platform2 = PLATFORM_MAP[process.platform] ?? process.platform;
13227
- const arch = ARCH_MAP[process.arch] ?? process.arch;
13228
- return `${platform2}-${arch}`;
13229
- }
13230
- function resolveDistribution(agent) {
13231
- const dist = agent.distribution;
13232
- if (dist.npx) {
13233
- return { type: "npx", package: dist.npx.package, args: dist.npx.args ?? [], env: dist.npx.env };
13234
- }
13235
- if (dist.uvx) {
13236
- return { type: "uvx", package: dist.uvx.package, args: dist.uvx.args ?? [], env: dist.uvx.env };
13237
- }
13238
- if (dist.binary) {
13239
- const platformKey = getPlatformKey();
13240
- const target = dist.binary[platformKey];
13241
- if (!target) return null;
13242
- return { type: "binary", archive: target.archive, cmd: target.cmd, args: target.args ?? [], env: target.env };
13243
- }
13244
- return null;
13245
- }
13246
- function buildInstalledAgent(registryId, name, version, dist, binaryPath) {
13247
- if (dist.type === "npx") {
13248
- const npxPackage = stripPackageVersion(dist.package);
13249
- return {
13250
- registryId,
13251
- name,
13252
- version,
13253
- distribution: "npx",
13254
- command: "npx",
13255
- args: [npxPackage, ...dist.args],
13256
- env: dist.env ?? {},
13257
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
13258
- binaryPath: null
13259
- };
13260
- }
13261
- if (dist.type === "uvx") {
13262
- const uvxPackage = stripPythonPackageVersion(dist.package);
13263
- return {
13264
- registryId,
13265
- name,
13266
- version,
13267
- distribution: "uvx",
13268
- command: "uvx",
13269
- args: [uvxPackage, ...dist.args],
13270
- env: dist.env ?? {},
13271
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
13272
- binaryPath: null
13273
- };
13274
- }
13275
- const absCmd = path11.resolve(binaryPath, dist.cmd);
13276
- return {
13277
- registryId,
13278
- name,
13279
- version,
13280
- distribution: "binary",
13281
- command: absCmd,
13282
- args: dist.args,
13283
- env: dist.env ?? {},
13284
- installedAt: (/* @__PURE__ */ new Date()).toISOString(),
13285
- binaryPath
13286
- };
13287
- }
13288
- function stripPackageVersion(pkg) {
13289
- if (pkg.startsWith("@")) {
13290
- const afterScope = pkg.indexOf("/");
13291
- if (afterScope === -1) return pkg;
13292
- const versionAt = pkg.indexOf("@", afterScope + 1);
13293
- return versionAt === -1 ? pkg : pkg.slice(0, versionAt);
13294
- }
13295
- const at = pkg.indexOf("@");
13296
- return at === -1 ? pkg : pkg.slice(0, at);
13297
- }
13298
- function stripPythonPackageVersion(pkg) {
13299
- const pyMatch = pkg.match(/^([^=@><!]+)/);
13300
- if (pyMatch && pkg.includes("==")) return pyMatch[1];
13301
- const at = pkg.indexOf("@");
13302
- return at === -1 ? pkg : pkg.slice(0, at);
13303
- }
13304
- async function installAgent(agent, store, progress, agentsDir) {
13305
- const agentKey = getAgentAlias(agent.id);
13306
- await progress?.onStart(agent.id, agent.name);
13307
- await progress?.onStep("Checking requirements...");
13308
- const depResult = checkDependencies(agent.id);
13309
- if (!depResult.available) {
13310
- const hints = depResult.missing.map((m) => ` ${m.label}: ${m.installHint}`).join("\n");
13311
- const msg = `${agent.name} needs some tools installed first:
13312
- ${hints}`;
13313
- await progress?.onError(msg);
13314
- return { ok: false, agentKey, error: msg };
13315
- }
13316
- const dist = resolveDistribution(agent);
13317
- if (!dist) {
13318
- const platformKey = getPlatformKey();
13319
- const msg = `${agent.name} is not available for your system (${platformKey}). Check their website for other install options.`;
13320
- await progress?.onError(msg);
13321
- return { ok: false, agentKey, error: msg };
13322
- }
13323
- if (dist.type === "uvx" && !checkRuntimeAvailable("uvx")) {
13324
- const msg = `${agent.name} requires Python's uvx tool.
13325
- Install it with: pip install uv`;
13326
- await progress?.onError(msg, "pip install uv");
13327
- return { ok: false, agentKey, error: msg, hint: "pip install uv" };
13328
- }
13329
- let binaryPath;
13330
- if (dist.type === "binary") {
13331
- try {
13332
- binaryPath = await downloadAndExtract(agent.id, dist.archive, progress, agentsDir);
13333
- } catch (err) {
13334
- const msg = `Failed to download ${agent.name}. Please try again or install manually.`;
13335
- await progress?.onError(msg);
13336
- return { ok: false, agentKey, error: msg };
13337
- }
13338
- } else {
13339
- await progress?.onStep("Setting up... (will download on first use)");
13340
- }
13341
- const installed = buildInstalledAgent(agent.id, agent.name, agent.version, dist, binaryPath);
13342
- store.addAgent(agentKey, installed);
13343
- const setup = getAgentSetup(agent.id);
13344
- await progress?.onSuccess(agent.name);
13345
- return { ok: true, agentKey, setupSteps: setup?.setupSteps };
13346
- }
13347
- async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir) {
13348
- const destDir = path11.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
13349
- fs13.mkdirSync(destDir, { recursive: true });
13350
- await progress?.onStep("Downloading...");
13351
- log11.info({ agentId, url: archiveUrl }, "Downloading agent binary");
13352
- const response = await fetch(archiveUrl);
13353
- if (!response.ok) {
13354
- throw new Error(`Download failed: ${response.status} ${response.statusText}`);
13355
- }
13356
- const contentLength = Number(response.headers.get("content-length") || 0);
13357
- const buffer = await readResponseWithProgress(response, contentLength, progress);
13358
- await progress?.onStep("Extracting...");
13359
- if (archiveUrl.endsWith(".zip")) {
13360
- await extractZip(buffer, destDir);
13361
- } else {
13362
- await extractTarGz(buffer, destDir);
13363
- }
13364
- await progress?.onStep("Ready!");
13365
- return destDir;
13366
- }
13367
- async function readResponseWithProgress(response, contentLength, progress) {
13368
- if (!response.body || contentLength === 0) {
13369
- const arrayBuffer = await response.arrayBuffer();
13370
- return Buffer.from(arrayBuffer);
13371
- }
13372
- const reader = response.body.getReader();
13373
- const chunks = [];
13374
- let received = 0;
13375
- while (true) {
13376
- const { done, value } = await reader.read();
13377
- if (done) break;
13378
- chunks.push(value);
13379
- received += value.length;
13380
- if (contentLength > 0) {
13381
- await progress?.onDownloadProgress(Math.round(received / contentLength * 100));
13382
- }
13383
- }
13384
- return Buffer.concat(chunks);
13385
- }
13386
- function validateExtractedPaths(destDir) {
13387
- const realDest = fs13.realpathSync(destDir);
13388
- const entries = fs13.readdirSync(destDir, { recursive: true, withFileTypes: true });
13389
- for (const entry of entries) {
13390
- const dirent = entry;
13391
- const parentPath = dirent.parentPath ?? dirent.path ?? destDir;
13392
- const fullPath = path11.join(parentPath, entry.name);
13393
- let realPath;
13394
- try {
13395
- realPath = fs13.realpathSync(fullPath);
13396
- } catch {
13397
- const linkTarget = fs13.readlinkSync(fullPath);
13398
- realPath = path11.resolve(path11.dirname(fullPath), linkTarget);
13399
- }
13400
- if (!realPath.startsWith(realDest + path11.sep) && realPath !== realDest) {
13401
- fs13.rmSync(destDir, { recursive: true, force: true });
13402
- throw new Error(`Archive contains unsafe path: ${entry.name}`);
13403
- }
13404
- }
13405
- }
13406
- async function extractTarGz(buffer, destDir) {
13407
- const { execFileSync: execFileSync7 } = await import("child_process");
13408
- const tmpFile = path11.join(destDir, "_archive.tar.gz");
13409
- fs13.writeFileSync(tmpFile, buffer);
13410
- try {
13411
- execFileSync7("tar", ["xzf", tmpFile, "-C", destDir], { stdio: "pipe" });
13412
- } finally {
13413
- fs13.unlinkSync(tmpFile);
13414
- }
13415
- validateExtractedPaths(destDir);
13416
- }
13417
- async function extractZip(buffer, destDir) {
13418
- const { execFileSync: execFileSync7 } = await import("child_process");
13419
- const tmpFile = path11.join(destDir, "_archive.zip");
13420
- fs13.writeFileSync(tmpFile, buffer);
13421
- try {
13422
- execFileSync7("unzip", ["-o", tmpFile, "-d", destDir], { stdio: "pipe" });
13423
- } finally {
13424
- fs13.unlinkSync(tmpFile);
13425
- }
13426
- validateExtractedPaths(destDir);
13427
- }
13428
- async function uninstallAgent(agentKey, store) {
13429
- const agent = store.getAgent(agentKey);
13430
- if (!agent) return;
13431
- if (agent.binaryPath && fs13.existsSync(agent.binaryPath)) {
13432
- fs13.rmSync(agent.binaryPath, { recursive: true, force: true });
13433
- log11.info({ agentKey, binaryPath: agent.binaryPath }, "Deleted agent binary");
13434
- }
13435
- store.removeAgent(agentKey);
13436
- }
13437
-
13438
13778
  // src/core/agents/agent-catalog.ts
13779
+ init_agent_installer();
13439
13780
  init_agent_dependencies();
13440
13781
  init_log();
13441
13782
  var log12 = createChildLogger({ module: "agent-catalog" });
@@ -13448,7 +13789,7 @@ var AgentCatalog = class {
13448
13789
  agentsDir;
13449
13790
  constructor(store, cachePath, agentsDir) {
13450
13791
  this.store = store ?? new AgentStore();
13451
- this.cachePath = cachePath ?? path12.join(os6.homedir(), ".openacp", "registry-cache.json");
13792
+ this.cachePath = cachePath ?? path14.join(os6.homedir(), ".openacp", "registry-cache.json");
13452
13793
  this.agentsDir = agentsDir;
13453
13794
  }
13454
13795
  load() {
@@ -13469,8 +13810,8 @@ var AgentCatalog = class {
13469
13810
  ttlHours: DEFAULT_TTL_HOURS,
13470
13811
  data
13471
13812
  };
13472
- fs14.mkdirSync(path12.dirname(this.cachePath), { recursive: true });
13473
- fs14.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2));
13813
+ fs15.mkdirSync(path14.dirname(this.cachePath), { recursive: true });
13814
+ fs15.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
13474
13815
  log12.info({ count: this.registryAgents.length }, "Registry updated");
13475
13816
  } catch (err) {
13476
13817
  log12.warn({ err }, "Failed to fetch registry, using cached data");
@@ -13569,6 +13910,14 @@ var AgentCatalog = class {
13569
13910
  }
13570
13911
  return installAgent(agent, this.store, progress, this.agentsDir);
13571
13912
  }
13913
+ /**
13914
+ * Register an agent directly into the catalog store without going through
13915
+ * the registry installer. Used to pre-register bundled agents (e.g. Claude)
13916
+ * when their CLI dependency is not yet installed.
13917
+ */
13918
+ registerFallbackAgent(key, data) {
13919
+ this.store.addAgent(key, data);
13920
+ }
13572
13921
  async uninstall(key) {
13573
13922
  if (!this.store.hasAgent(key)) {
13574
13923
  return { ok: false, error: `"${key}" is not installed.` };
@@ -13630,9 +13979,9 @@ var AgentCatalog = class {
13630
13979
  }
13631
13980
  }
13632
13981
  isCacheStale() {
13633
- if (!fs14.existsSync(this.cachePath)) return true;
13982
+ if (!fs15.existsSync(this.cachePath)) return true;
13634
13983
  try {
13635
- const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
13984
+ const raw = JSON.parse(fs15.readFileSync(this.cachePath, "utf-8"));
13636
13985
  const fetchedAt = new Date(raw.fetchedAt).getTime();
13637
13986
  const ttlMs = (raw.ttlHours ?? DEFAULT_TTL_HOURS) * 60 * 60 * 1e3;
13638
13987
  return Date.now() - fetchedAt > ttlMs;
@@ -13641,9 +13990,9 @@ var AgentCatalog = class {
13641
13990
  }
13642
13991
  }
13643
13992
  loadRegistryFromCacheOrSnapshot() {
13644
- if (fs14.existsSync(this.cachePath)) {
13993
+ if (fs15.existsSync(this.cachePath)) {
13645
13994
  try {
13646
- const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
13995
+ const raw = JSON.parse(fs15.readFileSync(this.cachePath, "utf-8"));
13647
13996
  if (raw.data?.agents) {
13648
13997
  this.registryAgents = raw.data.agents;
13649
13998
  log12.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
@@ -13655,13 +14004,13 @@ var AgentCatalog = class {
13655
14004
  }
13656
14005
  try {
13657
14006
  const candidates = [
13658
- path12.join(import.meta.dirname, "data", "registry-snapshot.json"),
13659
- path12.join(import.meta.dirname, "..", "data", "registry-snapshot.json"),
13660
- path12.join(import.meta.dirname, "..", "..", "data", "registry-snapshot.json")
14007
+ path14.join(import.meta.dirname, "data", "registry-snapshot.json"),
14008
+ path14.join(import.meta.dirname, "..", "data", "registry-snapshot.json"),
14009
+ path14.join(import.meta.dirname, "..", "..", "data", "registry-snapshot.json")
13661
14010
  ];
13662
14011
  for (const candidate of candidates) {
13663
- if (fs14.existsSync(candidate)) {
13664
- const raw = JSON.parse(fs14.readFileSync(candidate, "utf-8"));
14012
+ if (fs15.existsSync(candidate)) {
14013
+ const raw = JSON.parse(fs15.readFileSync(candidate, "utf-8"));
13665
14014
  this.registryAgents = raw.agents ?? [];
13666
14015
  log12.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
13667
14016
  return;
@@ -13922,31 +14271,31 @@ var ErrorTracker = class {
13922
14271
  };
13923
14272
 
13924
14273
  // src/core/plugin/plugin-context.ts
13925
- import path14 from "path";
14274
+ import path16 from "path";
13926
14275
  import os7 from "os";
13927
14276
 
13928
14277
  // src/core/plugin/plugin-storage.ts
13929
- import fs15 from "fs";
13930
- import path13 from "path";
14278
+ import fs16 from "fs";
14279
+ import path15 from "path";
13931
14280
  var PluginStorageImpl = class {
13932
14281
  kvPath;
13933
14282
  dataDir;
13934
14283
  writeChain = Promise.resolve();
13935
14284
  constructor(baseDir) {
13936
- this.dataDir = path13.join(baseDir, "data");
13937
- this.kvPath = path13.join(baseDir, "kv.json");
13938
- fs15.mkdirSync(baseDir, { recursive: true });
14285
+ this.dataDir = path15.join(baseDir, "data");
14286
+ this.kvPath = path15.join(baseDir, "kv.json");
14287
+ fs16.mkdirSync(baseDir, { recursive: true });
13939
14288
  }
13940
14289
  readKv() {
13941
14290
  try {
13942
- const raw = fs15.readFileSync(this.kvPath, "utf-8");
14291
+ const raw = fs16.readFileSync(this.kvPath, "utf-8");
13943
14292
  return JSON.parse(raw);
13944
14293
  } catch {
13945
14294
  return {};
13946
14295
  }
13947
14296
  }
13948
14297
  writeKv(data) {
13949
- fs15.writeFileSync(this.kvPath, JSON.stringify(data), "utf-8");
14298
+ fs16.writeFileSync(this.kvPath, JSON.stringify(data), "utf-8");
13950
14299
  }
13951
14300
  async get(key) {
13952
14301
  const data = this.readKv();
@@ -13972,7 +14321,7 @@ var PluginStorageImpl = class {
13972
14321
  return Object.keys(this.readKv());
13973
14322
  }
13974
14323
  getDataDir() {
13975
- fs15.mkdirSync(this.dataDir, { recursive: true });
14324
+ fs16.mkdirSync(this.dataDir, { recursive: true });
13976
14325
  return this.dataDir;
13977
14326
  }
13978
14327
  };
@@ -13996,7 +14345,7 @@ function createPluginContext(opts) {
13996
14345
  config,
13997
14346
  core
13998
14347
  } = opts;
13999
- const instanceRoot = opts.instanceRoot ?? path14.join(os7.homedir(), ".openacp");
14348
+ const instanceRoot = opts.instanceRoot ?? path16.join(os7.homedir(), ".openacp");
14000
14349
  const registeredListeners = [];
14001
14350
  const registeredCommands = [];
14002
14351
  const noopLog = {
@@ -14017,7 +14366,7 @@ function createPluginContext(opts) {
14017
14366
  }
14018
14367
  };
14019
14368
  const baseLog = opts.log ?? noopLog;
14020
- const log33 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
14369
+ const log34 = typeof baseLog.child === "function" ? baseLog.child({ plugin: pluginName }) : baseLog;
14021
14370
  const storageImpl = new PluginStorageImpl(storagePath);
14022
14371
  const storage = {
14023
14372
  async get(key) {
@@ -14044,7 +14393,7 @@ function createPluginContext(opts) {
14044
14393
  const ctx = {
14045
14394
  pluginName,
14046
14395
  pluginConfig,
14047
- log: log33,
14396
+ log: log34,
14048
14397
  storage,
14049
14398
  on(event, handler) {
14050
14399
  requirePermission(permissions, "events:read", "on()");
@@ -14079,7 +14428,7 @@ function createPluginContext(opts) {
14079
14428
  const registry = serviceRegistry.get("command-registry");
14080
14429
  if (registry && typeof registry.register === "function") {
14081
14430
  registry.register(def, pluginName);
14082
- log33.debug(`Command '/${def.name}' registered`);
14431
+ log34.debug(`Command '/${def.name}' registered`);
14083
14432
  }
14084
14433
  },
14085
14434
  async sendMessage(_sessionId, _content) {
@@ -14826,7 +15175,7 @@ var OpenACPCore = class {
14826
15175
  );
14827
15176
  this.agentCatalog.load();
14828
15177
  this.agentManager = new AgentManager(this.agentCatalog);
14829
- const storePath = ctx?.paths.sessions ?? path15.join(os8.homedir(), ".openacp", "sessions.json");
15178
+ const storePath = ctx?.paths.sessions ?? path17.join(os8.homedir(), ".openacp", "sessions.json");
14830
15179
  this.sessionStore = new JsonFileSessionStore(
14831
15180
  storePath,
14832
15181
  config.sessionStore.ttlDays
@@ -14850,7 +15199,7 @@ var OpenACPCore = class {
14850
15199
  sessions: this.sessionManager,
14851
15200
  config: this.configManager,
14852
15201
  core: this,
14853
- storagePath: ctx?.paths.pluginsData ?? path15.join(os8.homedir(), ".openacp", "plugins", "data"),
15202
+ storagePath: ctx?.paths.pluginsData ?? path17.join(os8.homedir(), ".openacp", "plugins", "data"),
14854
15203
  instanceRoot: ctx?.root,
14855
15204
  log: createChildLogger({ module: "plugin" })
14856
15205
  });
@@ -14862,6 +15211,10 @@ var OpenACPCore = class {
14862
15211
  this.sessionFactory.configManager = this.configManager;
14863
15212
  this.sessionFactory.agentCatalog = this.agentCatalog;
14864
15213
  this.sessionFactory.getContextManager = () => this.lifecycleManager.serviceRegistry.get("context");
15214
+ this.sessionFactory.getAgentAllowedPaths = () => {
15215
+ const fileService = this.lifecycleManager.serviceRegistry.get("file-service");
15216
+ return fileService ? [fileService.baseDir] : [];
15217
+ };
14865
15218
  this.agentSwitchHandler = new AgentSwitchHandler({
14866
15219
  sessionManager: this.sessionManager,
14867
15220
  agentManager: this.agentManager,
@@ -14911,7 +15264,7 @@ var OpenACPCore = class {
14911
15264
  );
14912
15265
  registerCoreMenuItems(this.menuRegistry);
14913
15266
  if (ctx?.root) {
14914
- this.assistantRegistry.setInstanceRoot(path15.dirname(ctx.root));
15267
+ this.assistantRegistry.setInstanceRoot(path17.dirname(ctx.root));
14915
15268
  }
14916
15269
  this.assistantRegistry.register(createSessionsSection(this));
14917
15270
  this.assistantRegistry.register(createAgentsSection(this));
@@ -15413,19 +15766,19 @@ init_doctor();
15413
15766
  init_config_registry();
15414
15767
 
15415
15768
  // src/core/config/config-editor.ts
15416
- import * as path27 from "path";
15769
+ import * as path29 from "path";
15417
15770
  import * as clack2 from "@clack/prompts";
15418
15771
 
15419
15772
  // src/cli/autostart.ts
15420
15773
  init_log();
15421
15774
  import { execFileSync as execFileSync5 } from "child_process";
15422
- import * as fs26 from "fs";
15423
- import * as path23 from "path";
15775
+ import * as fs27 from "fs";
15776
+ import * as path25 from "path";
15424
15777
  import * as os11 from "os";
15425
15778
  var log18 = createChildLogger({ module: "autostart" });
15426
15779
  var LAUNCHD_LABEL = "com.openacp.daemon";
15427
- var LAUNCHD_PLIST_PATH = path23.join(os11.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
15428
- var SYSTEMD_SERVICE_PATH = path23.join(os11.homedir(), ".config", "systemd", "user", "openacp.service");
15780
+ var LAUNCHD_PLIST_PATH = path25.join(os11.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
15781
+ var SYSTEMD_SERVICE_PATH = path25.join(os11.homedir(), ".config", "systemd", "user", "openacp.service");
15429
15782
  function isAutoStartSupported() {
15430
15783
  return process.platform === "darwin" || process.platform === "linux";
15431
15784
  }
@@ -15437,7 +15790,7 @@ function escapeSystemdValue(str) {
15437
15790
  return `"${escaped}"`;
15438
15791
  }
15439
15792
  function generateLaunchdPlist(nodePath, cliPath, logDir2) {
15440
- const logFile = path23.join(logDir2, "openacp.log");
15793
+ const logFile = path25.join(logDir2, "openacp.log");
15441
15794
  return `<?xml version="1.0" encoding="UTF-8"?>
15442
15795
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
15443
15796
  <plist version="1.0">
@@ -15482,23 +15835,23 @@ function installAutoStart(logDir2) {
15482
15835
  return { success: false, error: "Auto-start not supported on this platform" };
15483
15836
  }
15484
15837
  const nodePath = process.execPath;
15485
- const cliPath = path23.resolve(process.argv[1]);
15486
- const resolvedLogDir = logDir2.startsWith("~") ? path23.join(os11.homedir(), logDir2.slice(1)) : logDir2;
15838
+ const cliPath = path25.resolve(process.argv[1]);
15839
+ const resolvedLogDir = logDir2.startsWith("~") ? path25.join(os11.homedir(), logDir2.slice(1)) : logDir2;
15487
15840
  try {
15488
15841
  if (process.platform === "darwin") {
15489
15842
  const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir);
15490
- const dir = path23.dirname(LAUNCHD_PLIST_PATH);
15491
- fs26.mkdirSync(dir, { recursive: true });
15492
- fs26.writeFileSync(LAUNCHD_PLIST_PATH, plist);
15843
+ const dir = path25.dirname(LAUNCHD_PLIST_PATH);
15844
+ fs27.mkdirSync(dir, { recursive: true });
15845
+ fs27.writeFileSync(LAUNCHD_PLIST_PATH, plist);
15493
15846
  execFileSync5("launchctl", ["load", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
15494
15847
  log18.info("LaunchAgent installed");
15495
15848
  return { success: true };
15496
15849
  }
15497
15850
  if (process.platform === "linux") {
15498
15851
  const unit = generateSystemdUnit(nodePath, cliPath);
15499
- const dir = path23.dirname(SYSTEMD_SERVICE_PATH);
15500
- fs26.mkdirSync(dir, { recursive: true });
15501
- fs26.writeFileSync(SYSTEMD_SERVICE_PATH, unit);
15852
+ const dir = path25.dirname(SYSTEMD_SERVICE_PATH);
15853
+ fs27.mkdirSync(dir, { recursive: true });
15854
+ fs27.writeFileSync(SYSTEMD_SERVICE_PATH, unit);
15502
15855
  execFileSync5("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
15503
15856
  execFileSync5("systemctl", ["--user", "enable", "openacp"], { stdio: "pipe" });
15504
15857
  log18.info("systemd user service installed");
@@ -15517,23 +15870,23 @@ function uninstallAutoStart() {
15517
15870
  }
15518
15871
  try {
15519
15872
  if (process.platform === "darwin") {
15520
- if (fs26.existsSync(LAUNCHD_PLIST_PATH)) {
15873
+ if (fs27.existsSync(LAUNCHD_PLIST_PATH)) {
15521
15874
  try {
15522
15875
  execFileSync5("launchctl", ["unload", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
15523
15876
  } catch {
15524
15877
  }
15525
- fs26.unlinkSync(LAUNCHD_PLIST_PATH);
15878
+ fs27.unlinkSync(LAUNCHD_PLIST_PATH);
15526
15879
  log18.info("LaunchAgent removed");
15527
15880
  }
15528
15881
  return { success: true };
15529
15882
  }
15530
15883
  if (process.platform === "linux") {
15531
- if (fs26.existsSync(SYSTEMD_SERVICE_PATH)) {
15884
+ if (fs27.existsSync(SYSTEMD_SERVICE_PATH)) {
15532
15885
  try {
15533
15886
  execFileSync5("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
15534
15887
  } catch {
15535
15888
  }
15536
- fs26.unlinkSync(SYSTEMD_SERVICE_PATH);
15889
+ fs27.unlinkSync(SYSTEMD_SERVICE_PATH);
15537
15890
  execFileSync5("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
15538
15891
  log18.info("systemd user service removed");
15539
15892
  }
@@ -15548,10 +15901,10 @@ function uninstallAutoStart() {
15548
15901
  }
15549
15902
  function isAutoStartInstalled() {
15550
15903
  if (process.platform === "darwin") {
15551
- return fs26.existsSync(LAUNCHD_PLIST_PATH);
15904
+ return fs27.existsSync(LAUNCHD_PLIST_PATH);
15552
15905
  }
15553
15906
  if (process.platform === "linux") {
15554
- return fs26.existsSync(SYSTEMD_SERVICE_PATH);
15907
+ return fs27.existsSync(SYSTEMD_SERVICE_PATH);
15555
15908
  }
15556
15909
  return false;
15557
15910
  }
@@ -15756,7 +16109,7 @@ async function editDiscord(_config, _updates) {
15756
16109
  const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
15757
16110
  const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
15758
16111
  const root = getGlobalRoot2();
15759
- const basePath = path27.join(root, "plugins", "data");
16112
+ const basePath = path29.join(root, "plugins", "data");
15760
16113
  const settingsManager = new SettingsManager2(basePath);
15761
16114
  const ctx = createInstallContext2({
15762
16115
  pluginName: plugin.name,
@@ -16277,17 +16630,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
16277
16630
  async function sendConfigViaApi(port, updates) {
16278
16631
  const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
16279
16632
  const paths = flattenToPaths(updates);
16280
- for (const { path: path33, value } of paths) {
16633
+ for (const { path: path35, value } of paths) {
16281
16634
  const res = await call(port, "/api/config", {
16282
16635
  method: "PATCH",
16283
16636
  headers: { "Content-Type": "application/json" },
16284
- body: JSON.stringify({ path: path33, value })
16637
+ body: JSON.stringify({ path: path35, value })
16285
16638
  });
16286
16639
  const data = await res.json();
16287
16640
  if (!res.ok) {
16288
- console.log(warn(`Failed to update ${path33}: ${data.error}`));
16641
+ console.log(warn(`Failed to update ${path35}: ${data.error}`));
16289
16642
  } else if (data.needsRestart) {
16290
- console.log(warn(`${path33} updated \u2014 restart required`));
16643
+ console.log(warn(`${path35} updated \u2014 restart required`));
16291
16644
  }
16292
16645
  }
16293
16646
  }
@@ -16307,30 +16660,30 @@ function flattenToPaths(obj, prefix = "") {
16307
16660
  // src/cli/daemon.ts
16308
16661
  init_config();
16309
16662
  import { spawn as spawn3 } from "child_process";
16310
- import * as fs29 from "fs";
16311
- import * as path28 from "path";
16663
+ import * as fs30 from "fs";
16664
+ import * as path30 from "path";
16312
16665
  import * as os14 from "os";
16313
- var DEFAULT_ROOT2 = path28.join(os14.homedir(), ".openacp");
16666
+ var DEFAULT_ROOT2 = path30.join(os14.homedir(), ".openacp");
16314
16667
  function getPidPath(root) {
16315
16668
  const base = root ?? DEFAULT_ROOT2;
16316
- return path28.join(base, "openacp.pid");
16669
+ return path30.join(base, "openacp.pid");
16317
16670
  }
16318
16671
  function getLogDir(root) {
16319
16672
  const base = root ?? DEFAULT_ROOT2;
16320
- return path28.join(base, "logs");
16673
+ return path30.join(base, "logs");
16321
16674
  }
16322
16675
  function getRunningMarker(root) {
16323
16676
  const base = root ?? DEFAULT_ROOT2;
16324
- return path28.join(base, "running");
16677
+ return path30.join(base, "running");
16325
16678
  }
16326
16679
  function writePidFile(pidPath, pid) {
16327
- const dir = path28.dirname(pidPath);
16328
- fs29.mkdirSync(dir, { recursive: true });
16329
- fs29.writeFileSync(pidPath, String(pid));
16680
+ const dir = path30.dirname(pidPath);
16681
+ fs30.mkdirSync(dir, { recursive: true });
16682
+ fs30.writeFileSync(pidPath, String(pid));
16330
16683
  }
16331
16684
  function readPidFile(pidPath) {
16332
16685
  try {
16333
- const content = fs29.readFileSync(pidPath, "utf-8").trim();
16686
+ const content = fs30.readFileSync(pidPath, "utf-8").trim();
16334
16687
  const pid = parseInt(content, 10);
16335
16688
  return isNaN(pid) ? null : pid;
16336
16689
  } catch {
@@ -16339,7 +16692,7 @@ function readPidFile(pidPath) {
16339
16692
  }
16340
16693
  function removePidFile(pidPath) {
16341
16694
  try {
16342
- fs29.unlinkSync(pidPath);
16695
+ fs30.unlinkSync(pidPath);
16343
16696
  } catch {
16344
16697
  }
16345
16698
  }
@@ -16372,12 +16725,12 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
16372
16725
  return { error: `Already running (PID ${pid})` };
16373
16726
  }
16374
16727
  const resolvedLogDir = logDir2 ? expandHome3(logDir2) : getLogDir(instanceRoot);
16375
- fs29.mkdirSync(resolvedLogDir, { recursive: true });
16376
- const logFile = path28.join(resolvedLogDir, "openacp.log");
16377
- const cliPath = path28.resolve(process.argv[1]);
16728
+ fs30.mkdirSync(resolvedLogDir, { recursive: true });
16729
+ const logFile = path30.join(resolvedLogDir, "openacp.log");
16730
+ const cliPath = path30.resolve(process.argv[1]);
16378
16731
  const nodePath = process.execPath;
16379
- const out = fs29.openSync(logFile, "a");
16380
- const err = fs29.openSync(logFile, "a");
16732
+ const out = fs30.openSync(logFile, "a");
16733
+ const err = fs30.openSync(logFile, "a");
16381
16734
  const child = spawn3(nodePath, [cliPath, "--daemon-child"], {
16382
16735
  detached: true,
16383
16736
  stdio: ["ignore", out, err],
@@ -16386,8 +16739,8 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
16386
16739
  ...instanceRoot ? { OPENACP_INSTANCE_ROOT: instanceRoot } : {}
16387
16740
  }
16388
16741
  });
16389
- fs29.closeSync(out);
16390
- fs29.closeSync(err);
16742
+ fs30.closeSync(out);
16743
+ fs30.closeSync(err);
16391
16744
  if (!child.pid) {
16392
16745
  return { error: "Failed to spawn daemon process" };
16393
16746
  }
@@ -16458,12 +16811,12 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
16458
16811
  }
16459
16812
  function markRunning(root) {
16460
16813
  const marker = getRunningMarker(root);
16461
- fs29.mkdirSync(path28.dirname(marker), { recursive: true });
16462
- fs29.writeFileSync(marker, "");
16814
+ fs30.mkdirSync(path30.dirname(marker), { recursive: true });
16815
+ fs30.writeFileSync(marker, "");
16463
16816
  }
16464
16817
  function clearRunning(root) {
16465
16818
  try {
16466
- fs29.unlinkSync(getRunningMarker(root));
16819
+ fs30.unlinkSync(getRunningMarker(root));
16467
16820
  } catch {
16468
16821
  }
16469
16822
  }
@@ -16479,7 +16832,7 @@ init_static_server();
16479
16832
 
16480
16833
  // src/plugins/telegram/topic-manager.ts
16481
16834
  init_log();
16482
- var log20 = createChildLogger({ module: "topic-manager" });
16835
+ var log21 = createChildLogger({ module: "topic-manager" });
16483
16836
  var TopicManager = class {
16484
16837
  constructor(sessionManager, adapter, systemTopicIds) {
16485
16838
  this.sessionManager = sessionManager;
@@ -16518,7 +16871,7 @@ var TopicManager = class {
16518
16871
  try {
16519
16872
  await this.adapter.deleteSessionThread?.(sessionId);
16520
16873
  } catch (err) {
16521
- log20.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
16874
+ log21.warn({ err, sessionId, topicId }, "Failed to delete platform thread, removing record anyway");
16522
16875
  }
16523
16876
  }
16524
16877
  await this.sessionManager.removeRecord(sessionId);
@@ -16541,7 +16894,7 @@ var TopicManager = class {
16541
16894
  try {
16542
16895
  await this.adapter.deleteSessionThread?.(record.sessionId);
16543
16896
  } catch (err) {
16544
- log20.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
16897
+ log21.warn({ err, sessionId: record.sessionId }, "Failed to delete platform thread during cleanup");
16545
16898
  }
16546
16899
  }
16547
16900
  await this.sessionManager.removeRecord(record.sessionId);