@geminixiang/mama 0.2.0-beta.1 → 0.2.0-beta.3

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 (149) hide show
  1. package/README.md +133 -78
  2. package/dist/adapter.d.ts +22 -10
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +10 -7
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +228 -69
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/discord/context.d.ts.map +1 -1
  10. package/dist/adapters/discord/context.js +92 -34
  11. package/dist/adapters/discord/context.js.map +1 -1
  12. package/dist/adapters/shared.d.ts +23 -0
  13. package/dist/adapters/shared.d.ts.map +1 -0
  14. package/dist/adapters/shared.js +57 -0
  15. package/dist/adapters/shared.js.map +1 -0
  16. package/dist/adapters/slack/bot.d.ts +19 -11
  17. package/dist/adapters/slack/bot.d.ts.map +1 -1
  18. package/dist/adapters/slack/bot.js +356 -96
  19. package/dist/adapters/slack/bot.js.map +1 -1
  20. package/dist/adapters/slack/branch-manager.d.ts +21 -0
  21. package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
  22. package/dist/adapters/slack/branch-manager.js +96 -0
  23. package/dist/adapters/slack/branch-manager.js.map +1 -0
  24. package/dist/adapters/slack/context.d.ts.map +1 -1
  25. package/dist/adapters/slack/context.js +100 -67
  26. package/dist/adapters/slack/context.js.map +1 -1
  27. package/dist/adapters/slack/session.d.ts +3 -0
  28. package/dist/adapters/slack/session.d.ts.map +1 -0
  29. package/dist/adapters/slack/session.js +16 -0
  30. package/dist/adapters/slack/session.js.map +1 -0
  31. package/dist/adapters/telegram/bot.d.ts +4 -2
  32. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  33. package/dist/adapters/telegram/bot.js +141 -74
  34. package/dist/adapters/telegram/bot.js.map +1 -1
  35. package/dist/adapters/telegram/context.d.ts.map +1 -1
  36. package/dist/adapters/telegram/context.js +49 -109
  37. package/dist/adapters/telegram/context.js.map +1 -1
  38. package/dist/adapters/telegram/html.d.ts +3 -0
  39. package/dist/adapters/telegram/html.d.ts.map +1 -0
  40. package/dist/adapters/telegram/html.js +98 -0
  41. package/dist/adapters/telegram/html.js.map +1 -0
  42. package/dist/agent.d.ts +4 -11
  43. package/dist/agent.d.ts.map +1 -1
  44. package/dist/agent.js +116 -196
  45. package/dist/agent.js.map +1 -1
  46. package/dist/bindings.d.ts +1 -20
  47. package/dist/bindings.d.ts.map +1 -1
  48. package/dist/bindings.js +1 -21
  49. package/dist/bindings.js.map +1 -1
  50. package/dist/config.d.ts +9 -27
  51. package/dist/config.d.ts.map +1 -1
  52. package/dist/config.js +89 -63
  53. package/dist/config.js.map +1 -1
  54. package/dist/context.d.ts +13 -3
  55. package/dist/context.d.ts.map +1 -1
  56. package/dist/context.js +102 -18
  57. package/dist/context.js.map +1 -1
  58. package/dist/events.d.ts +18 -6
  59. package/dist/events.d.ts.map +1 -1
  60. package/dist/events.js +86 -35
  61. package/dist/events.js.map +1 -1
  62. package/dist/execution-resolver.d.ts.map +1 -1
  63. package/dist/execution-resolver.js +1 -3
  64. package/dist/execution-resolver.js.map +1 -1
  65. package/dist/instrument.d.ts.map +1 -1
  66. package/dist/instrument.js +5 -11
  67. package/dist/instrument.js.map +1 -1
  68. package/dist/{login.d.ts → login/index.d.ts} +2 -2
  69. package/dist/login/index.d.ts.map +1 -0
  70. package/dist/{login.js → login/index.js} +2 -2
  71. package/dist/login/index.js.map +1 -0
  72. package/dist/{link-server.d.ts → login/portal.d.ts} +6 -4
  73. package/dist/login/portal.d.ts.map +1 -0
  74. package/dist/login/portal.js +1453 -0
  75. package/dist/login/portal.js.map +1 -0
  76. package/dist/{link-token.d.ts → login/session.d.ts} +1 -1
  77. package/dist/login/session.d.ts.map +1 -0
  78. package/dist/{link-token.js → login/session.js} +1 -1
  79. package/dist/login/session.js.map +1 -0
  80. package/dist/main.d.ts.map +1 -1
  81. package/dist/main.js +175 -119
  82. package/dist/main.js.map +1 -1
  83. package/dist/provisioner.d.ts +17 -43
  84. package/dist/provisioner.d.ts.map +1 -1
  85. package/dist/provisioner.js +84 -50
  86. package/dist/provisioner.js.map +1 -1
  87. package/dist/sandbox/host.d.ts +0 -2
  88. package/dist/sandbox/host.d.ts.map +1 -1
  89. package/dist/sandbox/host.js +1 -5
  90. package/dist/sandbox/host.js.map +1 -1
  91. package/dist/sentry.d.ts.map +1 -1
  92. package/dist/sentry.js +2 -0
  93. package/dist/sentry.js.map +1 -1
  94. package/dist/session-policy.d.ts +13 -0
  95. package/dist/session-policy.d.ts.map +1 -0
  96. package/dist/session-policy.js +23 -0
  97. package/dist/session-policy.js.map +1 -0
  98. package/dist/session-store.d.ts +27 -1
  99. package/dist/session-store.d.ts.map +1 -1
  100. package/dist/session-store.js +162 -9
  101. package/dist/session-store.js.map +1 -1
  102. package/dist/session-view/command.d.ts +5 -0
  103. package/dist/session-view/command.d.ts.map +1 -0
  104. package/dist/session-view/command.js +11 -0
  105. package/dist/session-view/command.js.map +1 -0
  106. package/dist/session-view/portal.d.ts +9 -0
  107. package/dist/session-view/portal.d.ts.map +1 -0
  108. package/dist/session-view/portal.js +766 -0
  109. package/dist/session-view/portal.js.map +1 -0
  110. package/dist/session-view/service.d.ts +34 -0
  111. package/dist/session-view/service.d.ts.map +1 -0
  112. package/dist/session-view/service.js +380 -0
  113. package/dist/session-view/service.js.map +1 -0
  114. package/dist/session-view/store.d.ts +16 -0
  115. package/dist/session-view/store.d.ts.map +1 -0
  116. package/dist/session-view/store.js +38 -0
  117. package/dist/session-view/store.js.map +1 -0
  118. package/dist/store.d.ts +3 -6
  119. package/dist/store.d.ts.map +1 -1
  120. package/dist/store.js +15 -35
  121. package/dist/store.js.map +1 -1
  122. package/dist/tools/event.d.ts +3 -0
  123. package/dist/tools/event.d.ts.map +1 -1
  124. package/dist/tools/event.js +27 -8
  125. package/dist/tools/event.js.map +1 -1
  126. package/dist/tools/index.d.ts +3 -0
  127. package/dist/tools/index.d.ts.map +1 -1
  128. package/dist/tools/index.js +2 -2
  129. package/dist/tools/index.js.map +1 -1
  130. package/dist/ui-copy.d.ts +1 -0
  131. package/dist/ui-copy.d.ts.map +1 -1
  132. package/dist/ui-copy.js +3 -0
  133. package/dist/ui-copy.js.map +1 -1
  134. package/dist/vault-routing.d.ts +1 -2
  135. package/dist/vault-routing.d.ts.map +1 -1
  136. package/dist/vault-routing.js +1 -7
  137. package/dist/vault-routing.js.map +1 -1
  138. package/package.json +1 -1
  139. package/dist/link-server.d.ts.map +0 -1
  140. package/dist/link-server.js +0 -839
  141. package/dist/link-server.js.map +0 -1
  142. package/dist/link-token.d.ts.map +0 -1
  143. package/dist/link-token.js.map +0 -1
  144. package/dist/login.d.ts.map +0 -1
  145. package/dist/login.js.map +0 -1
  146. package/dist/vault.test.d.ts +0 -2
  147. package/dist/vault.test.d.ts.map +0 -1
  148. package/dist/vault.test.js +0 -67
  149. package/dist/vault.test.js.map +0 -1
@@ -1,108 +1,30 @@
1
1
  import * as log from "../../log.js";
2
+ import { formatToolArgs, splitText } from "../shared.js";
3
+ import { sanitizeTelegramHtml } from "./html.js";
2
4
  export const TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)
3
5
  Bold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>
4
6
  Links: <a href="url">text</a>
5
7
  Do NOT use Markdown asterisks or backtick syntax.
6
8
  Do NOT use <table> tags — they are unsupported. Use <pre> with ASCII art for tables instead.`;
7
- function htmlTableToText(tableHtml) {
8
- const rows = [];
9
- for (const rowMatch of tableHtml.matchAll(/<tr[^>]*>([\s\S]*?)<\/tr>/gi)) {
10
- const cells = [];
11
- for (const cellMatch of rowMatch[1].matchAll(/<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi)) {
12
- cells.push(cellMatch[1].replace(/<[^>]+>/g, "").trim());
13
- }
14
- if (cells.length > 0)
15
- rows.push(cells);
16
- }
17
- if (rows.length === 0)
18
- return "";
19
- const numCols = Math.max(...rows.map((r) => r.length));
20
- const colWidths = Array(numCols).fill(0);
21
- for (const row of rows) {
22
- for (let i = 0; i < row.length; i++) {
23
- colWidths[i] = Math.max(colWidths[i], (row[i] ?? "").length);
24
- }
25
- }
26
- const sep = "+" + colWidths.map((w) => "-".repeat(w + 2)).join("+") + "+";
27
- const lines = [sep];
28
- for (let i = 0; i < rows.length; i++) {
29
- const cells = colWidths.map((w, j) => ` ${(rows[i][j] ?? "").padEnd(w)} `);
30
- lines.push("|" + cells.join("|") + "|");
31
- if (i === 0)
32
- lines.push(sep);
33
- }
34
- lines.push(sep);
35
- return lines.join("\n");
36
- }
37
- const ALLOWED_TELEGRAM_TAGS = new Set([
38
- "b",
39
- "strong",
40
- "i",
41
- "em",
42
- "u",
43
- "ins",
44
- "s",
45
- "strike",
46
- "del",
47
- "code",
48
- "pre",
49
- "a",
50
- ]);
51
- function escapeHtml(text) {
52
- return text
53
- .replace(/&(?!(?:[a-z]+|#\d+|#x[0-9a-f]+);)/gi, "&amp;")
54
- .replace(/</g, "&lt;")
55
- .replace(/>/g, "&gt;");
56
- }
57
- function escapeAttr(value) {
58
- return escapeHtml(value).replace(/"/g, "&quot;");
59
- }
60
- function normalizeAllowedTelegramTag(tag) {
61
- const closing = tag.match(/^<\s*\/\s*([a-z0-9-]+)\s*>$/i);
62
- if (closing) {
63
- const name = closing[1].toLowerCase();
64
- return ALLOWED_TELEGRAM_TAGS.has(name) ? `</${name}>` : null;
65
- }
66
- const simple = tag.match(/^<\s*([a-z0-9-]+)\s*>$/i);
67
- if (simple) {
68
- const name = simple[1].toLowerCase();
69
- return ALLOWED_TELEGRAM_TAGS.has(name) && name !== "a" ? `<${name}>` : null;
70
- }
71
- const anchor = tag.match(/^<\s*a\s+href\s*=\s*(?:"([^"]*)"|'([^']*)')\s*>$/i);
72
- if (anchor) {
73
- const href = anchor[1] ?? anchor[2] ?? "";
74
- return `<a href="${escapeAttr(href)}">`;
75
- }
76
- return null;
77
- }
78
- function sanitizeTelegramHtml(text) {
79
- const withTablesNormalized = text.replace(/<table[\s\S]*?<\/table>/gi, (tableHtml) => {
80
- const ascii = htmlTableToText(tableHtml);
81
- return ascii ? `<pre>${escapeHtml(ascii)}</pre>` : "";
82
- });
83
- const placeholders = [];
84
- const tokenized = withTablesNormalized.replace(/<\/?[a-z][^>]*>/gi, (tag) => {
85
- const normalized = normalizeAllowedTelegramTag(tag);
86
- if (!normalized) {
87
- return tag;
88
- }
89
- const token = `__TG_HTML_${placeholders.length}__`;
90
- placeholders.push(normalized);
91
- return token;
92
- });
93
- const escaped = escapeHtml(tokenized);
94
- return escaped.replace(/__TG_HTML_(\d+)__/g, (_match, index) => placeholders[Number(index)] ?? "");
95
- }
9
+ // Telegram message length limit is 4096 chars; 3800 leaves headroom for HTML escapes.
10
+ const MAX_LENGTH = 3800;
11
+ const formatTelegramContinuation = (partNum) => `(continued ${partNum})`;
96
12
  async function notifyError(bot, chatId, label, err) {
97
13
  const errMsg = err instanceof Error ? err.message : String(err);
98
14
  log.logWarning(`Telegram ${label} error`, errMsg);
99
15
  try {
100
- await bot.postPlainMessage(chatId, `Delivery failed: ${errMsg}`);
16
+ await bot.postPlainMessage(chatId, `⚠️ 發送失敗:${errMsg}`);
101
17
  }
102
18
  catch {
103
19
  // ignore secondary failure
104
20
  }
105
21
  }
22
+ function formatToolResult(result) {
23
+ const argsFormatted = formatToolArgs(result.args);
24
+ const duration = (result.durationMs / 1000).toFixed(1);
25
+ const title = `${result.isError ? "Error" : "Done"} ${result.toolName}${result.label ? `: ${result.label}` : ""} (${duration}s)`;
26
+ return [title, argsFormatted, result.result].filter(Boolean).join("\n\n");
27
+ }
106
28
  export function createTelegramAdapters(event, bot, _isEvent) {
107
29
  let messageId = null;
108
30
  let accumulatedText = "";
@@ -114,11 +36,13 @@ export function createTelegramAdapters(event, bot, _isEvent) {
114
36
  typingInterval = null;
115
37
  }
116
38
  }
117
- const chatId = parseInt(event.conversationId);
39
+ const conversationId = event.conversationId;
40
+ const chatId = parseInt(conversationId);
118
41
  const replyToId = event.thread_ts ? parseInt(event.thread_ts) : null;
119
42
  const message = {
120
43
  id: event.ts,
121
- sessionKey: event.sessionKey ?? `${event.conversationId}:${event.thread_ts ?? event.ts}`,
44
+ sessionKey: event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`,
45
+ conversationKind: event.conversationKind,
122
46
  userId: event.user,
123
47
  userName: event.userName,
124
48
  text: event.text,
@@ -131,18 +55,12 @@ export function createTelegramAdapters(event, bot, _isEvent) {
131
55
  channels: [],
132
56
  users: [],
133
57
  };
134
- // Telegram message length limit is 4096 chars; use 3800 for safety
135
- const MAX_LENGTH = 3800;
136
- const truncationNote = "\n\n<i>(message truncated, ask me to elaborate on specific parts)</i>";
137
- function truncate(text, limit, note) {
138
- if (text.length > limit) {
139
- return text.substring(0, limit - note.length) + note;
140
- }
141
- return text;
58
+ async function sendContinuation(text) {
59
+ await bot.postMessageRaw(chatId, text);
142
60
  }
143
61
  async function sendOrUpdate(displayText) {
144
62
  if (messageId !== null) {
145
- await bot.updateMessage(event.conversationId, String(messageId), displayText);
63
+ await bot.updateMessage(conversationId, String(messageId), displayText);
146
64
  }
147
65
  else if (replyToId !== null) {
148
66
  messageId = await bot.postReply(chatId, replyToId, displayText);
@@ -157,10 +75,13 @@ export function createTelegramAdapters(event, bot, _isEvent) {
157
75
  try {
158
76
  const sanitized = sanitizeTelegramHtml(text);
159
77
  accumulatedText = accumulatedText ? `${accumulatedText}\n${sanitized}` : sanitized;
160
- const displayText = truncate(accumulatedText, MAX_LENGTH, truncationNote);
161
- await sendOrUpdate(displayText);
78
+ const [firstPart, ...extraParts] = splitText(accumulatedText, MAX_LENGTH, formatTelegramContinuation);
79
+ await sendOrUpdate(firstPart);
80
+ for (const part of extraParts) {
81
+ await sendContinuation(part);
82
+ }
162
83
  if (messageId !== null) {
163
- bot.logBotResponse(event.conversationId, text, String(messageId));
84
+ bot.logBotResponse(conversationId, text, String(messageId));
164
85
  }
165
86
  }
166
87
  catch (err) {
@@ -172,8 +93,12 @@ export function createTelegramAdapters(event, bot, _isEvent) {
172
93
  replaceResponse: async (text) => {
173
94
  updatePromise = updatePromise.then(async () => {
174
95
  try {
175
- accumulatedText = truncate(sanitizeTelegramHtml(text), MAX_LENGTH, truncationNote);
176
- await sendOrUpdate(accumulatedText);
96
+ accumulatedText = sanitizeTelegramHtml(text);
97
+ const [firstPart, ...extraParts] = splitText(accumulatedText, MAX_LENGTH, formatTelegramContinuation);
98
+ await sendOrUpdate(firstPart);
99
+ for (const part of extraParts) {
100
+ await sendContinuation(part);
101
+ }
177
102
  }
178
103
  catch (err) {
179
104
  await notifyError(bot, chatId, "replaceResponse", err);
@@ -181,8 +106,23 @@ export function createTelegramAdapters(event, bot, _isEvent) {
181
106
  });
182
107
  await updatePromise;
183
108
  },
184
- // Telegram has no threads — discard thread-only messages (e.g. usage summary)
185
- respondInThread: async (_text) => { },
109
+ respondDiagnostic: async (text, options) => {
110
+ updatePromise = updatePromise.then(async () => {
111
+ try {
112
+ const prefix = options?.style === "error" ? "Error: " : "";
113
+ for (const part of splitText(sanitizeTelegramHtml(`${prefix}${text}`), MAX_LENGTH, formatTelegramContinuation)) {
114
+ await sendContinuation(part);
115
+ }
116
+ }
117
+ catch (err) {
118
+ await notifyError(bot, chatId, "respondDiagnostic", err);
119
+ }
120
+ });
121
+ await updatePromise;
122
+ },
123
+ respondToolResult: async (result) => {
124
+ await responseCtx.respondDiagnostic(formatToolResult(result));
125
+ },
186
126
  setTyping: async (isTyping) => {
187
127
  if (isTyping && typingInterval === null) {
188
128
  // Send immediately and repeat every 4s (Telegram clears indicator after ~5s)
@@ -200,7 +140,7 @@ export function createTelegramAdapters(event, bot, _isEvent) {
200
140
  stopTyping();
201
141
  },
202
142
  uploadFile: async (filePath, title) => {
203
- await bot.uploadFile(event.conversationId, filePath, title);
143
+ await bot.uploadFile(conversationId, filePath, title);
204
144
  },
205
145
  deleteResponse: async () => {
206
146
  updatePromise = updatePromise.then(async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/telegram/context.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ChatMessage, ChatResponseContext, PlatformInfo } from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport type { TelegramBot, TelegramEvent } from \"./bot.js\";\n\nexport const TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)\nBold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>\nLinks: <a href=\"url\">text</a>\nDo NOT use Markdown asterisks or backtick syntax.\nDo NOT use <table> tags — they are unsupported. Use <pre> with ASCII art for tables instead.`;\n\nfunction htmlTableToText(tableHtml: string): string {\n const rows: string[][] = [];\n for (const rowMatch of tableHtml.matchAll(/<tr[^>]*>([\\s\\S]*?)<\\/tr>/gi)) {\n const cells: string[] = [];\n for (const cellMatch of rowMatch[1].matchAll(/<t[dh][^>]*>([\\s\\S]*?)<\\/t[dh]>/gi)) {\n cells.push(cellMatch[1].replace(/<[^>]+>/g, \"\").trim());\n }\n if (cells.length > 0) rows.push(cells);\n }\n\n if (rows.length === 0) return \"\";\n\n const numCols = Math.max(...rows.map((r) => r.length));\n const colWidths: number[] = Array(numCols).fill(0);\n for (const row of rows) {\n for (let i = 0; i < row.length; i++) {\n colWidths[i] = Math.max(colWidths[i], (row[i] ?? \"\").length);\n }\n }\n\n const sep = \"+\" + colWidths.map((w) => \"-\".repeat(w + 2)).join(\"+\") + \"+\";\n const lines: string[] = [sep];\n for (let i = 0; i < rows.length; i++) {\n const cells = colWidths.map((w, j) => ` ${(rows[i][j] ?? \"\").padEnd(w)} `);\n lines.push(\"|\" + cells.join(\"|\") + \"|\");\n if (i === 0) lines.push(sep);\n }\n lines.push(sep);\n return lines.join(\"\\n\");\n}\n\nconst ALLOWED_TELEGRAM_TAGS = new Set([\n \"b\",\n \"strong\",\n \"i\",\n \"em\",\n \"u\",\n \"ins\",\n \"s\",\n \"strike\",\n \"del\",\n \"code\",\n \"pre\",\n \"a\",\n]);\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&(?!(?:[a-z]+|#\\d+|#x[0-9a-f]+);)/gi, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nfunction escapeAttr(value: string): string {\n return escapeHtml(value).replace(/\"/g, \"&quot;\");\n}\n\nfunction normalizeAllowedTelegramTag(tag: string): string | null {\n const closing = tag.match(/^<\\s*\\/\\s*([a-z0-9-]+)\\s*>$/i);\n if (closing) {\n const name = closing[1].toLowerCase();\n return ALLOWED_TELEGRAM_TAGS.has(name) ? `</${name}>` : null;\n }\n\n const simple = tag.match(/^<\\s*([a-z0-9-]+)\\s*>$/i);\n if (simple) {\n const name = simple[1].toLowerCase();\n return ALLOWED_TELEGRAM_TAGS.has(name) && name !== \"a\" ? `<${name}>` : null;\n }\n\n const anchor = tag.match(/^<\\s*a\\s+href\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)')\\s*>$/i);\n if (anchor) {\n const href = anchor[1] ?? anchor[2] ?? \"\";\n return `<a href=\"${escapeAttr(href)}\">`;\n }\n\n return null;\n}\n\nfunction sanitizeTelegramHtml(text: string): string {\n const withTablesNormalized = text.replace(/<table[\\s\\S]*?<\\/table>/gi, (tableHtml) => {\n const ascii = htmlTableToText(tableHtml);\n return ascii ? `<pre>${escapeHtml(ascii)}</pre>` : \"\";\n });\n\n const placeholders: string[] = [];\n const tokenized = withTablesNormalized.replace(/<\\/?[a-z][^>]*>/gi, (tag) => {\n const normalized = normalizeAllowedTelegramTag(tag);\n if (!normalized) {\n return tag;\n }\n const token = `__TG_HTML_${placeholders.length}__`;\n placeholders.push(normalized);\n return token;\n });\n\n const escaped = escapeHtml(tokenized);\n return escaped.replace(\n /__TG_HTML_(\\d+)__/g,\n (_match, index) => placeholders[Number(index)] ?? \"\",\n );\n}\n\nasync function notifyError(\n bot: TelegramBot,\n chatId: number,\n label: string,\n err: unknown,\n): Promise<void> {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(`Telegram ${label} error`, errMsg);\n try {\n await bot.postPlainMessage(chatId, `Delivery failed: ${errMsg}`);\n } catch {\n // ignore secondary failure\n }\n}\n\nexport function createTelegramAdapters(\n event: TelegramEvent,\n bot: TelegramBot,\n _isEvent?: boolean,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageId: number | null = null;\n let accumulatedText = \"\";\n let updatePromise = Promise.resolve();\n let typingInterval: ReturnType<typeof setInterval> | null = null;\n\n function stopTyping() {\n if (typingInterval !== null) {\n clearInterval(typingInterval);\n typingInterval = null;\n }\n }\n\n const chatId = parseInt(event.conversationId);\n const replyToId = event.thread_ts ? parseInt(event.thread_ts) : null;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: event.sessionKey ?? `${event.conversationId}:${event.thread_ts ?? event.ts}`,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"telegram\",\n formattingGuide: TELEGRAM_FORMATTING_GUIDE,\n channels: [],\n users: [],\n };\n\n // Telegram message length limit is 4096 chars; use 3800 for safety\n const MAX_LENGTH = 3800;\n const truncationNote = \"\\n\\n<i>(message truncated, ask me to elaborate on specific parts)</i>\";\n\n function truncate(text: string, limit: number, note: string): string {\n if (text.length > limit) {\n return text.substring(0, limit - note.length) + note;\n }\n return text;\n }\n\n async function sendOrUpdate(displayText: string): Promise<void> {\n if (messageId !== null) {\n await bot.updateMessage(event.conversationId, String(messageId), displayText);\n } else if (replyToId !== null) {\n messageId = await bot.postReply(chatId, replyToId, displayText);\n } else {\n messageId = await bot.postMessageRaw(chatId, displayText);\n }\n }\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n const sanitized = sanitizeTelegramHtml(text);\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${sanitized}` : sanitized;\n const displayText = truncate(accumulatedText, MAX_LENGTH, truncationNote);\n await sendOrUpdate(displayText);\n if (messageId !== null) {\n bot.logBotResponse(event.conversationId, text, String(messageId));\n }\n } catch (err) {\n await notifyError(bot, chatId, \"respond\", err);\n }\n });\n await updatePromise;\n },\n\n replaceResponse: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = truncate(sanitizeTelegramHtml(text), MAX_LENGTH, truncationNote);\n await sendOrUpdate(accumulatedText);\n } catch (err) {\n await notifyError(bot, chatId, \"replaceResponse\", err);\n }\n });\n await updatePromise;\n },\n\n // Telegram has no threads — discard thread-only messages (e.g. usage summary)\n respondInThread: async (_text: string) => {},\n\n setTyping: async (isTyping: boolean) => {\n if (isTyping && typingInterval === null) {\n // Send immediately and repeat every 4s (Telegram clears indicator after ~5s)\n bot.sendTyping(chatId).catch(() => {});\n typingInterval = setInterval(() => {\n bot.sendTyping(chatId).catch(() => {});\n }, 4000);\n } else if (!isTyping) {\n stopTyping();\n }\n },\n\n setWorking: async (working: boolean) => {\n if (!working) stopTyping();\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await bot.uploadFile(event.conversationId, filePath, title);\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n if (messageId !== null) {\n try {\n await bot.deleteMessageRaw(chatId, messageId);\n } catch {\n // Ignore errors\n }\n messageId = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/telegram/context.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ChatMessage,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { formatToolArgs, splitText } from \"../shared.js\";\nimport { sanitizeTelegramHtml } from \"./html.js\";\nimport type { TelegramBot, TelegramEvent } from \"./bot.js\";\n\nexport const TELEGRAM_FORMATTING_GUIDE = `## Telegram Formatting (HTML mode)\nBold: <b>text</b>, Italic: <i>text</i>, Code: <code>code</code>, Pre: <pre>code</pre>\nLinks: <a href=\"url\">text</a>\nDo NOT use Markdown asterisks or backtick syntax.\nDo NOT use <table> tags — they are unsupported. Use <pre> with ASCII art for tables instead.`;\n\n// Telegram message length limit is 4096 chars; 3800 leaves headroom for HTML escapes.\nconst MAX_LENGTH = 3800;\n\nconst formatTelegramContinuation = (partNum: number): string => `(continued ${partNum})`;\n\nasync function notifyError(\n bot: TelegramBot,\n chatId: number,\n label: string,\n err: unknown,\n): Promise<void> {\n const errMsg = err instanceof Error ? err.message : String(err);\n log.logWarning(`Telegram ${label} error`, errMsg);\n try {\n await bot.postPlainMessage(chatId, `⚠️ 發送失敗:${errMsg}`);\n } catch {\n // ignore secondary failure\n }\n}\n\nfunction formatToolResult(result: ChatToolResult): string {\n const argsFormatted = formatToolArgs(result.args);\n const duration = (result.durationMs / 1000).toFixed(1);\n const title = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName}${result.label ? `: ${result.label}` : \"\"} (${duration}s)`;\n return [title, argsFormatted, result.result].filter(Boolean).join(\"\\n\\n\");\n}\n\nexport function createTelegramAdapters(\n event: TelegramEvent,\n bot: TelegramBot,\n _isEvent?: boolean,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageId: number | null = null;\n let accumulatedText = \"\";\n let updatePromise = Promise.resolve();\n let typingInterval: ReturnType<typeof setInterval> | null = null;\n\n function stopTyping() {\n if (typingInterval !== null) {\n clearInterval(typingInterval);\n typingInterval = null;\n }\n }\n\n const conversationId = event.conversationId;\n const chatId = parseInt(conversationId);\n const replyToId = event.thread_ts ? parseInt(event.thread_ts) : null;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: event.sessionKey ?? `${conversationId}:${event.thread_ts ?? event.ts}`,\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"telegram\",\n formattingGuide: TELEGRAM_FORMATTING_GUIDE,\n channels: [],\n users: [],\n };\n\n async function sendContinuation(text: string): Promise<void> {\n await bot.postMessageRaw(chatId, text);\n }\n\n async function sendOrUpdate(displayText: string): Promise<void> {\n if (messageId !== null) {\n await bot.updateMessage(conversationId, String(messageId), displayText);\n } else if (replyToId !== null) {\n messageId = await bot.postReply(chatId, replyToId, displayText);\n } else {\n messageId = await bot.postMessageRaw(chatId, displayText);\n }\n }\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n const sanitized = sanitizeTelegramHtml(text);\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${sanitized}` : sanitized;\n const [firstPart, ...extraParts] = splitText(\n accumulatedText,\n MAX_LENGTH,\n formatTelegramContinuation,\n );\n await sendOrUpdate(firstPart);\n for (const part of extraParts) {\n await sendContinuation(part);\n }\n if (messageId !== null) {\n bot.logBotResponse(conversationId, text, String(messageId));\n }\n } catch (err) {\n await notifyError(bot, chatId, \"respond\", err);\n }\n });\n await updatePromise;\n },\n\n replaceResponse: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = sanitizeTelegramHtml(text);\n const [firstPart, ...extraParts] = splitText(\n accumulatedText,\n MAX_LENGTH,\n formatTelegramContinuation,\n );\n await sendOrUpdate(firstPart);\n for (const part of extraParts) {\n await sendContinuation(part);\n }\n } catch (err) {\n await notifyError(bot, chatId, \"replaceResponse\", err);\n }\n });\n await updatePromise;\n },\n\n respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n updatePromise = updatePromise.then(async () => {\n try {\n const prefix = options?.style === \"error\" ? \"Error: \" : \"\";\n for (const part of splitText(\n sanitizeTelegramHtml(`${prefix}${text}`),\n MAX_LENGTH,\n formatTelegramContinuation,\n )) {\n await sendContinuation(part);\n }\n } catch (err) {\n await notifyError(bot, chatId, \"respondDiagnostic\", err);\n }\n });\n await updatePromise;\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatToolResult(result));\n },\n\n setTyping: async (isTyping: boolean) => {\n if (isTyping && typingInterval === null) {\n // Send immediately and repeat every 4s (Telegram clears indicator after ~5s)\n bot.sendTyping(chatId).catch(() => {});\n typingInterval = setInterval(() => {\n bot.sendTyping(chatId).catch(() => {});\n }, 4000);\n } else if (!isTyping) {\n stopTyping();\n }\n },\n\n setWorking: async (working: boolean) => {\n if (!working) stopTyping();\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await bot.uploadFile(conversationId, filePath, title);\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n if (messageId !== null) {\n try {\n await bot.deleteMessageRaw(chatId, messageId);\n } catch {\n // Ignore errors\n }\n messageId = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export declare function escapeTelegramHtml(text: string): string;
2
+ export declare function sanitizeTelegramHtml(text: string): string;
3
+ //# sourceMappingURL=html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/html.ts"],"names":[],"mappings":"AA+CA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKvD;AAkCD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBzD","sourcesContent":["function htmlTableToText(tableHtml: string): string {\n const rows: string[][] = [];\n for (const rowMatch of tableHtml.matchAll(/<tr[^>]*>([\\s\\S]*?)<\\/tr>/gi)) {\n const cells: string[] = [];\n for (const cellMatch of rowMatch[1].matchAll(/<t[dh][^>]*>([\\s\\S]*?)<\\/t[dh]>/gi)) {\n cells.push(cellMatch[1].replace(/<[^>]+>/g, \"\").trim());\n }\n if (cells.length > 0) rows.push(cells);\n }\n\n if (rows.length === 0) return \"\";\n\n const numCols = Math.max(...rows.map((r) => r.length));\n const colWidths: number[] = Array(numCols).fill(0);\n for (const row of rows) {\n for (let i = 0; i < row.length; i++) {\n colWidths[i] = Math.max(colWidths[i], (row[i] ?? \"\").length);\n }\n }\n\n const sep = \"+\" + colWidths.map((w) => \"-\".repeat(w + 2)).join(\"+\") + \"+\";\n const lines: string[] = [sep];\n for (let i = 0; i < rows.length; i++) {\n const cells = colWidths.map((w, j) => ` ${(rows[i][j] ?? \"\").padEnd(w)} `);\n lines.push(\"|\" + cells.join(\"|\") + \"|\");\n if (i === 0) lines.push(sep);\n }\n lines.push(sep);\n return lines.join(\"\\n\");\n}\n\nconst SIMPLE_TAG_ALIASES: Record<string, string> = {\n b: \"b\",\n strong: \"b\",\n i: \"i\",\n em: \"i\",\n u: \"u\",\n ins: \"u\",\n s: \"s\",\n strike: \"s\",\n del: \"s\",\n code: \"code\",\n pre: \"pre\",\n blockquote: \"blockquote\",\n \"tg-spoiler\": \"tg-spoiler\",\n};\n\nexport function escapeTelegramHtml(text: string): string {\n return text\n .replace(/&(?!(?:amp|lt|gt|quot|apos|#39|#\\d+|#x[\\da-f]+);)/gi, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nfunction escapeTelegramAttribute(text: string): string {\n return escapeTelegramHtml(text).replace(/\"/g, \"&quot;\");\n}\n\nfunction sanitizeTelegramTag(tag: string): string {\n const trimmed = tag.trim();\n\n if (/^<br\\s*\\/?>$/i.test(trimmed)) return \"\\n\";\n if (/^<(p|div)\\s*>$/i.test(trimmed)) return \"\";\n if (/^<\\/(p|div)\\s*>$/i.test(trimmed)) return \"\\n\";\n\n const match = trimmed.match(/^<\\s*(\\/?)\\s*([a-z0-9-]+)([^>]*)>$/i);\n if (!match) return escapeTelegramHtml(tag);\n\n const [, closing, rawName, rawAttrs] = match;\n const name = rawName.toLowerCase();\n const aliasedName = SIMPLE_TAG_ALIASES[name];\n\n if (aliasedName) {\n return `<${closing}${aliasedName}>`;\n }\n\n if (name === \"a\") {\n if (closing) return \"</a>\";\n const hrefMatch = rawAttrs.match(/\\bhref\\s*=\\s*([\"'])(.*?)\\1/i);\n if (!hrefMatch) return escapeTelegramHtml(tag);\n return `<a href=\"${escapeTelegramAttribute(hrefMatch[2])}\">`;\n }\n\n return escapeTelegramHtml(tag);\n}\n\nexport function sanitizeTelegramHtml(text: string): string {\n const withAsciiTables = text.replace(/<table[\\s\\S]*?<\\/table>/gi, (tableHtml) => {\n const ascii = htmlTableToText(tableHtml);\n return ascii ? `<pre>${ascii}</pre>` : \"\";\n });\n\n return withAsciiTables\n .split(/(<[^>]+>)/g)\n .filter(Boolean)\n .map((segment) => {\n if (segment.startsWith(\"<\") && segment.endsWith(\">\")) {\n return sanitizeTelegramTag(segment);\n }\n return escapeTelegramHtml(segment);\n })\n .join(\"\");\n}\n"]}
@@ -0,0 +1,98 @@
1
+ function htmlTableToText(tableHtml) {
2
+ const rows = [];
3
+ for (const rowMatch of tableHtml.matchAll(/<tr[^>]*>([\s\S]*?)<\/tr>/gi)) {
4
+ const cells = [];
5
+ for (const cellMatch of rowMatch[1].matchAll(/<t[dh][^>]*>([\s\S]*?)<\/t[dh]>/gi)) {
6
+ cells.push(cellMatch[1].replace(/<[^>]+>/g, "").trim());
7
+ }
8
+ if (cells.length > 0)
9
+ rows.push(cells);
10
+ }
11
+ if (rows.length === 0)
12
+ return "";
13
+ const numCols = Math.max(...rows.map((r) => r.length));
14
+ const colWidths = Array(numCols).fill(0);
15
+ for (const row of rows) {
16
+ for (let i = 0; i < row.length; i++) {
17
+ colWidths[i] = Math.max(colWidths[i], (row[i] ?? "").length);
18
+ }
19
+ }
20
+ const sep = "+" + colWidths.map((w) => "-".repeat(w + 2)).join("+") + "+";
21
+ const lines = [sep];
22
+ for (let i = 0; i < rows.length; i++) {
23
+ const cells = colWidths.map((w, j) => ` ${(rows[i][j] ?? "").padEnd(w)} `);
24
+ lines.push("|" + cells.join("|") + "|");
25
+ if (i === 0)
26
+ lines.push(sep);
27
+ }
28
+ lines.push(sep);
29
+ return lines.join("\n");
30
+ }
31
+ const SIMPLE_TAG_ALIASES = {
32
+ b: "b",
33
+ strong: "b",
34
+ i: "i",
35
+ em: "i",
36
+ u: "u",
37
+ ins: "u",
38
+ s: "s",
39
+ strike: "s",
40
+ del: "s",
41
+ code: "code",
42
+ pre: "pre",
43
+ blockquote: "blockquote",
44
+ "tg-spoiler": "tg-spoiler",
45
+ };
46
+ export function escapeTelegramHtml(text) {
47
+ return text
48
+ .replace(/&(?!(?:amp|lt|gt|quot|apos|#39|#\d+|#x[\da-f]+);)/gi, "&amp;")
49
+ .replace(/</g, "&lt;")
50
+ .replace(/>/g, "&gt;");
51
+ }
52
+ function escapeTelegramAttribute(text) {
53
+ return escapeTelegramHtml(text).replace(/"/g, "&quot;");
54
+ }
55
+ function sanitizeTelegramTag(tag) {
56
+ const trimmed = tag.trim();
57
+ if (/^<br\s*\/?>$/i.test(trimmed))
58
+ return "\n";
59
+ if (/^<(p|div)\s*>$/i.test(trimmed))
60
+ return "";
61
+ if (/^<\/(p|div)\s*>$/i.test(trimmed))
62
+ return "\n";
63
+ const match = trimmed.match(/^<\s*(\/?)\s*([a-z0-9-]+)([^>]*)>$/i);
64
+ if (!match)
65
+ return escapeTelegramHtml(tag);
66
+ const [, closing, rawName, rawAttrs] = match;
67
+ const name = rawName.toLowerCase();
68
+ const aliasedName = SIMPLE_TAG_ALIASES[name];
69
+ if (aliasedName) {
70
+ return `<${closing}${aliasedName}>`;
71
+ }
72
+ if (name === "a") {
73
+ if (closing)
74
+ return "</a>";
75
+ const hrefMatch = rawAttrs.match(/\bhref\s*=\s*(["'])(.*?)\1/i);
76
+ if (!hrefMatch)
77
+ return escapeTelegramHtml(tag);
78
+ return `<a href="${escapeTelegramAttribute(hrefMatch[2])}">`;
79
+ }
80
+ return escapeTelegramHtml(tag);
81
+ }
82
+ export function sanitizeTelegramHtml(text) {
83
+ const withAsciiTables = text.replace(/<table[\s\S]*?<\/table>/gi, (tableHtml) => {
84
+ const ascii = htmlTableToText(tableHtml);
85
+ return ascii ? `<pre>${ascii}</pre>` : "";
86
+ });
87
+ return withAsciiTables
88
+ .split(/(<[^>]+>)/g)
89
+ .filter(Boolean)
90
+ .map((segment) => {
91
+ if (segment.startsWith("<") && segment.endsWith(">")) {
92
+ return sanitizeTelegramTag(segment);
93
+ }
94
+ return escapeTelegramHtml(segment);
95
+ })
96
+ .join("");
97
+ }
98
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../../src/adapters/telegram/html.ts"],"names":[],"mappings":"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","sourcesContent":["function htmlTableToText(tableHtml: string): string {\n const rows: string[][] = [];\n for (const rowMatch of tableHtml.matchAll(/<tr[^>]*>([\\s\\S]*?)<\\/tr>/gi)) {\n const cells: string[] = [];\n for (const cellMatch of rowMatch[1].matchAll(/<t[dh][^>]*>([\\s\\S]*?)<\\/t[dh]>/gi)) {\n cells.push(cellMatch[1].replace(/<[^>]+>/g, \"\").trim());\n }\n if (cells.length > 0) rows.push(cells);\n }\n\n if (rows.length === 0) return \"\";\n\n const numCols = Math.max(...rows.map((r) => r.length));\n const colWidths: number[] = Array(numCols).fill(0);\n for (const row of rows) {\n for (let i = 0; i < row.length; i++) {\n colWidths[i] = Math.max(colWidths[i], (row[i] ?? \"\").length);\n }\n }\n\n const sep = \"+\" + colWidths.map((w) => \"-\".repeat(w + 2)).join(\"+\") + \"+\";\n const lines: string[] = [sep];\n for (let i = 0; i < rows.length; i++) {\n const cells = colWidths.map((w, j) => ` ${(rows[i][j] ?? \"\").padEnd(w)} `);\n lines.push(\"|\" + cells.join(\"|\") + \"|\");\n if (i === 0) lines.push(sep);\n }\n lines.push(sep);\n return lines.join(\"\\n\");\n}\n\nconst SIMPLE_TAG_ALIASES: Record<string, string> = {\n b: \"b\",\n strong: \"b\",\n i: \"i\",\n em: \"i\",\n u: \"u\",\n ins: \"u\",\n s: \"s\",\n strike: \"s\",\n del: \"s\",\n code: \"code\",\n pre: \"pre\",\n blockquote: \"blockquote\",\n \"tg-spoiler\": \"tg-spoiler\",\n};\n\nexport function escapeTelegramHtml(text: string): string {\n return text\n .replace(/&(?!(?:amp|lt|gt|quot|apos|#39|#\\d+|#x[\\da-f]+);)/gi, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nfunction escapeTelegramAttribute(text: string): string {\n return escapeTelegramHtml(text).replace(/\"/g, \"&quot;\");\n}\n\nfunction sanitizeTelegramTag(tag: string): string {\n const trimmed = tag.trim();\n\n if (/^<br\\s*\\/?>$/i.test(trimmed)) return \"\\n\";\n if (/^<(p|div)\\s*>$/i.test(trimmed)) return \"\";\n if (/^<\\/(p|div)\\s*>$/i.test(trimmed)) return \"\\n\";\n\n const match = trimmed.match(/^<\\s*(\\/?)\\s*([a-z0-9-]+)([^>]*)>$/i);\n if (!match) return escapeTelegramHtml(tag);\n\n const [, closing, rawName, rawAttrs] = match;\n const name = rawName.toLowerCase();\n const aliasedName = SIMPLE_TAG_ALIASES[name];\n\n if (aliasedName) {\n return `<${closing}${aliasedName}>`;\n }\n\n if (name === \"a\") {\n if (closing) return \"</a>\";\n const hrefMatch = rawAttrs.match(/\\bhref\\s*=\\s*([\"'])(.*?)\\1/i);\n if (!hrefMatch) return escapeTelegramHtml(tag);\n return `<a href=\"${escapeTelegramAttribute(hrefMatch[2])}\">`;\n }\n\n return escapeTelegramHtml(tag);\n}\n\nexport function sanitizeTelegramHtml(text: string): string {\n const withAsciiTables = text.replace(/<table[\\s\\S]*?<\\/table>/gi, (tableHtml) => {\n const ascii = htmlTableToText(tableHtml);\n return ascii ? `<pre>${ascii}</pre>` : \"\";\n });\n\n return withAsciiTables\n .split(/(<[^>]+>)/g)\n .filter(Boolean)\n .map((segment) => {\n if (segment.startsWith(\"<\") && segment.endsWith(\">\")) {\n return sanitizeTelegramTag(segment);\n }\n return escapeTelegramHtml(segment);\n })\n .join(\"\");\n}\n"]}
package/dist/agent.d.ts CHANGED
@@ -3,14 +3,7 @@ import type { UserBindingStore } from "./bindings.js";
3
3
  import type { DockerContainerManager } from "./provisioner.js";
4
4
  import { type SandboxConfig } from "./sandbox.js";
5
5
  import type { VaultManager } from "./vault.js";
6
- export interface PendingMessage {
7
- userName: string;
8
- text: string;
9
- attachments: {
10
- localPath: string;
11
- }[];
12
- timestamp: number;
13
- }
6
+ import { type ResolvedSessionScope } from "./session-store.js";
14
7
  export interface AgentRunner {
15
8
  run(message: ChatMessage, responseCtx: ChatResponseContext, platform: PlatformInfo): Promise<{
16
9
  stopReason: string;
@@ -24,11 +17,11 @@ export interface AgentRunner {
24
17
  } | undefined;
25
18
  }
26
19
  /**
27
- * Create a new AgentRunner for a conversation.
20
+ * Create a new AgentRunner for a channel.
28
21
  * Sets up the session and subscribes to events once.
29
22
  *
30
- * Runner caching is handled by the caller (conversationStates in main.ts).
23
+ * Runner caching is handled by the caller (channelStates in main.ts).
31
24
  * This is a stateless factory function.
32
25
  */
33
- export declare function createRunner(sandboxConfig: SandboxConfig, sessionKey: string, conversationId: string, conversationDir: string, workspaceDir: string, vaultManager?: VaultManager, bindingStore?: UserBindingStore, provisioner?: DockerContainerManager, stateDir?: string): Promise<AgentRunner>;
26
+ export declare function createRunner(sandboxConfig: SandboxConfig, sessionKey: string, conversationId: string, conversationDir: string, workspaceDir: string, sessionScope: ResolvedSessionScope, vaultManager?: VaultManager, bindingStore?: UserBindingStore, provisioner?: DockerContainerManager): Promise<AgentRunner>;
34
27
  //# sourceMappingURL=agent.d.ts.map