@openacp/cli 0.2.23 → 0.2.25

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 (44) hide show
  1. package/dist/autostart-YBYXQA77.js +18 -0
  2. package/dist/autostart-YBYXQA77.js.map +1 -0
  3. package/dist/{setup-XQBEZZQB.js → chunk-4BN7NSKB.js} +140 -9
  4. package/dist/chunk-4BN7NSKB.js.map +1 -0
  5. package/dist/chunk-CQMS5U7Z.js +63 -0
  6. package/dist/chunk-CQMS5U7Z.js.map +1 -0
  7. package/dist/{chunk-XOVJLTEC.js → chunk-FGXG3H3F.js} +14 -58
  8. package/dist/chunk-FGXG3H3F.js.map +1 -0
  9. package/dist/{chunk-ZATQZUJT.js → chunk-MNJDYDGH.js} +9 -1
  10. package/dist/{chunk-ZATQZUJT.js.map → chunk-MNJDYDGH.js.map} +1 -1
  11. package/dist/chunk-PQRVTUNH.js +145 -0
  12. package/dist/chunk-PQRVTUNH.js.map +1 -0
  13. package/dist/chunk-QWUJIKTX.js +527 -0
  14. package/dist/chunk-QWUJIKTX.js.map +1 -0
  15. package/dist/{chunk-EIBLQU3H.js → chunk-S5MPFOR3.js} +632 -136
  16. package/dist/chunk-S5MPFOR3.js.map +1 -0
  17. package/dist/chunk-S6O7SM6A.js +129 -0
  18. package/dist/chunk-S6O7SM6A.js.map +1 -0
  19. package/dist/chunk-WXS6ONOD.js +103 -0
  20. package/dist/chunk-WXS6ONOD.js.map +1 -0
  21. package/dist/cli.js +354 -4
  22. package/dist/cli.js.map +1 -1
  23. package/dist/config-2XALNLAA.js +14 -0
  24. package/dist/config-2XALNLAA.js.map +1 -0
  25. package/dist/config-editor-56B6YU7B.js +11 -0
  26. package/dist/config-editor-56B6YU7B.js.map +1 -0
  27. package/dist/daemon-3E5OMLT3.js +29 -0
  28. package/dist/daemon-3E5OMLT3.js.map +1 -0
  29. package/dist/index.d.ts +99 -18
  30. package/dist/index.js +35 -6
  31. package/dist/install-cloudflared-57NRTI4E.js +8 -0
  32. package/dist/install-cloudflared-57NRTI4E.js.map +1 -0
  33. package/dist/{main-VJUX7RUY.js → main-TGYT34IJ.js} +43 -11
  34. package/dist/main-TGYT34IJ.js.map +1 -0
  35. package/dist/setup-FTNJACSC.js +27 -0
  36. package/dist/setup-FTNJACSC.js.map +1 -0
  37. package/dist/{tunnel-service-I6NUMBT4.js → tunnel-service-I6WM6USB.js} +10 -10
  38. package/dist/tunnel-service-I6WM6USB.js.map +1 -0
  39. package/package.json +2 -2
  40. package/dist/chunk-EIBLQU3H.js.map +0 -1
  41. package/dist/chunk-XOVJLTEC.js.map +0 -1
  42. package/dist/main-VJUX7RUY.js.map +0 -1
  43. package/dist/setup-XQBEZZQB.js.map +0 -1
  44. package/dist/tunnel-service-I6NUMBT4.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createChildLogger,
3
3
  createSessionLogger
4
- } from "./chunk-ZATQZUJT.js";
4
+ } from "./chunk-MNJDYDGH.js";
5
5
 
6
6
  // src/core/streams.ts
7
7
  function nodeToWebWritable(nodeStream) {
@@ -54,33 +54,35 @@ import { randomUUID } from "crypto";
54
54
  import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
55
55
  var log = createChildLogger({ module: "agent-instance" });
56
56
  function resolveAgentCommand(cmd) {
57
- const packageDirs = [
58
- path.resolve(
59
- process.cwd(),
60
- "node_modules",
61
- "@zed-industries",
62
- cmd,
63
- "dist",
64
- "index.js"
65
- ),
66
- path.resolve(process.cwd(), "node_modules", cmd, "dist", "index.js")
67
- ];
68
- for (const jsPath of packageDirs) {
69
- if (fs.existsSync(jsPath)) {
70
- return { command: process.execPath, args: [jsPath] };
71
- }
72
- }
73
- const localBin = path.resolve(process.cwd(), "node_modules", ".bin", cmd);
74
- if (fs.existsSync(localBin)) {
75
- const content = fs.readFileSync(localBin, "utf-8");
76
- if (content.startsWith("#!/usr/bin/env node")) {
77
- return { command: process.execPath, args: [localBin] };
78
- }
79
- const match = content.match(/"([^"]+\.js)"/);
80
- if (match) {
81
- const target = path.resolve(path.dirname(localBin), match[1]);
82
- if (fs.existsSync(target)) {
83
- return { command: process.execPath, args: [target] };
57
+ const searchRoots = [process.cwd()];
58
+ const ownDir = path.resolve(import.meta.dirname, "..", "..");
59
+ if (ownDir !== process.cwd()) {
60
+ searchRoots.push(ownDir);
61
+ }
62
+ for (const root of searchRoots) {
63
+ const packageDirs = [
64
+ path.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
65
+ path.resolve(root, "node_modules", cmd, "dist", "index.js")
66
+ ];
67
+ for (const jsPath of packageDirs) {
68
+ if (fs.existsSync(jsPath)) {
69
+ return { command: process.execPath, args: [jsPath] };
70
+ }
71
+ }
72
+ }
73
+ for (const root of searchRoots) {
74
+ const localBin = path.resolve(root, "node_modules", ".bin", cmd);
75
+ if (fs.existsSync(localBin)) {
76
+ const content = fs.readFileSync(localBin, "utf-8");
77
+ if (content.startsWith("#!/usr/bin/env node")) {
78
+ return { command: process.execPath, args: [localBin] };
79
+ }
80
+ const match = content.match(/"([^"]+\.js)"/);
81
+ if (match) {
82
+ const target = path.resolve(path.dirname(localBin), match[1]);
83
+ if (fs.existsSync(target)) {
84
+ return { command: process.execPath, args: [target] };
85
+ }
84
86
  }
85
87
  }
86
88
  }
@@ -580,6 +582,7 @@ var Session = class {
580
582
  try {
581
583
  const start = Date.now();
582
584
  await this.agentInstance.prompt('Reply with only "ready".');
585
+ this.status = "active";
583
586
  this.log.info({ durationMs: Date.now() - start }, "Warm-up complete");
584
587
  } catch (err) {
585
588
  this.log.error({ err }, "Warm-up failed");
@@ -1259,6 +1262,187 @@ var ChannelAdapter = class {
1259
1262
  }
1260
1263
  };
1261
1264
 
1265
+ // src/core/api-server.ts
1266
+ import * as http from "http";
1267
+ import * as fs3 from "fs";
1268
+ import * as path4 from "path";
1269
+ import * as os2 from "os";
1270
+ var log4 = createChildLogger({ module: "api-server" });
1271
+ var DEFAULT_PORT_FILE = path4.join(os2.homedir(), ".openacp", "api.port");
1272
+ var ApiServer = class {
1273
+ constructor(core, config, portFilePath) {
1274
+ this.core = core;
1275
+ this.config = config;
1276
+ this.portFilePath = portFilePath ?? DEFAULT_PORT_FILE;
1277
+ }
1278
+ server = null;
1279
+ actualPort = 0;
1280
+ portFilePath;
1281
+ async start() {
1282
+ this.server = http.createServer((req, res) => this.handleRequest(req, res));
1283
+ await new Promise((resolve, reject) => {
1284
+ this.server.on("error", (err) => {
1285
+ if (err.code === "EADDRINUSE") {
1286
+ log4.warn({ port: this.config.port }, "API port in use, continuing without API server");
1287
+ this.server = null;
1288
+ resolve();
1289
+ } else {
1290
+ reject(err);
1291
+ }
1292
+ });
1293
+ this.server.listen(this.config.port, this.config.host, () => {
1294
+ const addr = this.server.address();
1295
+ if (addr && typeof addr === "object") {
1296
+ this.actualPort = addr.port;
1297
+ }
1298
+ this.writePortFile();
1299
+ log4.info({ host: this.config.host, port: this.actualPort }, "API server listening");
1300
+ resolve();
1301
+ });
1302
+ });
1303
+ }
1304
+ async stop() {
1305
+ this.removePortFile();
1306
+ if (this.server) {
1307
+ await new Promise((resolve) => {
1308
+ this.server.close(() => resolve());
1309
+ });
1310
+ this.server = null;
1311
+ }
1312
+ }
1313
+ getPort() {
1314
+ return this.actualPort;
1315
+ }
1316
+ writePortFile() {
1317
+ const dir = path4.dirname(this.portFilePath);
1318
+ fs3.mkdirSync(dir, { recursive: true });
1319
+ fs3.writeFileSync(this.portFilePath, String(this.actualPort));
1320
+ }
1321
+ removePortFile() {
1322
+ try {
1323
+ fs3.unlinkSync(this.portFilePath);
1324
+ } catch {
1325
+ }
1326
+ }
1327
+ async handleRequest(req, res) {
1328
+ const method = req.method?.toUpperCase();
1329
+ const url = req.url || "";
1330
+ try {
1331
+ if (method === "POST" && url === "/api/sessions") {
1332
+ await this.handleCreateSession(req, res);
1333
+ } else if (method === "DELETE" && url.match(/^\/api\/sessions\/(.+)$/)) {
1334
+ const sessionId = url.match(/^\/api\/sessions\/(.+)$/)[1];
1335
+ await this.handleCancelSession(sessionId, res);
1336
+ } else if (method === "GET" && url === "/api/sessions") {
1337
+ await this.handleListSessions(res);
1338
+ } else if (method === "GET" && url === "/api/agents") {
1339
+ await this.handleListAgents(res);
1340
+ } else {
1341
+ this.sendJson(res, 404, { error: "Not found" });
1342
+ }
1343
+ } catch (err) {
1344
+ log4.error({ err }, "API request error");
1345
+ this.sendJson(res, 500, { error: "Internal server error" });
1346
+ }
1347
+ }
1348
+ async handleCreateSession(req, res) {
1349
+ const body = await this.readBody(req);
1350
+ let agent;
1351
+ let workspace;
1352
+ if (body) {
1353
+ try {
1354
+ const parsed = JSON.parse(body);
1355
+ agent = parsed.agent;
1356
+ workspace = parsed.workspace;
1357
+ } catch {
1358
+ this.sendJson(res, 400, { error: "Invalid JSON body" });
1359
+ return;
1360
+ }
1361
+ }
1362
+ const config = this.core.configManager.get();
1363
+ const activeSessions = this.core.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
1364
+ if (activeSessions.length >= config.security.maxConcurrentSessions) {
1365
+ this.sendJson(res, 429, {
1366
+ error: `Max concurrent sessions (${config.security.maxConcurrentSessions}) reached. Cancel a session first.`
1367
+ });
1368
+ return;
1369
+ }
1370
+ const [adapterId, adapter] = this.core.adapters.entries().next().value ?? [null, null];
1371
+ const channelId = adapterId ?? "api";
1372
+ const session = await this.core.handleNewSession(channelId, agent, workspace);
1373
+ if (adapter) {
1374
+ try {
1375
+ const threadId = await adapter.createSessionThread(session.id, `\u{1F504} ${session.agentName} \u2014 New Session`);
1376
+ session.threadId = threadId;
1377
+ this.core.wireSessionEvents(session, adapter);
1378
+ } catch (err) {
1379
+ log4.warn({ err, sessionId: session.id }, "Failed to create session thread on adapter, running headless");
1380
+ }
1381
+ }
1382
+ if (!adapter) {
1383
+ session.agentInstance.onPermissionRequest = async (request) => {
1384
+ const allowOption = request.options.find((o) => o.isAllow);
1385
+ log4.debug({ sessionId: session.id, permissionId: request.id, option: allowOption?.id }, "Auto-approving permission for API session");
1386
+ return allowOption?.id ?? request.options[0]?.id ?? "";
1387
+ };
1388
+ }
1389
+ session.warmup().catch((err) => log4.warn({ err, sessionId: session.id }, "API session warmup failed"));
1390
+ this.sendJson(res, 200, {
1391
+ sessionId: session.id,
1392
+ agent: session.agentName,
1393
+ status: session.status,
1394
+ workspace: session.workingDirectory
1395
+ });
1396
+ }
1397
+ async handleCancelSession(sessionId, res) {
1398
+ const session = this.core.sessionManager.getSession(sessionId);
1399
+ if (!session) {
1400
+ this.sendJson(res, 404, { error: `Session "${sessionId}" not found` });
1401
+ return;
1402
+ }
1403
+ await session.cancel();
1404
+ this.sendJson(res, 200, { ok: true });
1405
+ }
1406
+ async handleListSessions(res) {
1407
+ const sessions = this.core.sessionManager.listSessions();
1408
+ this.sendJson(res, 200, {
1409
+ sessions: sessions.map((s) => ({
1410
+ id: s.id,
1411
+ agent: s.agentName,
1412
+ status: s.status,
1413
+ name: s.name ?? null,
1414
+ workspace: s.workingDirectory
1415
+ }))
1416
+ });
1417
+ }
1418
+ async handleListAgents(res) {
1419
+ const agents = this.core.agentManager.getAvailableAgents();
1420
+ const defaultAgent = this.core.configManager.get().defaultAgent;
1421
+ this.sendJson(res, 200, {
1422
+ agents: agents.map((a) => ({
1423
+ name: a.name,
1424
+ command: a.command,
1425
+ args: a.args
1426
+ })),
1427
+ default: defaultAgent
1428
+ });
1429
+ }
1430
+ sendJson(res, status, data) {
1431
+ res.writeHead(status, { "Content-Type": "application/json" });
1432
+ res.end(JSON.stringify(data));
1433
+ }
1434
+ readBody(req) {
1435
+ return new Promise((resolve) => {
1436
+ let data = "";
1437
+ req.on("data", (chunk) => {
1438
+ data += chunk;
1439
+ });
1440
+ req.on("end", () => resolve(data));
1441
+ req.on("error", () => resolve(""));
1442
+ });
1443
+ }
1444
+ };
1445
+
1262
1446
  // src/adapters/telegram/adapter.ts
1263
1447
  import { Bot } from "grammy";
1264
1448
 
@@ -1406,20 +1590,25 @@ function splitMessage(text, maxLength = 4096) {
1406
1590
  }
1407
1591
 
1408
1592
  // src/adapters/telegram/streaming.ts
1593
+ var nextDraftId = 1;
1409
1594
  var MessageDraft = class {
1410
- // 1 second throttle
1411
- constructor(bot, chatId, threadId) {
1595
+ // Only set in fallback mode (sendMessageDraft returns true, not Message)
1596
+ constructor(bot, chatId, threadId, throttleMs = 200, sendQueue) {
1412
1597
  this.bot = bot;
1413
1598
  this.chatId = chatId;
1414
1599
  this.threadId = threadId;
1600
+ this.sendQueue = sendQueue;
1601
+ this.draftId = nextDraftId++;
1602
+ this.minInterval = throttleMs;
1415
1603
  }
1416
- messageId;
1604
+ draftId;
1417
1605
  buffer = "";
1418
1606
  lastFlush = 0;
1419
1607
  flushTimer;
1420
1608
  flushPromise = Promise.resolve();
1421
- // serialize flushes
1422
- minInterval = 1e3;
1609
+ minInterval;
1610
+ useFallback = false;
1611
+ messageId;
1423
1612
  append(text) {
1424
1613
  this.buffer += text;
1425
1614
  this.scheduleFlush();
@@ -1444,26 +1633,49 @@ var MessageDraft = class {
1444
1633
  const html = markdownToTelegramHtml(this.buffer);
1445
1634
  const truncated = html.length > 4096 ? html.slice(0, 4090) + "\n..." : html;
1446
1635
  if (!truncated) return;
1636
+ if (this.useFallback) {
1637
+ await this.flushFallback(truncated);
1638
+ return;
1639
+ }
1640
+ try {
1641
+ await this.bot.api.sendMessageDraft(this.chatId, this.draftId, truncated, {
1642
+ message_thread_id: this.threadId,
1643
+ parse_mode: "HTML"
1644
+ });
1645
+ } catch {
1646
+ this.useFallback = true;
1647
+ this.minInterval = 1e3;
1648
+ await this.flushFallback(truncated);
1649
+ }
1650
+ }
1651
+ async flushFallback(html) {
1652
+ const exec = this.sendQueue ? (fn) => this.sendQueue.enqueue(fn) : (fn) => fn();
1447
1653
  try {
1448
1654
  if (!this.messageId) {
1449
- const msg = await this.bot.api.sendMessage(this.chatId, truncated, {
1450
- message_thread_id: this.threadId,
1451
- parse_mode: "HTML",
1452
- disable_notification: true
1453
- });
1655
+ const msg = await exec(
1656
+ () => this.bot.api.sendMessage(this.chatId, html, {
1657
+ message_thread_id: this.threadId,
1658
+ parse_mode: "HTML",
1659
+ disable_notification: true
1660
+ })
1661
+ );
1454
1662
  this.messageId = msg.message_id;
1455
1663
  } else {
1456
- await this.bot.api.editMessageText(this.chatId, this.messageId, truncated, {
1457
- parse_mode: "HTML"
1458
- });
1664
+ await exec(
1665
+ () => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
1666
+ parse_mode: "HTML"
1667
+ })
1668
+ );
1459
1669
  }
1460
1670
  } catch {
1461
1671
  try {
1462
1672
  if (!this.messageId) {
1463
- const msg = await this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
1464
- message_thread_id: this.threadId,
1465
- disable_notification: true
1466
- });
1673
+ const msg = await exec(
1674
+ () => this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
1675
+ message_thread_id: this.threadId,
1676
+ disable_notification: true
1677
+ })
1678
+ );
1467
1679
  this.messageId = msg.message_id;
1468
1680
  }
1469
1681
  } catch {
@@ -1545,7 +1757,7 @@ function buildDeepLink(chatId, messageId) {
1545
1757
  // src/adapters/telegram/commands.ts
1546
1758
  import { InlineKeyboard } from "grammy";
1547
1759
  import { nanoid as nanoid2 } from "nanoid";
1548
- var log5 = createChildLogger({ module: "telegram-commands" });
1760
+ var log6 = createChildLogger({ module: "telegram-commands" });
1549
1761
  function setupCommands(bot, core, chatId) {
1550
1762
  bot.command("new", (ctx) => handleNew(ctx, core, chatId));
1551
1763
  bot.command("new_chat", (ctx) => handleNewChat(ctx, core, chatId));
@@ -1600,7 +1812,7 @@ async function handleNew(ctx, core, chatId) {
1600
1812
  const args = matchStr.split(" ").filter(Boolean);
1601
1813
  const agentName = args[0];
1602
1814
  const workspace = args[1];
1603
- log5.info({ userId: ctx.from?.id, agentName }, "New session command");
1815
+ log6.info({ userId: ctx.from?.id, agentName }, "New session command");
1604
1816
  let threadId;
1605
1817
  try {
1606
1818
  const topicName = `\u{1F504} New Session`;
@@ -1633,15 +1845,16 @@ async function handleNew(ctx, core, chatId) {
1633
1845
  parse_mode: "HTML"
1634
1846
  }
1635
1847
  );
1636
- session.warmup().catch((err) => log5.error({ err }, "Warm-up error"));
1848
+ session.warmup().catch((err) => log6.error({ err }, "Warm-up error"));
1637
1849
  } catch (err) {
1850
+ log6.error({ err }, "Session creation failed");
1638
1851
  if (threadId) {
1639
1852
  try {
1640
1853
  await ctx.api.deleteForumTopic(chatId, threadId);
1641
1854
  } catch {
1642
1855
  }
1643
1856
  }
1644
- const message = err instanceof Error ? err.message : String(err);
1857
+ const message = err instanceof Error ? err.message : typeof err === "object" ? JSON.stringify(err) : String(err);
1645
1858
  await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
1646
1859
  }
1647
1860
  }
@@ -1686,7 +1899,7 @@ async function handleNewChat(ctx, core, chatId) {
1686
1899
  parse_mode: "HTML"
1687
1900
  }
1688
1901
  );
1689
- session.warmup().catch((err) => log5.error({ err }, "Warm-up error"));
1902
+ session.warmup().catch((err) => log6.error({ err }, "Warm-up error"));
1690
1903
  } catch (err) {
1691
1904
  const message = err instanceof Error ? err.message : String(err);
1692
1905
  await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
@@ -1700,7 +1913,7 @@ async function handleCancel(ctx, core) {
1700
1913
  String(threadId)
1701
1914
  );
1702
1915
  if (session) {
1703
- log5.info({ sessionId: session.id }, "Cancel session command");
1916
+ log6.info({ sessionId: session.id }, "Cancel session command");
1704
1917
  await session.cancel();
1705
1918
  await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
1706
1919
  }
@@ -1808,6 +2021,41 @@ function setupSkillCallbacks(bot, core) {
1808
2021
  await session.enqueuePrompt(`/${entry.commandName}`);
1809
2022
  });
1810
2023
  }
2024
+ async function executeNewSession(bot, core, chatId, agentName, workspace) {
2025
+ const threadId = await createSessionTopic(bot, chatId, "\u{1F504} New Session");
2026
+ await bot.api.sendMessage(chatId, "\u23F3 Setting up session, please wait...", {
2027
+ message_thread_id: threadId,
2028
+ parse_mode: "HTML"
2029
+ });
2030
+ try {
2031
+ const session = await core.handleNewSession(
2032
+ "telegram",
2033
+ agentName,
2034
+ workspace
2035
+ );
2036
+ session.threadId = String(threadId);
2037
+ await core.sessionManager.updateSessionPlatform(session.id, {
2038
+ topicId: threadId
2039
+ });
2040
+ const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
2041
+ await renameSessionTopic(bot, chatId, threadId, finalName);
2042
+ session.warmup().catch((err) => log6.error({ err }, "Warm-up error"));
2043
+ return { session, threadId };
2044
+ } catch (err) {
2045
+ try {
2046
+ await bot.api.deleteForumTopic(chatId, threadId);
2047
+ } catch {
2048
+ }
2049
+ throw err;
2050
+ }
2051
+ }
2052
+ async function executeCancelSession(core, excludeSessionId) {
2053
+ const sessions = core.sessionManager.listSessions("telegram").filter((s) => s.status === "active" && s.id !== excludeSessionId).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
2054
+ const session = sessions[0];
2055
+ if (!session) return null;
2056
+ await session.cancel();
2057
+ return session;
2058
+ }
1811
2059
  var STATIC_COMMANDS = [
1812
2060
  { command: "new", description: "Create new session" },
1813
2061
  { command: "new_chat", description: "New chat, same agent & workspace" },
@@ -1821,7 +2069,7 @@ var STATIC_COMMANDS = [
1821
2069
  // src/adapters/telegram/permissions.ts
1822
2070
  import { InlineKeyboard as InlineKeyboard2 } from "grammy";
1823
2071
  import { nanoid as nanoid3 } from "nanoid";
1824
- var log6 = createChildLogger({ module: "telegram-permissions" });
2072
+ var log7 = createChildLogger({ module: "telegram-permissions" });
1825
2073
  var PermissionHandler = class {
1826
2074
  constructor(bot, chatId, getSession, sendNotification) {
1827
2075
  this.bot = bot;
@@ -1881,7 +2129,7 @@ ${escapeHtml(request.description)}`,
1881
2129
  }
1882
2130
  const session = this.getSession(pending.sessionId);
1883
2131
  const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
1884
- log6.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
2132
+ log7.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
1885
2133
  if (session?.pendingPermission?.requestId === pending.requestId) {
1886
2134
  session.pendingPermission.resolve(optionId);
1887
2135
  session.pendingPermission = void 0;
@@ -1947,8 +2195,186 @@ function redirectToAssistant(chatId, assistantTopicId) {
1947
2195
  return `\u{1F4AC} Please use the <a href="${link}">\u{1F916} Assistant</a> topic to chat with OpenACP.`;
1948
2196
  }
1949
2197
 
2198
+ // src/adapters/telegram/send-queue.ts
2199
+ var TelegramSendQueue = class {
2200
+ queue = Promise.resolve();
2201
+ lastExec = 0;
2202
+ minInterval;
2203
+ constructor(minInterval = 100) {
2204
+ this.minInterval = minInterval;
2205
+ }
2206
+ enqueue(fn) {
2207
+ let resolve;
2208
+ let reject;
2209
+ const resultPromise = new Promise((res, rej) => {
2210
+ resolve = res;
2211
+ reject = rej;
2212
+ });
2213
+ this.queue = this.queue.then(async () => {
2214
+ const elapsed = Date.now() - this.lastExec;
2215
+ if (elapsed < this.minInterval) {
2216
+ await new Promise((r) => setTimeout(r, this.minInterval - elapsed));
2217
+ }
2218
+ try {
2219
+ const result = await fn();
2220
+ resolve(result);
2221
+ } catch (err) {
2222
+ reject(err);
2223
+ } finally {
2224
+ this.lastExec = Date.now();
2225
+ }
2226
+ });
2227
+ return resultPromise;
2228
+ }
2229
+ };
2230
+
2231
+ // src/adapters/telegram/action-detect.ts
2232
+ import { nanoid as nanoid4 } from "nanoid";
2233
+ import { InlineKeyboard as InlineKeyboard3 } from "grammy";
2234
+ var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
2235
+ var CMD_CANCEL_RE = /\/cancel\b/;
2236
+ var KW_NEW_RE = /(?:tao|tạo|create|new)\s+session/i;
2237
+ var KW_CANCEL_RE = /(?:huy|huỷ|cancel|dung|dừng)\s+session/i;
2238
+ function detectAction(text) {
2239
+ if (!text) return null;
2240
+ const cancelCmd = CMD_CANCEL_RE.exec(text);
2241
+ if (cancelCmd) return { action: "cancel_session" };
2242
+ const newCmd = CMD_NEW_RE.exec(text);
2243
+ if (newCmd) {
2244
+ return {
2245
+ action: "new_session",
2246
+ agent: newCmd[1] || void 0,
2247
+ workspace: newCmd[2] || void 0
2248
+ };
2249
+ }
2250
+ if (KW_CANCEL_RE.test(text)) return { action: "cancel_session" };
2251
+ if (KW_NEW_RE.test(text))
2252
+ return { action: "new_session", agent: void 0, workspace: void 0 };
2253
+ return null;
2254
+ }
2255
+ var ACTION_TTL_MS = 5 * 60 * 1e3;
2256
+ var actionMap = /* @__PURE__ */ new Map();
2257
+ function storeAction(action) {
2258
+ const id = nanoid4(10);
2259
+ actionMap.set(id, { action, createdAt: Date.now() });
2260
+ for (const [key, entry] of actionMap) {
2261
+ if (Date.now() - entry.createdAt > ACTION_TTL_MS) {
2262
+ actionMap.delete(key);
2263
+ }
2264
+ }
2265
+ return id;
2266
+ }
2267
+ function getAction(id) {
2268
+ const entry = actionMap.get(id);
2269
+ if (!entry) return void 0;
2270
+ if (Date.now() - entry.createdAt > ACTION_TTL_MS) {
2271
+ actionMap.delete(id);
2272
+ return void 0;
2273
+ }
2274
+ return entry.action;
2275
+ }
2276
+ function removeAction(id) {
2277
+ actionMap.delete(id);
2278
+ }
2279
+ function buildActionKeyboard(actionId, action) {
2280
+ const keyboard = new InlineKeyboard3();
2281
+ if (action.action === "new_session") {
2282
+ keyboard.text("\u2705 T\u1EA1o session", `a:${actionId}`);
2283
+ keyboard.text("\u274C Hu\u1EF7", `a:dismiss:${actionId}`);
2284
+ } else {
2285
+ keyboard.text("\u26D4 Hu\u1EF7 session", `a:${actionId}`);
2286
+ keyboard.text("\u274C Kh\xF4ng", `a:dismiss:${actionId}`);
2287
+ }
2288
+ return keyboard;
2289
+ }
2290
+ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
2291
+ bot.callbackQuery(/^a:dismiss:/, async (ctx) => {
2292
+ const actionId = ctx.callbackQuery.data.replace("a:dismiss:", "");
2293
+ removeAction(actionId);
2294
+ try {
2295
+ await ctx.editMessageReplyMarkup({
2296
+ reply_markup: { inline_keyboard: [] }
2297
+ });
2298
+ } catch {
2299
+ }
2300
+ await ctx.answerCallbackQuery({ text: "\u0110\xE3 hu\u1EF7" });
2301
+ });
2302
+ bot.callbackQuery(/^a:(?!dismiss)/, async (ctx) => {
2303
+ const actionId = ctx.callbackQuery.data.replace("a:", "");
2304
+ const action = getAction(actionId);
2305
+ if (!action) {
2306
+ await ctx.answerCallbackQuery({ text: "Action \u0111\xE3 h\u1EBFt h\u1EA1n" });
2307
+ return;
2308
+ }
2309
+ removeAction(actionId);
2310
+ try {
2311
+ if (action.action === "new_session") {
2312
+ await ctx.answerCallbackQuery({ text: "\u23F3 \u0110ang t\u1EA1o session..." });
2313
+ const { threadId } = await executeNewSession(
2314
+ bot,
2315
+ core,
2316
+ chatId,
2317
+ action.agent,
2318
+ action.workspace
2319
+ );
2320
+ const topicLink = `https://t.me/c/${String(chatId).replace("-100", "")}/${threadId}`;
2321
+ const originalText = ctx.callbackQuery.message?.text ?? "";
2322
+ try {
2323
+ await ctx.editMessageText(
2324
+ originalText + `
2325
+
2326
+ \u2705 Session created \u2192 <a href="${topicLink}">Go to topic</a>`,
2327
+ { parse_mode: "HTML" }
2328
+ );
2329
+ } catch {
2330
+ await ctx.editMessageReplyMarkup({
2331
+ reply_markup: { inline_keyboard: [] }
2332
+ });
2333
+ }
2334
+ } else if (action.action === "cancel_session") {
2335
+ const assistantId = getAssistantSessionId();
2336
+ const cancelled = await executeCancelSession(core, assistantId);
2337
+ if (cancelled) {
2338
+ await ctx.answerCallbackQuery({ text: "\u26D4 Session \u0111\xE3 hu\u1EF7" });
2339
+ const originalText = ctx.callbackQuery.message?.text ?? "";
2340
+ try {
2341
+ await ctx.editMessageText(
2342
+ originalText + `
2343
+
2344
+ \u26D4 Session "${cancelled.name ?? cancelled.id}" \u0111\xE3 hu\u1EF7`,
2345
+ { parse_mode: "HTML" }
2346
+ );
2347
+ } catch {
2348
+ await ctx.editMessageReplyMarkup({
2349
+ reply_markup: { inline_keyboard: [] }
2350
+ });
2351
+ }
2352
+ } else {
2353
+ await ctx.answerCallbackQuery({
2354
+ text: "Kh\xF4ng c\xF3 session n\xE0o \u0111ang ch\u1EA1y"
2355
+ });
2356
+ try {
2357
+ await ctx.editMessageReplyMarkup({
2358
+ reply_markup: { inline_keyboard: [] }
2359
+ });
2360
+ } catch {
2361
+ }
2362
+ }
2363
+ }
2364
+ } catch {
2365
+ await ctx.answerCallbackQuery({ text: "\u274C L\u1ED7i, th\u1EED l\u1EA1i sau" });
2366
+ try {
2367
+ await ctx.editMessageReplyMarkup({
2368
+ reply_markup: { inline_keyboard: [] }
2369
+ });
2370
+ } catch {
2371
+ }
2372
+ }
2373
+ });
2374
+ }
2375
+
1950
2376
  // src/adapters/telegram/adapter.ts
1951
- var log7 = createChildLogger({ module: "telegram" });
2377
+ var log8 = createChildLogger({ module: "telegram" });
1952
2378
  function patchedFetch(input, init) {
1953
2379
  if (init?.signal && !(init.signal instanceof AbortSignal)) {
1954
2380
  const nativeController = new AbortController();
@@ -1966,6 +2392,7 @@ var TelegramAdapter = class extends ChannelAdapter {
1966
2392
  bot;
1967
2393
  telegramConfig;
1968
2394
  sessionDrafts = /* @__PURE__ */ new Map();
2395
+ sessionTextBuffers = /* @__PURE__ */ new Map();
1969
2396
  toolCallMessages = /* @__PURE__ */ new Map();
1970
2397
  // sessionId → (toolCallId → state)
1971
2398
  permissionHandler;
@@ -1974,6 +2401,7 @@ var TelegramAdapter = class extends ChannelAdapter {
1974
2401
  assistantTopicId;
1975
2402
  skillMessages = /* @__PURE__ */ new Map();
1976
2403
  // sessionId → pinned messageId
2404
+ sendQueue = new TelegramSendQueue();
1977
2405
  constructor(core, config) {
1978
2406
  super(core, config);
1979
2407
  this.telegramConfig = config;
@@ -1982,7 +2410,23 @@ var TelegramAdapter = class extends ChannelAdapter {
1982
2410
  this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
1983
2411
  this.bot.catch((err) => {
1984
2412
  const rootCause = err.error instanceof Error ? err.error : err;
1985
- log7.error({ err: rootCause }, "Telegram bot error");
2413
+ log8.error({ err: rootCause }, "Telegram bot error");
2414
+ });
2415
+ this.bot.api.config.use(async (prev, method, payload, signal) => {
2416
+ const maxRetries = 3;
2417
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2418
+ const result = await prev(method, payload, signal);
2419
+ if (result.ok || result.error_code !== 429 || attempt === maxRetries) {
2420
+ return result;
2421
+ }
2422
+ const retryAfter = (result.parameters?.retry_after ?? 5) + 1;
2423
+ log8.warn(
2424
+ { method, retryAfter, attempt: attempt + 1 },
2425
+ "Rate limited by Telegram, retrying"
2426
+ );
2427
+ await new Promise((r) => setTimeout(r, retryAfter * 1e3));
2428
+ }
2429
+ return prev(method, payload, signal);
1986
2430
  });
1987
2431
  this.bot.api.config.use((prev, method, payload, signal) => {
1988
2432
  if (method === "getUpdates") {
@@ -2021,6 +2465,12 @@ var TelegramAdapter = class extends ChannelAdapter {
2021
2465
  (notification) => this.sendNotification(notification)
2022
2466
  );
2023
2467
  setupSkillCallbacks(this.bot, this.core);
2468
+ setupActionCallbacks(
2469
+ this.bot,
2470
+ this.core,
2471
+ this.telegramConfig.chatId,
2472
+ () => this.assistantSession?.id
2473
+ );
2024
2474
  setupMenuCallbacks(
2025
2475
  this.bot,
2026
2476
  this.core,
@@ -2035,7 +2485,7 @@ var TelegramAdapter = class extends ChannelAdapter {
2035
2485
  this.setupRoutes();
2036
2486
  this.bot.start({
2037
2487
  allowed_updates: ["message", "callback_query"],
2038
- onStart: () => log7.info(
2488
+ onStart: () => log8.info(
2039
2489
  { chatId: this.telegramConfig.chatId },
2040
2490
  "Telegram bot started"
2041
2491
  )
@@ -2047,7 +2497,7 @@ var TelegramAdapter = class extends ChannelAdapter {
2047
2497
  this.assistantTopicId
2048
2498
  );
2049
2499
  } catch (err) {
2050
- log7.error({ err }, "Failed to spawn assistant");
2500
+ log8.error({ err }, "Failed to spawn assistant");
2051
2501
  }
2052
2502
  try {
2053
2503
  const config = this.core.configManager.get();
@@ -2068,7 +2518,7 @@ Workspace: <code>${workspace}</code>
2068
2518
  reply_markup: buildMenuKeyboard()
2069
2519
  });
2070
2520
  } catch (err) {
2071
- log7.warn({ err }, "Failed to send welcome message");
2521
+ log8.warn({ err }, "Failed to send welcome message");
2072
2522
  }
2073
2523
  }
2074
2524
  async stop() {
@@ -2076,7 +2526,7 @@ Workspace: <code>${workspace}</code>
2076
2526
  await this.assistantSession.destroy();
2077
2527
  }
2078
2528
  await this.bot.stop();
2079
- log7.info("Telegram bot stopped");
2529
+ log8.info("Telegram bot stopped");
2080
2530
  }
2081
2531
  setupRoutes() {
2082
2532
  this.bot.on("message:text", async (ctx) => {
@@ -2094,7 +2544,7 @@ Workspace: <code>${workspace}</code>
2094
2544
  ctx.replyWithChatAction("typing").catch(() => {
2095
2545
  });
2096
2546
  handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
2097
- (err) => log7.error({ err }, "Assistant error")
2547
+ (err) => log8.error({ err }, "Assistant error")
2098
2548
  );
2099
2549
  return;
2100
2550
  }
@@ -2105,7 +2555,7 @@ Workspace: <code>${workspace}</code>
2105
2555
  threadId: String(threadId),
2106
2556
  userId: String(ctx.from.id),
2107
2557
  text: ctx.message.text
2108
- }).catch((err) => log7.error({ err }, "handleMessage error"));
2558
+ }).catch((err) => log8.error({ err }, "handleMessage error"));
2109
2559
  });
2110
2560
  }
2111
2561
  // --- ChannelAdapter implementations ---
@@ -2125,24 +2575,32 @@ Workspace: <code>${workspace}</code>
2125
2575
  draft = new MessageDraft(
2126
2576
  this.bot,
2127
2577
  this.telegramConfig.chatId,
2128
- threadId
2578
+ threadId,
2579
+ this.telegramConfig.streamThrottleMs,
2580
+ this.sendQueue
2129
2581
  );
2130
2582
  this.sessionDrafts.set(sessionId, draft);
2131
2583
  }
2132
2584
  draft.append(content.text);
2585
+ this.sessionTextBuffers.set(
2586
+ sessionId,
2587
+ (this.sessionTextBuffers.get(sessionId) ?? "") + content.text
2588
+ );
2133
2589
  break;
2134
2590
  }
2135
2591
  case "tool_call": {
2136
2592
  await this.finalizeDraft(sessionId);
2137
2593
  const meta = content.metadata;
2138
- const msg = await this.bot.api.sendMessage(
2139
- this.telegramConfig.chatId,
2140
- formatToolCall(meta),
2141
- {
2142
- message_thread_id: threadId,
2143
- parse_mode: "HTML",
2144
- disable_notification: true
2145
- }
2594
+ const msg = await this.sendQueue.enqueue(
2595
+ () => this.bot.api.sendMessage(
2596
+ this.telegramConfig.chatId,
2597
+ formatToolCall(meta),
2598
+ {
2599
+ message_thread_id: threadId,
2600
+ parse_mode: "HTML",
2601
+ disable_notification: true
2602
+ }
2603
+ )
2146
2604
  );
2147
2605
  if (!this.toolCallMessages.has(sessionId)) {
2148
2606
  this.toolCallMessages.set(sessionId, /* @__PURE__ */ new Map());
@@ -2168,11 +2626,13 @@ Workspace: <code>${workspace}</code>
2168
2626
  viewerLinks
2169
2627
  };
2170
2628
  try {
2171
- await this.bot.api.editMessageText(
2172
- this.telegramConfig.chatId,
2173
- toolState.msgId,
2174
- formatToolUpdate(merged),
2175
- { parse_mode: "HTML" }
2629
+ await this.sendQueue.enqueue(
2630
+ () => this.bot.api.editMessageText(
2631
+ this.telegramConfig.chatId,
2632
+ toolState.msgId,
2633
+ formatToolUpdate(merged),
2634
+ { parse_mode: "HTML" }
2635
+ )
2176
2636
  );
2177
2637
  } catch {
2178
2638
  }
@@ -2181,30 +2641,35 @@ Workspace: <code>${workspace}</code>
2181
2641
  }
2182
2642
  case "plan": {
2183
2643
  await this.finalizeDraft(sessionId);
2184
- await this.bot.api.sendMessage(
2185
- this.telegramConfig.chatId,
2186
- formatPlan(
2187
- content.metadata
2188
- ),
2189
- {
2190
- message_thread_id: threadId,
2191
- parse_mode: "HTML",
2192
- disable_notification: true
2193
- }
2644
+ await this.sendQueue.enqueue(
2645
+ () => this.bot.api.sendMessage(
2646
+ this.telegramConfig.chatId,
2647
+ formatPlan(
2648
+ content.metadata
2649
+ ),
2650
+ {
2651
+ message_thread_id: threadId,
2652
+ parse_mode: "HTML",
2653
+ disable_notification: true
2654
+ }
2655
+ )
2194
2656
  );
2195
2657
  break;
2196
2658
  }
2197
2659
  case "usage": {
2198
- await this.bot.api.sendMessage(
2199
- this.telegramConfig.chatId,
2200
- formatUsage(
2201
- content.metadata
2202
- ),
2203
- {
2204
- message_thread_id: threadId,
2205
- parse_mode: "HTML",
2206
- disable_notification: true
2207
- }
2660
+ await this.finalizeDraft(sessionId);
2661
+ await this.sendQueue.enqueue(
2662
+ () => this.bot.api.sendMessage(
2663
+ this.telegramConfig.chatId,
2664
+ formatUsage(
2665
+ content.metadata
2666
+ ),
2667
+ {
2668
+ message_thread_id: threadId,
2669
+ parse_mode: "HTML",
2670
+ disable_notification: true
2671
+ }
2672
+ )
2208
2673
  );
2209
2674
  break;
2210
2675
  }
@@ -2213,42 +2678,48 @@ Workspace: <code>${workspace}</code>
2213
2678
  this.sessionDrafts.delete(sessionId);
2214
2679
  this.toolCallMessages.delete(sessionId);
2215
2680
  await this.cleanupSkillCommands(sessionId);
2216
- await this.bot.api.sendMessage(
2217
- this.telegramConfig.chatId,
2218
- `\u2705 <b>Done</b>`,
2219
- {
2220
- message_thread_id: threadId,
2221
- parse_mode: "HTML",
2222
- disable_notification: true
2223
- }
2681
+ await this.sendQueue.enqueue(
2682
+ () => this.bot.api.sendMessage(
2683
+ this.telegramConfig.chatId,
2684
+ `\u2705 <b>Done</b>`,
2685
+ {
2686
+ message_thread_id: threadId,
2687
+ parse_mode: "HTML",
2688
+ disable_notification: true
2689
+ }
2690
+ )
2224
2691
  );
2225
2692
  break;
2226
2693
  }
2227
2694
  case "error": {
2228
2695
  await this.finalizeDraft(sessionId);
2229
- await this.bot.api.sendMessage(
2230
- this.telegramConfig.chatId,
2231
- `\u274C <b>Error:</b> ${escapeHtml(content.text)}`,
2232
- {
2233
- message_thread_id: threadId,
2234
- parse_mode: "HTML",
2235
- disable_notification: true
2236
- }
2696
+ await this.sendQueue.enqueue(
2697
+ () => this.bot.api.sendMessage(
2698
+ this.telegramConfig.chatId,
2699
+ `\u274C <b>Error:</b> ${escapeHtml(content.text)}`,
2700
+ {
2701
+ message_thread_id: threadId,
2702
+ parse_mode: "HTML",
2703
+ disable_notification: true
2704
+ }
2705
+ )
2237
2706
  );
2238
2707
  break;
2239
2708
  }
2240
2709
  }
2241
2710
  }
2242
2711
  async sendPermissionRequest(sessionId, request) {
2243
- log7.info({ sessionId, requestId: request.id }, "Permission request sent");
2712
+ log8.info({ sessionId, requestId: request.id }, "Permission request sent");
2244
2713
  const session = this.core.sessionManager.getSession(
2245
2714
  sessionId
2246
2715
  );
2247
2716
  if (!session) return;
2248
- await this.permissionHandler.sendPermissionRequest(session, request);
2717
+ await this.sendQueue.enqueue(
2718
+ () => this.permissionHandler.sendPermissionRequest(session, request)
2719
+ );
2249
2720
  }
2250
2721
  async sendNotification(notification) {
2251
- log7.info(
2722
+ log8.info(
2252
2723
  { sessionId: notification.sessionId, type: notification.type },
2253
2724
  "Notification sent"
2254
2725
  );
@@ -2267,14 +2738,16 @@ Workspace: <code>${workspace}</code>
2267
2738
 
2268
2739
  <a href="${notification.deepLink}">\u2192 Go to message</a>`;
2269
2740
  }
2270
- await this.bot.api.sendMessage(this.telegramConfig.chatId, text, {
2271
- message_thread_id: this.notificationTopicId,
2272
- parse_mode: "HTML",
2273
- disable_notification: false
2274
- });
2741
+ await this.sendQueue.enqueue(
2742
+ () => this.bot.api.sendMessage(this.telegramConfig.chatId, text, {
2743
+ message_thread_id: this.notificationTopicId,
2744
+ parse_mode: "HTML",
2745
+ disable_notification: false
2746
+ })
2747
+ );
2275
2748
  }
2276
2749
  async createSessionThread(sessionId, name) {
2277
- log7.info({ sessionId, name }, "Session topic created");
2750
+ log8.info({ sessionId, name }, "Session topic created");
2278
2751
  return String(
2279
2752
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
2280
2753
  );
@@ -2319,15 +2792,17 @@ Workspace: <code>${workspace}</code>
2319
2792
  }
2320
2793
  }
2321
2794
  try {
2322
- const msg = await this.bot.api.sendMessage(
2323
- this.telegramConfig.chatId,
2324
- text,
2325
- {
2326
- message_thread_id: threadId,
2327
- parse_mode: "HTML",
2328
- reply_markup: keyboard,
2329
- disable_notification: true
2330
- }
2795
+ const msg = await this.sendQueue.enqueue(
2796
+ () => this.bot.api.sendMessage(
2797
+ this.telegramConfig.chatId,
2798
+ text,
2799
+ {
2800
+ message_thread_id: threadId,
2801
+ parse_mode: "HTML",
2802
+ reply_markup: keyboard,
2803
+ disable_notification: true
2804
+ }
2805
+ )
2331
2806
  );
2332
2807
  this.skillMessages.set(sessionId, msg.message_id);
2333
2808
  await this.bot.api.pinChatMessage(
@@ -2338,7 +2813,7 @@ Workspace: <code>${workspace}</code>
2338
2813
  }
2339
2814
  );
2340
2815
  } catch (err) {
2341
- log7.error({ err, sessionId }, "Failed to send skill commands");
2816
+ log8.error({ err, sessionId }, "Failed to send skill commands");
2342
2817
  }
2343
2818
  await this.updateCommandAutocomplete(session.agentName, commands);
2344
2819
  }
@@ -2369,12 +2844,12 @@ Workspace: <code>${workspace}</code>
2369
2844
  await this.bot.api.setMyCommands(all, {
2370
2845
  scope: { type: "chat", chat_id: this.telegramConfig.chatId }
2371
2846
  });
2372
- log7.info(
2847
+ log8.info(
2373
2848
  { count: all.length, skills: validSkills.length },
2374
2849
  "Updated command autocomplete"
2375
2850
  );
2376
2851
  } catch (err) {
2377
- log7.error(
2852
+ log8.error(
2378
2853
  { err, commands: all },
2379
2854
  "Failed to update command autocomplete"
2380
2855
  );
@@ -2382,9 +2857,29 @@ Workspace: <code>${workspace}</code>
2382
2857
  }
2383
2858
  async finalizeDraft(sessionId) {
2384
2859
  const draft = this.sessionDrafts.get(sessionId);
2385
- if (draft) {
2386
- await draft.finalize();
2387
- this.sessionDrafts.delete(sessionId);
2860
+ if (!draft) return;
2861
+ const finalMsgId = await draft.finalize();
2862
+ this.sessionDrafts.delete(sessionId);
2863
+ if (sessionId === this.assistantSession?.id) {
2864
+ const fullText = this.sessionTextBuffers.get(sessionId);
2865
+ this.sessionTextBuffers.delete(sessionId);
2866
+ if (fullText && finalMsgId) {
2867
+ const detected = detectAction(fullText);
2868
+ if (detected) {
2869
+ const actionId = storeAction(detected);
2870
+ const keyboard = buildActionKeyboard(actionId, detected);
2871
+ try {
2872
+ await this.bot.api.editMessageReplyMarkup(
2873
+ this.telegramConfig.chatId,
2874
+ finalMsgId,
2875
+ { reply_markup: keyboard }
2876
+ );
2877
+ } catch {
2878
+ }
2879
+ }
2880
+ }
2881
+ } else {
2882
+ this.sessionTextBuffers.delete(sessionId);
2388
2883
  }
2389
2884
  }
2390
2885
  };
@@ -2400,6 +2895,7 @@ export {
2400
2895
  NotificationManager,
2401
2896
  OpenACPCore,
2402
2897
  ChannelAdapter,
2898
+ ApiServer,
2403
2899
  TelegramAdapter
2404
2900
  };
2405
- //# sourceMappingURL=chunk-EIBLQU3H.js.map
2901
+ //# sourceMappingURL=chunk-S5MPFOR3.js.map