@geminixiang/mama 0.1.8 → 0.1.10

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 (48) hide show
  1. package/README.md +149 -9
  2. package/dist/adapter.d.ts +14 -1
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/context.d.ts.map +1 -1
  6. package/dist/adapters/discord/context.js +1 -0
  7. package/dist/adapters/discord/context.js.map +1 -1
  8. package/dist/adapters/slack/bot.d.ts +5 -1
  9. package/dist/adapters/slack/bot.d.ts.map +1 -1
  10. package/dist/adapters/slack/bot.js +137 -13
  11. package/dist/adapters/slack/bot.js.map +1 -1
  12. package/dist/adapters/slack/context.d.ts.map +1 -1
  13. package/dist/adapters/slack/context.js +50 -25
  14. package/dist/adapters/slack/context.js.map +1 -1
  15. package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
  16. package/dist/adapters/slack/tools/attach.js +4 -2
  17. package/dist/adapters/slack/tools/attach.js.map +1 -1
  18. package/dist/adapters/telegram/bot.d.ts +4 -3
  19. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  20. package/dist/adapters/telegram/bot.js +11 -23
  21. package/dist/adapters/telegram/bot.js.map +1 -1
  22. package/dist/adapters/telegram/context.d.ts +1 -1
  23. package/dist/adapters/telegram/context.d.ts.map +1 -1
  24. package/dist/adapters/telegram/context.js +23 -40
  25. package/dist/adapters/telegram/context.js.map +1 -1
  26. package/dist/agent.d.ts +5 -0
  27. package/dist/agent.d.ts.map +1 -1
  28. package/dist/agent.js +63 -17
  29. package/dist/agent.js.map +1 -1
  30. package/dist/context.d.ts +13 -1
  31. package/dist/context.d.ts.map +1 -1
  32. package/dist/context.js +20 -2
  33. package/dist/context.js.map +1 -1
  34. package/dist/events.d.ts +10 -5
  35. package/dist/events.d.ts.map +1 -1
  36. package/dist/events.js +44 -10
  37. package/dist/events.js.map +1 -1
  38. package/dist/log.d.ts.map +1 -1
  39. package/dist/log.js +1 -1
  40. package/dist/log.js.map +1 -1
  41. package/dist/main.d.ts.map +1 -1
  42. package/dist/main.js +113 -37
  43. package/dist/main.js.map +1 -1
  44. package/dist/sandbox.d.ts +7 -1
  45. package/dist/sandbox.d.ts.map +1 -1
  46. package/dist/sandbox.js +127 -27
  47. package/dist/sandbox.js.map +1 -1
  48. package/package.json +12 -12
@@ -14,6 +14,14 @@ export function createSlackAdapters(event, slack, isEvent) {
14
14
  const eventFilename = isEvent ? event.text.match(/^\[EVENT:([^:]+):/)?.[1] : undefined;
15
15
  const rootTs = event.thread_ts ?? event.ts;
16
16
  const isThreaded = !!event.thread_ts;
17
+ /** Post first top-level message and register thread alias so stop commands can find this session */
18
+ const postFirstMessage = async (text) => {
19
+ const ts = await slack.postMessage(event.channel, text);
20
+ if (ts) {
21
+ slack.registerThreadAlias(`${event.channel}:${ts}`, `${event.channel}:${rootTs}`);
22
+ }
23
+ return ts;
24
+ };
17
25
  const message = {
18
26
  id: event.ts,
19
27
  sessionKey: `${event.channel}:${rootTs}`,
@@ -21,6 +29,7 @@ export function createSlackAdapters(event, slack, isEvent) {
21
29
  userName: user?.userName,
22
30
  text: event.text,
23
31
  attachments: (event.attachments || []).map((a) => ({ name: a.local, localPath: a.local })),
32
+ threadTs: event.thread_ts,
24
33
  };
25
34
  const platform = {
26
35
  name: "slack",
@@ -52,10 +61,10 @@ export function createSlackAdapters(event, slack, isEvent) {
52
61
  messageTs = await slack.postInThread(event.channel, rootTs, displayText);
53
62
  }
54
63
  else {
55
- messageTs = await slack.postMessage(event.channel, displayText);
64
+ messageTs = await postFirstMessage(displayText);
56
65
  }
57
66
  if (messageTs) {
58
- slack.logBotResponse(event.channel, text, messageTs, rootTs);
67
+ slack.logBotResponse(event.channel, text, messageTs, isThreaded ? rootTs : undefined);
59
68
  }
60
69
  }
61
70
  catch (err) {
@@ -85,7 +94,7 @@ export function createSlackAdapters(event, slack, isEvent) {
85
94
  messageTs = await slack.postInThread(event.channel, rootTs, displayText);
86
95
  }
87
96
  else {
88
- messageTs = await slack.postMessage(event.channel, displayText);
97
+ messageTs = await postFirstMessage(displayText);
89
98
  }
90
99
  }
91
100
  catch (err) {
@@ -94,7 +103,7 @@ export function createSlackAdapters(event, slack, isEvent) {
94
103
  });
95
104
  await updatePromise;
96
105
  },
97
- respondInThread: async (text) => {
106
+ respondInThread: async (text, options) => {
98
107
  updatePromise = updatePromise.then(async () => {
99
108
  try {
100
109
  // For threaded sessions, anchor to the user's root thread
@@ -107,8 +116,21 @@ export function createSlackAdapters(event, slack, isEvent) {
107
116
  if (threadText.length > MAX_THREAD_LENGTH) {
108
117
  threadText = `${threadText.substring(0, MAX_THREAD_LENGTH - 50)}\n\n_(truncated)_`;
109
118
  }
110
- const ts = await slack.postInThread(event.channel, threadAnchor, threadText);
111
- threadMessageTs.push(ts);
119
+ // Use context block for muted style (small gray text like Slack's Home tab)
120
+ if (options?.style === "muted") {
121
+ const CONTEXT_TEXT_LIMIT = 3000;
122
+ const blockText = threadText.length > CONTEXT_TEXT_LIMIT
123
+ ? threadText.substring(0, CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
124
+ : threadText;
125
+ const ts = await slack.postInThreadBlocks(event.channel, threadAnchor, threadText, [
126
+ { type: "context", elements: [{ type: "mrkdwn", text: blockText }] },
127
+ ]);
128
+ threadMessageTs.push(ts);
129
+ }
130
+ else {
131
+ const ts = await slack.postInThread(event.channel, threadAnchor, threadText);
132
+ threadMessageTs.push(ts);
133
+ }
112
134
  }
113
135
  }
114
136
  catch (err) {
@@ -119,27 +141,17 @@ export function createSlackAdapters(event, slack, isEvent) {
119
141
  },
120
142
  setTyping: async (isTyping) => {
121
143
  if (isTyping && !messageTs) {
122
- updatePromise = updatePromise.then(async () => {
123
- try {
124
- if (!messageTs) {
125
- accumulatedText = eventFilename ? `_Starting event: ${eventFilename}_` : "_Thinking_";
126
- if (isThreaded) {
127
- messageTs = await slack.postInThread(event.channel, rootTs, accumulatedText + workingIndicator);
128
- }
129
- else {
130
- messageTs = await slack.postMessage(event.channel, accumulatedText + workingIndicator);
131
- }
132
- }
133
- }
134
- catch (err) {
135
- log.logWarning("Slack setTyping error", err instanceof Error ? err.message : String(err));
136
- }
137
- });
138
- await updatePromise;
144
+ try {
145
+ const statusText = eventFilename ? `Starting event: ${eventFilename}` : "Thinking";
146
+ await slack.setAssistantStatus(event.channel, rootTs, statusText);
147
+ }
148
+ catch {
149
+ // Assistant API not available — first respond() call will create the message
150
+ }
139
151
  }
140
152
  },
141
153
  uploadFile: async (filePath, title) => {
142
- await slack.uploadFile(event.channel, filePath, title);
154
+ await slack.uploadFile(event.channel, filePath, title, rootTs);
143
155
  },
144
156
  setWorking: async (working) => {
145
157
  updatePromise = updatePromise.then(async () => {
@@ -147,7 +159,13 @@ export function createSlackAdapters(event, slack, isEvent) {
147
159
  isWorking = working;
148
160
  if (messageTs) {
149
161
  const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;
150
- await slack.updateMessage(event.channel, messageTs, displayText);
162
+ const updates = [
163
+ slack.updateMessage(event.channel, messageTs, displayText),
164
+ ];
165
+ if (!working) {
166
+ updates.push(slack.setAssistantStatus(event.channel, rootTs, "").catch(() => { }));
167
+ }
168
+ await Promise.all(updates);
151
169
  }
152
170
  }
153
171
  catch (err) {
@@ -158,6 +176,13 @@ export function createSlackAdapters(event, slack, isEvent) {
158
176
  },
159
177
  deleteResponse: async () => {
160
178
  updatePromise = updatePromise.then(async () => {
179
+ // Clear assistant status first
180
+ try {
181
+ await slack.setAssistantStatus(event.channel, rootTs, "");
182
+ }
183
+ catch {
184
+ // Ignore errors clearing status
185
+ }
161
186
  // Delete thread messages first (in reverse order)
162
187
  for (let i = threadMessageTs.length - 1; i >= 0; i--) {
163
188
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/slack/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 { SlackBot, SlackEvent } from \"./bot.js\";\n\nexport const SLACK_FORMATTING_GUIDE = `## Slack Formatting (mrkdwn, NOT Markdown)\nBold: *text*, Italic: _text_, Code: \\`code\\`, Block: \\`\\`\\`code\\`\\`\\`, Links: <url|text>\nDo NOT use **double asterisks** or [markdown](links).`;\n\nexport function createSlackAdapters(\n event: SlackEvent,\n slack: SlackBot,\n isEvent?: boolean,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageTs: string | null = null;\n const threadMessageTs: string[] = [];\n let accumulatedText = \"\";\n let isWorking = true;\n const workingIndicator = \" ...\";\n let updatePromise = Promise.resolve();\n\n const user = slack.getUser(event.user);\n\n // Extract event filename for status message\n const eventFilename = isEvent ? event.text.match(/^\\[EVENT:([^:]+):/)?.[1] : undefined;\n\n const rootTs = event.thread_ts ?? event.ts;\n const isThreaded = !!event.thread_ts;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: `${event.channel}:${rootTs}`,\n userId: event.user,\n userName: user?.userName,\n text: event.text,\n attachments: (event.attachments || []).map((a) => ({ name: a.local, localPath: a.local })),\n };\n\n const platform: PlatformInfo = {\n name: \"slack\",\n formattingGuide: SLACK_FORMATTING_GUIDE,\n channels: slack.getAllChannels().map((c) => ({ id: c.id, name: c.name })),\n users: slack\n .getAllUsers()\n .map((u) => ({ id: u.id, userName: u.userName, displayName: u.displayName })),\n };\n\n const responseCtx = {\n respond: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${text}` : text;\n\n // Truncate accumulated text if too long (Slack limit is 40K, we use 35K for safety)\n const MAX_MAIN_LENGTH = 35000;\n const truncationNote = \"\\n\\n_(message truncated, ask me to elaborate on specific parts)_\";\n if (accumulatedText.length > MAX_MAIN_LENGTH) {\n accumulatedText =\n accumulatedText.substring(0, MAX_MAIN_LENGTH - truncationNote.length) +\n truncationNote;\n }\n\n const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n if (messageTs) {\n await slack.updateMessage(event.channel, messageTs, displayText);\n } else if (isThreaded) {\n // Reply within the user's thread\n messageTs = await slack.postInThread(event.channel, rootTs, displayText);\n } else {\n messageTs = await slack.postMessage(event.channel, displayText);\n }\n\n if (messageTs) {\n slack.logBotResponse(event.channel, text, messageTs, rootTs);\n }\n } catch (err) {\n log.logWarning(\"Slack respond error\", err instanceof Error ? err.message : String(err));\n }\n });\n await updatePromise;\n },\n\n replaceResponse: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n // Replace the accumulated text entirely, with truncation\n const MAX_MAIN_LENGTH = 35000;\n const truncationNote = \"\\n\\n_(message truncated, ask me to elaborate on specific parts)_\";\n if (text.length > MAX_MAIN_LENGTH) {\n accumulatedText =\n text.substring(0, MAX_MAIN_LENGTH - truncationNote.length) + truncationNote;\n } else {\n accumulatedText = text;\n }\n\n const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n if (messageTs) {\n await slack.updateMessage(event.channel, messageTs, displayText);\n } else if (isThreaded) {\n messageTs = await slack.postInThread(event.channel, rootTs, displayText);\n } else {\n messageTs = await slack.postMessage(event.channel, displayText);\n }\n } catch (err) {\n log.logWarning(\n \"Slack replaceResponse error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n },\n\n respondInThread: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n // For threaded sessions, anchor to the user's root thread\n // For channel sessions, anchor to the main bot message\n const threadAnchor = isThreaded ? rootTs : messageTs;\n if (threadAnchor) {\n // Truncate thread messages if too long (20K limit for safety)\n const MAX_THREAD_LENGTH = 20000;\n let threadText = text;\n if (threadText.length > MAX_THREAD_LENGTH) {\n threadText = `${threadText.substring(0, MAX_THREAD_LENGTH - 50)}\\n\\n_(truncated)_`;\n }\n\n const ts = await slack.postInThread(event.channel, threadAnchor, threadText);\n threadMessageTs.push(ts);\n }\n } catch (err) {\n log.logWarning(\n \"Slack respondInThread error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n },\n\n setTyping: async (isTyping: boolean) => {\n if (isTyping && !messageTs) {\n updatePromise = updatePromise.then(async () => {\n try {\n if (!messageTs) {\n accumulatedText = eventFilename ? `_Starting event: ${eventFilename}_` : \"_Thinking_\";\n if (isThreaded) {\n messageTs = await slack.postInThread(\n event.channel,\n rootTs,\n accumulatedText + workingIndicator,\n );\n } else {\n messageTs = await slack.postMessage(\n event.channel,\n accumulatedText + workingIndicator,\n );\n }\n }\n } catch (err) {\n log.logWarning(\n \"Slack setTyping error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n }\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await slack.uploadFile(event.channel, filePath, title);\n },\n\n setWorking: async (working: boolean) => {\n updatePromise = updatePromise.then(async () => {\n try {\n isWorking = working;\n if (messageTs) {\n const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n await slack.updateMessage(event.channel, messageTs, displayText);\n }\n } catch (err) {\n log.logWarning(\n \"Slack setWorking error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n // Delete thread messages first (in reverse order)\n for (let i = threadMessageTs.length - 1; i >= 0; i--) {\n try {\n await slack.deleteMessage(event.channel, threadMessageTs[i]);\n } catch {\n // Ignore errors deleting thread messages\n }\n }\n threadMessageTs.length = 0;\n // Then delete main message\n if (messageTs) {\n await slack.deleteMessage(event.channel, messageTs);\n messageTs = 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/slack/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 { SlackBot, SlackEvent } from \"./bot.js\";\n\nexport const SLACK_FORMATTING_GUIDE = `## Slack Formatting (mrkdwn, NOT Markdown)\nBold: *text*, Italic: _text_, Code: \\`code\\`, Block: \\`\\`\\`code\\`\\`\\`, Links: <url|text>\nDo NOT use **double asterisks** or [markdown](links).`;\n\nexport function createSlackAdapters(\n event: SlackEvent,\n slack: SlackBot,\n isEvent?: boolean,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageTs: string | null = null;\n const threadMessageTs: string[] = [];\n let accumulatedText = \"\";\n let isWorking = true;\n const workingIndicator = \" ...\";\n let updatePromise = Promise.resolve();\n\n const user = slack.getUser(event.user);\n\n // Extract event filename for status message\n const eventFilename = isEvent ? event.text.match(/^\\[EVENT:([^:]+):/)?.[1] : undefined;\n\n const rootTs = event.thread_ts ?? event.ts;\n const isThreaded = !!event.thread_ts;\n\n /** Post first top-level message and register thread alias so stop commands can find this session */\n const postFirstMessage = async (text: string): Promise<string> => {\n const ts = await slack.postMessage(event.channel, text);\n if (ts) {\n slack.registerThreadAlias(`${event.channel}:${ts}`, `${event.channel}:${rootTs}`);\n }\n return ts;\n };\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: `${event.channel}:${rootTs}`,\n userId: event.user,\n userName: user?.userName,\n text: event.text,\n attachments: (event.attachments || []).map((a) => ({ name: a.local, localPath: a.local })),\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"slack\",\n formattingGuide: SLACK_FORMATTING_GUIDE,\n channels: slack.getAllChannels().map((c) => ({ id: c.id, name: c.name })),\n users: slack\n .getAllUsers()\n .map((u) => ({ id: u.id, userName: u.userName, displayName: u.displayName })),\n };\n\n const responseCtx = {\n respond: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${text}` : text;\n\n // Truncate accumulated text if too long (Slack limit is 40K, we use 35K for safety)\n const MAX_MAIN_LENGTH = 35000;\n const truncationNote = \"\\n\\n_(message truncated, ask me to elaborate on specific parts)_\";\n if (accumulatedText.length > MAX_MAIN_LENGTH) {\n accumulatedText =\n accumulatedText.substring(0, MAX_MAIN_LENGTH - truncationNote.length) +\n truncationNote;\n }\n\n const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n if (messageTs) {\n await slack.updateMessage(event.channel, messageTs, displayText);\n } else if (isThreaded) {\n // Reply within the user's thread\n messageTs = await slack.postInThread(event.channel, rootTs, displayText);\n } else {\n messageTs = await postFirstMessage(displayText);\n }\n\n if (messageTs) {\n slack.logBotResponse(event.channel, text, messageTs, isThreaded ? rootTs : undefined);\n }\n } catch (err) {\n log.logWarning(\"Slack respond error\", err instanceof Error ? err.message : String(err));\n }\n });\n await updatePromise;\n },\n\n replaceResponse: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n // Replace the accumulated text entirely, with truncation\n const MAX_MAIN_LENGTH = 35000;\n const truncationNote = \"\\n\\n_(message truncated, ask me to elaborate on specific parts)_\";\n if (text.length > MAX_MAIN_LENGTH) {\n accumulatedText =\n text.substring(0, MAX_MAIN_LENGTH - truncationNote.length) + truncationNote;\n } else {\n accumulatedText = text;\n }\n\n const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n if (messageTs) {\n await slack.updateMessage(event.channel, messageTs, displayText);\n } else if (isThreaded) {\n messageTs = await slack.postInThread(event.channel, rootTs, displayText);\n } else {\n messageTs = await postFirstMessage(displayText);\n }\n } catch (err) {\n log.logWarning(\n \"Slack replaceResponse error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n },\n\n respondInThread: async (text: string, options?: { style?: \"muted\" }) => {\n updatePromise = updatePromise.then(async () => {\n try {\n // For threaded sessions, anchor to the user's root thread\n // For channel sessions, anchor to the main bot message\n const threadAnchor = isThreaded ? rootTs : messageTs;\n if (threadAnchor) {\n // Truncate thread messages if too long (20K limit for safety)\n const MAX_THREAD_LENGTH = 20000;\n let threadText = text;\n if (threadText.length > MAX_THREAD_LENGTH) {\n threadText = `${threadText.substring(0, MAX_THREAD_LENGTH - 50)}\\n\\n_(truncated)_`;\n }\n\n // Use context block for muted style (small gray text like Slack's Home tab)\n if (options?.style === \"muted\") {\n const CONTEXT_TEXT_LIMIT = 3000;\n const blockText =\n threadText.length > CONTEXT_TEXT_LIMIT\n ? threadText.substring(0, CONTEXT_TEXT_LIMIT - 20) + \"\\n_(truncated)_\"\n : threadText;\n const ts = await slack.postInThreadBlocks(event.channel, threadAnchor, threadText, [\n { type: \"context\", elements: [{ type: \"mrkdwn\", text: blockText }] },\n ]);\n threadMessageTs.push(ts);\n } else {\n const ts = await slack.postInThread(event.channel, threadAnchor, threadText);\n threadMessageTs.push(ts);\n }\n }\n } catch (err) {\n log.logWarning(\n \"Slack respondInThread error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n },\n\n setTyping: async (isTyping: boolean) => {\n if (isTyping && !messageTs) {\n try {\n const statusText = eventFilename ? `Starting event: ${eventFilename}` : \"Thinking\";\n await slack.setAssistantStatus(event.channel, rootTs, statusText);\n } catch {\n // Assistant API not available — first respond() call will create the message\n }\n }\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await slack.uploadFile(event.channel, filePath, title, rootTs);\n },\n\n setWorking: async (working: boolean) => {\n updatePromise = updatePromise.then(async () => {\n try {\n isWorking = working;\n if (messageTs) {\n const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n const updates: Promise<void>[] = [\n slack.updateMessage(event.channel, messageTs, displayText),\n ];\n if (!working) {\n updates.push(slack.setAssistantStatus(event.channel, rootTs, \"\").catch(() => {}));\n }\n await Promise.all(updates);\n }\n } catch (err) {\n log.logWarning(\n \"Slack setWorking error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n // Clear assistant status first\n try {\n await slack.setAssistantStatus(event.channel, rootTs, \"\");\n } catch {\n // Ignore errors clearing status\n }\n\n // Delete thread messages first (in reverse order)\n for (let i = threadMessageTs.length - 1; i >= 0; i--) {\n try {\n await slack.deleteMessage(event.channel, threadMessageTs[i]);\n } catch {\n // Ignore errors deleting thread messages\n }\n }\n threadMessageTs.length = 0;\n // Then delete main message\n if (messageTs) {\n await slack.deleteMessage(event.channel, messageTs);\n messageTs = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../../../src/adapters/slack/tools/attach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAI7D,QAAA,MAAM,YAAY;;;;EAIhB,CAAC;AAEH,wBAAgB,gBAAgB,IAAI;IAClC,IAAI,EAAE,SAAS,CAAC,OAAO,YAAY,CAAC,CAAC;IACrC,iBAAiB,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;CACtF,CAwCA","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport { basename, resolve as resolvePath } from \"path\";\n\nconst attachSchema = Type.Object({\n label: Type.String({ description: \"Brief description of what you're sharing (shown to user)\" }),\n path: Type.String({ description: \"Path to the file to attach\" }),\n title: Type.Optional(Type.String({ description: \"Title for the file (defaults to filename)\" })),\n});\n\nexport function createAttachTool(): {\n tool: AgentTool<typeof attachSchema>;\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n} {\n let uploadFn: ((filePath: string, title?: string) => Promise<void>) | null = null;\n\n const tool: AgentTool<typeof attachSchema> = {\n name: \"attach\",\n label: \"attach\",\n description:\n \"Attach a file to your response. Use this to share files, images, or documents with the user. Only files from /workspace/ can be attached.\",\n parameters: attachSchema,\n execute: async (\n _toolCallId: string,\n { path, title }: { label: string; path: string; title?: string },\n signal?: AbortSignal,\n ) => {\n if (!uploadFn) {\n throw new Error(\"Upload function not configured\");\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const absolutePath = resolvePath(path);\n const fileName = title || basename(absolutePath);\n\n await uploadFn(absolutePath, fileName);\n\n return {\n content: [{ type: \"text\" as const, text: `Attached file: ${fileName}` }],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setUploadFunction: (fn) => {\n uploadFn = fn;\n },\n };\n}\n"]}
1
+ {"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../../../../src/adapters/slack/tools/attach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AAI7D,QAAA,MAAM,YAAY;;;;EAIhB,CAAC;AAEH,wBAAgB,gBAAgB,IAAI;IAClC,IAAI,EAAE,SAAS,CAAC,OAAO,YAAY,CAAC,CAAC;IACrC,iBAAiB,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;CACtF,CA0CA","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport { basename, extname, resolve as resolvePath } from \"path\";\n\nconst attachSchema = Type.Object({\n label: Type.String({ description: \"Brief description of what you're sharing (shown to user)\" }),\n path: Type.String({ description: \"Path to the file to attach\" }),\n title: Type.Optional(Type.String({ description: \"Title for the file (defaults to filename)\" })),\n});\n\nexport function createAttachTool(): {\n tool: AgentTool<typeof attachSchema>;\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n} {\n let uploadFn: ((filePath: string, title?: string) => Promise<void>) | null = null;\n\n const tool: AgentTool<typeof attachSchema> = {\n name: \"attach\",\n label: \"attach\",\n description:\n \"Attach a file to your response. Use this to share files, images, or documents with the user. Only files from /workspace/ can be attached.\",\n parameters: attachSchema,\n execute: async (\n _toolCallId: string,\n { path, title }: { label: string; path: string; title?: string },\n signal?: AbortSignal,\n ) => {\n if (!uploadFn) {\n throw new Error(\"Upload function not configured\");\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const absolutePath = resolvePath(path);\n const base = basename(absolutePath);\n const ext = extname(base);\n const fileName = title ? (ext && !title.endsWith(ext) ? `${title}${ext}` : title) : base;\n\n await uploadFn(absolutePath, fileName);\n\n return {\n content: [{ type: \"text\" as const, text: `Attached file: ${fileName}` }],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setUploadFunction: (fn) => {\n uploadFn = fn;\n },\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { Type } from "@sinclair/typebox";
2
- import { basename, resolve as resolvePath } from "path";
2
+ import { basename, extname, resolve as resolvePath } from "path";
3
3
  const attachSchema = Type.Object({
4
4
  label: Type.String({ description: "Brief description of what you're sharing (shown to user)" }),
5
5
  path: Type.String({ description: "Path to the file to attach" }),
@@ -20,7 +20,9 @@ export function createAttachTool() {
20
20
  throw new Error("Operation aborted");
21
21
  }
22
22
  const absolutePath = resolvePath(path);
23
- const fileName = title || basename(absolutePath);
23
+ const base = basename(absolutePath);
24
+ const ext = extname(base);
25
+ const fileName = title ? (ext && !title.endsWith(ext) ? `${title}${ext}` : title) : base;
24
26
  await uploadFn(absolutePath, fileName);
25
27
  return {
26
28
  content: [{ type: "text", text: `Attached file: ${fileName}` }],
@@ -1 +1 @@
1
- {"version":3,"file":"attach.js","sourceRoot":"","sources":["../../../../src/adapters/slack/tools/attach.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,MAAM,CAAC;AAExD,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,0DAA0D,EAAE,CAAC;IAC/F,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC;IAChE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC,CAAC;CAChG,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB;IAI9B,IAAI,QAAQ,GAAiE,IAAI,CAAC;IAElF,MAAM,IAAI,GAAmC;QAC3C,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,WAAW,EACT,2IAA2I;QAC7I,UAAU,EAAE,YAAY;QACxB,OAAO,EAAE,KAAK,EACZ,WAAmB,EACnB,EAAE,IAAI,EAAE,KAAK,EAAmD,EAChE,MAAoB,EACpB,EAAE;YACF,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YACvC,CAAC;YAED,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,QAAQ,GAAG,KAAK,IAAI,QAAQ,CAAC,YAAY,CAAC,CAAC;YAEjD,MAAM,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;YAEvC,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,kBAAkB,QAAQ,EAAE,EAAE,CAAC;gBACxE,OAAO,EAAE,SAAS;aACnB,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,OAAO;QACL,IAAI;QACJ,iBAAiB,EAAE,CAAC,EAAE,EAAE,EAAE;YACxB,QAAQ,GAAG,EAAE,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport { basename, resolve as resolvePath } from \"path\";\n\nconst attachSchema = Type.Object({\n label: Type.String({ description: \"Brief description of what you're sharing (shown to user)\" }),\n path: Type.String({ description: \"Path to the file to attach\" }),\n title: Type.Optional(Type.String({ description: \"Title for the file (defaults to filename)\" })),\n});\n\nexport function createAttachTool(): {\n tool: AgentTool<typeof attachSchema>;\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n} {\n let uploadFn: ((filePath: string, title?: string) => Promise<void>) | null = null;\n\n const tool: AgentTool<typeof attachSchema> = {\n name: \"attach\",\n label: \"attach\",\n description:\n \"Attach a file to your response. Use this to share files, images, or documents with the user. Only files from /workspace/ can be attached.\",\n parameters: attachSchema,\n execute: async (\n _toolCallId: string,\n { path, title }: { label: string; path: string; title?: string },\n signal?: AbortSignal,\n ) => {\n if (!uploadFn) {\n throw new Error(\"Upload function not configured\");\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const absolutePath = resolvePath(path);\n const fileName = title || basename(absolutePath);\n\n await uploadFn(absolutePath, fileName);\n\n return {\n content: [{ type: \"text\" as const, text: `Attached file: ${fileName}` }],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setUploadFunction: (fn) => {\n uploadFn = fn;\n },\n };\n}\n"]}
1
+ {"version":3,"file":"attach.js","sourceRoot":"","sources":["../../../../src/adapters/slack/tools/attach.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,MAAM,CAAC;AAEjE,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,0DAA0D,EAAE,CAAC;IAC/F,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC;IAChE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC,CAAC;CAChG,CAAC,CAAC;AAEH,MAAM,UAAU,gBAAgB;IAI9B,IAAI,QAAQ,GAAiE,IAAI,CAAC;IAElF,MAAM,IAAI,GAAmC;QAC3C,IAAI,EAAE,QAAQ;QACd,KAAK,EAAE,QAAQ;QACf,WAAW,EACT,2IAA2I;QAC7I,UAAU,EAAE,YAAY;QACxB,OAAO,EAAE,KAAK,EACZ,WAAmB,EACnB,EAAE,IAAI,EAAE,KAAK,EAAmD,EAChE,MAAoB,EACpB,EAAE;YACF,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;YACvC,CAAC;YAED,MAAM,YAAY,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;YACpC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1B,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAEzF,MAAM,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;YAEvC,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,kBAAkB,QAAQ,EAAE,EAAE,CAAC;gBACxE,OAAO,EAAE,SAAS;aACnB,CAAC;QACJ,CAAC;KACF,CAAC;IAEF,OAAO;QACL,IAAI;QACJ,iBAAiB,EAAE,CAAC,EAAE,EAAE,EAAE;YACxB,QAAQ,GAAG,EAAE,CAAC;QAChB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { Type } from \"@sinclair/typebox\";\nimport { basename, extname, resolve as resolvePath } from \"path\";\n\nconst attachSchema = Type.Object({\n label: Type.String({ description: \"Brief description of what you're sharing (shown to user)\" }),\n path: Type.String({ description: \"Path to the file to attach\" }),\n title: Type.Optional(Type.String({ description: \"Title for the file (defaults to filename)\" })),\n});\n\nexport function createAttachTool(): {\n tool: AgentTool<typeof attachSchema>;\n setUploadFunction: (fn: (filePath: string, title?: string) => Promise<void>) => void;\n} {\n let uploadFn: ((filePath: string, title?: string) => Promise<void>) | null = null;\n\n const tool: AgentTool<typeof attachSchema> = {\n name: \"attach\",\n label: \"attach\",\n description:\n \"Attach a file to your response. Use this to share files, images, or documents with the user. Only files from /workspace/ can be attached.\",\n parameters: attachSchema,\n execute: async (\n _toolCallId: string,\n { path, title }: { label: string; path: string; title?: string },\n signal?: AbortSignal,\n ) => {\n if (!uploadFn) {\n throw new Error(\"Upload function not configured\");\n }\n\n if (signal?.aborted) {\n throw new Error(\"Operation aborted\");\n }\n\n const absolutePath = resolvePath(path);\n const base = basename(absolutePath);\n const ext = extname(base);\n const fileName = title ? (ext && !title.endsWith(ext) ? `${title}${ext}` : title) : base;\n\n await uploadFn(absolutePath, fileName);\n\n return {\n content: [{ type: \"text\" as const, text: `Attached file: ${fileName}` }],\n details: undefined,\n };\n },\n };\n\n return {\n tool,\n setUploadFunction: (fn) => {\n uploadFn = fn;\n },\n };\n}\n"]}
@@ -6,6 +6,7 @@ export interface TelegramEvent extends BotEvent {
6
6
  export declare class TelegramBot implements Bot {
7
7
  private client;
8
8
  private handler;
9
+ private botToken;
9
10
  private workingDir;
10
11
  private botUserId;
11
12
  private botUsername;
@@ -29,13 +30,13 @@ export declare class TelegramBot implements Bot {
29
30
  logBotResponse(channel: string, text: string, ts: string): void;
30
31
  /**
31
32
  * Process attachments from a Telegram message
32
- * Downloads files in background and returns metadata
33
+ * Downloads files before returning metadata so the agent can read them immediately
33
34
  * Returns format compatible with ChatMessage: { name: string, localPath: string }[]
34
35
  */
35
- processAttachments(chatId: string, message: any): {
36
+ processAttachments(chatId: string, message: any): Promise<{
36
37
  name: string;
37
38
  localPath: string;
38
- }[];
39
+ }[]>;
39
40
  private processTelegramFile;
40
41
  private getQueue;
41
42
  private isAddressedToBot;
@@ -1 +1 @@
1
- {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/bot.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAQhF,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAuCD,qBAAa,WAAY,YAAW,GAAG;IACrC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,WAAW,CAAa;IAEhC,YAAY,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAO7E;IAMK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAe3B;IAEK,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGhE;IAEK,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW5E;IAED,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAcrC;IAED,eAAe,IAAI,YAAY,CAQ9B;IAMK,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;IAEK,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMvF;IAEK,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;IAEK,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9C;IAEK,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF;IAED,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAI9C;IAED,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAS9D;IAED;;;;OAIG;IACH,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAkCtF;YAKa,mBAAmB;IAgDjC,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,kBAAkB;CAyE3B","sourcesContent":["import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { Bot as GrammyBot, InputFile } from \"grammy\";\nimport type { Bot, BotEvent, BotHandler, PlatformInfo } from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { createTelegramAdapters } from \"./context.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TelegramEvent extends BotEvent {\n type: \"message\" | \"command\";\n userName?: string;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n private queue: QueuedWork[] = [];\n private processing = false;\n\n enqueue(work: QueuedWork): void {\n this.queue.push(work);\n this.processNext();\n }\n\n size(): number {\n return this.queue.length;\n }\n\n private async processNext(): Promise<void> {\n if (this.processing || this.queue.length === 0) return;\n this.processing = true;\n const work = this.queue.shift()!;\n try {\n await work();\n } catch (err) {\n log.logWarning(\"Telegram queue error\", err instanceof Error ? err.message : String(err));\n }\n this.processing = false;\n this.processNext();\n }\n}\n\n// ============================================================================\n// TelegramBot\n// ============================================================================\n\nexport class TelegramBot implements Bot {\n private client: GrammyBot;\n private handler: BotHandler;\n private workingDir: string;\n private botUserId: string | null = null;\n private botUsername: string | null = null;\n private queues = new Map<string, ChannelQueue>();\n private startupTime: number = 0;\n\n constructor(handler: BotHandler, config: { token: string; workingDir: string }) {\n this.handler = handler;\n this.workingDir = config.workingDir;\n this.client = new GrammyBot(config.token);\n this.client.catch((err) => {\n log.logWarning(\"Telegram error\", err instanceof Error ? err.message : String(err));\n });\n }\n\n // ==========================================================================\n // Public API (implements Bot)\n // ==========================================================================\n\n async start(): Promise<void> {\n const me = await this.client.api.getMe();\n this.botUserId = String(me.id);\n this.botUsername = me.username ?? null;\n this.startupTime = Date.now();\n\n this.setupEventHandlers();\n\n // Start polling in background (bot.start() runs indefinitely)\n this.client.start().catch((err) => {\n log.logWarning(\"Telegram polling error\", err instanceof Error ? err.message : String(err));\n });\n\n log.logConnected();\n log.logInfo(`Telegram bot started as @${this.botUsername ?? this.botUserId}`);\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n const result = await this.postMessageRaw(parseInt(channel), text);\n return String(result);\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n try {\n await this.client.api.editMessageText(parseInt(channel), parseInt(ts), text, {\n parse_mode: \"HTML\",\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"message is not modified\")) {\n throw err;\n }\n }\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const queue = this.getQueue(event.channel);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${event.channel}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${event.channel}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const adapters = createTelegramAdapters(event as TelegramEvent, this, true);\n return this.handler.handleEvent(event, this, adapters, true);\n });\n return true;\n }\n\n getPlatformInfo(): PlatformInfo {\n return {\n name: \"telegram\",\n formattingGuide:\n '## 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>',\n channels: [],\n users: [],\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async postMessageRaw(chatId: number, text: string): Promise<number> {\n const result = await this.client.api.sendMessage(chatId, text, { parse_mode: \"HTML\" });\n return result.message_id;\n }\n\n async postReply(chatId: number, replyToMessageId: number, text: string): Promise<number> {\n const result = await this.client.api.sendMessage(chatId, text, {\n parse_mode: \"HTML\",\n reply_parameters: { message_id: replyToMessageId },\n });\n return result.message_id;\n }\n\n async deleteMessageRaw(chatId: number, messageId: number): Promise<void> {\n await this.client.api.deleteMessage(chatId, messageId);\n }\n\n async sendTyping(chatId: number): Promise<void> {\n await this.client.api.sendChatAction(chatId, \"typing\");\n }\n\n async uploadFile(channel: string, filePath: string, title?: string): Promise<void> {\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await this.client.api.sendDocument(parseInt(channel), new InputFile(fileContent, fileName));\n }\n\n logToFile(channel: string, entry: object): void {\n const dir = join(this.workingDir, channel);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, \"log.jsonl\"), `${JSON.stringify(entry)}\\n`);\n }\n\n logBotResponse(channel: string, text: string, ts: string): void {\n this.logToFile(channel, {\n date: new Date().toISOString(),\n ts,\n user: \"bot\",\n text,\n attachments: [],\n isBot: true,\n });\n }\n\n /**\n * Process attachments from a Telegram message\n * Downloads files in background and returns metadata\n * Returns format compatible with ChatMessage: { name: string, localPath: string }[]\n */\n processAttachments(chatId: string, message: any): { name: string; localPath: string }[] {\n const result: { name: string; localPath: string }[] = [];\n\n // Handle photos (take the largest size for best quality)\n if (message.photo && message.photo.length > 0) {\n const photos = message.photo;\n const photo = photos[photos.length - 1]; // Largest photo\n const fileId = photo.file_id;\n\n this.processTelegramFile(chatId, fileId, `photo_${message.message_id}.jpg`)\n .then((attachment) => {\n if (attachment) result.push(attachment);\n })\n .catch((err) => {\n log.logWarning(`Failed to download Telegram photo`, `${err}`);\n });\n }\n\n // Handle documents\n if (message.document) {\n const doc = message.document;\n const fileId = doc.file_id;\n const fileName = doc.file_name ?? `document_${message.message_id}`;\n\n this.processTelegramFile(chatId, fileId, fileName)\n .then((attachment) => {\n if (attachment) result.push(attachment);\n })\n .catch((err) => {\n log.logWarning(`Failed to download Telegram document`, `${err}`);\n });\n }\n\n return result;\n }\n\n /**\n * Download a file from Telegram and return attachment metadata\n */\n private async processTelegramFile(\n chatId: string,\n fileId: string,\n originalName: string,\n ): Promise<{ name: string; localPath: string } | null> {\n try {\n // Get file info from Telegram\n const file = await this.client.api.getFile(fileId);\n if (!file.file_path) {\n log.logWarning(\"Telegram file has no path\", fileId);\n return null;\n }\n\n // Generate local filename\n const ts = Date.now();\n const sanitizedName = originalName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const filename = `${ts}_${sanitizedName}`;\n const localPath = `${chatId}/attachments/${filename}`;\n const fullDir = join(this.workingDir, chatId, \"attachments\");\n\n if (!existsSync(fullDir)) mkdirSync(fullDir, { recursive: true });\n\n // Construct download URL\n const downloadUrl = `https://api.telegram.org/file/bot${this.botUserId}/${file.file_path}`;\n\n // Download the file\n const response = await fetch(downloadUrl);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n writeFileSync(join(fullDir, filename), Buffer.from(buffer));\n\n return {\n name: originalName,\n localPath: localPath,\n };\n } catch (err) {\n log.logWarning(`Failed to process Telegram file`, `${originalName}: ${err}`);\n return null;\n }\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): ChannelQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new ChannelQueue();\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private isAddressedToBot(text: string, chatType: string): boolean {\n if (chatType === \"private\") return true;\n if (!this.botUsername) return false;\n return text.includes(`@${this.botUsername}`);\n }\n\n private cleanText(text: string): string {\n if (!this.botUsername) return text.trim();\n return text.replace(new RegExp(`@${this.botUsername}`, \"g\"), \"\").trim();\n }\n\n private setupEventHandlers(): void {\n this.client.on(\"message\", (ctx) => {\n const msg = ctx.message;\n\n // Skip messages from before startup (Telegram replays recent messages on poll start)\n if (msg.date * 1000 < this.startupTime) return;\n // Skip bot messages\n if (msg.from?.is_bot) return;\n\n const text = msg.text ?? msg.caption ?? \"\";\n if (!text && !msg.document && !msg.photo) return;\n\n const chatId = String(msg.chat.id);\n const chatType = msg.chat.type;\n const userId = String(msg.from?.id ?? \"unknown\");\n const userName = msg.from?.username ?? msg.from?.first_name ?? userId;\n const msgId = String(msg.message_id);\n\n // Determine thread: if this is a reply, use parent message_id as thread_ts\n const replyToId = msg.reply_to_message?.message_id;\n const threadTs = replyToId ? String(replyToId) : undefined;\n\n // Check if addressed to bot\n if (!this.isAddressedToBot(text, chatType)) return;\n\n const cleanedText = this.cleanText(text);\n const sessionKey = `${chatId}:${threadTs ?? msgId}`;\n\n // Process attachments (starts download in background)\n const processedAttachments = this.processAttachments(chatId, msg);\n\n const event: TelegramEvent = {\n type: \"message\",\n channel: chatId,\n ts: msgId,\n thread_ts: threadTs,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log the message\n this.logToFile(chatId, {\n date: new Date(msg.date * 1000).toISOString(),\n ts: msgId,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n // Handle /stop command\n if (cleanedText.toLowerCase() === \"/stop\" || cleanedText.toLowerCase() === \"stop\") {\n if (this.handler.isRunning(sessionKey)) {\n this.handler.handleStop(sessionKey, chatId, this);\n } else {\n this.postMessage(chatId, \"Nothing running.\");\n }\n return;\n }\n\n if (this.handler.isRunning(sessionKey)) {\n this.postMessage(chatId, \"Already working. Say <code>stop</code> to cancel.\");\n } else {\n this.getQueue(sessionKey).enqueue(() => {\n const adapters = createTelegramAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n }\n });\n }\n}\n"]}
1
+ {"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/telegram/bot.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAQhF,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAuCD,qBAAa,WAAY,YAAW,GAAG;IACrC,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,WAAW,CAAa;IAEhC,YAAY,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,EAQ7E;IAMK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAe3B;IAEK,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGhE;IAEK,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAW5E;IAED,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAcrC;IAED,eAAe,IAAI,YAAY,CAQ9B;IAMK,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGlE;IAEK,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMvF;IAEK,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;IAEK,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9C;IAEK,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF;IAED,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAI9C;IAED,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAS9D;IAED;;;;OAIG;IACG,kBAAkB,CACtB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,GAAG,GACX,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAyBhD;YAKa,mBAAmB;IAgDjC,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,kBAAkB;CAyE3B","sourcesContent":["import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { Bot as GrammyBot, InputFile } from \"grammy\";\nimport type { Bot, BotEvent, BotHandler, PlatformInfo } from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { createTelegramAdapters } from \"./context.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TelegramEvent extends BotEvent {\n type: \"message\" | \"command\";\n userName?: string;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n private queue: QueuedWork[] = [];\n private processing = false;\n\n enqueue(work: QueuedWork): void {\n this.queue.push(work);\n this.processNext();\n }\n\n size(): number {\n return this.queue.length;\n }\n\n private async processNext(): Promise<void> {\n if (this.processing || this.queue.length === 0) return;\n this.processing = true;\n const work = this.queue.shift()!;\n try {\n await work();\n } catch (err) {\n log.logWarning(\"Telegram queue error\", err instanceof Error ? err.message : String(err));\n }\n this.processing = false;\n this.processNext();\n }\n}\n\n// ============================================================================\n// TelegramBot\n// ============================================================================\n\nexport class TelegramBot implements Bot {\n private client: GrammyBot;\n private handler: BotHandler;\n private botToken: string;\n private workingDir: string;\n private botUserId: string | null = null;\n private botUsername: string | null = null;\n private queues = new Map<string, ChannelQueue>();\n private startupTime: number = 0;\n\n constructor(handler: BotHandler, config: { token: string; workingDir: string }) {\n this.handler = handler;\n this.botToken = config.token;\n this.workingDir = config.workingDir;\n this.client = new GrammyBot(config.token);\n this.client.catch((err) => {\n log.logWarning(\"Telegram error\", err instanceof Error ? err.message : String(err));\n });\n }\n\n // ==========================================================================\n // Public API (implements Bot)\n // ==========================================================================\n\n async start(): Promise<void> {\n const me = await this.client.api.getMe();\n this.botUserId = String(me.id);\n this.botUsername = me.username ?? null;\n this.startupTime = Date.now();\n\n this.setupEventHandlers();\n\n // Start polling in background (bot.start() runs indefinitely)\n this.client.start().catch((err) => {\n log.logWarning(\"Telegram polling error\", err instanceof Error ? err.message : String(err));\n });\n\n log.logConnected();\n log.logInfo(`Telegram bot started as @${this.botUsername ?? this.botUserId}`);\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n const result = await this.postMessageRaw(parseInt(channel), text);\n return String(result);\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n try {\n await this.client.api.editMessageText(parseInt(channel), parseInt(ts), text, {\n parse_mode: \"HTML\",\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"message is not modified\")) {\n throw err;\n }\n }\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const queue = this.getQueue(event.channel);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${event.channel}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${event.channel}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const adapters = createTelegramAdapters(event as TelegramEvent, this, true);\n return this.handler.handleEvent(event, this, adapters, true);\n });\n return true;\n }\n\n getPlatformInfo(): PlatformInfo {\n return {\n name: \"telegram\",\n formattingGuide:\n '## 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>',\n channels: [],\n users: [],\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async postMessageRaw(chatId: number, text: string): Promise<number> {\n const result = await this.client.api.sendMessage(chatId, text, { parse_mode: \"HTML\" });\n return result.message_id;\n }\n\n async postReply(chatId: number, replyToMessageId: number, text: string): Promise<number> {\n const result = await this.client.api.sendMessage(chatId, text, {\n parse_mode: \"HTML\",\n reply_parameters: { message_id: replyToMessageId },\n });\n return result.message_id;\n }\n\n async deleteMessageRaw(chatId: number, messageId: number): Promise<void> {\n await this.client.api.deleteMessage(chatId, messageId);\n }\n\n async sendTyping(chatId: number): Promise<void> {\n await this.client.api.sendChatAction(chatId, \"typing\");\n }\n\n async uploadFile(channel: string, filePath: string, title?: string): Promise<void> {\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await this.client.api.sendDocument(parseInt(channel), new InputFile(fileContent, fileName));\n }\n\n logToFile(channel: string, entry: object): void {\n const dir = join(this.workingDir, channel);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, \"log.jsonl\"), `${JSON.stringify(entry)}\\n`);\n }\n\n logBotResponse(channel: string, text: string, ts: string): void {\n this.logToFile(channel, {\n date: new Date().toISOString(),\n ts,\n user: \"bot\",\n text,\n attachments: [],\n isBot: true,\n });\n }\n\n /**\n * Process attachments from a Telegram message\n * Downloads files before returning metadata so the agent can read them immediately\n * Returns format compatible with ChatMessage: { name: string, localPath: string }[]\n */\n async processAttachments(\n chatId: string,\n message: any,\n ): Promise<{ name: string; localPath: string }[]> {\n const downloads: Array<Promise<{ name: string; localPath: string } | null>> = [];\n\n // Handle photos (take the largest size for best quality)\n if (message.photo && message.photo.length > 0) {\n const photos = message.photo;\n const photo = photos[photos.length - 1]; // Largest photo\n const fileId = photo.file_id;\n\n downloads.push(this.processTelegramFile(chatId, fileId, `photo_${message.message_id}.jpg`));\n }\n\n // Handle documents\n if (message.document) {\n const doc = message.document;\n const fileId = doc.file_id;\n const fileName = doc.file_name ?? `document_${message.message_id}`;\n\n downloads.push(this.processTelegramFile(chatId, fileId, fileName));\n }\n\n const attachments = await Promise.all(downloads);\n return attachments.filter(\n (attachment): attachment is { name: string; localPath: string } => attachment !== null,\n );\n }\n\n /**\n * Download a file from Telegram and return attachment metadata\n */\n private async processTelegramFile(\n chatId: string,\n fileId: string,\n originalName: string,\n ): Promise<{ name: string; localPath: string } | null> {\n try {\n // Get file info from Telegram\n const file = await this.client.api.getFile(fileId);\n if (!file.file_path) {\n log.logWarning(\"Telegram file has no path\", fileId);\n return null;\n }\n\n // Generate local filename\n const ts = Date.now();\n const sanitizedName = originalName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const filename = `${ts}_${sanitizedName}`;\n const localPath = `${chatId}/attachments/${filename}`;\n const fullDir = join(this.workingDir, chatId, \"attachments\");\n\n if (!existsSync(fullDir)) mkdirSync(fullDir, { recursive: true });\n\n // Construct download URL\n const downloadUrl = `https://api.telegram.org/file/bot${this.botToken}/${file.file_path}`;\n\n // Download the file\n const response = await fetch(downloadUrl);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n writeFileSync(join(fullDir, filename), Buffer.from(buffer));\n\n return {\n name: originalName,\n localPath: localPath,\n };\n } catch (err) {\n log.logWarning(`Failed to process Telegram file`, `${originalName}: ${err}`);\n return null;\n }\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): ChannelQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new ChannelQueue();\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private isAddressedToBot(text: string, chatType: string): boolean {\n if (chatType === \"private\") return true;\n if (!this.botUsername) return false;\n return text.includes(`@${this.botUsername}`);\n }\n\n private cleanText(text: string): string {\n if (!this.botUsername) return text.trim();\n return text.replace(new RegExp(`@${this.botUsername}`, \"g\"), \"\").trim();\n }\n\n private setupEventHandlers(): void {\n this.client.on(\"message\", async (ctx) => {\n const msg = ctx.message;\n\n // Skip messages from before startup (Telegram replays recent messages on poll start)\n if (msg.date * 1000 < this.startupTime) return;\n // Skip bot messages\n if (msg.from?.is_bot) return;\n\n const text = msg.text ?? msg.caption ?? \"\";\n if (!text && !msg.document && !msg.photo) return;\n\n const chatId = String(msg.chat.id);\n const chatType = msg.chat.type;\n const userId = String(msg.from?.id ?? \"unknown\");\n const userName = msg.from?.username ?? msg.from?.first_name ?? userId;\n const msgId = String(msg.message_id);\n\n // Determine thread: if this is a reply, use parent message_id as thread_ts\n const replyToId = msg.reply_to_message?.message_id;\n const threadTs = replyToId ? String(replyToId) : undefined;\n\n // Check if addressed to bot\n if (!this.isAddressedToBot(text, chatType)) return;\n\n const cleanedText = this.cleanText(text);\n const sessionKey = `${chatId}:${threadTs ?? msgId}`;\n\n // Process attachments (starts download in background)\n const processedAttachments = await this.processAttachments(chatId, msg);\n\n const event: TelegramEvent = {\n type: \"message\",\n channel: chatId,\n ts: msgId,\n thread_ts: threadTs,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log the message\n this.logToFile(chatId, {\n date: new Date(msg.date * 1000).toISOString(),\n ts: msgId,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n // Handle /stop command\n if (cleanedText.toLowerCase() === \"/stop\" || cleanedText.toLowerCase() === \"stop\") {\n if (this.handler.isRunning(sessionKey)) {\n this.handler.handleStop(sessionKey, chatId, this);\n } else {\n this.postMessage(chatId, \"Nothing running.\");\n }\n return;\n }\n\n if (this.handler.isRunning(sessionKey)) {\n this.postMessage(chatId, \"Already working. Say <code>stop</code> to cancel.\");\n } else {\n this.getQueue(sessionKey).enqueue(() => {\n const adapters = createTelegramAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n }\n });\n }\n}\n"]}
@@ -40,6 +40,7 @@ export class TelegramBot {
40
40
  this.queues = new Map();
41
41
  this.startupTime = 0;
42
42
  this.handler = handler;
43
+ this.botToken = config.token;
43
44
  this.workingDir = config.workingDir;
44
45
  this.client = new GrammyBot(config.token);
45
46
  this.client.catch((err) => {
@@ -143,40 +144,27 @@ export class TelegramBot {
143
144
  }
144
145
  /**
145
146
  * Process attachments from a Telegram message
146
- * Downloads files in background and returns metadata
147
+ * Downloads files before returning metadata so the agent can read them immediately
147
148
  * Returns format compatible with ChatMessage: { name: string, localPath: string }[]
148
149
  */
149
- processAttachments(chatId, message) {
150
- const result = [];
150
+ async processAttachments(chatId, message) {
151
+ const downloads = [];
151
152
  // Handle photos (take the largest size for best quality)
152
153
  if (message.photo && message.photo.length > 0) {
153
154
  const photos = message.photo;
154
155
  const photo = photos[photos.length - 1]; // Largest photo
155
156
  const fileId = photo.file_id;
156
- this.processTelegramFile(chatId, fileId, `photo_${message.message_id}.jpg`)
157
- .then((attachment) => {
158
- if (attachment)
159
- result.push(attachment);
160
- })
161
- .catch((err) => {
162
- log.logWarning(`Failed to download Telegram photo`, `${err}`);
163
- });
157
+ downloads.push(this.processTelegramFile(chatId, fileId, `photo_${message.message_id}.jpg`));
164
158
  }
165
159
  // Handle documents
166
160
  if (message.document) {
167
161
  const doc = message.document;
168
162
  const fileId = doc.file_id;
169
163
  const fileName = doc.file_name ?? `document_${message.message_id}`;
170
- this.processTelegramFile(chatId, fileId, fileName)
171
- .then((attachment) => {
172
- if (attachment)
173
- result.push(attachment);
174
- })
175
- .catch((err) => {
176
- log.logWarning(`Failed to download Telegram document`, `${err}`);
177
- });
164
+ downloads.push(this.processTelegramFile(chatId, fileId, fileName));
178
165
  }
179
- return result;
166
+ const attachments = await Promise.all(downloads);
167
+ return attachments.filter((attachment) => attachment !== null);
180
168
  }
181
169
  /**
182
170
  * Download a file from Telegram and return attachment metadata
@@ -198,7 +186,7 @@ export class TelegramBot {
198
186
  if (!existsSync(fullDir))
199
187
  mkdirSync(fullDir, { recursive: true });
200
188
  // Construct download URL
201
- const downloadUrl = `https://api.telegram.org/file/bot${this.botUserId}/${file.file_path}`;
189
+ const downloadUrl = `https://api.telegram.org/file/bot${this.botToken}/${file.file_path}`;
202
190
  // Download the file
203
191
  const response = await fetch(downloadUrl);
204
192
  if (!response.ok) {
@@ -240,7 +228,7 @@ export class TelegramBot {
240
228
  return text.replace(new RegExp(`@${this.botUsername}`, "g"), "").trim();
241
229
  }
242
230
  setupEventHandlers() {
243
- this.client.on("message", (ctx) => {
231
+ this.client.on("message", async (ctx) => {
244
232
  const msg = ctx.message;
245
233
  // Skip messages from before startup (Telegram replays recent messages on poll start)
246
234
  if (msg.date * 1000 < this.startupTime)
@@ -265,7 +253,7 @@ export class TelegramBot {
265
253
  const cleanedText = this.cleanText(text);
266
254
  const sessionKey = `${chatId}:${threadTs ?? msgId}`;
267
255
  // Process attachments (starts download in background)
268
- const processedAttachments = this.processAttachments(chatId, msg);
256
+ const processedAttachments = await this.processAttachments(chatId, msg);
269
257
  const event = {
270
258
  type: "message",
271
259
  channel: chatId,
@@ -1 +1 @@
1
- {"version":3,"file":"bot.js","sourceRoot":"","sources":["../../../src/adapters/telegram/bot.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { Bot as GrammyBot, InputFile } from \"grammy\";\nimport type { Bot, BotEvent, BotHandler, PlatformInfo } from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { createTelegramAdapters } from \"./context.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TelegramEvent extends BotEvent {\n type: \"message\" | \"command\";\n userName?: string;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n private queue: QueuedWork[] = [];\n private processing = false;\n\n enqueue(work: QueuedWork): void {\n this.queue.push(work);\n this.processNext();\n }\n\n size(): number {\n return this.queue.length;\n }\n\n private async processNext(): Promise<void> {\n if (this.processing || this.queue.length === 0) return;\n this.processing = true;\n const work = this.queue.shift()!;\n try {\n await work();\n } catch (err) {\n log.logWarning(\"Telegram queue error\", err instanceof Error ? err.message : String(err));\n }\n this.processing = false;\n this.processNext();\n }\n}\n\n// ============================================================================\n// TelegramBot\n// ============================================================================\n\nexport class TelegramBot implements Bot {\n private client: GrammyBot;\n private handler: BotHandler;\n private workingDir: string;\n private botUserId: string | null = null;\n private botUsername: string | null = null;\n private queues = new Map<string, ChannelQueue>();\n private startupTime: number = 0;\n\n constructor(handler: BotHandler, config: { token: string; workingDir: string }) {\n this.handler = handler;\n this.workingDir = config.workingDir;\n this.client = new GrammyBot(config.token);\n this.client.catch((err) => {\n log.logWarning(\"Telegram error\", err instanceof Error ? err.message : String(err));\n });\n }\n\n // ==========================================================================\n // Public API (implements Bot)\n // ==========================================================================\n\n async start(): Promise<void> {\n const me = await this.client.api.getMe();\n this.botUserId = String(me.id);\n this.botUsername = me.username ?? null;\n this.startupTime = Date.now();\n\n this.setupEventHandlers();\n\n // Start polling in background (bot.start() runs indefinitely)\n this.client.start().catch((err) => {\n log.logWarning(\"Telegram polling error\", err instanceof Error ? err.message : String(err));\n });\n\n log.logConnected();\n log.logInfo(`Telegram bot started as @${this.botUsername ?? this.botUserId}`);\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n const result = await this.postMessageRaw(parseInt(channel), text);\n return String(result);\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n try {\n await this.client.api.editMessageText(parseInt(channel), parseInt(ts), text, {\n parse_mode: \"HTML\",\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"message is not modified\")) {\n throw err;\n }\n }\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const queue = this.getQueue(event.channel);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${event.channel}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${event.channel}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const adapters = createTelegramAdapters(event as TelegramEvent, this, true);\n return this.handler.handleEvent(event, this, adapters, true);\n });\n return true;\n }\n\n getPlatformInfo(): PlatformInfo {\n return {\n name: \"telegram\",\n formattingGuide:\n '## 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>',\n channels: [],\n users: [],\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async postMessageRaw(chatId: number, text: string): Promise<number> {\n const result = await this.client.api.sendMessage(chatId, text, { parse_mode: \"HTML\" });\n return result.message_id;\n }\n\n async postReply(chatId: number, replyToMessageId: number, text: string): Promise<number> {\n const result = await this.client.api.sendMessage(chatId, text, {\n parse_mode: \"HTML\",\n reply_parameters: { message_id: replyToMessageId },\n });\n return result.message_id;\n }\n\n async deleteMessageRaw(chatId: number, messageId: number): Promise<void> {\n await this.client.api.deleteMessage(chatId, messageId);\n }\n\n async sendTyping(chatId: number): Promise<void> {\n await this.client.api.sendChatAction(chatId, \"typing\");\n }\n\n async uploadFile(channel: string, filePath: string, title?: string): Promise<void> {\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await this.client.api.sendDocument(parseInt(channel), new InputFile(fileContent, fileName));\n }\n\n logToFile(channel: string, entry: object): void {\n const dir = join(this.workingDir, channel);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, \"log.jsonl\"), `${JSON.stringify(entry)}\\n`);\n }\n\n logBotResponse(channel: string, text: string, ts: string): void {\n this.logToFile(channel, {\n date: new Date().toISOString(),\n ts,\n user: \"bot\",\n text,\n attachments: [],\n isBot: true,\n });\n }\n\n /**\n * Process attachments from a Telegram message\n * Downloads files in background and returns metadata\n * Returns format compatible with ChatMessage: { name: string, localPath: string }[]\n */\n processAttachments(chatId: string, message: any): { name: string; localPath: string }[] {\n const result: { name: string; localPath: string }[] = [];\n\n // Handle photos (take the largest size for best quality)\n if (message.photo && message.photo.length > 0) {\n const photos = message.photo;\n const photo = photos[photos.length - 1]; // Largest photo\n const fileId = photo.file_id;\n\n this.processTelegramFile(chatId, fileId, `photo_${message.message_id}.jpg`)\n .then((attachment) => {\n if (attachment) result.push(attachment);\n })\n .catch((err) => {\n log.logWarning(`Failed to download Telegram photo`, `${err}`);\n });\n }\n\n // Handle documents\n if (message.document) {\n const doc = message.document;\n const fileId = doc.file_id;\n const fileName = doc.file_name ?? `document_${message.message_id}`;\n\n this.processTelegramFile(chatId, fileId, fileName)\n .then((attachment) => {\n if (attachment) result.push(attachment);\n })\n .catch((err) => {\n log.logWarning(`Failed to download Telegram document`, `${err}`);\n });\n }\n\n return result;\n }\n\n /**\n * Download a file from Telegram and return attachment metadata\n */\n private async processTelegramFile(\n chatId: string,\n fileId: string,\n originalName: string,\n ): Promise<{ name: string; localPath: string } | null> {\n try {\n // Get file info from Telegram\n const file = await this.client.api.getFile(fileId);\n if (!file.file_path) {\n log.logWarning(\"Telegram file has no path\", fileId);\n return null;\n }\n\n // Generate local filename\n const ts = Date.now();\n const sanitizedName = originalName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const filename = `${ts}_${sanitizedName}`;\n const localPath = `${chatId}/attachments/${filename}`;\n const fullDir = join(this.workingDir, chatId, \"attachments\");\n\n if (!existsSync(fullDir)) mkdirSync(fullDir, { recursive: true });\n\n // Construct download URL\n const downloadUrl = `https://api.telegram.org/file/bot${this.botUserId}/${file.file_path}`;\n\n // Download the file\n const response = await fetch(downloadUrl);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n writeFileSync(join(fullDir, filename), Buffer.from(buffer));\n\n return {\n name: originalName,\n localPath: localPath,\n };\n } catch (err) {\n log.logWarning(`Failed to process Telegram file`, `${originalName}: ${err}`);\n return null;\n }\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): ChannelQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new ChannelQueue();\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private isAddressedToBot(text: string, chatType: string): boolean {\n if (chatType === \"private\") return true;\n if (!this.botUsername) return false;\n return text.includes(`@${this.botUsername}`);\n }\n\n private cleanText(text: string): string {\n if (!this.botUsername) return text.trim();\n return text.replace(new RegExp(`@${this.botUsername}`, \"g\"), \"\").trim();\n }\n\n private setupEventHandlers(): void {\n this.client.on(\"message\", (ctx) => {\n const msg = ctx.message;\n\n // Skip messages from before startup (Telegram replays recent messages on poll start)\n if (msg.date * 1000 < this.startupTime) return;\n // Skip bot messages\n if (msg.from?.is_bot) return;\n\n const text = msg.text ?? msg.caption ?? \"\";\n if (!text && !msg.document && !msg.photo) return;\n\n const chatId = String(msg.chat.id);\n const chatType = msg.chat.type;\n const userId = String(msg.from?.id ?? \"unknown\");\n const userName = msg.from?.username ?? msg.from?.first_name ?? userId;\n const msgId = String(msg.message_id);\n\n // Determine thread: if this is a reply, use parent message_id as thread_ts\n const replyToId = msg.reply_to_message?.message_id;\n const threadTs = replyToId ? String(replyToId) : undefined;\n\n // Check if addressed to bot\n if (!this.isAddressedToBot(text, chatType)) return;\n\n const cleanedText = this.cleanText(text);\n const sessionKey = `${chatId}:${threadTs ?? msgId}`;\n\n // Process attachments (starts download in background)\n const processedAttachments = this.processAttachments(chatId, msg);\n\n const event: TelegramEvent = {\n type: \"message\",\n channel: chatId,\n ts: msgId,\n thread_ts: threadTs,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log the message\n this.logToFile(chatId, {\n date: new Date(msg.date * 1000).toISOString(),\n ts: msgId,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n // Handle /stop command\n if (cleanedText.toLowerCase() === \"/stop\" || cleanedText.toLowerCase() === \"stop\") {\n if (this.handler.isRunning(sessionKey)) {\n this.handler.handleStop(sessionKey, chatId, this);\n } else {\n this.postMessage(chatId, \"Nothing running.\");\n }\n return;\n }\n\n if (this.handler.isRunning(sessionKey)) {\n this.postMessage(chatId, \"Already working. Say <code>stop</code> to cancel.\");\n } else {\n this.getQueue(sessionKey).enqueue(() => {\n const adapters = createTelegramAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n }\n });\n }\n}\n"]}
1
+ {"version":3,"file":"bot.js","sourceRoot":"","sources":["../../../src/adapters/telegram/bot.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { Bot as GrammyBot, InputFile } from \"grammy\";\nimport type { Bot, BotEvent, BotHandler, PlatformInfo } from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport { createTelegramAdapters } from \"./context.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TelegramEvent extends BotEvent {\n type: \"message\" | \"command\";\n userName?: string;\n}\n\n// ============================================================================\n// Per-channel queue for sequential processing\n// ============================================================================\n\ntype QueuedWork = () => Promise<void>;\n\nclass ChannelQueue {\n private queue: QueuedWork[] = [];\n private processing = false;\n\n enqueue(work: QueuedWork): void {\n this.queue.push(work);\n this.processNext();\n }\n\n size(): number {\n return this.queue.length;\n }\n\n private async processNext(): Promise<void> {\n if (this.processing || this.queue.length === 0) return;\n this.processing = true;\n const work = this.queue.shift()!;\n try {\n await work();\n } catch (err) {\n log.logWarning(\"Telegram queue error\", err instanceof Error ? err.message : String(err));\n }\n this.processing = false;\n this.processNext();\n }\n}\n\n// ============================================================================\n// TelegramBot\n// ============================================================================\n\nexport class TelegramBot implements Bot {\n private client: GrammyBot;\n private handler: BotHandler;\n private botToken: string;\n private workingDir: string;\n private botUserId: string | null = null;\n private botUsername: string | null = null;\n private queues = new Map<string, ChannelQueue>();\n private startupTime: number = 0;\n\n constructor(handler: BotHandler, config: { token: string; workingDir: string }) {\n this.handler = handler;\n this.botToken = config.token;\n this.workingDir = config.workingDir;\n this.client = new GrammyBot(config.token);\n this.client.catch((err) => {\n log.logWarning(\"Telegram error\", err instanceof Error ? err.message : String(err));\n });\n }\n\n // ==========================================================================\n // Public API (implements Bot)\n // ==========================================================================\n\n async start(): Promise<void> {\n const me = await this.client.api.getMe();\n this.botUserId = String(me.id);\n this.botUsername = me.username ?? null;\n this.startupTime = Date.now();\n\n this.setupEventHandlers();\n\n // Start polling in background (bot.start() runs indefinitely)\n this.client.start().catch((err) => {\n log.logWarning(\"Telegram polling error\", err instanceof Error ? err.message : String(err));\n });\n\n log.logConnected();\n log.logInfo(`Telegram bot started as @${this.botUsername ?? this.botUserId}`);\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n const result = await this.postMessageRaw(parseInt(channel), text);\n return String(result);\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n try {\n await this.client.api.editMessageText(parseInt(channel), parseInt(ts), text, {\n parse_mode: \"HTML\",\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!msg.includes(\"message is not modified\")) {\n throw err;\n }\n }\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const queue = this.getQueue(event.channel);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${event.channel}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${event.channel}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const adapters = createTelegramAdapters(event as TelegramEvent, this, true);\n return this.handler.handleEvent(event, this, adapters, true);\n });\n return true;\n }\n\n getPlatformInfo(): PlatformInfo {\n return {\n name: \"telegram\",\n formattingGuide:\n '## 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>',\n channels: [],\n users: [],\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async postMessageRaw(chatId: number, text: string): Promise<number> {\n const result = await this.client.api.sendMessage(chatId, text, { parse_mode: \"HTML\" });\n return result.message_id;\n }\n\n async postReply(chatId: number, replyToMessageId: number, text: string): Promise<number> {\n const result = await this.client.api.sendMessage(chatId, text, {\n parse_mode: \"HTML\",\n reply_parameters: { message_id: replyToMessageId },\n });\n return result.message_id;\n }\n\n async deleteMessageRaw(chatId: number, messageId: number): Promise<void> {\n await this.client.api.deleteMessage(chatId, messageId);\n }\n\n async sendTyping(chatId: number): Promise<void> {\n await this.client.api.sendChatAction(chatId, \"typing\");\n }\n\n async uploadFile(channel: string, filePath: string, title?: string): Promise<void> {\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await this.client.api.sendDocument(parseInt(channel), new InputFile(fileContent, fileName));\n }\n\n logToFile(channel: string, entry: object): void {\n const dir = join(this.workingDir, channel);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, \"log.jsonl\"), `${JSON.stringify(entry)}\\n`);\n }\n\n logBotResponse(channel: string, text: string, ts: string): void {\n this.logToFile(channel, {\n date: new Date().toISOString(),\n ts,\n user: \"bot\",\n text,\n attachments: [],\n isBot: true,\n });\n }\n\n /**\n * Process attachments from a Telegram message\n * Downloads files before returning metadata so the agent can read them immediately\n * Returns format compatible with ChatMessage: { name: string, localPath: string }[]\n */\n async processAttachments(\n chatId: string,\n message: any,\n ): Promise<{ name: string; localPath: string }[]> {\n const downloads: Array<Promise<{ name: string; localPath: string } | null>> = [];\n\n // Handle photos (take the largest size for best quality)\n if (message.photo && message.photo.length > 0) {\n const photos = message.photo;\n const photo = photos[photos.length - 1]; // Largest photo\n const fileId = photo.file_id;\n\n downloads.push(this.processTelegramFile(chatId, fileId, `photo_${message.message_id}.jpg`));\n }\n\n // Handle documents\n if (message.document) {\n const doc = message.document;\n const fileId = doc.file_id;\n const fileName = doc.file_name ?? `document_${message.message_id}`;\n\n downloads.push(this.processTelegramFile(chatId, fileId, fileName));\n }\n\n const attachments = await Promise.all(downloads);\n return attachments.filter(\n (attachment): attachment is { name: string; localPath: string } => attachment !== null,\n );\n }\n\n /**\n * Download a file from Telegram and return attachment metadata\n */\n private async processTelegramFile(\n chatId: string,\n fileId: string,\n originalName: string,\n ): Promise<{ name: string; localPath: string } | null> {\n try {\n // Get file info from Telegram\n const file = await this.client.api.getFile(fileId);\n if (!file.file_path) {\n log.logWarning(\"Telegram file has no path\", fileId);\n return null;\n }\n\n // Generate local filename\n const ts = Date.now();\n const sanitizedName = originalName.replace(/[^a-zA-Z0-9._-]/g, \"_\");\n const filename = `${ts}_${sanitizedName}`;\n const localPath = `${chatId}/attachments/${filename}`;\n const fullDir = join(this.workingDir, chatId, \"attachments\");\n\n if (!existsSync(fullDir)) mkdirSync(fullDir, { recursive: true });\n\n // Construct download URL\n const downloadUrl = `https://api.telegram.org/file/bot${this.botToken}/${file.file_path}`;\n\n // Download the file\n const response = await fetch(downloadUrl);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const buffer = await response.arrayBuffer();\n writeFileSync(join(fullDir, filename), Buffer.from(buffer));\n\n return {\n name: originalName,\n localPath: localPath,\n };\n } catch (err) {\n log.logWarning(`Failed to process Telegram file`, `${originalName}: ${err}`);\n return null;\n }\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): ChannelQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new ChannelQueue();\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private isAddressedToBot(text: string, chatType: string): boolean {\n if (chatType === \"private\") return true;\n if (!this.botUsername) return false;\n return text.includes(`@${this.botUsername}`);\n }\n\n private cleanText(text: string): string {\n if (!this.botUsername) return text.trim();\n return text.replace(new RegExp(`@${this.botUsername}`, \"g\"), \"\").trim();\n }\n\n private setupEventHandlers(): void {\n this.client.on(\"message\", async (ctx) => {\n const msg = ctx.message;\n\n // Skip messages from before startup (Telegram replays recent messages on poll start)\n if (msg.date * 1000 < this.startupTime) return;\n // Skip bot messages\n if (msg.from?.is_bot) return;\n\n const text = msg.text ?? msg.caption ?? \"\";\n if (!text && !msg.document && !msg.photo) return;\n\n const chatId = String(msg.chat.id);\n const chatType = msg.chat.type;\n const userId = String(msg.from?.id ?? \"unknown\");\n const userName = msg.from?.username ?? msg.from?.first_name ?? userId;\n const msgId = String(msg.message_id);\n\n // Determine thread: if this is a reply, use parent message_id as thread_ts\n const replyToId = msg.reply_to_message?.message_id;\n const threadTs = replyToId ? String(replyToId) : undefined;\n\n // Check if addressed to bot\n if (!this.isAddressedToBot(text, chatType)) return;\n\n const cleanedText = this.cleanText(text);\n const sessionKey = `${chatId}:${threadTs ?? msgId}`;\n\n // Process attachments (starts download in background)\n const processedAttachments = await this.processAttachments(chatId, msg);\n\n const event: TelegramEvent = {\n type: \"message\",\n channel: chatId,\n ts: msgId,\n thread_ts: threadTs,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log the message\n this.logToFile(chatId, {\n date: new Date(msg.date * 1000).toISOString(),\n ts: msgId,\n user: userId,\n userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n // Handle /stop command\n if (cleanedText.toLowerCase() === \"/stop\" || cleanedText.toLowerCase() === \"stop\") {\n if (this.handler.isRunning(sessionKey)) {\n this.handler.handleStop(sessionKey, chatId, this);\n } else {\n this.postMessage(chatId, \"Nothing running.\");\n }\n return;\n }\n\n if (this.handler.isRunning(sessionKey)) {\n this.postMessage(chatId, \"Already working. Say <code>stop</code> to cancel.\");\n } else {\n this.getQueue(sessionKey).enqueue(() => {\n const adapters = createTelegramAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n }\n });\n }\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import type { ChatMessage, ChatResponseContext, PlatformInfo } from "../../adapter.js";
2
2
  import type { TelegramBot, TelegramEvent } from "./bot.js";
3
3
  export declare 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.";
4
- export declare function createTelegramAdapters(event: TelegramEvent, bot: TelegramBot, isEvent?: boolean): {
4
+ export declare function createTelegramAdapters(event: TelegramEvent, bot: TelegramBot, _isEvent?: boolean): {
5
5
  message: ChatMessage;
6
6
  responseCtx: ChatResponseContext;
7
7
  platform: PlatformInfo;