@openacp/cli 0.2.29 → 0.3.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 (45) hide show
  1. package/dist/{autostart-YBYXQA77.js → autostart-N4HIL6C3.js} +3 -3
  2. package/dist/{chunk-PQRVTUNH.js → chunk-5E6ZXCNN.js} +2 -2
  3. package/dist/{chunk-S6O7SM6A.js → chunk-7W5SOJPD.js} +2 -2
  4. package/dist/{chunk-6HORD4FS.js → chunk-C6IFPAWN.js} +823 -243
  5. package/dist/chunk-C6IFPAWN.js.map +1 -0
  6. package/dist/{chunk-4BN7NSKB.js → chunk-CA6FXPLH.js} +11 -10
  7. package/dist/chunk-CA6FXPLH.js.map +1 -0
  8. package/dist/{chunk-MNJDYDGH.js → chunk-JOSJGZGF.js} +19 -15
  9. package/dist/chunk-JOSJGZGF.js.map +1 -0
  10. package/dist/{chunk-CQMS5U7Z.js → chunk-LVSQQRCF.js} +3 -3
  11. package/dist/{chunk-QWUJIKTX.js → chunk-NS2L445T.js} +5 -5
  12. package/dist/chunk-NS2L445T.js.map +1 -0
  13. package/dist/{chunk-WXS6ONOD.js → chunk-RBDPCHGD.js} +2 -2
  14. package/dist/{chunk-FGXG3H3F.js → chunk-YXMRR2E3.js} +24 -4
  15. package/dist/chunk-YXMRR2E3.js.map +1 -0
  16. package/dist/cli.js +18 -18
  17. package/dist/{config-2XALNLAA.js → config-2CBRLF3R.js} +3 -3
  18. package/dist/config-editor-UN56HQCW.js +11 -0
  19. package/dist/{daemon-3E5OMLT3.js → daemon-UXC7PB4P.js} +4 -4
  20. package/dist/index.d.ts +10 -1
  21. package/dist/index.js +8 -8
  22. package/dist/install-cloudflared-LMM7MFQX.js +8 -0
  23. package/dist/{main-XAUS3VZW.js → main-OVEJEUX5.js} +13 -13
  24. package/dist/{setup-FTNJACSC.js → setup-UKWBLJIT.js} +4 -4
  25. package/dist/{tunnel-service-YQ4RG652.js → tunnel-service-4GISQZNP.js} +3 -3
  26. package/package.json +2 -2
  27. package/dist/chunk-4BN7NSKB.js.map +0 -1
  28. package/dist/chunk-6HORD4FS.js.map +0 -1
  29. package/dist/chunk-FGXG3H3F.js.map +0 -1
  30. package/dist/chunk-MNJDYDGH.js.map +0 -1
  31. package/dist/chunk-QWUJIKTX.js.map +0 -1
  32. package/dist/config-editor-56B6YU7B.js +0 -11
  33. package/dist/install-cloudflared-57NRTI4E.js +0 -8
  34. /package/dist/{autostart-YBYXQA77.js.map → autostart-N4HIL6C3.js.map} +0 -0
  35. /package/dist/{chunk-PQRVTUNH.js.map → chunk-5E6ZXCNN.js.map} +0 -0
  36. /package/dist/{chunk-S6O7SM6A.js.map → chunk-7W5SOJPD.js.map} +0 -0
  37. /package/dist/{chunk-CQMS5U7Z.js.map → chunk-LVSQQRCF.js.map} +0 -0
  38. /package/dist/{chunk-WXS6ONOD.js.map → chunk-RBDPCHGD.js.map} +0 -0
  39. /package/dist/{config-2XALNLAA.js.map → config-2CBRLF3R.js.map} +0 -0
  40. /package/dist/{config-editor-56B6YU7B.js.map → config-editor-UN56HQCW.js.map} +0 -0
  41. /package/dist/{daemon-3E5OMLT3.js.map → daemon-UXC7PB4P.js.map} +0 -0
  42. /package/dist/{install-cloudflared-57NRTI4E.js.map → install-cloudflared-LMM7MFQX.js.map} +0 -0
  43. /package/dist/{main-XAUS3VZW.js.map → main-OVEJEUX5.js.map} +0 -0
  44. /package/dist/{setup-FTNJACSC.js.map → setup-UKWBLJIT.js.map} +0 -0
  45. /package/dist/{tunnel-service-YQ4RG652.js.map → tunnel-service-4GISQZNP.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createChildLogger,
3
3
  createSessionLogger
4
- } from "./chunk-MNJDYDGH.js";
4
+ } from "./chunk-JOSJGZGF.js";
5
5
 
6
6
  // src/core/streams.ts
7
7
  function nodeToWebWritable(nodeStream) {
@@ -304,6 +304,8 @@ ${stderr}`
304
304
  event = {
305
305
  type: "tool_update",
306
306
  id: update.toolCallId,
307
+ name: update.title ?? void 0,
308
+ kind: update.kind ?? void 0,
307
309
  status: update.status ?? "pending",
308
310
  content: update.content ?? void 0,
309
311
  rawInput: update.rawInput ?? void 0,
@@ -521,6 +523,7 @@ var Session = class {
521
523
  adapter;
522
524
  // Set by wireSessionEvents for renaming
523
525
  pendingPermission;
526
+ dangerousMode = false;
524
527
  log;
525
528
  constructor(opts) {
526
529
  this.id = opts.id || nanoid(12);
@@ -591,7 +594,8 @@ var Session = class {
591
594
  async warmup() {
592
595
  this.promptRunning = true;
593
596
  const prevHandler = this.agentInstance.onSessionUpdate;
594
- this.agentInstance.onSessionUpdate = () => {
597
+ this.agentInstance.onSessionUpdate = (event) => {
598
+ if (event.type === "commands_update") prevHandler(event);
595
599
  };
596
600
  try {
597
601
  const start = Date.now();
@@ -664,6 +668,12 @@ var SessionManager = class {
664
668
  }
665
669
  return void 0;
666
670
  }
671
+ getRecordByThread(channelId, threadId) {
672
+ return this.store?.findByPlatform(
673
+ channelId,
674
+ (p) => String(p.topicId) === threadId
675
+ );
676
+ }
667
677
  registerSession(session) {
668
678
  this.sessions.set(session.id, session);
669
679
  }
@@ -691,15 +701,25 @@ var SessionManager = class {
691
701
  await this.store.save({ ...record, status });
692
702
  }
693
703
  }
704
+ async updateSessionName(sessionId, name) {
705
+ if (!this.store) return;
706
+ const record = this.store.get(sessionId);
707
+ if (record) {
708
+ await this.store.save({ ...record, name });
709
+ }
710
+ }
711
+ getSessionRecord(sessionId) {
712
+ return this.store?.get(sessionId);
713
+ }
694
714
  async cancelSession(sessionId) {
695
715
  const session = this.sessions.get(sessionId);
696
716
  if (session) {
697
717
  await session.cancel();
698
- if (this.store) {
699
- const record = this.store.get(sessionId);
700
- if (record) {
701
- await this.store.save({ ...record, status: "cancelled" });
702
- }
718
+ }
719
+ if (this.store) {
720
+ const record = this.store.get(sessionId);
721
+ if (record && record.status !== "cancelled") {
722
+ await this.store.save({ ...record, status: "cancelled" });
703
723
  }
704
724
  }
705
725
  }
@@ -871,17 +891,21 @@ function extractFileInfo(name, kind, content, rawInput, meta) {
871
891
  let info = null;
872
892
  if (meta) {
873
893
  const m = meta;
874
- const file = m?.claudeCode?.toolResponse?.file;
894
+ const tr = m?.claudeCode?.toolResponse;
895
+ const file = tr?.file;
875
896
  if (file?.filePath && file?.content) {
876
897
  info = { filePath: file.filePath, content: file.content };
877
898
  }
899
+ if (!info && tr?.filePath && tr?.content) {
900
+ info = { filePath: tr.filePath, content: tr.content };
901
+ }
878
902
  }
879
903
  if (!info && rawInput) {
880
904
  const ri = rawInput;
881
905
  const filePath = ri?.file_path || ri?.filePath || ri?.path;
882
906
  if (typeof filePath === "string") {
883
907
  const parsed = content ? parseContent(content) : null;
884
- info = { filePath, content: parsed?.content || ri?.content };
908
+ info = { filePath, content: parsed?.content || ri?.content, oldContent: parsed?.oldContent };
885
909
  }
886
910
  }
887
911
  if (!info && content) {
@@ -1028,9 +1052,9 @@ var OpenACPCore = class {
1028
1052
  );
1029
1053
  const adapter = this.adapters.get(message.channelId);
1030
1054
  if (adapter) {
1031
- await adapter.sendMessage("system", {
1055
+ await adapter.sendMessage(message.threadId, {
1032
1056
  type: "error",
1033
- text: `Max concurrent sessions (${config.security.maxConcurrentSessions}) reached. Cancel a session first.`
1057
+ text: `\u26A0\uFE0F Session limit reached (${config.security.maxConcurrentSessions}). Please cancel existing sessions with /cancel before starting new ones.`
1034
1058
  });
1035
1059
  }
1036
1060
  return;
@@ -1070,11 +1094,19 @@ var OpenACPCore = class {
1070
1094
  channelId,
1071
1095
  currentThreadId
1072
1096
  );
1073
- if (!currentSession) return null;
1097
+ if (currentSession) {
1098
+ return this.handleNewSession(
1099
+ channelId,
1100
+ currentSession.agentName,
1101
+ currentSession.workingDirectory
1102
+ );
1103
+ }
1104
+ const record = this.sessionManager.getRecordByThread(channelId, currentThreadId);
1105
+ if (!record || record.status === "cancelled" || record.status === "error") return null;
1074
1106
  return this.handleNewSession(
1075
1107
  channelId,
1076
- currentSession.agentName,
1077
- currentSession.workingDirectory
1108
+ record.agentName,
1109
+ record.workingDir
1078
1110
  );
1079
1111
  }
1080
1112
  // --- Lazy Resume ---
@@ -1144,6 +1176,7 @@ var OpenACPCore = class {
1144
1176
  case "tool_call": {
1145
1177
  const metadata = {
1146
1178
  id: event.id,
1179
+ name: event.name,
1147
1180
  kind: event.kind,
1148
1181
  status: event.status,
1149
1182
  content: event.content,
@@ -1155,6 +1188,8 @@ var OpenACPCore = class {
1155
1188
  case "tool_update": {
1156
1189
  const metadata = {
1157
1190
  id: event.id,
1191
+ name: event.name,
1192
+ kind: event.kind,
1158
1193
  status: event.status,
1159
1194
  content: event.content
1160
1195
  };
@@ -1538,6 +1573,7 @@ function extractContentText(content, depth = 0) {
1538
1573
  if (c.type === "text" && typeof c.text === "string") return c.text;
1539
1574
  if (typeof c.text === "string") return c.text;
1540
1575
  if (typeof c.content === "string") return c.content;
1576
+ if (c.content && typeof c.content === "object") return extractContentText(c.content, depth + 1);
1541
1577
  if (c.input) return extractContentText(c.input, depth + 1);
1542
1578
  if (c.output) return extractContentText(c.output, depth + 1);
1543
1579
  const keys = Object.keys(c).filter((k) => k !== "type");
@@ -1554,12 +1590,14 @@ function formatToolCall(tool) {
1554
1590
  const si = STATUS_ICON[tool.status || ""] || "\u{1F527}";
1555
1591
  const ki = KIND_ICON[tool.kind || ""] || "\u{1F6E0}\uFE0F";
1556
1592
  let text = `${si} ${ki} <b>${escapeHtml(tool.name || "Tool")}</b>`;
1557
- const details = extractContentText(tool.content);
1558
- if (details) {
1559
- text += `
1593
+ text += formatViewerLinks(tool.viewerLinks, tool.viewerFilePath);
1594
+ if (!tool.viewerLinks) {
1595
+ const details = extractContentText(tool.content);
1596
+ if (details) {
1597
+ text += `
1560
1598
  <pre>${escapeHtml(truncateContent(details))}</pre>`;
1599
+ }
1561
1600
  }
1562
- text += formatViewerLinks(tool.viewerLinks, tool.viewerFilePath);
1563
1601
  return text;
1564
1602
  }
1565
1603
  function formatToolUpdate(update) {
@@ -1567,12 +1605,14 @@ function formatToolUpdate(update) {
1567
1605
  const ki = KIND_ICON[update.kind || ""] || "\u{1F6E0}\uFE0F";
1568
1606
  const name = update.name || "Tool";
1569
1607
  let text = `${si} ${ki} <b>${escapeHtml(name)}</b>`;
1570
- const details = extractContentText(update.content);
1571
- if (details) {
1572
- text += `
1608
+ text += formatViewerLinks(update.viewerLinks, update.viewerFilePath);
1609
+ if (!update.viewerLinks) {
1610
+ const details = extractContentText(update.content);
1611
+ if (details) {
1612
+ text += `
1573
1613
  <pre>${escapeHtml(truncateContent(details))}</pre>`;
1614
+ }
1574
1615
  }
1575
- text += formatViewerLinks(update.viewerLinks, update.viewerFilePath);
1576
1616
  return text;
1577
1617
  }
1578
1618
  function formatViewerLinks(links, filePath) {
@@ -1585,20 +1625,23 @@ function formatViewerLinks(links, filePath) {
1585
1625
  \u{1F4DD} <a href="${escapeHtml(links.diff)}">View diff${fileName ? ` \u2014 ${escapeHtml(fileName)}` : ""}</a>`;
1586
1626
  return text;
1587
1627
  }
1588
- function formatPlan(plan) {
1589
- const statusIcon = { pending: "\u2B1C", in_progress: "\u{1F504}", completed: "\u2705" };
1590
- const lines = plan.entries.map(
1591
- (e, i) => `${statusIcon[e.status] || "\u2B1C"} ${i + 1}. ${escapeHtml(e.content)}`
1592
- );
1593
- return `<b>Plan:</b>
1594
- ${lines.join("\n")}`;
1628
+ function formatTokens(n) {
1629
+ return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
1630
+ }
1631
+ function progressBar(ratio) {
1632
+ const filled = Math.round(Math.min(ratio, 1) * 10);
1633
+ return "\u2593".repeat(filled) + "\u2591".repeat(10 - filled);
1595
1634
  }
1596
1635
  function formatUsage(usage) {
1597
- const parts = [];
1598
- if (usage.tokensUsed != null) parts.push(`Tokens: ${usage.tokensUsed.toLocaleString()}`);
1599
- if (usage.contextSize != null) parts.push(`Context: ${usage.contextSize.toLocaleString()}`);
1600
- if (usage.cost) parts.push(`Cost: $${usage.cost.amount.toFixed(4)}`);
1601
- return `\u{1F4CA} ${parts.join(" | ")}`;
1636
+ const { tokensUsed, contextSize } = usage;
1637
+ if (tokensUsed == null) return "\u{1F4CA} Usage data unavailable";
1638
+ if (contextSize == null) return `\u{1F4CA} ${formatTokens(tokensUsed)} tokens`;
1639
+ const ratio = tokensUsed / contextSize;
1640
+ const pct = Math.round(ratio * 100);
1641
+ const bar = progressBar(ratio);
1642
+ const emoji = pct >= 85 ? "\u26A0\uFE0F" : "\u{1F4CA}";
1643
+ return `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens
1644
+ ${bar} ${pct}%`;
1602
1645
  }
1603
1646
  function splitMessage(text, maxLength = 4096) {
1604
1647
  if (text.length <= maxLength) return [text];
@@ -1623,94 +1666,68 @@ function splitMessage(text, maxLength = 4096) {
1623
1666
  }
1624
1667
 
1625
1668
  // src/adapters/telegram/streaming.ts
1626
- var nextDraftId = 1;
1669
+ var FLUSH_INTERVAL = 5e3;
1627
1670
  var MessageDraft = class {
1628
- // Only set in fallback mode (sendMessageDraft returns true, not Message)
1629
- constructor(bot, chatId, threadId, throttleMs = 200, sendQueue) {
1671
+ constructor(bot, chatId, threadId, sendQueue, sessionId) {
1630
1672
  this.bot = bot;
1631
1673
  this.chatId = chatId;
1632
1674
  this.threadId = threadId;
1633
1675
  this.sendQueue = sendQueue;
1634
- this.draftId = nextDraftId++;
1635
- this.minInterval = throttleMs;
1676
+ this.sessionId = sessionId;
1636
1677
  }
1637
- draftId;
1638
1678
  buffer = "";
1639
- lastFlush = 0;
1679
+ messageId;
1680
+ firstFlushPending = false;
1640
1681
  flushTimer;
1641
1682
  flushPromise = Promise.resolve();
1642
- minInterval;
1643
- useFallback = false;
1644
- messageId;
1683
+ lastSentBuffer = "";
1645
1684
  append(text) {
1685
+ if (!text) return;
1646
1686
  this.buffer += text;
1647
1687
  this.scheduleFlush();
1648
1688
  }
1649
1689
  scheduleFlush() {
1650
- const now = Date.now();
1651
- const elapsed = now - this.lastFlush;
1652
- if (elapsed >= this.minInterval) {
1690
+ if (this.flushTimer) return;
1691
+ this.flushTimer = setTimeout(() => {
1692
+ this.flushTimer = void 0;
1653
1693
  this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => {
1654
1694
  });
1655
- } else if (!this.flushTimer) {
1656
- this.flushTimer = setTimeout(() => {
1657
- this.flushTimer = void 0;
1658
- this.flushPromise = this.flushPromise.then(() => this.flush()).catch(() => {
1659
- });
1660
- }, this.minInterval - elapsed);
1661
- }
1695
+ }, FLUSH_INTERVAL);
1662
1696
  }
1663
1697
  async flush() {
1664
1698
  if (!this.buffer) return;
1665
- this.lastFlush = Date.now();
1699
+ if (this.firstFlushPending) return;
1666
1700
  const html = markdownToTelegramHtml(this.buffer);
1667
1701
  const truncated = html.length > 4096 ? html.slice(0, 4090) + "\n..." : html;
1668
1702
  if (!truncated) return;
1669
- if (this.useFallback) {
1670
- await this.flushFallback(truncated);
1671
- return;
1672
- }
1673
- try {
1674
- await this.bot.api.sendMessageDraft(this.chatId, this.draftId, truncated, {
1675
- message_thread_id: this.threadId,
1676
- parse_mode: "HTML"
1677
- });
1678
- } catch {
1679
- this.useFallback = true;
1680
- this.minInterval = 1e3;
1681
- await this.flushFallback(truncated);
1682
- }
1683
- }
1684
- async flushFallback(html) {
1685
- const exec = this.sendQueue ? (fn) => this.sendQueue.enqueue(fn) : (fn) => fn();
1686
- try {
1687
- if (!this.messageId) {
1688
- const msg = await exec(
1689
- () => this.bot.api.sendMessage(this.chatId, html, {
1703
+ if (!this.messageId) {
1704
+ this.firstFlushPending = true;
1705
+ try {
1706
+ const result = await this.sendQueue.enqueue(
1707
+ () => this.bot.api.sendMessage(this.chatId, truncated, {
1690
1708
  message_thread_id: this.threadId,
1691
1709
  parse_mode: "HTML",
1692
1710
  disable_notification: true
1693
- })
1694
- );
1695
- this.messageId = msg.message_id;
1696
- } else {
1697
- await exec(
1698
- () => this.bot.api.editMessageText(this.chatId, this.messageId, html, {
1699
- parse_mode: "HTML"
1700
- })
1711
+ }),
1712
+ { type: "other" }
1701
1713
  );
1714
+ if (result) {
1715
+ this.messageId = result.message_id;
1716
+ this.lastSentBuffer = this.buffer;
1717
+ }
1718
+ } catch {
1719
+ } finally {
1720
+ this.firstFlushPending = false;
1702
1721
  }
1703
- } catch {
1722
+ } else {
1704
1723
  try {
1705
- if (!this.messageId) {
1706
- const msg = await exec(
1707
- () => this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
1708
- message_thread_id: this.threadId,
1709
- disable_notification: true
1710
- })
1711
- );
1712
- this.messageId = msg.message_id;
1713
- }
1724
+ await this.sendQueue.enqueue(
1725
+ () => this.bot.api.editMessageText(this.chatId, this.messageId, truncated, {
1726
+ parse_mode: "HTML"
1727
+ }),
1728
+ { type: "text", key: this.sessionId }
1729
+ );
1730
+ this.lastSentBuffer = this.buffer;
1714
1731
  } catch {
1715
1732
  }
1716
1733
  }
@@ -1722,31 +1739,47 @@ var MessageDraft = class {
1722
1739
  }
1723
1740
  await this.flushPromise;
1724
1741
  if (!this.buffer) return this.messageId;
1742
+ if (this.messageId && this.buffer === this.lastSentBuffer) {
1743
+ return this.messageId;
1744
+ }
1725
1745
  const html = markdownToTelegramHtml(this.buffer);
1726
1746
  const chunks = splitMessage(html);
1727
1747
  try {
1728
1748
  for (let i = 0; i < chunks.length; i++) {
1729
1749
  const chunk = chunks[i];
1730
1750
  if (i === 0 && this.messageId) {
1731
- await this.bot.api.editMessageText(this.chatId, this.messageId, chunk, {
1732
- parse_mode: "HTML"
1733
- });
1751
+ await this.sendQueue.enqueue(
1752
+ () => this.bot.api.editMessageText(this.chatId, this.messageId, chunk, {
1753
+ parse_mode: "HTML"
1754
+ }),
1755
+ { type: "other" }
1756
+ );
1734
1757
  } else {
1735
- const msg = await this.bot.api.sendMessage(this.chatId, chunk, {
1736
- message_thread_id: this.threadId,
1737
- parse_mode: "HTML",
1738
- disable_notification: true
1739
- });
1740
- this.messageId = msg.message_id;
1758
+ const msg = await this.sendQueue.enqueue(
1759
+ () => this.bot.api.sendMessage(this.chatId, chunk, {
1760
+ message_thread_id: this.threadId,
1761
+ parse_mode: "HTML",
1762
+ disable_notification: true
1763
+ }),
1764
+ { type: "other" }
1765
+ );
1766
+ if (msg) {
1767
+ this.messageId = msg.message_id;
1768
+ }
1741
1769
  }
1742
1770
  }
1743
1771
  } catch {
1744
- try {
1745
- await this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
1746
- message_thread_id: this.threadId,
1747
- disable_notification: true
1748
- });
1749
- } catch {
1772
+ if (this.buffer !== this.lastSentBuffer) {
1773
+ try {
1774
+ await this.sendQueue.enqueue(
1775
+ () => this.bot.api.sendMessage(this.chatId, this.buffer.slice(0, 4096), {
1776
+ message_thread_id: this.threadId,
1777
+ disable_notification: true
1778
+ }),
1779
+ { type: "other" }
1780
+ );
1781
+ } catch {
1782
+ }
1750
1783
  }
1751
1784
  }
1752
1785
  return this.messageId;
@@ -1799,6 +1832,8 @@ function setupCommands(bot, core, chatId, assistant) {
1799
1832
  bot.command("agents", (ctx) => handleAgents(ctx, core));
1800
1833
  bot.command("help", (ctx) => handleHelp(ctx));
1801
1834
  bot.command("menu", (ctx) => handleMenu(ctx));
1835
+ bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
1836
+ bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
1802
1837
  }
1803
1838
  function buildMenuKeyboard() {
1804
1839
  return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:new_chat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F916} Agents", "m:agents").text("\u2753 Help", "m:help");
@@ -1884,7 +1919,8 @@ async function handleNew(ctx, core, chatId, assistant) {
1884
1919
  <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`,
1885
1920
  {
1886
1921
  message_thread_id: threadId,
1887
- parse_mode: "HTML"
1922
+ parse_mode: "HTML",
1923
+ reply_markup: buildDangerousModeKeyboard(session.id, false)
1888
1924
  }
1889
1925
  );
1890
1926
  session.warmup().catch((err) => log6.error({ err }, "Warm-up error"));
@@ -1938,7 +1974,8 @@ async function handleNewChat(ctx, core, chatId) {
1938
1974
  <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>`,
1939
1975
  {
1940
1976
  message_thread_id: newThreadId,
1941
- parse_mode: "HTML"
1977
+ parse_mode: "HTML",
1978
+ reply_markup: buildDangerousModeKeyboard(session.id, false)
1942
1979
  }
1943
1980
  );
1944
1981
  session.warmup().catch((err) => log6.error({ err }, "Warm-up error"));
@@ -1967,6 +2004,13 @@ async function handleCancel(ctx, core, assistant) {
1967
2004
  log6.info({ sessionId: session.id }, "Cancel session command");
1968
2005
  await session.cancel();
1969
2006
  await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
2007
+ return;
2008
+ }
2009
+ const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
2010
+ if (record && record.status !== "cancelled" && record.status !== "error") {
2011
+ log6.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
2012
+ await core.sessionManager.cancelSession(record.sessionId);
2013
+ await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
1970
2014
  }
1971
2015
  }
1972
2016
  async function handleStatus(ctx, core) {
@@ -1986,9 +2030,20 @@ async function handleStatus(ctx, core) {
1986
2030
  { parse_mode: "HTML" }
1987
2031
  );
1988
2032
  } else {
1989
- await ctx.reply("No active session in this topic.", {
1990
- parse_mode: "HTML"
1991
- });
2033
+ const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
2034
+ if (record) {
2035
+ await ctx.reply(
2036
+ `<b>Session:</b> ${escapeHtml(record.name || record.sessionId)}
2037
+ <b>Agent:</b> ${escapeHtml(record.agentName)}
2038
+ <b>Status:</b> ${escapeHtml(record.status)} (not loaded)
2039
+ <b>Workspace:</b> <code>${escapeHtml(record.workingDir)}</code>`,
2040
+ { parse_mode: "HTML" }
2041
+ );
2042
+ } else {
2043
+ await ctx.reply("No active session in this topic.", {
2044
+ parse_mode: "HTML"
2045
+ });
2046
+ }
1992
2047
  }
1993
2048
  } else {
1994
2049
  const sessions = core.sessionManager.listSessions("telegram");
@@ -2033,6 +2088,81 @@ Or just chat in the \u{1F916} Assistant topic for help!`,
2033
2088
  { parse_mode: "HTML" }
2034
2089
  );
2035
2090
  }
2091
+ function buildDangerousModeKeyboard(sessionId, enabled) {
2092
+ return new InlineKeyboard().text(
2093
+ enabled ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode",
2094
+ `d:${sessionId}`
2095
+ );
2096
+ }
2097
+ function setupDangerousModeCallbacks(bot, core) {
2098
+ bot.callbackQuery(/^d:/, async (ctx) => {
2099
+ const sessionId = ctx.callbackQuery.data.slice(2);
2100
+ const session = core.sessionManager.getSession(sessionId);
2101
+ if (!session) {
2102
+ try {
2103
+ await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or already ended." });
2104
+ } catch {
2105
+ }
2106
+ return;
2107
+ }
2108
+ session.dangerousMode = !session.dangerousMode;
2109
+ log6.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
2110
+ const toastText = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
2111
+ try {
2112
+ await ctx.answerCallbackQuery({ text: toastText });
2113
+ } catch {
2114
+ }
2115
+ try {
2116
+ await ctx.editMessageReplyMarkup({
2117
+ reply_markup: buildDangerousModeKeyboard(sessionId, session.dangerousMode)
2118
+ });
2119
+ } catch {
2120
+ }
2121
+ });
2122
+ }
2123
+ async function handleEnableDangerous(ctx, core) {
2124
+ const threadId = ctx.message?.message_thread_id;
2125
+ if (!threadId) {
2126
+ await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
2127
+ return;
2128
+ }
2129
+ const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
2130
+ if (!session) {
2131
+ await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
2132
+ return;
2133
+ }
2134
+ if (session.dangerousMode) {
2135
+ await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
2136
+ return;
2137
+ }
2138
+ session.dangerousMode = true;
2139
+ await ctx.reply(
2140
+ `\u26A0\uFE0F <b>Dangerous mode enabled</b>
2141
+
2142
+ All permission requests will be auto-approved. Claude can run arbitrary commands without asking.
2143
+
2144
+ Use /disable_dangerous to restore normal behaviour.`,
2145
+ { parse_mode: "HTML" }
2146
+ );
2147
+ }
2148
+ async function handleDisableDangerous(ctx, core) {
2149
+ const threadId = ctx.message?.message_thread_id;
2150
+ if (!threadId) {
2151
+ await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
2152
+ return;
2153
+ }
2154
+ const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
2155
+ if (!session) {
2156
+ await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
2157
+ return;
2158
+ }
2159
+ if (!session.dangerousMode) {
2160
+ await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
2161
+ return;
2162
+ }
2163
+ session.dangerousMode = false;
2164
+ await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
2165
+ }
2036
2166
  function botFromCtx(ctx) {
2037
2167
  return { api: ctx.api };
2038
2168
  }
@@ -2114,7 +2244,9 @@ var STATIC_COMMANDS = [
2114
2244
  { command: "status", description: "Show status" },
2115
2245
  { command: "agents", description: "List available agents" },
2116
2246
  { command: "help", description: "Help" },
2117
- { command: "menu", description: "Show menu" }
2247
+ { command: "menu", description: "Show menu" },
2248
+ { command: "enable_dangerous", description: "Auto-approve all permission requests (session only)" },
2249
+ { command: "disable_dangerous", description: "Restore normal permission prompts (session only)" }
2118
2250
  ];
2119
2251
 
2120
2252
  // src/adapters/telegram/permissions.ts
@@ -2155,7 +2287,7 @@ ${escapeHtml(request.description)}`,
2155
2287
  }
2156
2288
  );
2157
2289
  const deepLink = buildDeepLink(this.chatId, msg.message_id);
2158
- await this.sendNotification({
2290
+ void this.sendNotification({
2159
2291
  sessionId: session.id,
2160
2292
  sessionName: session.name,
2161
2293
  type: "permission",
@@ -2199,8 +2331,10 @@ ${escapeHtml(request.description)}`,
2199
2331
  };
2200
2332
 
2201
2333
  // src/adapters/telegram/assistant.ts
2334
+ var log8 = createChildLogger({ module: "telegram-assistant" });
2202
2335
  async function spawnAssistant(core, adapter, assistantTopicId) {
2203
2336
  const config = core.configManager.get();
2337
+ log8.info({ agent: config.defaultAgent }, "Creating assistant session...");
2204
2338
  const session = await core.sessionManager.createSession(
2205
2339
  "telegram",
2206
2340
  config.defaultAgent,
@@ -2208,10 +2342,16 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
2208
2342
  core.agentManager
2209
2343
  );
2210
2344
  session.threadId = String(assistantTopicId);
2211
- const systemPrompt = buildAssistantSystemPrompt(config);
2212
- await session.enqueuePrompt(systemPrompt);
2345
+ session.name = "Assistant";
2346
+ log8.info({ sessionId: session.id }, "Assistant agent spawned");
2213
2347
  core.wireSessionEvents(session, adapter);
2214
- return session;
2348
+ const systemPrompt = buildAssistantSystemPrompt(config);
2349
+ const ready = session.enqueuePrompt(systemPrompt).then(() => {
2350
+ log8.info({ sessionId: session.id }, "Assistant system prompt completed");
2351
+ }).catch((err) => {
2352
+ log8.warn({ err }, "Assistant system prompt failed");
2353
+ });
2354
+ return { session, ready };
2215
2355
  }
2216
2356
  function buildAssistantSystemPrompt(config) {
2217
2357
  const agentNames = Object.keys(config.agents).join(", ");
@@ -2246,36 +2386,342 @@ function redirectToAssistant(chatId, assistantTopicId) {
2246
2386
  return `\u{1F4AC} Please use the <a href="${link}">\u{1F916} Assistant</a> topic to chat with OpenACP.`;
2247
2387
  }
2248
2388
 
2389
+ // src/adapters/telegram/activity.ts
2390
+ var log9 = createChildLogger({ module: "telegram:activity" });
2391
+ var THINKING_REFRESH_MS = 15e3;
2392
+ var THINKING_MAX_MS = 3 * 60 * 1e3;
2393
+ var ThinkingIndicator = class {
2394
+ constructor(api, chatId, threadId, sendQueue) {
2395
+ this.api = api;
2396
+ this.chatId = chatId;
2397
+ this.threadId = threadId;
2398
+ this.sendQueue = sendQueue;
2399
+ }
2400
+ msgId;
2401
+ sending = false;
2402
+ dismissed = false;
2403
+ refreshTimer;
2404
+ showTime = 0;
2405
+ async show() {
2406
+ if (this.msgId || this.sending || this.dismissed) return;
2407
+ this.sending = true;
2408
+ this.showTime = Date.now();
2409
+ try {
2410
+ const result = await this.sendQueue.enqueue(
2411
+ () => this.api.sendMessage(this.chatId, "\u{1F4AD} <i>Thinking...</i>", {
2412
+ message_thread_id: this.threadId,
2413
+ parse_mode: "HTML",
2414
+ disable_notification: true
2415
+ })
2416
+ );
2417
+ if (result && !this.dismissed) {
2418
+ this.msgId = result.message_id;
2419
+ this.startRefreshTimer();
2420
+ }
2421
+ } catch (err) {
2422
+ log9.warn({ err }, "ThinkingIndicator.show() failed");
2423
+ } finally {
2424
+ this.sending = false;
2425
+ }
2426
+ }
2427
+ /** Clear state — stops refresh timer, no Telegram API call */
2428
+ dismiss() {
2429
+ this.dismissed = true;
2430
+ this.msgId = void 0;
2431
+ this.stopRefreshTimer();
2432
+ }
2433
+ /** Reset for a new prompt cycle */
2434
+ reset() {
2435
+ this.dismissed = false;
2436
+ }
2437
+ startRefreshTimer() {
2438
+ this.stopRefreshTimer();
2439
+ this.refreshTimer = setInterval(() => {
2440
+ if (this.dismissed || !this.msgId || Date.now() - this.showTime >= THINKING_MAX_MS) {
2441
+ this.stopRefreshTimer();
2442
+ return;
2443
+ }
2444
+ const elapsed = Math.round((Date.now() - this.showTime) / 1e3);
2445
+ this.sendQueue.enqueue(() => {
2446
+ if (this.dismissed) return Promise.resolve(void 0);
2447
+ return this.api.sendMessage(this.chatId, `\u{1F4AD} <i>Still thinking... (${elapsed}s)</i>`, {
2448
+ message_thread_id: this.threadId,
2449
+ parse_mode: "HTML",
2450
+ disable_notification: true
2451
+ });
2452
+ }).then((result) => {
2453
+ if (result && !this.dismissed) {
2454
+ this.msgId = result.message_id;
2455
+ }
2456
+ }).catch(() => {
2457
+ });
2458
+ }, THINKING_REFRESH_MS);
2459
+ }
2460
+ stopRefreshTimer() {
2461
+ if (this.refreshTimer) {
2462
+ clearInterval(this.refreshTimer);
2463
+ this.refreshTimer = void 0;
2464
+ }
2465
+ }
2466
+ };
2467
+ var UsageMessage = class {
2468
+ constructor(api, chatId, threadId, sendQueue) {
2469
+ this.api = api;
2470
+ this.chatId = chatId;
2471
+ this.threadId = threadId;
2472
+ this.sendQueue = sendQueue;
2473
+ }
2474
+ msgId;
2475
+ async send(usage) {
2476
+ const text = formatUsage(usage);
2477
+ try {
2478
+ if (this.msgId) {
2479
+ await this.sendQueue.enqueue(
2480
+ () => this.api.editMessageText(this.chatId, this.msgId, text, {
2481
+ parse_mode: "HTML"
2482
+ })
2483
+ );
2484
+ } else {
2485
+ const result = await this.sendQueue.enqueue(
2486
+ () => this.api.sendMessage(this.chatId, text, {
2487
+ message_thread_id: this.threadId,
2488
+ parse_mode: "HTML",
2489
+ disable_notification: true
2490
+ })
2491
+ );
2492
+ if (result) this.msgId = result.message_id;
2493
+ }
2494
+ } catch (err) {
2495
+ log9.warn({ err }, "UsageMessage.send() failed");
2496
+ }
2497
+ }
2498
+ async delete() {
2499
+ if (!this.msgId) return;
2500
+ const id = this.msgId;
2501
+ this.msgId = void 0;
2502
+ try {
2503
+ await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
2504
+ } catch (err) {
2505
+ log9.warn({ err }, "UsageMessage.delete() failed");
2506
+ }
2507
+ }
2508
+ };
2509
+ function formatPlanCard(entries) {
2510
+ const statusIcon = {
2511
+ completed: "\u2705",
2512
+ in_progress: "\u{1F504}",
2513
+ pending: "\u2B1C",
2514
+ failed: "\u274C"
2515
+ };
2516
+ const total = entries.length;
2517
+ const done = entries.filter((e) => e.status === "completed").length;
2518
+ const ratio = total > 0 ? done / total : 0;
2519
+ const filled = Math.round(ratio * 10);
2520
+ const bar = "\u2593".repeat(filled) + "\u2591".repeat(10 - filled);
2521
+ const pct = Math.round(ratio * 100);
2522
+ const header = `\u{1F4CB} <b>Plan</b>
2523
+ ${bar} ${pct}% \xB7 ${done}/${total}`;
2524
+ const lines = entries.map((e, i) => {
2525
+ const icon = statusIcon[e.status] ?? "\u2B1C";
2526
+ return `${icon} ${i + 1}. ${e.content}`;
2527
+ });
2528
+ return [header, ...lines].join("\n");
2529
+ }
2530
+ var PlanCard = class {
2531
+ constructor(api, chatId, threadId, sendQueue) {
2532
+ this.api = api;
2533
+ this.chatId = chatId;
2534
+ this.threadId = threadId;
2535
+ this.sendQueue = sendQueue;
2536
+ }
2537
+ msgId;
2538
+ flushPromise = Promise.resolve();
2539
+ latestEntries;
2540
+ flushTimer;
2541
+ update(entries) {
2542
+ this.latestEntries = entries;
2543
+ if (this.flushTimer) clearTimeout(this.flushTimer);
2544
+ this.flushTimer = setTimeout(() => {
2545
+ this.flushTimer = void 0;
2546
+ this.flushPromise = this.flushPromise.then(() => this._flush()).catch(() => {
2547
+ });
2548
+ }, 3500);
2549
+ }
2550
+ async finalize() {
2551
+ if (!this.latestEntries) return;
2552
+ if (this.flushTimer) {
2553
+ clearTimeout(this.flushTimer);
2554
+ this.flushTimer = void 0;
2555
+ }
2556
+ await this.flushPromise;
2557
+ this.flushPromise = this.flushPromise.then(() => this._flush()).catch(() => {
2558
+ });
2559
+ await this.flushPromise;
2560
+ }
2561
+ destroy() {
2562
+ if (this.flushTimer) {
2563
+ clearTimeout(this.flushTimer);
2564
+ this.flushTimer = void 0;
2565
+ }
2566
+ }
2567
+ async _flush() {
2568
+ if (!this.latestEntries) return;
2569
+ const text = formatPlanCard(this.latestEntries);
2570
+ try {
2571
+ if (this.msgId) {
2572
+ await this.sendQueue.enqueue(
2573
+ () => this.api.editMessageText(this.chatId, this.msgId, text, {
2574
+ parse_mode: "HTML"
2575
+ })
2576
+ );
2577
+ } else {
2578
+ const result = await this.sendQueue.enqueue(
2579
+ () => this.api.sendMessage(this.chatId, text, {
2580
+ message_thread_id: this.threadId,
2581
+ parse_mode: "HTML",
2582
+ disable_notification: true
2583
+ })
2584
+ );
2585
+ if (result) this.msgId = result.message_id;
2586
+ }
2587
+ } catch (err) {
2588
+ log9.warn({ err }, "PlanCard flush failed");
2589
+ }
2590
+ }
2591
+ };
2592
+ var ActivityTracker = class {
2593
+ constructor(api, chatId, threadId, sendQueue) {
2594
+ this.api = api;
2595
+ this.chatId = chatId;
2596
+ this.threadId = threadId;
2597
+ this.sendQueue = sendQueue;
2598
+ this.thinking = new ThinkingIndicator(api, chatId, threadId, sendQueue);
2599
+ this.planCard = new PlanCard(api, chatId, threadId, sendQueue);
2600
+ this.usage = new UsageMessage(api, chatId, threadId, sendQueue);
2601
+ }
2602
+ isFirstEvent = true;
2603
+ hasPlanCard = false;
2604
+ thinking;
2605
+ planCard;
2606
+ usage;
2607
+ async onNewPrompt() {
2608
+ this.isFirstEvent = true;
2609
+ this.hasPlanCard = false;
2610
+ this.thinking.dismiss();
2611
+ this.thinking.reset();
2612
+ }
2613
+ async onThought() {
2614
+ await this._firstEventGuard();
2615
+ await this.thinking.show();
2616
+ }
2617
+ async onPlan(entries) {
2618
+ await this._firstEventGuard();
2619
+ this.thinking.dismiss();
2620
+ this.hasPlanCard = true;
2621
+ this.planCard.update(entries);
2622
+ }
2623
+ async onToolCall() {
2624
+ await this._firstEventGuard();
2625
+ this.thinking.dismiss();
2626
+ this.thinking.reset();
2627
+ }
2628
+ async onTextStart() {
2629
+ await this._firstEventGuard();
2630
+ this.thinking.dismiss();
2631
+ }
2632
+ async sendUsage(data) {
2633
+ await this.usage.send(data);
2634
+ }
2635
+ async onComplete() {
2636
+ if (this.hasPlanCard) {
2637
+ await this.planCard.finalize();
2638
+ } else {
2639
+ try {
2640
+ await this.sendQueue.enqueue(
2641
+ () => this.api.sendMessage(this.chatId, "\u2705 <b>Done</b>", {
2642
+ message_thread_id: this.threadId,
2643
+ parse_mode: "HTML",
2644
+ disable_notification: true
2645
+ })
2646
+ );
2647
+ } catch (err) {
2648
+ log9.warn({ err }, "ActivityTracker.onComplete() Done send failed");
2649
+ }
2650
+ }
2651
+ }
2652
+ destroy() {
2653
+ this.planCard.destroy();
2654
+ }
2655
+ async _firstEventGuard() {
2656
+ if (!this.isFirstEvent) return;
2657
+ this.isFirstEvent = false;
2658
+ await this.usage.delete();
2659
+ }
2660
+ };
2661
+
2249
2662
  // src/adapters/telegram/send-queue.ts
2250
2663
  var TelegramSendQueue = class {
2251
- queue = Promise.resolve();
2664
+ items = [];
2665
+ processing = false;
2252
2666
  lastExec = 0;
2253
2667
  minInterval;
2254
- constructor(minInterval = 100) {
2668
+ constructor(minInterval = 3e3) {
2255
2669
  this.minInterval = minInterval;
2256
2670
  }
2257
- enqueue(fn) {
2258
- let resolve;
2259
- let reject;
2260
- const resultPromise = new Promise((res, rej) => {
2261
- resolve = res;
2262
- reject = rej;
2263
- });
2264
- this.queue = this.queue.then(async () => {
2265
- const elapsed = Date.now() - this.lastExec;
2266
- if (elapsed < this.minInterval) {
2267
- await new Promise((r) => setTimeout(r, this.minInterval - elapsed));
2268
- }
2269
- try {
2270
- const result = await fn();
2271
- resolve(result);
2272
- } catch (err) {
2273
- reject(err);
2274
- } finally {
2275
- this.lastExec = Date.now();
2671
+ enqueue(fn, opts) {
2672
+ const type = opts?.type ?? "other";
2673
+ const key = opts?.key;
2674
+ return new Promise((resolve, reject) => {
2675
+ if (type === "text" && key) {
2676
+ const idx = this.items.findIndex(
2677
+ (item) => item.type === "text" && item.key === key
2678
+ );
2679
+ if (idx !== -1) {
2680
+ this.items[idx].resolve(void 0);
2681
+ this.items[idx] = { fn, type, key, resolve, reject };
2682
+ this.scheduleProcess();
2683
+ return;
2684
+ }
2276
2685
  }
2686
+ this.items.push({ fn, type, key, resolve, reject });
2687
+ this.scheduleProcess();
2277
2688
  });
2278
- return resultPromise;
2689
+ }
2690
+ onRateLimited() {
2691
+ const remaining = [];
2692
+ for (const item of this.items) {
2693
+ if (item.type === "text") {
2694
+ item.resolve(void 0);
2695
+ } else {
2696
+ remaining.push(item);
2697
+ }
2698
+ }
2699
+ this.items = remaining;
2700
+ }
2701
+ scheduleProcess() {
2702
+ if (this.processing) return;
2703
+ if (this.items.length === 0) return;
2704
+ const elapsed = Date.now() - this.lastExec;
2705
+ const delay = Math.max(0, this.minInterval - elapsed);
2706
+ this.processing = true;
2707
+ setTimeout(() => void this.processNext(), delay);
2708
+ }
2709
+ async processNext() {
2710
+ const item = this.items.shift();
2711
+ if (!item) {
2712
+ this.processing = false;
2713
+ return;
2714
+ }
2715
+ try {
2716
+ const result = await item.fn();
2717
+ item.resolve(result);
2718
+ } catch (err) {
2719
+ item.reject(err);
2720
+ } finally {
2721
+ this.lastExec = Date.now();
2722
+ this.processing = false;
2723
+ this.scheduleProcess();
2724
+ }
2279
2725
  }
2280
2726
  };
2281
2727
 
@@ -2284,8 +2730,8 @@ import { nanoid as nanoid4 } from "nanoid";
2284
2730
  import { InlineKeyboard as InlineKeyboard3 } from "grammy";
2285
2731
  var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
2286
2732
  var CMD_CANCEL_RE = /\/cancel\b/;
2287
- var KW_NEW_RE = /(?:tao|tạo|create|new)\s+session/i;
2288
- var KW_CANCEL_RE = /(?:huy|huỷ|cancel|dung|dừng)\s+session/i;
2733
+ var KW_NEW_RE = /(?:create|new)\s+session/i;
2734
+ var KW_CANCEL_RE = /(?:cancel|stop)\s+session/i;
2289
2735
  function detectAction(text) {
2290
2736
  if (!text) return null;
2291
2737
  const cancelCmd = CMD_CANCEL_RE.exec(text);
@@ -2330,11 +2776,11 @@ function removeAction(id) {
2330
2776
  function buildActionKeyboard(actionId, action) {
2331
2777
  const keyboard = new InlineKeyboard3();
2332
2778
  if (action.action === "new_session") {
2333
- keyboard.text("\u2705 T\u1EA1o session", `a:${actionId}`);
2334
- keyboard.text("\u274C Hu\u1EF7", `a:dismiss:${actionId}`);
2779
+ keyboard.text("\u2705 Create session", `a:${actionId}`);
2780
+ keyboard.text("\u274C Cancel", `a:dismiss:${actionId}`);
2335
2781
  } else {
2336
- keyboard.text("\u26D4 Hu\u1EF7 session", `a:${actionId}`);
2337
- keyboard.text("\u274C Kh\xF4ng", `a:dismiss:${actionId}`);
2782
+ keyboard.text("\u26D4 Cancel session", `a:${actionId}`);
2783
+ keyboard.text("\u274C No", `a:dismiss:${actionId}`);
2338
2784
  }
2339
2785
  return keyboard;
2340
2786
  }
@@ -2348,19 +2794,19 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
2348
2794
  });
2349
2795
  } catch {
2350
2796
  }
2351
- await ctx.answerCallbackQuery({ text: "\u0110\xE3 hu\u1EF7" });
2797
+ await ctx.answerCallbackQuery({ text: "Dismissed" });
2352
2798
  });
2353
2799
  bot.callbackQuery(/^a:(?!dismiss)/, async (ctx) => {
2354
2800
  const actionId = ctx.callbackQuery.data.replace("a:", "");
2355
2801
  const action = getAction(actionId);
2356
2802
  if (!action) {
2357
- await ctx.answerCallbackQuery({ text: "Action \u0111\xE3 h\u1EBFt h\u1EA1n" });
2803
+ await ctx.answerCallbackQuery({ text: "Action expired" });
2358
2804
  return;
2359
2805
  }
2360
2806
  removeAction(actionId);
2361
2807
  try {
2362
2808
  if (action.action === "new_session") {
2363
- await ctx.answerCallbackQuery({ text: "\u23F3 \u0110ang t\u1EA1o session..." });
2809
+ await ctx.answerCallbackQuery({ text: "\u23F3 Creating session..." });
2364
2810
  const { threadId } = await executeNewSession(
2365
2811
  bot,
2366
2812
  core,
@@ -2386,13 +2832,13 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
2386
2832
  const assistantId = getAssistantSessionId();
2387
2833
  const cancelled = await executeCancelSession(core, assistantId);
2388
2834
  if (cancelled) {
2389
- await ctx.answerCallbackQuery({ text: "\u26D4 Session \u0111\xE3 hu\u1EF7" });
2835
+ await ctx.answerCallbackQuery({ text: "\u26D4 Session cancelled" });
2390
2836
  const originalText = ctx.callbackQuery.message?.text ?? "";
2391
2837
  try {
2392
2838
  await ctx.editMessageText(
2393
2839
  originalText + `
2394
2840
 
2395
- \u26D4 Session "${cancelled.name ?? cancelled.id}" \u0111\xE3 hu\u1EF7`,
2841
+ \u26D4 Session "${cancelled.name ?? cancelled.id}" cancelled`,
2396
2842
  { parse_mode: "HTML" }
2397
2843
  );
2398
2844
  } catch {
@@ -2402,7 +2848,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
2402
2848
  }
2403
2849
  } else {
2404
2850
  await ctx.answerCallbackQuery({
2405
- text: "Kh\xF4ng c\xF3 session n\xE0o \u0111ang ch\u1EA1y"
2851
+ text: "No active session"
2406
2852
  });
2407
2853
  try {
2408
2854
  await ctx.editMessageReplyMarkup({
@@ -2413,7 +2859,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
2413
2859
  }
2414
2860
  }
2415
2861
  } catch {
2416
- await ctx.answerCallbackQuery({ text: "\u274C L\u1ED7i, th\u1EED l\u1EA1i sau" });
2862
+ await ctx.answerCallbackQuery({ text: "\u274C Error, try again later" });
2417
2863
  try {
2418
2864
  await ctx.editMessageReplyMarkup({
2419
2865
  reply_markup: { inline_keyboard: [] }
@@ -2425,7 +2871,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
2425
2871
  }
2426
2872
 
2427
2873
  // src/adapters/telegram/adapter.ts
2428
- var log8 = createChildLogger({ module: "telegram" });
2874
+ var log10 = createChildLogger({ module: "telegram" });
2429
2875
  function patchedFetch(input, init) {
2430
2876
  if (init?.signal && !(init.signal instanceof AbortSignal)) {
2431
2877
  const nativeController = new AbortController();
@@ -2448,11 +2894,26 @@ var TelegramAdapter = class extends ChannelAdapter {
2448
2894
  // sessionId → (toolCallId → state)
2449
2895
  permissionHandler;
2450
2896
  assistantSession = null;
2897
+ assistantInitializing = false;
2451
2898
  notificationTopicId;
2452
2899
  assistantTopicId;
2453
2900
  skillMessages = /* @__PURE__ */ new Map();
2454
2901
  // sessionId → pinned messageId
2455
- sendQueue = new TelegramSendQueue();
2902
+ sendQueue = new TelegramSendQueue(3e3);
2903
+ sessionTrackers = /* @__PURE__ */ new Map();
2904
+ getOrCreateTracker(sessionId, threadId) {
2905
+ let tracker = this.sessionTrackers.get(sessionId);
2906
+ if (!tracker) {
2907
+ tracker = new ActivityTracker(
2908
+ this.bot.api,
2909
+ this.telegramConfig.chatId,
2910
+ threadId,
2911
+ this.sendQueue
2912
+ );
2913
+ this.sessionTrackers.set(sessionId, tracker);
2914
+ }
2915
+ return tracker;
2916
+ }
2456
2917
  constructor(core, config) {
2457
2918
  super(core, config);
2458
2919
  this.telegramConfig = config;
@@ -2461,7 +2922,7 @@ var TelegramAdapter = class extends ChannelAdapter {
2461
2922
  this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
2462
2923
  this.bot.catch((err) => {
2463
2924
  const rootCause = err.error instanceof Error ? err.error : err;
2464
- log8.error({ err: rootCause }, "Telegram bot error");
2925
+ log10.error({ err: rootCause }, "Telegram bot error");
2465
2926
  });
2466
2927
  this.bot.api.config.use(async (prev, method, payload, signal) => {
2467
2928
  const maxRetries = 3;
@@ -2471,7 +2932,11 @@ var TelegramAdapter = class extends ChannelAdapter {
2471
2932
  return result;
2472
2933
  }
2473
2934
  const retryAfter = (result.parameters?.retry_after ?? 5) + 1;
2474
- log8.warn(
2935
+ const rateLimitedMethods = ["sendMessage", "editMessageText", "editMessageReplyMarkup"];
2936
+ if (rateLimitedMethods.includes(method)) {
2937
+ this.sendQueue.onRateLimited();
2938
+ }
2939
+ log10.warn(
2475
2940
  { method, retryAfter, attempt: attempt + 1 },
2476
2941
  "Rate limited by Telegram, retrying"
2477
2942
  );
@@ -2516,6 +2981,7 @@ var TelegramAdapter = class extends ChannelAdapter {
2516
2981
  (notification) => this.sendNotification(notification)
2517
2982
  );
2518
2983
  setupSkillCallbacks(this.bot, this.core);
2984
+ setupDangerousModeCallbacks(this.bot, this.core);
2519
2985
  setupActionCallbacks(
2520
2986
  this.bot,
2521
2987
  this.core,
@@ -2540,26 +3006,15 @@ var TelegramAdapter = class extends ChannelAdapter {
2540
3006
  this.setupRoutes();
2541
3007
  this.bot.start({
2542
3008
  allowed_updates: ["message", "callback_query"],
2543
- onStart: () => log8.info(
3009
+ onStart: () => log10.info(
2544
3010
  { chatId: this.telegramConfig.chatId },
2545
3011
  "Telegram bot started"
2546
3012
  )
2547
3013
  });
2548
- try {
2549
- this.assistantSession = await spawnAssistant(
2550
- this.core,
2551
- this,
2552
- this.assistantTopicId
2553
- );
2554
- } catch (err) {
2555
- log8.error({ err }, "Failed to spawn assistant");
2556
- }
2557
3014
  try {
2558
3015
  const config = this.core.configManager.get();
2559
3016
  const agents = this.core.agentManager.getAvailableAgents();
2560
- const agentList = agents.map(
2561
- (a) => `${escapeHtml(a.name)}${a.name === config.defaultAgent ? " (default)" : ""}`
2562
- ).join(", ");
3017
+ const agentList = agents.map((a) => `${escapeHtml(a.name)}${a.name === config.defaultAgent ? " (default)" : ""}`).join(", ");
2563
3018
  const workspace = escapeHtml(config.workspace.baseDir);
2564
3019
  const welcomeText = `\u{1F44B} <b>OpenACP Assistant</b> is online.
2565
3020
 
@@ -2573,7 +3028,32 @@ Workspace: <code>${workspace}</code>
2573
3028
  reply_markup: buildMenuKeyboard()
2574
3029
  });
2575
3030
  } catch (err) {
2576
- log8.warn({ err }, "Failed to send welcome message");
3031
+ log10.warn({ err }, "Failed to send welcome message");
3032
+ }
3033
+ try {
3034
+ log10.info("Spawning assistant session...");
3035
+ const { session, ready } = await spawnAssistant(
3036
+ this.core,
3037
+ this,
3038
+ this.assistantTopicId
3039
+ );
3040
+ this.assistantSession = session;
3041
+ this.assistantInitializing = true;
3042
+ log10.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
3043
+ ready.then(() => {
3044
+ this.assistantInitializing = false;
3045
+ log10.info({ sessionId: session.id }, "Assistant ready for user messages");
3046
+ });
3047
+ } catch (err) {
3048
+ log10.error({ err }, "Failed to spawn assistant");
3049
+ this.bot.api.sendMessage(
3050
+ this.telegramConfig.chatId,
3051
+ `\u26A0\uFE0F <b>Failed to start assistant session.</b>
3052
+
3053
+ <code>${err instanceof Error ? err.message : String(err)}</code>`,
3054
+ { message_thread_id: this.assistantTopicId, parse_mode: "HTML" }
3055
+ ).catch(() => {
3056
+ });
2577
3057
  }
2578
3058
  }
2579
3059
  async stop() {
@@ -2581,7 +3061,7 @@ Workspace: <code>${workspace}</code>
2581
3061
  await this.assistantSession.destroy();
2582
3062
  }
2583
3063
  await this.bot.stop();
2584
- log8.info("Telegram bot stopped");
3064
+ log10.info("Telegram bot stopped");
2585
3065
  }
2586
3066
  setupRoutes() {
2587
3067
  this.bot.on("message:text", async (ctx) => {
@@ -2596,13 +3076,24 @@ Workspace: <code>${workspace}</code>
2596
3076
  }
2597
3077
  if (threadId === this.notificationTopicId) return;
2598
3078
  if (threadId === this.assistantTopicId) {
3079
+ if (!this.assistantSession) {
3080
+ await ctx.reply("\u26A0\uFE0F Assistant is not available yet. Please try again shortly.", { parse_mode: "HTML" });
3081
+ return;
3082
+ }
3083
+ await this.finalizeDraft(this.assistantSession.id);
2599
3084
  ctx.replyWithChatAction("typing").catch(() => {
2600
3085
  });
2601
3086
  handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
2602
- (err) => log8.error({ err }, "Assistant error")
3087
+ (err) => log10.error({ err }, "Assistant error")
2603
3088
  );
2604
3089
  return;
2605
3090
  }
3091
+ const sessionId = this.core.sessionManager.getSessionByThread("telegram", String(threadId))?.id;
3092
+ if (sessionId) await this.finalizeDraft(sessionId);
3093
+ if (sessionId) {
3094
+ const tracker = this.sessionTrackers.get(sessionId);
3095
+ if (tracker) await tracker.onNewPrompt();
3096
+ }
2606
3097
  ctx.replyWithChatAction("typing").catch(() => {
2607
3098
  });
2608
3099
  this.core.handleMessage({
@@ -2610,11 +3101,12 @@ Workspace: <code>${workspace}</code>
2610
3101
  threadId: String(threadId),
2611
3102
  userId: String(ctx.from.id),
2612
3103
  text: ctx.message.text
2613
- }).catch((err) => log8.error({ err }, "handleMessage error"));
3104
+ }).catch((err) => log10.error({ err }, "handleMessage error"));
2614
3105
  });
2615
3106
  }
2616
3107
  // --- ChannelAdapter implementations ---
2617
3108
  async sendMessage(sessionId, content) {
3109
+ if (this.assistantInitializing && sessionId === this.assistantSession?.id) return;
2618
3110
  const session = this.core.sessionManager.getSession(
2619
3111
  sessionId
2620
3112
  );
@@ -2622,17 +3114,21 @@ Workspace: <code>${workspace}</code>
2622
3114
  const threadId = Number(session.threadId);
2623
3115
  switch (content.type) {
2624
3116
  case "thought": {
3117
+ const tracker = this.getOrCreateTracker(sessionId, threadId);
3118
+ await tracker.onThought();
2625
3119
  break;
2626
3120
  }
2627
3121
  case "text": {
2628
3122
  let draft = this.sessionDrafts.get(sessionId);
2629
3123
  if (!draft) {
3124
+ const tracker = this.getOrCreateTracker(sessionId, threadId);
3125
+ await tracker.onTextStart();
2630
3126
  draft = new MessageDraft(
2631
3127
  this.bot,
2632
3128
  this.telegramConfig.chatId,
2633
3129
  threadId,
2634
- this.telegramConfig.streamThrottleMs,
2635
- this.sendQueue
3130
+ this.sendQueue,
3131
+ sessionId
2636
3132
  );
2637
3133
  this.sessionDrafts.set(sessionId, draft);
2638
3134
  }
@@ -2644,8 +3140,25 @@ Workspace: <code>${workspace}</code>
2644
3140
  break;
2645
3141
  }
2646
3142
  case "tool_call": {
3143
+ const tracker = this.getOrCreateTracker(sessionId, threadId);
3144
+ await tracker.onToolCall();
2647
3145
  await this.finalizeDraft(sessionId);
2648
3146
  const meta = content.metadata;
3147
+ if (!this.toolCallMessages.has(sessionId)) {
3148
+ this.toolCallMessages.set(sessionId, /* @__PURE__ */ new Map());
3149
+ }
3150
+ let resolveReady;
3151
+ const ready = new Promise((r) => {
3152
+ resolveReady = r;
3153
+ });
3154
+ this.toolCallMessages.get(sessionId).set(meta.id, {
3155
+ msgId: 0,
3156
+ name: meta.name,
3157
+ kind: meta.kind,
3158
+ viewerLinks: meta.viewerLinks,
3159
+ viewerFilePath: content.metadata?.viewerFilePath,
3160
+ ready
3161
+ });
2649
3162
  const msg = await this.sendQueue.enqueue(
2650
3163
  () => this.bot.api.sendMessage(
2651
3164
  this.telegramConfig.chatId,
@@ -2657,79 +3170,92 @@ Workspace: <code>${workspace}</code>
2657
3170
  }
2658
3171
  )
2659
3172
  );
2660
- if (!this.toolCallMessages.has(sessionId)) {
2661
- this.toolCallMessages.set(sessionId, /* @__PURE__ */ new Map());
2662
- }
2663
- this.toolCallMessages.get(sessionId).set(meta.id, {
2664
- msgId: msg.message_id,
2665
- name: meta.name,
2666
- kind: meta.kind,
2667
- viewerLinks: meta.viewerLinks,
2668
- viewerFilePath: content.metadata?.viewerFilePath
2669
- });
3173
+ const toolEntry = this.toolCallMessages.get(sessionId).get(meta.id);
3174
+ toolEntry.msgId = msg.message_id;
3175
+ resolveReady();
2670
3176
  break;
2671
3177
  }
2672
3178
  case "tool_update": {
2673
3179
  const meta = content.metadata;
2674
3180
  const toolState = this.toolCallMessages.get(sessionId)?.get(meta.id);
2675
3181
  if (toolState) {
2676
- const viewerLinks = meta.viewerLinks || toolState.viewerLinks;
2677
- const viewerFilePath = content.metadata?.viewerFilePath || toolState.viewerFilePath;
2678
- if (meta.viewerLinks) toolState.viewerLinks = meta.viewerLinks;
3182
+ if (meta.viewerLinks) {
3183
+ toolState.viewerLinks = meta.viewerLinks;
3184
+ log10.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
3185
+ }
3186
+ const viewerFilePath = content.metadata?.viewerFilePath;
2679
3187
  if (viewerFilePath) toolState.viewerFilePath = viewerFilePath;
3188
+ if (meta.name) toolState.name = meta.name;
3189
+ if (meta.kind) toolState.kind = meta.kind;
3190
+ const isTerminal = meta.status === "completed" || meta.status === "failed";
3191
+ if (!isTerminal && !meta.viewerLinks) break;
3192
+ await toolState.ready;
3193
+ log10.debug(
3194
+ { toolId: meta.id, status: meta.status, hasViewerLinks: !!toolState.viewerLinks, viewerLinks: toolState.viewerLinks, name: toolState.name, msgId: toolState.msgId },
3195
+ "Tool completed, preparing edit"
3196
+ );
2680
3197
  const merged = {
2681
3198
  ...meta,
2682
- name: meta.name || toolState.name,
2683
- kind: meta.kind || toolState.kind,
2684
- viewerLinks,
2685
- viewerFilePath
3199
+ name: toolState.name,
3200
+ kind: toolState.kind,
3201
+ viewerLinks: toolState.viewerLinks,
3202
+ viewerFilePath: toolState.viewerFilePath
2686
3203
  };
3204
+ const formattedText = formatToolUpdate(merged);
2687
3205
  try {
2688
3206
  await this.sendQueue.enqueue(
2689
3207
  () => this.bot.api.editMessageText(
2690
3208
  this.telegramConfig.chatId,
2691
3209
  toolState.msgId,
2692
- formatToolUpdate(merged),
3210
+ formattedText,
2693
3211
  { parse_mode: "HTML" }
2694
3212
  )
2695
3213
  );
2696
- } catch {
3214
+ } catch (err) {
3215
+ log10.warn(
3216
+ { err, msgId: toolState.msgId, textLen: formattedText.length, hasViewerLinks: !!merged.viewerLinks },
3217
+ "Tool update edit failed"
3218
+ );
2697
3219
  }
2698
3220
  }
2699
3221
  break;
2700
3222
  }
2701
3223
  case "plan": {
2702
- await this.finalizeDraft(sessionId);
2703
- await this.sendQueue.enqueue(
2704
- () => this.bot.api.sendMessage(
2705
- this.telegramConfig.chatId,
2706
- formatPlan(
2707
- content.metadata
2708
- ),
2709
- {
2710
- message_thread_id: threadId,
2711
- parse_mode: "HTML",
2712
- disable_notification: true
2713
- }
2714
- )
3224
+ const meta = content.metadata;
3225
+ const tracker = this.getOrCreateTracker(sessionId, threadId);
3226
+ await tracker.onPlan(
3227
+ meta.entries.map((e) => ({
3228
+ content: e.content,
3229
+ status: e.status,
3230
+ priority: e.priority ?? "medium"
3231
+ }))
2715
3232
  );
2716
3233
  break;
2717
3234
  }
2718
3235
  case "usage": {
3236
+ const meta = content.metadata;
2719
3237
  await this.finalizeDraft(sessionId);
2720
- await this.sendQueue.enqueue(
2721
- () => this.bot.api.sendMessage(
2722
- this.telegramConfig.chatId,
2723
- formatUsage(
2724
- content.metadata
2725
- ),
2726
- {
2727
- message_thread_id: threadId,
3238
+ const tracker = this.getOrCreateTracker(sessionId, threadId);
3239
+ await tracker.sendUsage(meta);
3240
+ if (this.notificationTopicId && sessionId !== this.assistantSession?.id) {
3241
+ const sess = this.core.sessionManager.getSession(sessionId);
3242
+ const sessionName = sess?.name || "Session";
3243
+ const chatIdStr = String(this.telegramConfig.chatId);
3244
+ const numericId = chatIdStr.startsWith("-100") ? chatIdStr.slice(4) : chatIdStr.replace("-", "");
3245
+ const deepLink = `https://t.me/c/${numericId}/${threadId}`;
3246
+ const text = `\u2705 <b>${escapeHtml(sessionName)}</b>
3247
+ Task completed.
3248
+
3249
+ <a href="${deepLink}">\u2192 Go to topic</a>`;
3250
+ this.sendQueue.enqueue(
3251
+ () => this.bot.api.sendMessage(this.telegramConfig.chatId, text, {
3252
+ message_thread_id: this.notificationTopicId,
2728
3253
  parse_mode: "HTML",
2729
- disable_notification: true
2730
- }
2731
- )
2732
- );
3254
+ disable_notification: false
3255
+ })
3256
+ ).catch(() => {
3257
+ });
3258
+ }
2733
3259
  break;
2734
3260
  }
2735
3261
  case "session_end": {
@@ -2737,21 +3263,33 @@ Workspace: <code>${workspace}</code>
2737
3263
  this.sessionDrafts.delete(sessionId);
2738
3264
  this.toolCallMessages.delete(sessionId);
2739
3265
  await this.cleanupSkillCommands(sessionId);
2740
- await this.sendQueue.enqueue(
2741
- () => this.bot.api.sendMessage(
2742
- this.telegramConfig.chatId,
2743
- `\u2705 <b>Done</b>`,
2744
- {
2745
- message_thread_id: threadId,
2746
- parse_mode: "HTML",
2747
- disable_notification: true
2748
- }
2749
- )
2750
- );
3266
+ const tracker = this.sessionTrackers.get(sessionId);
3267
+ if (tracker) {
3268
+ await tracker.onComplete();
3269
+ tracker.destroy();
3270
+ this.sessionTrackers.delete(sessionId);
3271
+ } else {
3272
+ await this.sendQueue.enqueue(
3273
+ () => this.bot.api.sendMessage(
3274
+ this.telegramConfig.chatId,
3275
+ `\u2705 <b>Done</b>`,
3276
+ {
3277
+ message_thread_id: threadId,
3278
+ parse_mode: "HTML",
3279
+ disable_notification: true
3280
+ }
3281
+ )
3282
+ );
3283
+ }
2751
3284
  break;
2752
3285
  }
2753
3286
  case "error": {
2754
3287
  await this.finalizeDraft(sessionId);
3288
+ const tracker = this.sessionTrackers.get(sessionId);
3289
+ if (tracker) {
3290
+ tracker.destroy();
3291
+ this.sessionTrackers.delete(sessionId);
3292
+ }
2755
3293
  await this.sendQueue.enqueue(
2756
3294
  () => this.bot.api.sendMessage(
2757
3295
  this.telegramConfig.chatId,
@@ -2768,17 +3306,27 @@ Workspace: <code>${workspace}</code>
2768
3306
  }
2769
3307
  }
2770
3308
  async sendPermissionRequest(sessionId, request) {
2771
- log8.info({ sessionId, requestId: request.id }, "Permission request sent");
3309
+ log10.info({ sessionId, requestId: request.id }, "Permission request sent");
2772
3310
  const session = this.core.sessionManager.getSession(
2773
3311
  sessionId
2774
3312
  );
2775
3313
  if (!session) return;
3314
+ if (session.dangerousMode) {
3315
+ const allowOption = request.options.find((o) => o.isAllow);
3316
+ if (allowOption && session.pendingPermission?.requestId === request.id) {
3317
+ log10.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
3318
+ session.pendingPermission.resolve(allowOption.id);
3319
+ session.pendingPermission = void 0;
3320
+ }
3321
+ return;
3322
+ }
2776
3323
  await this.sendQueue.enqueue(
2777
3324
  () => this.permissionHandler.sendPermissionRequest(session, request)
2778
3325
  );
2779
3326
  }
2780
3327
  async sendNotification(notification) {
2781
- log8.info(
3328
+ if (notification.sessionId === this.assistantSession?.id) return;
3329
+ log10.info(
2782
3330
  { sessionId: notification.sessionId, type: notification.type },
2783
3331
  "Notification sent"
2784
3332
  );
@@ -2789,13 +3337,21 @@ Workspace: <code>${workspace}</code>
2789
3337
  permission: "\u{1F510}",
2790
3338
  input_required: "\u{1F4AC}"
2791
3339
  };
2792
- let text = `${emoji[notification.type] || "\u2139\uFE0F"} <b>${escapeHtml(notification.sessionName || notification.sessionId)}</b>
3340
+ let text = `${emoji[notification.type] || "\u2139\uFE0F"} <b>${escapeHtml(notification.sessionName || "New session")}</b>
2793
3341
  `;
2794
3342
  text += escapeHtml(notification.summary);
2795
- if (notification.deepLink) {
3343
+ const deepLink = notification.deepLink ?? (() => {
3344
+ const session = this.core.sessionManager.getSession(notification.sessionId);
3345
+ const threadId = session?.threadId;
3346
+ if (!threadId) return void 0;
3347
+ const chatIdStr = String(this.telegramConfig.chatId);
3348
+ const numericId = chatIdStr.startsWith("-100") ? chatIdStr.slice(4) : chatIdStr.replace("-", "");
3349
+ return `https://t.me/c/${numericId}/${threadId}`;
3350
+ })();
3351
+ if (deepLink) {
2796
3352
  text += `
2797
3353
 
2798
- <a href="${notification.deepLink}">\u2192 Go to message</a>`;
3354
+ <a href="${deepLink}">\u2192 Go to topic</a>`;
2799
3355
  }
2800
3356
  await this.sendQueue.enqueue(
2801
3357
  () => this.bot.api.sendMessage(this.telegramConfig.chatId, text, {
@@ -2806,7 +3362,7 @@ Workspace: <code>${workspace}</code>
2806
3362
  );
2807
3363
  }
2808
3364
  async createSessionThread(sessionId, name) {
2809
- log8.info({ sessionId, name }, "Session topic created");
3365
+ log10.info({ sessionId, name }, "Session topic created");
2810
3366
  return String(
2811
3367
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
2812
3368
  );
@@ -2822,14 +3378,26 @@ Workspace: <code>${workspace}</code>
2822
3378
  Number(session.threadId),
2823
3379
  newName
2824
3380
  );
3381
+ await this.core.sessionManager.updateSessionName(
3382
+ sessionId,
3383
+ newName
3384
+ );
2825
3385
  }
2826
3386
  async sendSkillCommands(sessionId, commands) {
3387
+ if (sessionId === this.assistantSession?.id) return;
2827
3388
  const session = this.core.sessionManager.getSession(
2828
3389
  sessionId
2829
3390
  );
2830
3391
  if (!session) return;
2831
3392
  const threadId = Number(session.threadId);
2832
3393
  if (!threadId) return;
3394
+ if (!this.skillMessages.has(sessionId)) {
3395
+ const record = this.core.sessionManager.getSessionRecord(sessionId);
3396
+ const platform = record?.platform;
3397
+ if (platform?.skillMsgId) {
3398
+ this.skillMessages.set(sessionId, platform.skillMsgId);
3399
+ }
3400
+ }
2833
3401
  if (commands.length === 0) {
2834
3402
  await this.cleanupSkillCommands(sessionId);
2835
3403
  return;
@@ -2864,6 +3432,13 @@ Workspace: <code>${workspace}</code>
2864
3432
  )
2865
3433
  );
2866
3434
  this.skillMessages.set(sessionId, msg.message_id);
3435
+ const record = this.core.sessionManager.getSessionRecord(sessionId);
3436
+ if (record) {
3437
+ await this.core.sessionManager.updateSessionPlatform(
3438
+ sessionId,
3439
+ { ...record.platform, skillMsgId: msg.message_id }
3440
+ );
3441
+ }
2867
3442
  await this.bot.api.pinChatMessage(
2868
3443
  this.telegramConfig.chatId,
2869
3444
  msg.message_id,
@@ -2872,7 +3447,7 @@ Workspace: <code>${workspace}</code>
2872
3447
  }
2873
3448
  );
2874
3449
  } catch (err) {
2875
- log8.error({ err, sessionId }, "Failed to send skill commands");
3450
+ log10.error({ err, sessionId }, "Failed to send skill commands");
2876
3451
  }
2877
3452
  await this.updateCommandAutocomplete(session.agentName, commands);
2878
3453
  }
@@ -2891,6 +3466,11 @@ Workspace: <code>${workspace}</code>
2891
3466
  }
2892
3467
  this.skillMessages.delete(sessionId);
2893
3468
  clearSkillCallbacks(sessionId);
3469
+ const record = this.core.sessionManager.getSessionRecord(sessionId);
3470
+ if (record) {
3471
+ const { skillMsgId: _removed, ...rest } = record.platform;
3472
+ await this.core.sessionManager.updateSessionPlatform(sessionId, rest);
3473
+ }
2894
3474
  }
2895
3475
  async updateCommandAutocomplete(agentName, skillCommands) {
2896
3476
  const prefix = `[${agentName}] `;
@@ -2903,12 +3483,12 @@ Workspace: <code>${workspace}</code>
2903
3483
  await this.bot.api.setMyCommands(all, {
2904
3484
  scope: { type: "chat", chat_id: this.telegramConfig.chatId }
2905
3485
  });
2906
- log8.info(
3486
+ log10.info(
2907
3487
  { count: all.length, skills: validSkills.length },
2908
3488
  "Updated command autocomplete"
2909
3489
  );
2910
3490
  } catch (err) {
2911
- log8.error(
3491
+ log10.error(
2912
3492
  { err, commands: all },
2913
3493
  "Failed to update command autocomplete"
2914
3494
  );
@@ -2917,8 +3497,8 @@ Workspace: <code>${workspace}</code>
2917
3497
  async finalizeDraft(sessionId) {
2918
3498
  const draft = this.sessionDrafts.get(sessionId);
2919
3499
  if (!draft) return;
2920
- const finalMsgId = await draft.finalize();
2921
3500
  this.sessionDrafts.delete(sessionId);
3501
+ const finalMsgId = await draft.finalize();
2922
3502
  if (sessionId === this.assistantSession?.id) {
2923
3503
  const fullText = this.sessionTextBuffers.get(sessionId);
2924
3504
  this.sessionTextBuffers.delete(sessionId);
@@ -2957,4 +3537,4 @@ export {
2957
3537
  ApiServer,
2958
3538
  TelegramAdapter
2959
3539
  };
2960
- //# sourceMappingURL=chunk-6HORD4FS.js.map
3540
+ //# sourceMappingURL=chunk-C6IFPAWN.js.map