@openacp/cli 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +40 -14
  2. package/dist/action-detect-6M5GCGAU.js +15 -0
  3. package/dist/admin-IKPS5PFC.js +16 -0
  4. package/dist/agents-55NX3DHM.js +14 -0
  5. package/dist/{api-client-UN7BXQOQ.js → api-client-BH2JFHQW.js} +4 -2
  6. package/dist/{autostart-K73RQZVV.js → autostart-A7JRU4WJ.js} +6 -2
  7. package/dist/chunk-3WPG7GXA.js +134 -0
  8. package/dist/chunk-3WPG7GXA.js.map +1 -0
  9. package/dist/{chunk-NDR5JCS7.js → chunk-437NLISU.js} +2 -2
  10. package/dist/chunk-5NBWM7P6.js +438 -0
  11. package/dist/chunk-5NBWM7P6.js.map +1 -0
  12. package/dist/{chunk-F4TB4UBK.js → chunk-6Q7PZWCL.js} +171 -26
  13. package/dist/chunk-6Q7PZWCL.js.map +1 -0
  14. package/dist/chunk-7G5QKLLF.js +105 -0
  15. package/dist/chunk-7G5QKLLF.js.map +1 -0
  16. package/dist/chunk-AKIU4JBF.js +145 -0
  17. package/dist/chunk-AKIU4JBF.js.map +1 -0
  18. package/dist/{chunk-JRF4G4X7.js → chunk-DWQKUECJ.js} +13 -2
  19. package/dist/chunk-DWQKUECJ.js.map +1 -0
  20. package/dist/{chunk-KSIQZC3J.js → chunk-EVFJW45N.js} +1 -1
  21. package/dist/chunk-EVFJW45N.js.map +1 -0
  22. package/dist/chunk-H7ZMPBZC.js +203 -0
  23. package/dist/chunk-H7ZMPBZC.js.map +1 -0
  24. package/dist/chunk-I7WC6E5S.js +71 -0
  25. package/dist/chunk-I7WC6E5S.js.map +1 -0
  26. package/dist/{chunk-4ZGMSNRP.js → chunk-MHFCZGRW.js} +114 -16
  27. package/dist/chunk-MHFCZGRW.js.map +1 -0
  28. package/dist/{chunk-X6LLG7XN.js → chunk-PMGNLNSH.js} +15 -6
  29. package/dist/chunk-PMGNLNSH.js.map +1 -0
  30. package/dist/chunk-SM3G6UAX.js +122 -0
  31. package/dist/chunk-SM3G6UAX.js.map +1 -0
  32. package/dist/{chunk-IRGYTNLP.js → chunk-SPX7CKWV.js} +76 -2
  33. package/dist/chunk-SPX7CKWV.js.map +1 -0
  34. package/dist/chunk-T22OLSET.js +265 -0
  35. package/dist/chunk-T22OLSET.js.map +1 -0
  36. package/dist/chunk-THBR6OXH.js +62 -0
  37. package/dist/chunk-THBR6OXH.js.map +1 -0
  38. package/dist/{chunk-65XE66HK.js → chunk-V2V767XI.js} +373 -489
  39. package/dist/chunk-V2V767XI.js.map +1 -0
  40. package/dist/{chunk-OORPX73T.js → chunk-W3EYKZNQ.js} +17 -2
  41. package/dist/chunk-W3EYKZNQ.js.map +1 -0
  42. package/dist/{chunk-VBEWSWVL.js → chunk-YYQXWA62.js} +52 -9
  43. package/dist/chunk-YYQXWA62.js.map +1 -0
  44. package/dist/cli.js +121 -32
  45. package/dist/cli.js.map +1 -1
  46. package/dist/{config-PCPIBPUA.js → config-KF2MQWAP.js} +2 -2
  47. package/dist/config-editor-OTODXUF7.js +12 -0
  48. package/dist/{daemon-JZLFRUW6.js → daemon-U6UC7OM4.js} +3 -3
  49. package/dist/discord-SLLKRUP7.js +2034 -0
  50. package/dist/discord-SLLKRUP7.js.map +1 -0
  51. package/dist/doctor-DB5PRQ6D.js +14 -0
  52. package/dist/doctor-DB5PRQ6D.js.map +1 -0
  53. package/dist/doctor-SYWNJFYK.js +9 -0
  54. package/dist/doctor-SYWNJFYK.js.map +1 -0
  55. package/dist/index.d.ts +47 -7
  56. package/dist/index.js +11 -9
  57. package/dist/{integrate-BLETI3UO.js → integrate-VOUYBPPZ.js} +106 -3
  58. package/dist/integrate-VOUYBPPZ.js.map +1 -0
  59. package/dist/{main-AH3NCVM3.js → main-M6RH3SS5.js} +31 -23
  60. package/dist/main-M6RH3SS5.js.map +1 -0
  61. package/dist/new-session-DRRP2J7E.js +16 -0
  62. package/dist/new-session-DRRP2J7E.js.map +1 -0
  63. package/dist/session-FVFLBREJ.js +19 -0
  64. package/dist/session-FVFLBREJ.js.map +1 -0
  65. package/dist/settings-LPOLJ6SA.js +12 -0
  66. package/dist/settings-LPOLJ6SA.js.map +1 -0
  67. package/dist/{setup-7JINXQOA.js → setup-LI5CKYDK.js} +9 -5
  68. package/dist/setup-LI5CKYDK.js.map +1 -0
  69. package/dist/{tunnel-service-LEVPLXAZ.js → tunnel-service-U6V4HQOO.js} +263 -47
  70. package/dist/tunnel-service-U6V4HQOO.js.map +1 -0
  71. package/dist/{version-VC5CPXBX.js → version-ALWGGVKM.js} +2 -2
  72. package/dist/version-ALWGGVKM.js.map +1 -0
  73. package/package.json +2 -1
  74. package/dist/chunk-4ZGMSNRP.js.map +0 -1
  75. package/dist/chunk-65XE66HK.js.map +0 -1
  76. package/dist/chunk-F4TB4UBK.js.map +0 -1
  77. package/dist/chunk-IRGYTNLP.js.map +0 -1
  78. package/dist/chunk-JRF4G4X7.js.map +0 -1
  79. package/dist/chunk-KSIQZC3J.js.map +0 -1
  80. package/dist/chunk-OORPX73T.js.map +0 -1
  81. package/dist/chunk-VBEWSWVL.js.map +0 -1
  82. package/dist/chunk-X6LLG7XN.js.map +0 -1
  83. package/dist/config-editor-DDF3ZFJK.js +0 -12
  84. package/dist/doctor-N2HKKUUQ.js +0 -9
  85. package/dist/integrate-BLETI3UO.js.map +0 -1
  86. package/dist/main-AH3NCVM3.js.map +0 -1
  87. package/dist/tunnel-service-LEVPLXAZ.js.map +0 -1
  88. /package/dist/{api-client-UN7BXQOQ.js.map → action-detect-6M5GCGAU.js.map} +0 -0
  89. /package/dist/{autostart-K73RQZVV.js.map → admin-IKPS5PFC.js.map} +0 -0
  90. /package/dist/{config-PCPIBPUA.js.map → agents-55NX3DHM.js.map} +0 -0
  91. /package/dist/{config-editor-DDF3ZFJK.js.map → api-client-BH2JFHQW.js.map} +0 -0
  92. /package/dist/{daemon-JZLFRUW6.js.map → autostart-A7JRU4WJ.js.map} +0 -0
  93. /package/dist/{chunk-NDR5JCS7.js.map → chunk-437NLISU.js.map} +0 -0
  94. /package/dist/{doctor-N2HKKUUQ.js.map → config-KF2MQWAP.js.map} +0 -0
  95. /package/dist/{setup-7JINXQOA.js.map → config-editor-OTODXUF7.js.map} +0 -0
  96. /package/dist/{version-VC5CPXBX.js.map → daemon-U6UC7OM4.js.map} +0 -0
@@ -1,6 +1,10 @@
1
+ import {
2
+ ChannelAdapter,
3
+ PRODUCT_GUIDE
4
+ } from "./chunk-5NBWM7P6.js";
1
5
  import {
2
6
  DoctorEngine
3
- } from "./chunk-IRGYTNLP.js";
7
+ } from "./chunk-SPX7CKWV.js";
4
8
  import {
5
9
  buildMenuKeyboard,
6
10
  buildSkillMessages,
@@ -488,9 +492,10 @@ ${stderr}`
488
492
  }
489
493
  async prompt(text, attachments) {
490
494
  const contentBlocks = [{ type: "text", text }];
495
+ const SUPPORTED_IMAGE_MIMES = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/gif", "image/webp"]);
491
496
  for (const att of attachments ?? []) {
492
497
  const tooLarge = att.size > 10 * 1024 * 1024;
493
- if (att.type === "image" && this.promptCapabilities?.image && !tooLarge) {
498
+ if (att.type === "image" && this.promptCapabilities?.image && !tooLarge && SUPPORTED_IMAGE_MIMES.has(att.mimeType)) {
494
499
  const data = await fs.promises.readFile(att.filePath);
495
500
  contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
496
501
  } else if (att.type === "audio" && this.promptCapabilities?.audio && !tooLarge) {
@@ -640,6 +645,7 @@ var PromptQueue = class {
640
645
  }
641
646
  queue = [];
642
647
  processing = false;
648
+ abortController = null;
643
649
  async enqueue(text, attachments) {
644
650
  if (this.processing) {
645
651
  return new Promise((resolve2) => {
@@ -650,11 +656,21 @@ var PromptQueue = class {
650
656
  }
651
657
  async process(text, attachments) {
652
658
  this.processing = true;
659
+ this.abortController = new AbortController();
660
+ const { signal } = this.abortController;
653
661
  try {
654
- await this.processor(text, attachments);
662
+ await Promise.race([
663
+ this.processor(text, attachments),
664
+ new Promise((_, reject) => {
665
+ signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
666
+ })
667
+ ]);
655
668
  } catch (err) {
656
- this.onError?.(err);
669
+ if (!(err instanceof Error && err.message === "Prompt aborted")) {
670
+ this.onError?.(err);
671
+ }
657
672
  } finally {
673
+ this.abortController = null;
658
674
  this.processing = false;
659
675
  this.drainNext();
660
676
  }
@@ -666,6 +682,9 @@ var PromptQueue = class {
666
682
  }
667
683
  }
668
684
  clear() {
685
+ if (this.abortController) {
686
+ this.abortController.abort();
687
+ }
669
688
  for (const item of this.queue) {
670
689
  item.resolve();
671
690
  }
@@ -958,7 +977,7 @@ var SessionManager = class {
958
977
  getRecordByThread(channelId, threadId) {
959
978
  return this.store?.findByPlatform(
960
979
  channelId,
961
- (p) => String(p.topicId) === threadId
980
+ (p) => String(p.topicId) === threadId || p.threadId === threadId
962
981
  );
963
982
  }
964
983
  registerSession(session) {
@@ -1701,9 +1720,10 @@ var OpenACPCore = class {
1701
1720
  "Incoming message"
1702
1721
  );
1703
1722
  if (config.security.allowedUserIds.length > 0) {
1704
- if (!config.security.allowedUserIds.includes(message.userId)) {
1723
+ const userId = String(message.userId);
1724
+ if (!config.security.allowedUserIds.includes(userId)) {
1705
1725
  log5.warn(
1706
- { userId: message.userId },
1726
+ { userId },
1707
1727
  "Rejected message from unauthorized user"
1708
1728
  );
1709
1729
  return;
@@ -1779,12 +1799,32 @@ var OpenACPCore = class {
1779
1799
  const bridge = this.createBridge(session, adapter);
1780
1800
  bridge.connect();
1781
1801
  }
1802
+ session.on("status_change", (_from, to) => {
1803
+ if ((to === "finished" || to === "cancelled") && this._tunnelService) {
1804
+ this._tunnelService.stopBySession(session.id).then((stopped) => {
1805
+ for (const entry of stopped) {
1806
+ this.notificationManager.notifyAll({
1807
+ sessionId: session.id,
1808
+ sessionName: session.name,
1809
+ type: "completed",
1810
+ summary: `Tunnel stopped: port ${entry.port}${entry.label ? ` (${entry.label})` : ""} \u2014 session ended`
1811
+ }).catch(() => {
1812
+ });
1813
+ }
1814
+ }).catch(() => {
1815
+ });
1816
+ }
1817
+ });
1782
1818
  const existingRecord = this.sessionStore?.get(session.id);
1783
1819
  const platform = {
1784
1820
  ...existingRecord?.platform ?? {}
1785
1821
  };
1786
1822
  if (session.threadId) {
1787
- platform.topicId = Number(session.threadId);
1823
+ if (params.channelId === "telegram") {
1824
+ platform.topicId = Number(session.threadId);
1825
+ } else {
1826
+ platform.threadId = session.threadId;
1827
+ }
1788
1828
  }
1789
1829
  await this.sessionManager.patchRecord(session.id, {
1790
1830
  sessionId: session.id,
@@ -1928,10 +1968,10 @@ var OpenACPCore = class {
1928
1968
  );
1929
1969
  return null;
1930
1970
  }
1931
- if (record.status === "cancelled" || record.status === "error") {
1971
+ if (record.status === "error") {
1932
1972
  log5.debug(
1933
1973
  { threadId: message.threadId, sessionId: record.sessionId, status: record.status },
1934
- "Skipping resume of cancelled/error session"
1974
+ "Skipping resume of error session"
1935
1975
  );
1936
1976
  return null;
1937
1977
  }
@@ -1989,26 +2029,12 @@ var OpenACPCore = class {
1989
2029
  }
1990
2030
  };
1991
2031
 
1992
- // src/core/channel.ts
1993
- var ChannelAdapter = class {
1994
- constructor(core, config) {
1995
- this.core = core;
1996
- this.config = config;
1997
- }
1998
- async deleteSessionThread(_sessionId) {
1999
- }
2000
- // Skill commands — override in adapters that support dynamic commands
2001
- async sendSkillCommands(_sessionId, _commands) {
2002
- }
2003
- async cleanupSkillCommands(_sessionId) {
2004
- }
2005
- };
2006
-
2007
2032
  // src/core/api-server.ts
2008
2033
  import * as http from "http";
2009
2034
  import * as fs4 from "fs";
2010
2035
  import * as path5 from "path";
2011
2036
  import * as os2 from "os";
2037
+ import * as crypto from "crypto";
2012
2038
  import { fileURLToPath } from "url";
2013
2039
  var log6 = createChildLogger({ module: "api-server" });
2014
2040
  var DEFAULT_PORT_FILE = path5.join(os2.homedir(), ".openacp", "api.port");
@@ -2041,17 +2067,21 @@ function redactDeep(obj) {
2041
2067
  }
2042
2068
  }
2043
2069
  var ApiServer = class {
2044
- constructor(core, config, portFilePath, topicManager) {
2070
+ constructor(core, config, portFilePath, topicManager, secretFilePath) {
2045
2071
  this.core = core;
2046
2072
  this.config = config;
2047
2073
  this.topicManager = topicManager;
2048
2074
  this.portFilePath = portFilePath ?? DEFAULT_PORT_FILE;
2075
+ this.secretFilePath = secretFilePath ?? path5.join(os2.homedir(), ".openacp", "api-secret");
2049
2076
  }
2050
2077
  server = null;
2051
2078
  actualPort = 0;
2052
2079
  portFilePath;
2053
2080
  startedAt = Date.now();
2081
+ secret = "";
2082
+ secretFilePath;
2054
2083
  async start() {
2084
+ this.loadOrCreateSecret();
2055
2085
  this.server = http.createServer((req, res) => this.handleRequest(req, res));
2056
2086
  await new Promise((resolve2, reject) => {
2057
2087
  this.server.on("error", (err) => {
@@ -2097,9 +2127,42 @@ var ApiServer = class {
2097
2127
  } catch {
2098
2128
  }
2099
2129
  }
2130
+ loadOrCreateSecret() {
2131
+ const dir = path5.dirname(this.secretFilePath);
2132
+ fs4.mkdirSync(dir, { recursive: true });
2133
+ try {
2134
+ this.secret = fs4.readFileSync(this.secretFilePath, "utf-8").trim();
2135
+ if (this.secret) {
2136
+ try {
2137
+ const stat = fs4.statSync(this.secretFilePath);
2138
+ const mode = stat.mode & 511;
2139
+ if (mode & 63) {
2140
+ log6.warn({ path: this.secretFilePath, mode: "0" + mode.toString(8) }, "API secret file has insecure permissions (should be 0600). Run: chmod 600 %s", this.secretFilePath);
2141
+ }
2142
+ } catch {
2143
+ }
2144
+ return;
2145
+ }
2146
+ } catch {
2147
+ }
2148
+ this.secret = crypto.randomBytes(32).toString("hex");
2149
+ fs4.writeFileSync(this.secretFilePath, this.secret, { mode: 384 });
2150
+ }
2151
+ authenticate(req) {
2152
+ const authHeader = req.headers.authorization;
2153
+ if (!authHeader?.startsWith("Bearer ")) return false;
2154
+ const token = authHeader.slice(7);
2155
+ if (token.length !== this.secret.length) return false;
2156
+ return crypto.timingSafeEqual(Buffer.from(token, "utf-8"), Buffer.from(this.secret, "utf-8"));
2157
+ }
2100
2158
  async handleRequest(req, res) {
2101
2159
  const method = req.method?.toUpperCase();
2102
2160
  const url = req.url || "";
2161
+ const isExempt = method === "GET" && (url === "/api/health" || url === "/api/version");
2162
+ if (!isExempt && !this.authenticate(req)) {
2163
+ this.sendJson(res, 401, { error: "Unauthorized" });
2164
+ return;
2165
+ }
2103
2166
  try {
2104
2167
  if (method === "POST" && url === "/api/sessions/adopt") {
2105
2168
  await this.handleAdoptSession(req, res);
@@ -2135,6 +2198,15 @@ var ApiServer = class {
2135
2198
  await this.handleListAdapters(res);
2136
2199
  } else if (method === "GET" && url === "/api/tunnel") {
2137
2200
  await this.handleTunnelStatus(res);
2201
+ } else if (method === "GET" && url === "/api/tunnel/list") {
2202
+ await this.handleTunnelList(res);
2203
+ } else if (method === "POST" && url === "/api/tunnel") {
2204
+ await this.handleTunnelAdd(req, res);
2205
+ } else if (method === "DELETE" && url === "/api/tunnel") {
2206
+ await this.handleTunnelStopAll(res);
2207
+ } else if (method === "DELETE" && url.match(/^\/api\/tunnel\/(\d+)$/)) {
2208
+ const port = parseInt(url.match(/^\/api\/tunnel\/(\d+)$/)[1], 10);
2209
+ await this.handleTunnelStop(port, res);
2138
2210
  } else if (method === "POST" && url === "/api/notify") {
2139
2211
  await this.handleNotify(req, res);
2140
2212
  } else if (method === "POST" && url === "/api/restart") {
@@ -2361,7 +2433,7 @@ var ApiServer = class {
2361
2433
  return;
2362
2434
  }
2363
2435
  target[lastKey] = value;
2364
- const { ConfigSchema } = await import("./config-PCPIBPUA.js");
2436
+ const { ConfigSchema } = await import("./config-KF2MQWAP.js");
2365
2437
  const result = ConfigSchema.safeParse(cloned);
2366
2438
  if (!result.success) {
2367
2439
  this.sendJson(res, 400, {
@@ -2401,6 +2473,60 @@ var ApiServer = class {
2401
2473
  this.sendJson(res, 200, { enabled: false });
2402
2474
  }
2403
2475
  }
2476
+ async handleTunnelList(res) {
2477
+ const tunnel = this.core.tunnelService;
2478
+ if (!tunnel) {
2479
+ this.sendJson(res, 200, []);
2480
+ return;
2481
+ }
2482
+ this.sendJson(res, 200, tunnel.listTunnels());
2483
+ }
2484
+ async handleTunnelAdd(req, res) {
2485
+ const tunnel = this.core.tunnelService;
2486
+ if (!tunnel) {
2487
+ this.sendJson(res, 400, { error: "Tunnel service is not enabled" });
2488
+ return;
2489
+ }
2490
+ const body = await this.readBody(req);
2491
+ if (!body) {
2492
+ this.sendJson(res, 400, { error: "Missing request body" });
2493
+ return;
2494
+ }
2495
+ try {
2496
+ const { port, label, sessionId } = JSON.parse(body);
2497
+ if (!port || typeof port !== "number") {
2498
+ this.sendJson(res, 400, { error: "port is required and must be a number" });
2499
+ return;
2500
+ }
2501
+ const entry = await tunnel.addTunnel(port, { label, sessionId });
2502
+ this.sendJson(res, 200, entry);
2503
+ } catch (err) {
2504
+ this.sendJson(res, 400, { error: err.message });
2505
+ }
2506
+ }
2507
+ async handleTunnelStop(port, res) {
2508
+ const tunnel = this.core.tunnelService;
2509
+ if (!tunnel) {
2510
+ this.sendJson(res, 400, { error: "Tunnel service is not enabled" });
2511
+ return;
2512
+ }
2513
+ try {
2514
+ await tunnel.stopTunnel(port);
2515
+ this.sendJson(res, 200, { ok: true });
2516
+ } catch (err) {
2517
+ this.sendJson(res, 400, { error: err.message });
2518
+ }
2519
+ }
2520
+ async handleTunnelStopAll(res) {
2521
+ const tunnel = this.core.tunnelService;
2522
+ if (!tunnel) {
2523
+ this.sendJson(res, 400, { error: "Tunnel service is not enabled" });
2524
+ return;
2525
+ }
2526
+ const count = tunnel.listTunnels().length;
2527
+ await tunnel.stopAllUser();
2528
+ this.sendJson(res, 200, { ok: true, stopped: count });
2529
+ }
2404
2530
  async handleNotify(req, res) {
2405
2531
  const body = await this.readBody(req);
2406
2532
  let message;
@@ -2955,7 +3081,7 @@ async function handleUpdate(ctx, core) {
2955
3081
  await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
2956
3082
  return;
2957
3083
  }
2958
- const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-VC5CPXBX.js");
3084
+ const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-ALWGGVKM.js");
2959
3085
  const current = getCurrentVersion();
2960
3086
  const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
2961
3087
  const latest = await getLatestVersion();
@@ -3405,16 +3531,15 @@ async function handleCancel(ctx, core, assistant) {
3405
3531
  String(threadId)
3406
3532
  );
3407
3533
  if (session) {
3408
- log11.info({ sessionId: session.id }, "Cancel session command");
3534
+ log11.info({ sessionId: session.id }, "Abort prompt command");
3409
3535
  await session.abortPrompt();
3410
- await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
3536
+ await ctx.reply("\u26D4 Prompt aborted. Session is still active \u2014 send a new message to continue.", { parse_mode: "HTML" });
3411
3537
  return;
3412
3538
  }
3413
3539
  const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
3414
- if (record && record.status !== "cancelled" && record.status !== "error") {
3415
- log11.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
3416
- await core.sessionManager.cancelSession(record.sessionId);
3417
- await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
3540
+ if (record && record.status !== "error") {
3541
+ log11.info({ sessionId: record.sessionId, status: record.status }, "Cancel command \u2014 no active prompt to abort");
3542
+ await ctx.reply("\u2139\uFE0F No active prompt to cancel. Send a new message to resume the session.", { parse_mode: "HTML" });
3418
3543
  }
3419
3544
  }
3420
3545
  async function handleStatus(ctx, core) {
@@ -3874,7 +3999,7 @@ Downloading... ${bar} ${percent}%`, { parse_mode: "HTML" });
3874
3999
  const { getAgentCapabilities: getAgentCapabilities2 } = await import("./agent-dependencies-QY5QSULV.js");
3875
4000
  const caps = getAgentCapabilities2(result.agentKey);
3876
4001
  if (caps.integration) {
3877
- const { installIntegration } = await import("./integrate-BLETI3UO.js");
4002
+ const { installIntegration } = await import("./integrate-VOUYBPPZ.js");
3878
4003
  const intResult = await installIntegration(result.agentKey, caps.integration);
3879
4004
  if (intResult.success) {
3880
4005
  try {
@@ -3944,7 +4069,7 @@ function buildProgressBar(percent) {
3944
4069
  // src/adapters/telegram/commands/integrate.ts
3945
4070
  import { InlineKeyboard as InlineKeyboard5 } from "grammy";
3946
4071
  async function handleIntegrate(ctx, _core) {
3947
- const { listIntegrations } = await import("./integrate-BLETI3UO.js");
4072
+ const { listIntegrations } = await import("./integrate-VOUYBPPZ.js");
3948
4073
  const agents = listIntegrations();
3949
4074
  const keyboard = new InlineKeyboard5();
3950
4075
  for (const agent of agents) {
@@ -3977,7 +4102,7 @@ function setupIntegrateCallbacks(bot, core) {
3977
4102
  } catch {
3978
4103
  }
3979
4104
  if (data === "i:back") {
3980
- const { listIntegrations } = await import("./integrate-BLETI3UO.js");
4105
+ const { listIntegrations } = await import("./integrate-VOUYBPPZ.js");
3981
4106
  const agents = listIntegrations();
3982
4107
  const keyboard2 = new InlineKeyboard5();
3983
4108
  for (const agent of agents) {
@@ -3997,7 +4122,7 @@ Select an agent to manage its integrations.`,
3997
4122
  const agentMatch = data.match(/^i:agent:(.+)$/);
3998
4123
  if (agentMatch) {
3999
4124
  const agentName2 = agentMatch[1];
4000
- const { getIntegration: getIntegration2 } = await import("./integrate-BLETI3UO.js");
4125
+ const { getIntegration: getIntegration2 } = await import("./integrate-VOUYBPPZ.js");
4001
4126
  const integration2 = getIntegration2(agentName2);
4002
4127
  if (!integration2) {
4003
4128
  await ctx.reply(`\u274C No integration available for '${escapeHtml(agentName2)}'.`, { parse_mode: "HTML" });
@@ -4024,7 +4149,7 @@ ${integration2.items.map((i) => `\u2022 <b>${escapeHtml(i.name)}</b> \u2014 ${es
4024
4149
  const action = actionMatch[1];
4025
4150
  const agentName = actionMatch[2];
4026
4151
  const itemId = actionMatch[3];
4027
- const { getIntegration } = await import("./integrate-BLETI3UO.js");
4152
+ const { getIntegration } = await import("./integrate-VOUYBPPZ.js");
4028
4153
  const integration = getIntegration(agentName);
4029
4154
  if (!integration) return;
4030
4155
  const item = integration.items.find((i) => i.id === itemId);
@@ -4351,6 +4476,155 @@ function setupDoctorCallbacks(bot) {
4351
4476
  });
4352
4477
  }
4353
4478
 
4479
+ // src/adapters/telegram/commands/tunnel.ts
4480
+ import { InlineKeyboard as InlineKeyboard8 } from "grammy";
4481
+ var log14 = createChildLogger({ module: "telegram-cmd-tunnel" });
4482
+ async function handleTunnel(ctx, core) {
4483
+ if (!core.tunnelService) {
4484
+ await ctx.reply("\u274C Tunnel service is not enabled.", { parse_mode: "HTML" });
4485
+ return;
4486
+ }
4487
+ const match = ctx.match?.trim() ?? "";
4488
+ if (match.startsWith("stop ")) {
4489
+ const portStr = match.slice(5).trim();
4490
+ const port = parseInt(portStr, 10);
4491
+ if (isNaN(port)) {
4492
+ await ctx.reply("\u274C Invalid port number.", { parse_mode: "HTML" });
4493
+ return;
4494
+ }
4495
+ try {
4496
+ await core.tunnelService.stopTunnel(port);
4497
+ await ctx.reply(`\u{1F50C} Tunnel stopped: port ${port}`, { parse_mode: "HTML" });
4498
+ } catch (err) {
4499
+ await ctx.reply(`\u274C ${escapeHtml(err.message)}`, { parse_mode: "HTML" });
4500
+ }
4501
+ return;
4502
+ }
4503
+ if (match) {
4504
+ const parts = match.split(/\s+/);
4505
+ const port = parseInt(parts[0], 10);
4506
+ if (isNaN(port)) {
4507
+ await ctx.reply("\u274C Invalid port number. Usage: <code>/tunnel 3000 [label]</code>", { parse_mode: "HTML" });
4508
+ return;
4509
+ }
4510
+ const label = parts.slice(1).join(" ") || void 0;
4511
+ const threadId = ctx.message?.message_thread_id;
4512
+ let sessionId;
4513
+ if (threadId) {
4514
+ const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
4515
+ if (session) sessionId = session.id;
4516
+ }
4517
+ try {
4518
+ await ctx.reply(`\u23F3 Starting tunnel for port ${port}...`, { parse_mode: "HTML" });
4519
+ const entry = await core.tunnelService.addTunnel(port, { label, sessionId });
4520
+ await ctx.reply(
4521
+ `\u{1F517} <b>Tunnel active</b>
4522
+
4523
+ Port ${port}${label ? ` (${escapeHtml(label)})` : ""}
4524
+ \u2192 <a href="${escapeHtml(entry.publicUrl || "")}">${escapeHtml(entry.publicUrl || "")}</a>`,
4525
+ { parse_mode: "HTML" }
4526
+ );
4527
+ } catch (err) {
4528
+ await ctx.reply(`\u274C ${escapeHtml(err.message)}`, { parse_mode: "HTML" });
4529
+ }
4530
+ return;
4531
+ }
4532
+ await ctx.reply(
4533
+ `<b>Tunnel commands:</b>
4534
+
4535
+ <code>/tunnel &lt;port&gt; [label]</code> \u2014 Create tunnel
4536
+ <code>/tunnel stop &lt;port&gt;</code> \u2014 Stop tunnel
4537
+ <code>/tunnels</code> \u2014 List active tunnels`,
4538
+ { parse_mode: "HTML" }
4539
+ );
4540
+ }
4541
+ async function handleTunnels(ctx, core) {
4542
+ if (!core.tunnelService) {
4543
+ await ctx.reply("\u274C Tunnel service is not enabled.", { parse_mode: "HTML" });
4544
+ return;
4545
+ }
4546
+ const entries = core.tunnelService.listTunnels();
4547
+ if (entries.length === 0) {
4548
+ await ctx.reply(
4549
+ "No active tunnels.\n\nUse <code>/tunnel &lt;port&gt;</code> to create one.",
4550
+ { parse_mode: "HTML" }
4551
+ );
4552
+ return;
4553
+ }
4554
+ const lines = entries.map((e) => {
4555
+ const status = e.status === "active" ? "\u2705" : e.status === "starting" ? "\u23F3" : "\u274C";
4556
+ const label = e.label ? ` (${escapeHtml(e.label)})` : "";
4557
+ const url = e.publicUrl ? `
4558
+ \u2192 <a href="${escapeHtml(e.publicUrl)}">${escapeHtml(e.publicUrl)}</a>` : "";
4559
+ return `${status} Port <b>${e.port}</b>${label}${url}`;
4560
+ });
4561
+ const keyboard = new InlineKeyboard8();
4562
+ for (const e of entries) {
4563
+ keyboard.text(`\u{1F50C} Stop ${e.port}${e.label ? ` (${e.label})` : ""}`, `tn:stop:${e.port}`).row();
4564
+ }
4565
+ if (entries.length > 1) {
4566
+ keyboard.text("\u{1F50C} Stop all", "tn:stop-all").row();
4567
+ }
4568
+ await ctx.reply(
4569
+ `<b>Active tunnels:</b>
4570
+
4571
+ ${lines.join("\n\n")}`,
4572
+ { parse_mode: "HTML", reply_markup: keyboard }
4573
+ );
4574
+ }
4575
+ function setupTunnelCallbacks(bot, core) {
4576
+ bot.callbackQuery(/^tn:/, async (ctx) => {
4577
+ const data = ctx.callbackQuery.data;
4578
+ if (!core.tunnelService) {
4579
+ await ctx.answerCallbackQuery({ text: "Tunnel not enabled" });
4580
+ return;
4581
+ }
4582
+ try {
4583
+ if (data === "tn:stop-all") {
4584
+ const entries = core.tunnelService.listTunnels();
4585
+ for (const e of entries) {
4586
+ try {
4587
+ await core.tunnelService.stopTunnel(e.port);
4588
+ } catch {
4589
+ }
4590
+ }
4591
+ await ctx.answerCallbackQuery({ text: "All tunnels stopped" });
4592
+ await ctx.editMessageText("\u{1F50C} All tunnels stopped.", { parse_mode: "HTML" });
4593
+ } else if (data.startsWith("tn:stop:")) {
4594
+ const port = parseInt(data.replace("tn:stop:", ""), 10);
4595
+ await core.tunnelService.stopTunnel(port);
4596
+ await ctx.answerCallbackQuery({ text: `Port ${port} stopped` });
4597
+ const remaining = core.tunnelService.listTunnels();
4598
+ if (remaining.length === 0) {
4599
+ await ctx.editMessageText("\u{1F50C} All tunnels stopped.", { parse_mode: "HTML" });
4600
+ } else {
4601
+ const kb = new InlineKeyboard8();
4602
+ for (const e of remaining) {
4603
+ kb.text(`\u{1F50C} Stop ${e.port}${e.label ? ` (${e.label})` : ""}`, `tn:stop:${e.port}`).row();
4604
+ }
4605
+ if (remaining.length > 1) {
4606
+ kb.text("\u{1F50C} Stop all", "tn:stop-all").row();
4607
+ }
4608
+ await ctx.editMessageText(
4609
+ `<b>Active tunnels:</b>
4610
+
4611
+ ` + remaining.map((e) => {
4612
+ const status = e.status === "active" ? "\u2705" : "\u23F3";
4613
+ const label = e.label ? ` (${escapeHtml(e.label)})` : "";
4614
+ const url = e.publicUrl ? `
4615
+ \u2192 <a href="${escapeHtml(e.publicUrl)}">${escapeHtml(e.publicUrl)}</a>` : "";
4616
+ return `${status} Port <b>${e.port}</b>${label}${url}`;
4617
+ }).join("\n\n"),
4618
+ { parse_mode: "HTML", reply_markup: kb }
4619
+ );
4620
+ }
4621
+ }
4622
+ } catch (err) {
4623
+ await ctx.answerCallbackQuery({ text: err.message });
4624
+ }
4625
+ });
4626
+ }
4627
+
4354
4628
  // src/adapters/telegram/commands/index.ts
4355
4629
  function setupCommands(bot, core, chatId, assistant) {
4356
4630
  bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
@@ -4369,12 +4643,15 @@ function setupCommands(bot, core, chatId, assistant) {
4369
4643
  bot.command("integrate", (ctx) => handleIntegrate(ctx, core));
4370
4644
  bot.command("clear", (ctx) => handleClear(ctx, assistant));
4371
4645
  bot.command("doctor", (ctx) => handleDoctor(ctx));
4646
+ bot.command("tunnel", (ctx) => handleTunnel(ctx, core));
4647
+ bot.command("tunnels", (ctx) => handleTunnels(ctx, core));
4372
4648
  }
4373
4649
  function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession) {
4374
4650
  setupNewSessionCallbacks(bot, core, chatId);
4375
4651
  setupSessionCallbacks(bot, core, chatId, systemTopicIds);
4376
4652
  setupSettingsCallbacks(bot, core, getAssistantSession ?? (() => void 0));
4377
4653
  setupDoctorCallbacks(bot);
4654
+ setupTunnelCallbacks(bot, core);
4378
4655
  bot.callbackQuery(/^ag:/, (ctx) => handleAgentCallback(ctx, core));
4379
4656
  bot.callbackQuery(/^na:/, async (ctx) => {
4380
4657
  const agentKey = ctx.callbackQuery.data.replace("na:", "");
@@ -4435,13 +4712,15 @@ var STATIC_COMMANDS = [
4435
4712
  { command: "clear", description: "Clear assistant history" },
4436
4713
  { command: "restart", description: "Restart OpenACP" },
4437
4714
  { command: "update", description: "Update to latest version and restart" },
4438
- { command: "doctor", description: "Run system diagnostics" }
4715
+ { command: "doctor", description: "Run system diagnostics" },
4716
+ { command: "tunnel", description: "Create/stop tunnel for a local port" },
4717
+ { command: "tunnels", description: "List active tunnels" }
4439
4718
  ];
4440
4719
 
4441
4720
  // src/adapters/telegram/permissions.ts
4442
- import { InlineKeyboard as InlineKeyboard8 } from "grammy";
4721
+ import { InlineKeyboard as InlineKeyboard9 } from "grammy";
4443
4722
  import { nanoid as nanoid2 } from "nanoid";
4444
- var log14 = createChildLogger({ module: "telegram-permissions" });
4723
+ var log15 = createChildLogger({ module: "telegram-permissions" });
4445
4724
  var PermissionHandler = class {
4446
4725
  constructor(bot, chatId, getSession, sendNotification) {
4447
4726
  this.bot = bot;
@@ -4458,7 +4737,7 @@ var PermissionHandler = class {
4458
4737
  requestId: request.id,
4459
4738
  options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
4460
4739
  });
4461
- const keyboard = new InlineKeyboard8();
4740
+ const keyboard = new InlineKeyboard9();
4462
4741
  for (const option of request.options) {
4463
4742
  const emoji = option.isAllow ? "\u2705" : "\u274C";
4464
4743
  keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
@@ -4501,7 +4780,7 @@ ${escapeHtml(request.description)}`,
4501
4780
  }
4502
4781
  const session = this.getSession(pending.sessionId);
4503
4782
  const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
4504
- log14.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
4783
+ log15.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
4505
4784
  if (session?.permissionGate.requestId === pending.requestId) {
4506
4785
  session.permissionGate.resolve(optionId);
4507
4786
  }
@@ -4518,408 +4797,11 @@ ${escapeHtml(request.description)}`,
4518
4797
  }
4519
4798
  };
4520
4799
 
4521
- // src/product-guide.ts
4522
- var PRODUCT_GUIDE = `
4523
- # OpenACP \u2014 Product Guide
4524
-
4525
- OpenACP lets you chat with AI coding agents (like Claude Code) through Telegram.
4526
- You type messages in Telegram, the agent reads/writes/runs code in your project folder, and results stream back in real time.
4527
-
4528
- ---
4529
-
4530
- ## Quick Start
4531
-
4532
- 1. Start OpenACP: \`openacp\` (or \`openacp start\` for background daemon)
4533
- 2. Open your Telegram group \u2014 you'll see the Assistant topic
4534
- 3. Tap \u{1F195} New Session or type /new
4535
- 4. Pick an agent and a project folder
4536
- 5. Chat in the session topic \u2014 the agent works on your code
4537
-
4538
- ---
4539
-
4540
- ## Core Concepts
4541
-
4542
- ### Sessions
4543
- A session = one conversation with one AI agent working in one project folder.
4544
- Each session gets its own Telegram topic. Chat there to give instructions to the agent.
4545
-
4546
- ### Agents
4547
- An agent is an AI coding tool (e.g., Claude Code, Gemini, Cursor, Codex, etc.).
4548
- OpenACP supports 28+ agents from the official ACP Registry (agentclientprotocol.com).
4549
- You can install multiple agents and choose which one to use per session.
4550
- The default agent is used when you don't specify one.
4551
-
4552
- ### Agent Management
4553
- - Browse agents: \`/agents\` in Telegram or \`openacp agents\` in CLI
4554
- - Install: tap the install button in /agents, or \`openacp agents install <name>\`
4555
- - Uninstall: \`openacp agents uninstall <name>\`
4556
- - Setup/login: \`openacp agents run <name> -- <args>\` (e.g., \`openacp agents run gemini -- auth login\`)
4557
- - Details: \`openacp agents info <name>\` shows version, dependencies, and setup steps
4558
-
4559
- Some agents need additional setup before they can be used:
4560
- - Claude: requires \`claude login\`
4561
- - Gemini: requires \`openacp agents run gemini -- auth login\`
4562
- - Codex: requires setting \`OPENAI_API_KEY\` environment variable
4563
- - GitHub Copilot: requires \`openacp agents run copilot -- auth login\`
4564
-
4565
- Agents are installed in three ways depending on the agent:
4566
- - **npx** \u2014 Node.js agents, downloaded automatically on first use
4567
- - **uvx** \u2014 Python agents, downloaded automatically on first use
4568
- - **binary** \u2014 Platform-specific binaries, downloaded to \`~/.openacp/agents/\`
4569
-
4570
- ### Project Folder (Workspace)
4571
- The directory where the agent reads, writes, and runs code.
4572
- When creating a session, you choose which folder the agent works in.
4573
- You can type a full path like \`~/code/my-project\` or just a name like \`my-project\` (it becomes \`<base-dir>/my-project\`).
4574
-
4575
- ### System Topics
4576
- - **Assistant** \u2014 Always-on helper that can answer questions, create sessions, check status, troubleshoot
4577
- - **Notifications** \u2014 System alerts (permission requests, session errors, completions)
4578
-
4579
- ---
4580
-
4581
- ## Creating Sessions
4582
-
4583
- ### From menu
4584
- Tap \u{1F195} New Session \u2192 choose agent (if multiple) \u2192 choose project folder \u2192 confirm
4585
-
4586
- ### From command
4587
- - \`/new\` \u2014 Interactive flow (asks agent + folder)
4588
- - \`/new claude ~/code/my-project\` \u2014 Create directly with specific agent and folder
4589
-
4590
- ### From Assistant topic
4591
- Just ask: "Create a session for my-project with claude" \u2014 the assistant handles it
4592
-
4593
- ### Quick new chat
4594
- \`/newchat\` in a session topic \u2014 creates new session with same agent and folder as current one
4595
-
4596
- ---
4597
-
4598
- ## Working with Sessions
4599
-
4600
- ### Chat
4601
- Type messages in the session topic. The agent responds with code changes, explanations, tool outputs.
4602
-
4603
- ### What you see while the agent works
4604
- - **\u{1F4AD} Thinking indicator** \u2014 Shows when the agent is reasoning, with elapsed time
4605
- - **Text responses** \u2014 Streamed in real time, updated every few seconds
4606
- - **Tool calls** \u2014 When the agent runs commands or edits files, you see tool name, input, status, and output
4607
- - **\u{1F4CB} Plan card** \u2014 Visual task progress with completed/in-progress/pending items and progress bar
4608
- - **"View File" / "View Diff" buttons** \u2014 Opens in browser with Monaco editor (requires tunnel)
4609
-
4610
- ### Session lifecycle
4611
- 1. **Creating** \u2014 Topic created, agent spawning
4612
- 2. **Warming up** \u2014 Agent primes its cache (happens automatically, invisible to you)
4613
- 3. **Active** \u2014 Ready for your messages
4614
- 4. **Auto-naming** \u2014 After your first message, the session gets a descriptive name (agent summarizes in ~5 words). The topic title updates automatically.
4615
- 5. **Finished/Error** \u2014 Session completed or hit an error
4616
-
4617
- ### Agent skills
4618
- Some agents provide slash commands (e.g., /compact, /review). Available skills are pinned in the session topic.
4619
-
4620
- ### Permission requests
4621
- When the agent wants to run a command, it asks for permission.
4622
- You see buttons: \u2705 Allow, \u274C Reject (and sometimes "Always Allow").
4623
- A notification also appears in the Notifications topic with a link to the request.
4624
-
4625
- ### Dangerous mode
4626
- Auto-approves ALL permission requests \u2014 the agent runs any command without asking.
4627
- - Enable: \`/enable_dangerous\` or tap the \u2620\uFE0F button in the session
4628
- - Disable: \`/disable_dangerous\` or tap the \u{1F510} button
4629
- - \u26A0\uFE0F Use with caution \u2014 the agent can execute anything
4630
-
4631
- ### Session timeout
4632
- Idle sessions are automatically cancelled after a configurable timeout (default: 60 minutes).
4633
- Configure via \`security.sessionTimeoutMinutes\` in config.
4634
-
4635
- ---
4636
-
4637
- ## Session Transfer (Handoff)
4638
-
4639
- ### Telegram \u2192 Terminal
4640
- 1. Type \`/handoff\` in a session topic
4641
- 2. You get a command like \`claude --resume <SESSION_ID>\`
4642
- 3. Copy and run it in your terminal \u2014 the session continues there with full conversation history
4643
-
4644
- ### Terminal \u2192 Telegram
4645
- 1. First time: run \`openacp integrate claude\` to install the handoff skill (one-time setup)
4646
- 2. In Claude Code, use the /openacp:handoff slash command
4647
- 3. The session appears as a new topic in Telegram and you can continue chatting there
4648
-
4649
- ### How it works
4650
- - The agent session ID is shared between platforms
4651
- - Conversation history is preserved \u2014 pick up where you left off
4652
- - The agent that supports resume (e.g., Claude with \`--resume\`) handles the actual transfer
4653
-
4654
- ---
4655
-
4656
- ## Managing Sessions
4657
-
4658
- ### Status
4659
- - \`/status\` \u2014 Shows active sessions count and details
4660
- - Ask the Assistant: "What sessions are running?"
4661
-
4662
- ### List all sessions
4663
- - \`/sessions\` \u2014 Shows all sessions with status (active, finished, error)
4664
-
4665
- ### Cancel
4666
- - \`/cancel\` in a session topic \u2014 cancels that session
4667
- - Ask the Assistant: "Cancel the stuck session"
4668
-
4669
- ### Cleanup
4670
- - From \`/sessions\` \u2192 tap cleanup buttons (finished, errors, all)
4671
- - Ask the Assistant: "Clean up old sessions"
4672
-
4673
- ---
4674
-
4675
- ## Assistant Topic
4676
-
4677
- The Assistant is an always-on AI helper in its own topic. It can:
4678
- - Answer questions about OpenACP
4679
- - Create sessions for you
4680
- - Check status and health
4681
- - Cancel sessions
4682
- - Clean up old sessions
4683
- - Troubleshoot issues
4684
- - Manage configuration
4685
-
4686
- Just chat naturally: "How do I create a session?", "What's the status?", "Something is stuck"
4687
-
4688
- ### Clear history
4689
- \`/clear\` in the Assistant topic \u2014 resets the conversation
4690
-
4691
- ---
4692
-
4693
- ## System Commands
4694
-
4695
- | Command | Where | What it does |
4696
- |---------|-------|-------------|
4697
- | \`/new [agent] [path]\` | Anywhere | Create new session |
4698
- | \`/newchat\` | Session topic | New session, same agent + folder |
4699
- | \`/cancel\` | Session topic | Cancel current session |
4700
- | \`/status\` | Anywhere | Show status |
4701
- | \`/sessions\` | Anywhere | List all sessions |
4702
- | \`/agents\` | Anywhere | Browse & install agents from ACP Registry |
4703
- | \`/install <name>\` | Anywhere | Install an agent |
4704
- | \`/enable_dangerous\` | Session topic | Auto-approve all permissions |
4705
- | \`/disable_dangerous\` | Session topic | Restore permission prompts |
4706
- | \`/handoff\` | Session topic | Transfer session to terminal |
4707
- | \`/clear\` | Assistant topic | Clear assistant history |
4708
- | \`/menu\` | Anywhere | Show action menu |
4709
- | \`/help\` | Anywhere | Show help |
4710
- | \`/restart\` | Anywhere | Restart OpenACP |
4711
- | \`/update\` | Anywhere | Update to latest version |
4712
- | \`/integrate\` | Anywhere | Manage agent integrations |
4713
-
4714
- ---
4715
-
4716
- ## Menu Buttons
4717
-
4718
- | Button | Action |
4719
- |--------|--------|
4720
- | \u{1F195} New Session | Create new session (interactive) |
4721
- | \u{1F4CB} Sessions | List all sessions with cleanup options |
4722
- | \u{1F4CA} Status | Show active/total session count |
4723
- | \u{1F916} Agents | List available agents |
4724
- | \u{1F517} Integrate | Manage agent integrations |
4725
- | \u2753 Help | Show help text |
4726
- | \u{1F504} Restart | Restart OpenACP |
4727
- | \u2B06\uFE0F Update | Check and install updates |
4728
-
4729
- ---
4730
-
4731
- ## CLI Commands
4732
-
4733
- ### Server
4734
- - \`openacp\` \u2014 Start (uses configured mode: foreground or daemon)
4735
- - \`openacp start\` \u2014 Start as background daemon
4736
- - \`openacp stop\` \u2014 Stop daemon
4737
- - \`openacp status\` \u2014 Show daemon status
4738
- - \`openacp logs\` \u2014 Tail daemon logs
4739
- - \`openacp --foreground\` \u2014 Force foreground mode (useful for debugging or containers)
4740
-
4741
- ### Auto-start (run on boot)
4742
- - macOS: installs a LaunchAgent in \`~/Library/LaunchAgents/\`
4743
- - Linux: installs a systemd user service in \`~/.config/systemd/user/\`
4744
- - Enabled automatically when you start the daemon. Remove with \`openacp stop\`.
4745
-
4746
- ### Configuration
4747
- - \`openacp config\` \u2014 Interactive config editor
4748
- - \`openacp reset\` \u2014 Delete all data and start fresh
4749
-
4750
- ### Agent Management (CLI)
4751
- - \`openacp agents\` \u2014 List all agents (installed + available from ACP Registry)
4752
- - \`openacp agents install <name>\` \u2014 Install an agent
4753
- - \`openacp agents uninstall <name>\` \u2014 Remove an agent
4754
- - \`openacp agents info <name>\` \u2014 Show details, dependencies, and setup guide
4755
- - \`openacp agents run <name> [-- args]\` \u2014 Run agent CLI directly (for login, config, etc.)
4756
- - \`openacp agents refresh\` \u2014 Force-refresh registry cache
4757
-
4758
- ### Plugins
4759
- - \`openacp install <package>\` \u2014 Install adapter plugin (e.g., \`@openacp/adapter-discord\`)
4760
- - \`openacp uninstall <package>\` \u2014 Remove adapter plugin
4761
- - \`openacp plugins\` \u2014 List installed plugins
4762
-
4763
- ### Integration
4764
- - \`openacp integrate <agent>\` \u2014 Install agent integration (e.g., Claude handoff skill)
4765
- - \`openacp integrate <agent> --uninstall\` \u2014 Remove integration
4766
-
4767
- ### API (requires running daemon)
4768
- \`openacp api <command>\` \u2014 Interact with running daemon:
4769
-
4770
- | Command | Description |
4771
- |---------|-------------|
4772
- | \`status\` | List active sessions |
4773
- | \`session <id>\` | Session details |
4774
- | \`new <agent> <path>\` | Create session |
4775
- | \`send <id> "text"\` | Send prompt |
4776
- | \`cancel <id>\` | Cancel session |
4777
- | \`dangerous <id> on/off\` | Toggle dangerous mode |
4778
- | \`topics [--status x,y]\` | List topics |
4779
- | \`delete-topic <id> [--force]\` | Delete topic |
4780
- | \`cleanup [--status x,y]\` | Cleanup old topics |
4781
- | \`agents\` | List agents |
4782
- | \`health\` | System health |
4783
- | \`config\` | Show config |
4784
- | \`config set <key> <value>\` | Update config |
4785
- | \`adapters\` | List adapters |
4786
- | \`tunnel\` | Tunnel status |
4787
- | \`notify "message"\` | Send notification |
4788
- | \`version\` | Daemon version |
4789
- | \`restart\` | Restart daemon |
4790
-
4791
- ---
4792
-
4793
- ## File Viewer (Tunnel)
4794
-
4795
- When tunnel is enabled, file edits and diffs get "View" buttons that open in your browser:
4796
- - **Monaco Editor** \u2014 Full VS Code editor with syntax highlighting
4797
- - **Diff viewer** \u2014 Side-by-side or inline comparison
4798
- - **Line highlighting** \u2014 Click lines to highlight
4799
- - Dark/light theme toggle
4800
-
4801
- ### Setup
4802
- Enable in config: set \`tunnel.enabled\` to \`true\`.
4803
- Providers: Cloudflare (default, free), ngrok, bore, Tailscale Funnel.
4804
-
4805
- ---
4806
-
4807
- ## Configuration
4808
-
4809
- Config file: \`~/.openacp/config.json\`
4810
-
4811
- ### Telegram
4812
- - **telegram.botToken** \u2014 Your Telegram bot token
4813
- - **telegram.chatId** \u2014 Your Telegram supergroup ID
4814
-
4815
- ### Agents
4816
- - **defaultAgent** \u2014 Which agent to use by default
4817
- - Agents are managed via \`/agents\` (Telegram) or \`openacp agents\` (CLI)
4818
- - Installed agents are stored in \`~/.openacp/agents.json\`
4819
- - Agent list is fetched from the ACP Registry CDN and cached locally (24h)
4820
-
4821
- ### Workspace
4822
- - **workspace.baseDir** \u2014 Base directory for project folders (default: \`~/openacp-workspace\`)
4823
-
4824
- ### Security
4825
- - **security.allowedUserIds** \u2014 Restrict who can use the bot (empty = everyone)
4826
- - **security.maxConcurrentSessions** \u2014 Max parallel sessions (default: 5)
4827
- - **security.sessionTimeoutMinutes** \u2014 Auto-cancel idle sessions (default: 60)
4828
-
4829
- ### Tunnel / File Viewer
4830
- - **tunnel.enabled** \u2014 Enable file viewer tunnel
4831
- - **tunnel.provider** \u2014 Tunnel provider: cloudflare (default, free), ngrok, bore, tailscale
4832
- - **tunnel.port** \u2014 Local port for tunnel server (default: 3100)
4833
- - **tunnel.auth.enabled** \u2014 Enable authentication for tunnel URLs
4834
- - **tunnel.auth.token** \u2014 Auth token for tunnel access
4835
- - **tunnel.storeTtlMinutes** \u2014 How long viewer links stay cached (default: 60)
4836
-
4837
- ### Logging
4838
- - **logging.level** \u2014 Log level: silent, debug, info, warn, error, fatal (default: info)
4839
- - **logging.logDir** \u2014 Log directory (default: \`~/.openacp/logs\`)
4840
- - **logging.maxFileSize** \u2014 Max log file size before rotation
4841
- - **logging.maxFiles** \u2014 Max number of rotated log files
4842
- - **logging.sessionLogRetentionDays** \u2014 Auto-delete old session logs (default: 30)
4843
-
4844
- ### Data Retention
4845
- - **sessionStore.ttlDays** \u2014 How long session records persist (default: 30). Old records are cleaned up automatically.
4846
-
4847
- ### Environment variables
4848
- Override config with env vars:
4849
- - \`OPENACP_TELEGRAM_BOT_TOKEN\`
4850
- - \`OPENACP_TELEGRAM_CHAT_ID\`
4851
- - \`OPENACP_DEFAULT_AGENT\`
4852
- - \`OPENACP_RUN_MODE\` \u2014 foreground or daemon
4853
- - \`OPENACP_API_PORT\` \u2014 API server port (default: 21420)
4854
- - \`OPENACP_TUNNEL_ENABLED\`
4855
- - \`OPENACP_TUNNEL_PORT\`
4856
- - \`OPENACP_TUNNEL_PROVIDER\`
4857
- - \`OPENACP_LOG_LEVEL\`
4858
- - \`OPENACP_LOG_DIR\`
4859
- - \`OPENACP_DEBUG\` \u2014 Sets log level to debug
4860
-
4861
- ---
4862
-
4863
- ## Troubleshooting
4864
-
4865
- ### Session stuck / not responding
4866
- - Check status: ask Assistant "Is anything stuck?"
4867
- - Cancel and create new: \`/cancel\` then \`/new\`
4868
- - Check system health: Assistant can run health check
4869
-
4870
- ### Agent not found
4871
- - Check available agents: \`/agents\` or \`openacp agents\`
4872
- - Install missing agent: \`openacp agents install <name>\`
4873
- - Some agents need login first: \`openacp agents info <name>\` to see setup steps
4874
- - Run agent CLI for setup: \`openacp agents run <name> -- <args>\`
4875
-
4876
- ### Permission request not showing
4877
- - Check Notifications topic for the alert
4878
- - Try \`/enable_dangerous\` to auto-approve (if you trust the agent)
4879
-
4880
- ### Session disappeared after restart
4881
- - Sessions persist across restarts
4882
- - Send a message in the old topic \u2014 it auto-resumes
4883
- - If topic was deleted, the session record may still exist in status
4884
-
4885
- ### Bot not responding at all
4886
- - Check daemon: \`openacp status\`
4887
- - Check logs: \`openacp logs\`
4888
- - Restart: \`openacp start\` or \`/restart\`
4889
-
4890
- ### Messages going to wrong topic
4891
- - Each session is bound to a specific Telegram topic
4892
- - If you see messages appearing in the Assistant topic instead of the session topic, try creating a new session
4893
-
4894
- ### Viewing logs
4895
- - Session-specific logs: \`~/.openacp/logs/sessions/\`
4896
- - System logs: \`openacp logs\` to tail live
4897
- - Set \`OPENACP_DEBUG=true\` for verbose output
4898
-
4899
- ---
4900
-
4901
- ## Data & Storage
4902
-
4903
- All data is stored in \`~/.openacp/\`:
4904
- - \`config.json\` \u2014 Configuration
4905
- - \`agents.json\` \u2014 Installed agents (managed by AgentCatalog)
4906
- - \`registry-cache.json\` \u2014 Cached ACP Registry data (refreshes every 24h)
4907
- - \`agents/\` \u2014 Downloaded binary agents
4908
- - \`sessions/\` \u2014 Session records and state
4909
- - \`topics/\` \u2014 Topic-to-session mappings
4910
- - \`logs/\` \u2014 System and session logs
4911
- - \`plugins/\` \u2014 Installed adapter plugins
4912
- - \`openacp.pid\` \u2014 Daemon PID file
4913
-
4914
- Session records auto-cleanup: 30 days (configurable via \`sessionStore.ttlDays\`).
4915
- Session logs auto-cleanup: 30 days (configurable via \`logging.sessionLogRetentionDays\`).
4916
- `;
4917
-
4918
4800
  // src/adapters/telegram/assistant.ts
4919
- var log15 = createChildLogger({ module: "telegram-assistant" });
4801
+ var log16 = createChildLogger({ module: "telegram-assistant" });
4920
4802
  async function spawnAssistant(core, adapter, assistantTopicId) {
4921
4803
  const config = core.configManager.get();
4922
- log15.info({ agent: config.defaultAgent }, "Creating assistant session...");
4804
+ log16.info({ agent: config.defaultAgent }, "Creating assistant session...");
4923
4805
  const session = await core.createSession({
4924
4806
  channelId: "telegram",
4925
4807
  agentName: config.defaultAgent,
@@ -4928,7 +4810,7 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
4928
4810
  // Prevent auto-naming from triggering after system prompt
4929
4811
  });
4930
4812
  session.threadId = String(assistantTopicId);
4931
- log15.info({ sessionId: session.id }, "Assistant agent spawned");
4813
+ log16.info({ sessionId: session.id }, "Assistant agent spawned");
4932
4814
  const allRecords = core.sessionManager.listRecords();
4933
4815
  const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
4934
4816
  const statusCounts = /* @__PURE__ */ new Map();
@@ -4949,9 +4831,9 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
4949
4831
  };
4950
4832
  const systemPrompt = buildAssistantSystemPrompt(ctx);
4951
4833
  const ready = session.enqueuePrompt(systemPrompt).then(() => {
4952
- log15.info({ sessionId: session.id }, "Assistant system prompt completed");
4834
+ log16.info({ sessionId: session.id }, "Assistant system prompt completed");
4953
4835
  }).catch((err) => {
4954
- log15.warn({ err }, "Assistant system prompt failed");
4836
+ log16.warn({ err }, "Assistant system prompt failed");
4955
4837
  });
4956
4838
  return { session, ready };
4957
4839
  }
@@ -5106,7 +4988,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
5106
4988
  }
5107
4989
 
5108
4990
  // src/adapters/telegram/activity.ts
5109
- var log16 = createChildLogger({ module: "telegram:activity" });
4991
+ var log17 = createChildLogger({ module: "telegram:activity" });
5110
4992
  var THINKING_REFRESH_MS = 15e3;
5111
4993
  var THINKING_MAX_MS = 3 * 60 * 1e3;
5112
4994
  var ThinkingIndicator = class {
@@ -5138,7 +5020,7 @@ var ThinkingIndicator = class {
5138
5020
  this.startRefreshTimer();
5139
5021
  }
5140
5022
  } catch (err) {
5141
- log16.warn({ err }, "ThinkingIndicator.show() failed");
5023
+ log17.warn({ err }, "ThinkingIndicator.show() failed");
5142
5024
  } finally {
5143
5025
  this.sending = false;
5144
5026
  }
@@ -5211,7 +5093,7 @@ var UsageMessage = class {
5211
5093
  if (result) this.msgId = result.message_id;
5212
5094
  }
5213
5095
  } catch (err) {
5214
- log16.warn({ err }, "UsageMessage.send() failed");
5096
+ log17.warn({ err }, "UsageMessage.send() failed");
5215
5097
  }
5216
5098
  }
5217
5099
  getMsgId() {
@@ -5224,7 +5106,7 @@ var UsageMessage = class {
5224
5106
  try {
5225
5107
  await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
5226
5108
  } catch (err) {
5227
- log16.warn({ err }, "UsageMessage.delete() failed");
5109
+ log17.warn({ err }, "UsageMessage.delete() failed");
5228
5110
  }
5229
5111
  }
5230
5112
  };
@@ -5310,7 +5192,7 @@ var PlanCard = class {
5310
5192
  if (result) this.msgId = result.message_id;
5311
5193
  }
5312
5194
  } catch (err) {
5313
- log16.warn({ err }, "PlanCard flush failed");
5195
+ log17.warn({ err }, "PlanCard flush failed");
5314
5196
  }
5315
5197
  }
5316
5198
  };
@@ -5373,7 +5255,7 @@ var ActivityTracker = class {
5373
5255
  })
5374
5256
  );
5375
5257
  } catch (err) {
5376
- log16.warn({ err }, "ActivityTracker.onComplete() Done send failed");
5258
+ log17.warn({ err }, "ActivityTracker.onComplete() Done send failed");
5377
5259
  }
5378
5260
  }
5379
5261
  }
@@ -5456,7 +5338,7 @@ var TelegramSendQueue = class {
5456
5338
 
5457
5339
  // src/adapters/telegram/action-detect.ts
5458
5340
  import { nanoid as nanoid3 } from "nanoid";
5459
- import { InlineKeyboard as InlineKeyboard9 } from "grammy";
5341
+ import { InlineKeyboard as InlineKeyboard10 } from "grammy";
5460
5342
  var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
5461
5343
  var CMD_CANCEL_RE = /\/cancel\b/;
5462
5344
  var KW_NEW_RE = /(?:create|new)\s+session/i;
@@ -5503,7 +5385,7 @@ function removeAction(id) {
5503
5385
  actionMap.delete(id);
5504
5386
  }
5505
5387
  function buildActionKeyboard(actionId, action) {
5506
- const keyboard = new InlineKeyboard9();
5388
+ const keyboard = new InlineKeyboard10();
5507
5389
  if (action.action === "new_session") {
5508
5390
  keyboard.text("\u2705 Create session", `a:${actionId}`);
5509
5391
  keyboard.text("\u274C Cancel", `a:dismiss:${actionId}`);
@@ -5611,7 +5493,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
5611
5493
  }
5612
5494
 
5613
5495
  // src/adapters/telegram/tool-call-tracker.ts
5614
- var log17 = createChildLogger({ module: "tool-call-tracker" });
5496
+ var log18 = createChildLogger({ module: "tool-call-tracker" });
5615
5497
  var ToolCallTracker = class {
5616
5498
  constructor(bot, chatId, sendQueue) {
5617
5499
  this.bot = bot;
@@ -5655,7 +5537,7 @@ var ToolCallTracker = class {
5655
5537
  if (!toolState) return;
5656
5538
  if (meta.viewerLinks) {
5657
5539
  toolState.viewerLinks = meta.viewerLinks;
5658
- log17.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
5540
+ log18.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
5659
5541
  }
5660
5542
  if (meta.viewerFilePath) toolState.viewerFilePath = meta.viewerFilePath;
5661
5543
  if (meta.name) toolState.name = meta.name;
@@ -5663,7 +5545,7 @@ var ToolCallTracker = class {
5663
5545
  const isTerminal = meta.status === "completed" || meta.status === "failed";
5664
5546
  if (!isTerminal) return;
5665
5547
  await toolState.ready;
5666
- log17.debug(
5548
+ log18.debug(
5667
5549
  {
5668
5550
  toolId: meta.id,
5669
5551
  status: meta.status,
@@ -5692,7 +5574,7 @@ var ToolCallTracker = class {
5692
5574
  )
5693
5575
  );
5694
5576
  } catch (err) {
5695
- log17.warn(
5577
+ log18.warn(
5696
5578
  {
5697
5579
  err,
5698
5580
  msgId: toolState.msgId,
@@ -5942,7 +5824,7 @@ var DraftManager = class {
5942
5824
  };
5943
5825
 
5944
5826
  // src/adapters/telegram/skill-command-manager.ts
5945
- var log18 = createChildLogger({ module: "skill-commands" });
5827
+ var log19 = createChildLogger({ module: "skill-commands" });
5946
5828
  var SkillCommandManager = class {
5947
5829
  // sessionId → pinned msgId
5948
5830
  constructor(bot, chatId, sendQueue, sessionManager) {
@@ -6008,7 +5890,7 @@ var SkillCommandManager = class {
6008
5890
  disable_notification: true
6009
5891
  });
6010
5892
  } catch (err) {
6011
- log18.error({ err, sessionId }, "Failed to send skill commands");
5893
+ log19.error({ err, sessionId }, "Failed to send skill commands");
6012
5894
  }
6013
5895
  }
6014
5896
  async cleanup(sessionId) {
@@ -6034,7 +5916,7 @@ var SkillCommandManager = class {
6034
5916
  };
6035
5917
 
6036
5918
  // src/adapters/telegram/adapter.ts
6037
- var log19 = createChildLogger({ module: "telegram" });
5919
+ var log20 = createChildLogger({ module: "telegram" });
6038
5920
  function patchedFetch(input, init) {
6039
5921
  if (init?.signal && !(init.signal instanceof AbortSignal)) {
6040
5922
  const nativeController = new AbortController();
@@ -6093,7 +5975,7 @@ var TelegramAdapter = class extends ChannelAdapter {
6093
5975
  );
6094
5976
  this.bot.catch((err) => {
6095
5977
  const rootCause = err.error instanceof Error ? err.error : err;
6096
- log19.error({ err: rootCause }, "Telegram bot error");
5978
+ log20.error({ err: rootCause }, "Telegram bot error");
6097
5979
  });
6098
5980
  this.bot.api.config.use(async (prev, method, payload, signal) => {
6099
5981
  const maxRetries = 3;
@@ -6107,7 +5989,7 @@ var TelegramAdapter = class extends ChannelAdapter {
6107
5989
  if (rateLimitedMethods.includes(method)) {
6108
5990
  this.sendQueue.onRateLimited();
6109
5991
  }
6110
- log19.warn(
5992
+ log20.warn(
6111
5993
  { method, retryAfter, attempt: attempt + 1 },
6112
5994
  "Rate limited by Telegram, retrying"
6113
5995
  );
@@ -6239,7 +6121,7 @@ var TelegramAdapter = class extends ChannelAdapter {
6239
6121
  this.setupRoutes();
6240
6122
  this.bot.start({
6241
6123
  allowed_updates: ["message", "callback_query"],
6242
- onStart: () => log19.info(
6124
+ onStart: () => log20.info(
6243
6125
  { chatId: this.telegramConfig.chatId },
6244
6126
  "Telegram bot started"
6245
6127
  )
@@ -6261,10 +6143,10 @@ var TelegramAdapter = class extends ChannelAdapter {
6261
6143
  reply_markup: buildMenuKeyboard()
6262
6144
  });
6263
6145
  } catch (err) {
6264
- log19.warn({ err }, "Failed to send welcome message");
6146
+ log20.warn({ err }, "Failed to send welcome message");
6265
6147
  }
6266
6148
  try {
6267
- log19.info("Spawning assistant session...");
6149
+ log20.info("Spawning assistant session...");
6268
6150
  const { session, ready } = await spawnAssistant(
6269
6151
  this.core,
6270
6152
  this,
@@ -6272,13 +6154,13 @@ var TelegramAdapter = class extends ChannelAdapter {
6272
6154
  );
6273
6155
  this.assistantSession = session;
6274
6156
  this.assistantInitializing = true;
6275
- log19.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
6157
+ log20.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
6276
6158
  ready.then(() => {
6277
6159
  this.assistantInitializing = false;
6278
- log19.info({ sessionId: session.id }, "Assistant ready for user messages");
6160
+ log20.info({ sessionId: session.id }, "Assistant ready for user messages");
6279
6161
  });
6280
6162
  } catch (err) {
6281
- log19.error({ err }, "Failed to spawn assistant");
6163
+ log20.error({ err }, "Failed to spawn assistant");
6282
6164
  this.bot.api.sendMessage(
6283
6165
  this.telegramConfig.chatId,
6284
6166
  `\u26A0\uFE0F <b>Failed to start assistant session.</b>
@@ -6294,11 +6176,12 @@ var TelegramAdapter = class extends ChannelAdapter {
6294
6176
  await this.assistantSession.destroy();
6295
6177
  }
6296
6178
  await this.bot.stop();
6297
- log19.info("Telegram bot stopped");
6179
+ log20.info("Telegram bot stopped");
6298
6180
  }
6299
6181
  setupRoutes() {
6300
6182
  this.bot.on("message:text", async (ctx) => {
6301
6183
  const threadId = ctx.message.message_thread_id;
6184
+ const text = ctx.message.text;
6302
6185
  if (await handlePendingWorkspaceInput(ctx, this.core, this.telegramConfig.chatId, this.assistantTopicId)) {
6303
6186
  return;
6304
6187
  }
@@ -6311,6 +6194,7 @@ var TelegramAdapter = class extends ChannelAdapter {
6311
6194
  return;
6312
6195
  }
6313
6196
  if (threadId === this.notificationTopicId) return;
6197
+ const forwardText = text.startsWith("/") ? text.slice(1) : text;
6314
6198
  if (threadId === this.assistantTopicId) {
6315
6199
  if (!this.assistantSession) {
6316
6200
  await ctx.reply("\u26A0\uFE0F Assistant is not available yet. Please try again shortly.", { parse_mode: "HTML" });
@@ -6319,8 +6203,8 @@ var TelegramAdapter = class extends ChannelAdapter {
6319
6203
  await this.draftManager.finalize(this.assistantSession.id, this.assistantSession.id);
6320
6204
  ctx.replyWithChatAction("typing").catch(() => {
6321
6205
  });
6322
- handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
6323
- (err) => log19.error({ err }, "Assistant error")
6206
+ handleAssistantMessage(this.assistantSession, forwardText).catch(
6207
+ (err) => log20.error({ err }, "Assistant error")
6324
6208
  );
6325
6209
  return;
6326
6210
  }
@@ -6336,8 +6220,8 @@ var TelegramAdapter = class extends ChannelAdapter {
6336
6220
  channelId: "telegram",
6337
6221
  threadId: String(threadId),
6338
6222
  userId: String(ctx.from.id),
6339
- text: ctx.message.text
6340
- }).catch((err) => log19.error({ err }, "handleMessage error"));
6223
+ text: forwardText
6224
+ }).catch((err) => log20.error({ err }, "handleMessage error"));
6341
6225
  });
6342
6226
  this.bot.on("message:photo", async (ctx) => {
6343
6227
  const threadId = ctx.message.message_thread_id;
@@ -6414,7 +6298,7 @@ var TelegramAdapter = class extends ChannelAdapter {
6414
6298
  if (!session) return;
6415
6299
  const threadId = Number(session.threadId);
6416
6300
  if (!threadId || isNaN(threadId)) {
6417
- log19.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
6301
+ log20.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
6418
6302
  return;
6419
6303
  }
6420
6304
  switch (content.type) {
@@ -6496,7 +6380,7 @@ Task completed.
6496
6380
  if (!content.attachment) break;
6497
6381
  const { attachment } = content;
6498
6382
  if (attachment.size > 50 * 1024 * 1024) {
6499
- log19.warn({ sessionId, fileName: attachment.fileName, size: attachment.size }, "File too large for Telegram (>50MB)");
6383
+ log20.warn({ sessionId, fileName: attachment.fileName, size: attachment.size }, "File too large for Telegram (>50MB)");
6500
6384
  await this.sendQueue.enqueue(
6501
6385
  () => this.bot.api.sendMessage(
6502
6386
  this.telegramConfig.chatId,
@@ -6528,7 +6412,7 @@ Task completed.
6528
6412
  );
6529
6413
  }
6530
6414
  } catch (err) {
6531
- log19.error({ err, sessionId, fileName: attachment.fileName }, "Failed to send attachment");
6415
+ log20.error({ err, sessionId, fileName: attachment.fileName }, "Failed to send attachment");
6532
6416
  }
6533
6417
  break;
6534
6418
  }
@@ -6580,13 +6464,13 @@ Task completed.
6580
6464
  }
6581
6465
  }
6582
6466
  async sendPermissionRequest(sessionId, request) {
6583
- log19.info({ sessionId, requestId: request.id }, "Permission request sent");
6467
+ log20.info({ sessionId, requestId: request.id }, "Permission request sent");
6584
6468
  const session = this.core.sessionManager.getSession(sessionId);
6585
6469
  if (!session) return;
6586
6470
  if (request.description.includes("openacp")) {
6587
6471
  const allowOption = request.options.find((o) => o.isAllow);
6588
6472
  if (allowOption && session.permissionGate.requestId === request.id) {
6589
- log19.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
6473
+ log20.info({ sessionId, requestId: request.id }, "Auto-approving openacp command");
6590
6474
  session.permissionGate.resolve(allowOption.id);
6591
6475
  }
6592
6476
  return;
@@ -6594,7 +6478,7 @@ Task completed.
6594
6478
  if (session.dangerousMode) {
6595
6479
  const allowOption = request.options.find((o) => o.isAllow);
6596
6480
  if (allowOption && session.permissionGate.requestId === request.id) {
6597
- log19.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
6481
+ log20.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
6598
6482
  session.permissionGate.resolve(allowOption.id);
6599
6483
  }
6600
6484
  return;
@@ -6605,7 +6489,7 @@ Task completed.
6605
6489
  }
6606
6490
  async sendNotification(notification) {
6607
6491
  if (notification.sessionId === this.assistantSession?.id) return;
6608
- log19.info(
6492
+ log20.info(
6609
6493
  { sessionId: notification.sessionId, type: notification.type },
6610
6494
  "Notification sent"
6611
6495
  );
@@ -6641,7 +6525,7 @@ Task completed.
6641
6525
  );
6642
6526
  }
6643
6527
  async createSessionThread(sessionId, name) {
6644
- log19.info({ sessionId, name }, "Session topic created");
6528
+ log20.info({ sessionId, name }, "Session topic created");
6645
6529
  return String(
6646
6530
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
6647
6531
  );
@@ -6665,7 +6549,7 @@ Task completed.
6665
6549
  try {
6666
6550
  await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
6667
6551
  } catch (err) {
6668
- log19.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
6552
+ log20.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
6669
6553
  }
6670
6554
  }
6671
6555
  async sendSkillCommands(sessionId, commands) {
@@ -6689,7 +6573,7 @@ Task completed.
6689
6573
  const buffer = Buffer.from(await response.arrayBuffer());
6690
6574
  return { buffer, filePath: file.file_path };
6691
6575
  } catch (err) {
6692
- log19.error({ err }, "Failed to download file from Telegram");
6576
+ log20.error({ err }, "Failed to download file from Telegram");
6693
6577
  return null;
6694
6578
  }
6695
6579
  }
@@ -6701,14 +6585,15 @@ Task completed.
6701
6585
  try {
6702
6586
  buffer = await this.fileService.convertOggToWav(buffer);
6703
6587
  } catch (err) {
6704
- log19.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
6588
+ log20.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
6705
6589
  fileName = "voice.ogg";
6706
6590
  mimeType = "audio/ogg";
6707
6591
  }
6708
6592
  }
6709
6593
  const sessionId = this.resolveSessionId(threadId) || "unknown";
6710
6594
  const att = await this.fileService.saveFile(sessionId, fileName, buffer, mimeType);
6711
- const text = caption || `[${att.type === "image" ? "Photo" : att.type === "audio" ? "Audio" : "File"}: ${att.fileName}]`;
6595
+ const rawText = caption || `[${att.type === "image" ? "Photo" : att.type === "audio" ? "Audio" : "File"}: ${att.fileName}]`;
6596
+ const text = rawText.startsWith("/") ? rawText.slice(1) : rawText;
6712
6597
  if (threadId === this.assistantTopicId) {
6713
6598
  if (this.assistantSession) {
6714
6599
  await this.assistantSession.enqueuePrompt(text, [att]);
@@ -6723,7 +6608,7 @@ Task completed.
6723
6608
  userId: String(userId),
6724
6609
  text,
6725
6610
  attachments: [att]
6726
- }).catch((err) => log19.error({ err }, "handleMessage error"));
6611
+ }).catch((err) => log20.error({ err }, "handleMessage error"));
6727
6612
  }
6728
6613
  async cleanupSkillCommands(sessionId) {
6729
6614
  await this.skillManager.cleanup(sessionId);
@@ -6746,9 +6631,8 @@ export {
6746
6631
  NotificationManager,
6747
6632
  MessageTransformer,
6748
6633
  OpenACPCore,
6749
- ChannelAdapter,
6750
6634
  ApiServer,
6751
6635
  TopicManager,
6752
6636
  TelegramAdapter
6753
6637
  };
6754
- //# sourceMappingURL=chunk-65XE66HK.js.map
6638
+ //# sourceMappingURL=chunk-V2V767XI.js.map