@geminixiang/mama 0.2.0-beta.2 → 0.2.0-beta.4
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.
- package/README.md +69 -41
- package/dist/adapter.d.ts +14 -4
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +8 -5
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +252 -98
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +83 -21
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/shared.d.ts +71 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +168 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/adapters/slack/bot.d.ts +5 -21
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +148 -150
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +21 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -0
- package/dist/adapters/slack/branch-manager.js +96 -0
- package/dist/adapters/slack/branch-manager.js.map +1 -0
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +92 -56
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +3 -0
- package/dist/adapters/slack/session.d.ts.map +1 -0
- package/dist/adapters/slack/session.js +16 -0
- package/dist/adapters/slack/session.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +89 -103
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +40 -14
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/agent.d.ts +2 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +71 -142
- package/dist/agent.js.map +1 -1
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +3 -2
- package/dist/bindings.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -3
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +11 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +100 -16
- package/dist/context.js.map +1 -1
- package/dist/events.d.ts +7 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +61 -30
- package/dist/events.js.map +1 -1
- package/dist/fs-atomic.d.ts +10 -0
- package/dist/fs-atomic.d.ts.map +1 -0
- package/dist/fs-atomic.js +45 -0
- package/dist/fs-atomic.js.map +1 -0
- package/dist/{login.d.ts → login/index.d.ts} +1 -1
- package/dist/login/index.d.ts.map +1 -0
- package/dist/{login.js → login/index.js} +1 -1
- package/dist/login/index.js.map +1 -0
- package/dist/{link-server.d.ts → login/portal.d.ts} +5 -4
- package/dist/login/portal.d.ts.map +1 -0
- package/dist/login/portal.js +1453 -0
- package/dist/login/portal.js.map +1 -0
- package/dist/{link-token.d.ts → login/session.d.ts} +1 -1
- package/dist/login/session.d.ts.map +1 -0
- package/dist/{link-token.js → login/session.js} +1 -1
- package/dist/login/session.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +89 -19
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +17 -2
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +84 -5
- package/dist/provisioner.js.map +1 -1
- package/dist/session-policy.d.ts +13 -0
- package/dist/session-policy.d.ts.map +1 -0
- package/dist/session-policy.js +23 -0
- package/dist/session-policy.js.map +1 -0
- package/dist/session-store.d.ts +31 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +168 -6
- package/dist/session-store.js.map +1 -1
- package/dist/session-view/command.d.ts +5 -0
- package/dist/session-view/command.d.ts.map +1 -0
- package/dist/session-view/command.js +11 -0
- package/dist/session-view/command.js.map +1 -0
- package/dist/session-view/portal.d.ts +11 -0
- package/dist/session-view/portal.d.ts.map +1 -0
- package/dist/session-view/portal.js +795 -0
- package/dist/session-view/portal.js.map +1 -0
- package/dist/session-view/service.d.ts +34 -0
- package/dist/session-view/service.d.ts.map +1 -0
- package/dist/session-view/service.js +416 -0
- package/dist/session-view/service.js.map +1 -0
- package/dist/session-view/store.d.ts +16 -0
- package/dist/session-view/store.d.ts.map +1 -0
- package/dist/session-view/store.js +38 -0
- package/dist/session-view/store.js.map +1 -0
- package/dist/store.d.ts +3 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +15 -35
- package/dist/store.js.map +1 -1
- package/dist/tools/event.d.ts +2 -0
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js +21 -3
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +11 -55
- package/dist/vault.js.map +1 -1
- package/package.json +7 -8
- package/dist/link-server.d.ts.map +0 -1
- package/dist/link-server.js +0 -899
- package/dist/link-server.js.map +0 -1
- package/dist/link-token.d.ts.map +0 -1
- package/dist/link-token.js.map +0 -1
- package/dist/login.d.ts.map +0 -1
- package/dist/login.js.map +0 -1
|
@@ -1,7 +1,29 @@
|
|
|
1
1
|
import * as log from "../../log.js";
|
|
2
|
+
import { formatToolArgs, splitText } from "../shared.js";
|
|
3
|
+
import { resolveSlackRootTs, resolveSlackSessionKey } from "./session.js";
|
|
2
4
|
export const SLACK_FORMATTING_GUIDE = `## Slack Formatting (mrkdwn, NOT Markdown)
|
|
3
5
|
Bold: *text*, Italic: _text_, Code: \`code\`, Block: \`\`\`code\`\`\`, Links: <url|text>
|
|
4
6
|
Do NOT use **double asterisks** or [markdown](links).`;
|
|
7
|
+
const MAX_MAIN_LENGTH = 35000; // Slack hard limit is 40k
|
|
8
|
+
const MAX_THREAD_LENGTH = 20000;
|
|
9
|
+
const TRUNCATION_NOTE_INCREMENTAL = "\n\n_(message truncated, ask me to elaborate on specific parts)_";
|
|
10
|
+
const TRUNCATION_NOTE_FINAL = "\n\n_(see thread for full response)_";
|
|
11
|
+
const formatSlackContinuation = (partNum) => `_(continued ${partNum})_`;
|
|
12
|
+
function isSlackMessageTs(ts) {
|
|
13
|
+
return typeof ts === "string" && /^\d+\.\d+$/.test(ts);
|
|
14
|
+
}
|
|
15
|
+
function formatSlackToolResult(result) {
|
|
16
|
+
const argsFormatted = formatToolArgs(result.args);
|
|
17
|
+
const duration = (result.durationMs / 1000).toFixed(1);
|
|
18
|
+
let text = `*${result.isError ? "✗" : "✓"} ${result.toolName}*`;
|
|
19
|
+
if (result.label)
|
|
20
|
+
text += `: ${result.label}`;
|
|
21
|
+
text += ` (${duration}s)\n`;
|
|
22
|
+
if (argsFormatted)
|
|
23
|
+
text += `\`\`\`\n${argsFormatted}\n\`\`\`\n`;
|
|
24
|
+
text += `*Result:*\n\`\`\`\n${result.result}\n\`\`\``;
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
5
27
|
export function createSlackAdapters(event, slack, isEvent) {
|
|
6
28
|
let messageTs = null;
|
|
7
29
|
const threadMessageTs = [];
|
|
@@ -14,16 +36,49 @@ export function createSlackAdapters(event, slack, isEvent) {
|
|
|
14
36
|
const user = slack.getUser(event.user);
|
|
15
37
|
// Extract event filename for status message
|
|
16
38
|
const eventFilename = isEvent ? event.text.match(/^\[EVENT:([^:]+):/)?.[1] : undefined;
|
|
17
|
-
const rootTs = event.thread_ts ?? event.ts;
|
|
39
|
+
const rootTs = event.thread_ts ?? (isSlackMessageTs(event.ts) ? resolveSlackRootTs(event.ts) : undefined);
|
|
18
40
|
const isThreaded = !!event.thread_ts;
|
|
19
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Post the first visible reply.
|
|
43
|
+
* Normal Slack messages reply in-thread under the triggering user message.
|
|
44
|
+
* Synthetic event messages have no real Slack root ts, so they must post top-level.
|
|
45
|
+
*/
|
|
20
46
|
const postFirstMessage = async (text) => {
|
|
21
|
-
|
|
47
|
+
if (isEvent) {
|
|
48
|
+
if (event.thread_ts) {
|
|
49
|
+
return slack.postInThread(channelId, event.thread_ts, text);
|
|
50
|
+
}
|
|
51
|
+
return slack.postMessage(channelId, text);
|
|
52
|
+
}
|
|
53
|
+
return isSlackMessageTs(event.ts)
|
|
54
|
+
? slack.postInThread(channelId, event.ts, text)
|
|
55
|
+
: slack.postMessage(channelId, text);
|
|
56
|
+
};
|
|
57
|
+
const postDiagnosticDirect = async (text, options) => {
|
|
58
|
+
const threadAnchor = rootTs ?? messageTs;
|
|
59
|
+
if (!threadAnchor)
|
|
60
|
+
return;
|
|
61
|
+
for (const part of splitText(text, MAX_THREAD_LENGTH, formatSlackContinuation)) {
|
|
62
|
+
if (options?.style === "muted") {
|
|
63
|
+
const CONTEXT_TEXT_LIMIT = 3000;
|
|
64
|
+
const blockText = part.length > CONTEXT_TEXT_LIMIT
|
|
65
|
+
? part.substring(0, CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
|
|
66
|
+
: part;
|
|
67
|
+
const ts = await slack.postInThreadBlocks(channelId, threadAnchor, part, [
|
|
68
|
+
{ type: "context", elements: [{ type: "mrkdwn", text: blockText }] },
|
|
69
|
+
]);
|
|
70
|
+
threadMessageTs.push(ts);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const diagnosticText = options?.style === "error" ? `_${part}_` : part;
|
|
74
|
+
const ts = await slack.postInThread(channelId, threadAnchor, diagnosticText);
|
|
75
|
+
threadMessageTs.push(ts);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
22
78
|
};
|
|
23
79
|
const message = {
|
|
24
80
|
id: event.ts,
|
|
25
|
-
sessionKey: event.sessionKey ??
|
|
26
|
-
(event.thread_ts ? `${conversationId}:${event.thread_ts}` : conversationId),
|
|
81
|
+
sessionKey: event.sessionKey ?? resolveSlackSessionKey(conversationId, event.thread_ts),
|
|
27
82
|
conversationKind: event.conversationKind,
|
|
28
83
|
userId: event.user,
|
|
29
84
|
userName: user?.userName,
|
|
@@ -41,25 +96,25 @@ export function createSlackAdapters(event, slack, isEvent) {
|
|
|
41
96
|
users: slack
|
|
42
97
|
.getAllUsers()
|
|
43
98
|
.map((u) => ({ id: u.id, userName: u.userName, displayName: u.displayName })),
|
|
99
|
+
diagnostics: {
|
|
100
|
+
showUsageSummary: true,
|
|
101
|
+
},
|
|
44
102
|
};
|
|
45
103
|
const responseCtx = {
|
|
46
104
|
respond: async (text) => {
|
|
47
105
|
updatePromise = updatePromise.then(async () => {
|
|
48
106
|
try {
|
|
49
107
|
accumulatedText = accumulatedText ? `${accumulatedText}\n${text}` : text;
|
|
50
|
-
// Truncate accumulated text if too long (Slack limit is 40K, we use 35K for safety)
|
|
51
|
-
const MAX_MAIN_LENGTH = 35000;
|
|
52
|
-
const truncationNote = "\n\n_(message truncated, ask me to elaborate on specific parts)_";
|
|
53
108
|
if (accumulatedText.length > MAX_MAIN_LENGTH) {
|
|
54
109
|
accumulatedText =
|
|
55
|
-
accumulatedText.substring(0, MAX_MAIN_LENGTH -
|
|
56
|
-
|
|
110
|
+
accumulatedText.substring(0, MAX_MAIN_LENGTH - TRUNCATION_NOTE_INCREMENTAL.length) +
|
|
111
|
+
TRUNCATION_NOTE_INCREMENTAL;
|
|
57
112
|
}
|
|
58
113
|
const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;
|
|
59
114
|
if (messageTs) {
|
|
60
115
|
await slack.updateMessage(channelId, messageTs, displayText);
|
|
61
116
|
}
|
|
62
|
-
else if (isThreaded) {
|
|
117
|
+
else if (isThreaded && rootTs) {
|
|
63
118
|
// Reply within the user's thread
|
|
64
119
|
messageTs = await slack.postInThread(channelId, rootTs, displayText);
|
|
65
120
|
}
|
|
@@ -79,26 +134,24 @@ export function createSlackAdapters(event, slack, isEvent) {
|
|
|
79
134
|
replaceResponse: async (text) => {
|
|
80
135
|
updatePromise = updatePromise.then(async () => {
|
|
81
136
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
text.substring(0, MAX_MAIN_LENGTH - truncationNote.length) + truncationNote;
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
accumulatedText = text;
|
|
91
|
-
}
|
|
137
|
+
const overflowed = text.length > MAX_MAIN_LENGTH;
|
|
138
|
+
accumulatedText = overflowed
|
|
139
|
+
? text.substring(0, MAX_MAIN_LENGTH - TRUNCATION_NOTE_FINAL.length) +
|
|
140
|
+
TRUNCATION_NOTE_FINAL
|
|
141
|
+
: text;
|
|
92
142
|
const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;
|
|
93
143
|
if (messageTs) {
|
|
94
144
|
await slack.updateMessage(channelId, messageTs, displayText);
|
|
95
145
|
}
|
|
96
|
-
else if (isThreaded) {
|
|
146
|
+
else if (isThreaded && rootTs) {
|
|
97
147
|
messageTs = await slack.postInThread(channelId, rootTs, displayText);
|
|
98
148
|
}
|
|
99
149
|
else {
|
|
100
150
|
messageTs = await postFirstMessage(displayText);
|
|
101
151
|
}
|
|
152
|
+
if (overflowed) {
|
|
153
|
+
await postDiagnosticDirect(text);
|
|
154
|
+
}
|
|
102
155
|
}
|
|
103
156
|
catch (err) {
|
|
104
157
|
log.logWarning("Slack replaceResponse error", err instanceof Error ? err.message : String(err));
|
|
@@ -106,43 +159,22 @@ export function createSlackAdapters(event, slack, isEvent) {
|
|
|
106
159
|
});
|
|
107
160
|
await updatePromise;
|
|
108
161
|
},
|
|
109
|
-
|
|
162
|
+
respondDiagnostic: async (text, options) => {
|
|
110
163
|
updatePromise = updatePromise.then(async () => {
|
|
111
164
|
try {
|
|
112
|
-
|
|
113
|
-
const threadAnchor = rootTs;
|
|
114
|
-
if (threadAnchor) {
|
|
115
|
-
// Truncate thread messages if too long (20K limit for safety)
|
|
116
|
-
const MAX_THREAD_LENGTH = 20000;
|
|
117
|
-
let threadText = text;
|
|
118
|
-
if (threadText.length > MAX_THREAD_LENGTH) {
|
|
119
|
-
threadText = `${threadText.substring(0, MAX_THREAD_LENGTH - 50)}\n\n_(truncated)_`;
|
|
120
|
-
}
|
|
121
|
-
// Use context block for muted style (small gray text like Slack's Home tab)
|
|
122
|
-
if (options?.style === "muted") {
|
|
123
|
-
const CONTEXT_TEXT_LIMIT = 3000;
|
|
124
|
-
const blockText = threadText.length > CONTEXT_TEXT_LIMIT
|
|
125
|
-
? threadText.substring(0, CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
|
|
126
|
-
: threadText;
|
|
127
|
-
const ts = await slack.postInThreadBlocks(channelId, threadAnchor, threadText, [
|
|
128
|
-
{ type: "context", elements: [{ type: "mrkdwn", text: blockText }] },
|
|
129
|
-
]);
|
|
130
|
-
threadMessageTs.push(ts);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
const ts = await slack.postInThread(channelId, threadAnchor, threadText);
|
|
134
|
-
threadMessageTs.push(ts);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
165
|
+
await postDiagnosticDirect(text, options);
|
|
137
166
|
}
|
|
138
167
|
catch (err) {
|
|
139
|
-
log.logWarning("Slack
|
|
168
|
+
log.logWarning("Slack respondDiagnostic error", err instanceof Error ? err.message : String(err));
|
|
140
169
|
}
|
|
141
170
|
});
|
|
142
171
|
await updatePromise;
|
|
143
172
|
},
|
|
173
|
+
respondToolResult: async (result) => {
|
|
174
|
+
await responseCtx.respondDiagnostic(formatSlackToolResult(result));
|
|
175
|
+
},
|
|
144
176
|
setTyping: async (isTyping) => {
|
|
145
|
-
if (isTyping && !messageTs) {
|
|
177
|
+
if (isTyping && !messageTs && rootTs) {
|
|
146
178
|
try {
|
|
147
179
|
const statusText = eventFilename ? `Starting event: ${eventFilename}` : "Thinking";
|
|
148
180
|
await slack.setAssistantStatus(channelId, rootTs, statusText);
|
|
@@ -165,7 +197,9 @@ export function createSlackAdapters(event, slack, isEvent) {
|
|
|
165
197
|
slack.updateMessage(channelId, messageTs, displayText),
|
|
166
198
|
];
|
|
167
199
|
if (!working) {
|
|
168
|
-
|
|
200
|
+
if (rootTs) {
|
|
201
|
+
updates.push(slack.setAssistantStatus(channelId, rootTs, "").catch(() => { }));
|
|
202
|
+
}
|
|
169
203
|
}
|
|
170
204
|
await Promise.all(updates);
|
|
171
205
|
}
|
|
@@ -179,11 +213,13 @@ export function createSlackAdapters(event, slack, isEvent) {
|
|
|
179
213
|
deleteResponse: async () => {
|
|
180
214
|
updatePromise = updatePromise.then(async () => {
|
|
181
215
|
// Clear assistant status first
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
216
|
+
if (rootTs) {
|
|
217
|
+
try {
|
|
218
|
+
await slack.setAssistantStatus(channelId, rootTs, "");
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Ignore errors clearing status
|
|
222
|
+
}
|
|
187
223
|
}
|
|
188
224
|
// Delete thread messages first (in reverse order)
|
|
189
225
|
for (let i = threadMessageTs.length - 1; i >= 0; i--) {
|
|
@@ -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 channelId = event.channel;\n const conversationId = event.conversationId;\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 reply in-thread under the user's message, creating a thread if none exists */\n const postFirstMessage = async (text: string): Promise<string> => {\n return slack.postInThread(channelId, event.ts, text);\n };\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey:\n event.sessionKey ??\n (event.thread_ts ? `${conversationId}:${event.thread_ts}` : conversationId),\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: user?.userName,\n text: event.text,\n attachments: (event.attachments || []).map((a) => ({\n name: a.original,\n localPath: a.localPath,\n })),\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(channelId, messageTs, displayText);\n } else if (isThreaded) {\n // Reply within the user's thread\n messageTs = await slack.postInThread(channelId, rootTs, displayText);\n } else {\n messageTs = await postFirstMessage(displayText);\n }\n\n if (messageTs) {\n slack.logBotResponse(channelId, 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(channelId, messageTs, displayText);\n } else if (isThreaded) {\n messageTs = await slack.postInThread(channelId, 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 // Always anchor to the thread root (event.thread_ts ?? event.ts)\n const threadAnchor = rootTs;\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(channelId, threadAnchor, threadText, [\n { type: \"context\", elements: [{ type: \"mrkdwn\", text: blockText }] },\n ]);\n threadMessageTs.push(ts);\n } else {\n const ts = await slack.postInThread(channelId, 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(channelId, 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(channelId, 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(channelId, messageTs, displayText),\n ];\n if (!working) {\n updates.push(slack.setAssistantStatus(channelId, 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(channelId, 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(channelId, 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(channelId, 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 {\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 type { SlackBot, SlackEvent } from \"./bot.js\";\nimport { resolveSlackRootTs, resolveSlackSessionKey } from \"./session.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\nconst MAX_MAIN_LENGTH = 35000; // Slack hard limit is 40k\nconst MAX_THREAD_LENGTH = 20000;\nconst TRUNCATION_NOTE_INCREMENTAL =\n \"\\n\\n_(message truncated, ask me to elaborate on specific parts)_\";\nconst TRUNCATION_NOTE_FINAL = \"\\n\\n_(see thread for full response)_\";\n\nconst formatSlackContinuation = (partNum: number): string => `_(continued ${partNum})_`;\n\nfunction isSlackMessageTs(ts: string | undefined): ts is string {\n return typeof ts === \"string\" && /^\\d+\\.\\d+$/.test(ts);\n}\n\nfunction formatSlackToolResult(result: ChatToolResult): string {\n const argsFormatted = formatToolArgs(result.args);\n const duration = (result.durationMs / 1000).toFixed(1);\n let text = `*${result.isError ? \"✗\" : \"✓\"} ${result.toolName}*`;\n if (result.label) text += `: ${result.label}`;\n text += ` (${duration}s)\\n`;\n if (argsFormatted) text += `\\`\\`\\`\\n${argsFormatted}\\n\\`\\`\\`\\n`;\n text += `*Result:*\\n\\`\\`\\`\\n${result.result}\\n\\`\\`\\``;\n return text;\n}\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 channelId = event.channel;\n const conversationId = event.conversationId;\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 =\n event.thread_ts ?? (isSlackMessageTs(event.ts) ? resolveSlackRootTs(event.ts) : undefined);\n const isThreaded = !!event.thread_ts;\n\n /**\n * Post the first visible reply.\n * Normal Slack messages reply in-thread under the triggering user message.\n * Synthetic event messages have no real Slack root ts, so they must post top-level.\n */\n const postFirstMessage = async (text: string): Promise<string> => {\n if (isEvent) {\n if (event.thread_ts) {\n return slack.postInThread(channelId, event.thread_ts, text);\n }\n return slack.postMessage(channelId, text);\n }\n return isSlackMessageTs(event.ts)\n ? slack.postInThread(channelId, event.ts, text)\n : slack.postMessage(channelId, text);\n };\n\n const postDiagnosticDirect = async (\n text: string,\n options?: { style?: \"muted\" | \"error\" },\n ): Promise<void> => {\n const threadAnchor = rootTs ?? messageTs;\n if (!threadAnchor) return;\n\n for (const part of splitText(text, MAX_THREAD_LENGTH, formatSlackContinuation)) {\n if (options?.style === \"muted\") {\n const CONTEXT_TEXT_LIMIT = 3000;\n const blockText =\n part.length > CONTEXT_TEXT_LIMIT\n ? part.substring(0, CONTEXT_TEXT_LIMIT - 20) + \"\\n_(truncated)_\"\n : part;\n const ts = await slack.postInThreadBlocks(channelId, threadAnchor, part, [\n { type: \"context\", elements: [{ type: \"mrkdwn\", text: blockText }] },\n ]);\n threadMessageTs.push(ts);\n } else {\n const diagnosticText = options?.style === \"error\" ? `_${part}_` : part;\n const ts = await slack.postInThread(channelId, threadAnchor, diagnosticText);\n threadMessageTs.push(ts);\n }\n }\n };\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: event.sessionKey ?? resolveSlackSessionKey(conversationId, event.thread_ts),\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: user?.userName,\n text: event.text,\n attachments: (event.attachments || []).map((a) => ({\n name: a.original,\n localPath: a.localPath,\n })),\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 diagnostics: {\n showUsageSummary: true,\n },\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 if (accumulatedText.length > MAX_MAIN_LENGTH) {\n accumulatedText =\n accumulatedText.substring(0, MAX_MAIN_LENGTH - TRUNCATION_NOTE_INCREMENTAL.length) +\n TRUNCATION_NOTE_INCREMENTAL;\n }\n\n const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n if (messageTs) {\n await slack.updateMessage(channelId, messageTs, displayText);\n } else if (isThreaded && rootTs) {\n // Reply within the user's thread\n messageTs = await slack.postInThread(channelId, rootTs, displayText);\n } else {\n messageTs = await postFirstMessage(displayText);\n }\n\n if (messageTs) {\n slack.logBotResponse(channelId, 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 const overflowed = text.length > MAX_MAIN_LENGTH;\n accumulatedText = overflowed\n ? text.substring(0, MAX_MAIN_LENGTH - TRUNCATION_NOTE_FINAL.length) +\n TRUNCATION_NOTE_FINAL\n : text;\n\n const displayText = isWorking ? accumulatedText + workingIndicator : accumulatedText;\n\n if (messageTs) {\n await slack.updateMessage(channelId, messageTs, displayText);\n } else if (isThreaded && rootTs) {\n messageTs = await slack.postInThread(channelId, rootTs, displayText);\n } else {\n messageTs = await postFirstMessage(displayText);\n }\n\n if (overflowed) {\n await postDiagnosticDirect(text);\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 respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n updatePromise = updatePromise.then(async () => {\n try {\n await postDiagnosticDirect(text, options);\n } catch (err) {\n log.logWarning(\n \"Slack respondDiagnostic error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatSlackToolResult(result));\n },\n\n setTyping: async (isTyping: boolean) => {\n if (isTyping && !messageTs && rootTs) {\n try {\n const statusText = eventFilename ? `Starting event: ${eventFilename}` : \"Thinking\";\n await slack.setAssistantStatus(channelId, 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(channelId, 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(channelId, messageTs, displayText),\n ];\n if (!working) {\n if (rootTs) {\n updates.push(slack.setAssistantStatus(channelId, rootTs, \"\").catch(() => {}));\n }\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 if (rootTs) {\n try {\n await slack.setAssistantStatus(channelId, rootTs, \"\");\n } catch {\n // Ignore errors clearing status\n }\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(channelId, 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(channelId, messageTs);\n messageTs = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/session.ts"],"names":[],"mappings":"AAGA,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAUnF;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAE/E","sourcesContent":["import type { ConversationKind } from \"../../adapter.js\";\nimport { resolveChatSessionKey } from \"../../session-policy.js\";\n\nexport function resolveSlackSessionKey(channelId: string, threadTs?: string): string {\n const conversationKind: ConversationKind = channelId.startsWith(\"D\") ? \"direct\" : \"shared\";\n return resolveChatSessionKey({\n conversationId: channelId,\n conversationKind,\n messageId: channelId,\n threadTs,\n persistentTopLevel: true,\n scopeDirectThreads: true,\n });\n}\n\nexport function resolveSlackRootTs(messageTs: string, threadTs?: string): string {\n return threadTs || messageTs;\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { resolveChatSessionKey } from "../../session-policy.js";
|
|
2
|
+
export function resolveSlackSessionKey(channelId, threadTs) {
|
|
3
|
+
const conversationKind = channelId.startsWith("D") ? "direct" : "shared";
|
|
4
|
+
return resolveChatSessionKey({
|
|
5
|
+
conversationId: channelId,
|
|
6
|
+
conversationKind,
|
|
7
|
+
messageId: channelId,
|
|
8
|
+
threadTs,
|
|
9
|
+
persistentTopLevel: true,
|
|
10
|
+
scopeDirectThreads: true,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export function resolveSlackRootTs(messageTs, threadTs) {
|
|
14
|
+
return threadTs || messageTs;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../src/adapters/slack/session.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,MAAM,UAAU,sBAAsB,CAAC,SAAiB,EAAE,QAAiB;IACzE,MAAM,gBAAgB,GAAqB,SAAS,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC3F,OAAO,qBAAqB,CAAC;QAC3B,cAAc,EAAE,SAAS;QACzB,gBAAgB;QAChB,SAAS,EAAE,SAAS;QACpB,QAAQ;QACR,kBAAkB,EAAE,IAAI;QACxB,kBAAkB,EAAE,IAAI;KACzB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,SAAiB,EAAE,QAAiB;IACrE,OAAO,QAAQ,IAAI,SAAS,CAAC;AAC/B,CAAC","sourcesContent":["import type { ConversationKind } from \"../../adapter.js\";\nimport { resolveChatSessionKey } from \"../../session-policy.js\";\n\nexport function resolveSlackSessionKey(channelId: string, threadTs?: string): string {\n const conversationKind: ConversationKind = channelId.startsWith(\"D\") ? \"direct\" : \"shared\";\n return resolveChatSessionKey({\n conversationId: channelId,\n conversationKind,\n messageId: channelId,\n threadTs,\n persistentTopLevel: true,\n scopeDirectThreads: true,\n });\n}\n\nexport function resolveSlackRootTs(messageTs: string, threadTs?: string): string {\n return threadTs || messageTs;\n}\n"]}
|
|
@@ -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;AAUhF,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAwDD,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,CAuB3B;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,CAsB5E;IAED,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAerC;IAED,eAAe,IAAI,YAAY,CAQ9B;IAMK,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAclE;IAEK,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAElE;IAEK,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBvF;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,qBAAqB;IAmC7B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,iBAAiB;IAezB,OAAO,CAAC,kBAAkB;CA8H3B","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 { formatAlreadyWorking, formatNothingRunning } from \"../../ui-copy.js\";\nimport { createTelegramAdapters } from \"./context.js\";\nimport { escapeTelegramHtml } from \"./html.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TelegramEvent extends BotEvent {\n type: \"message\" | \"command\";\n userName?: string;\n}\n\ninterface MessageContext {\n msg: any;\n text: string;\n chatId: string;\n chatType: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n userName: string;\n msgId: string;\n threadTs: string | undefined;\n sessionKey: 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\nfunction isTelegramHtmlParseError(message: string): boolean {\n return message.includes(\"can't parse entities\");\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 await this.client.api.setMyCommands([\n { command: \"start\", description: \"Welcome message\" },\n { command: \"help\", description: \"Show available commands\" },\n { command: \"login\", description: \"Store credentials in your private vault\" },\n { command: \"stop\", description: \"Stop ongoing conversation\" },\n { command: \"new\", description: \"Reset conversation history and start fresh\" },\n ]);\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 return;\n }\n if (!isTelegramHtmlParseError(msg)) {\n throw err;\n }\n await this.client.api.editMessageText(\n parseInt(channel),\n parseInt(ts),\n escapeTelegramHtml(text),\n {\n parse_mode: \"HTML\",\n },\n );\n }\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const conversationId = event.conversationId;\n const queue = this.getQueue(conversationId);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${conversationId}: ${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 try {\n const result = await this.client.api.sendMessage(chatId, text, { parse_mode: \"HTML\" });\n return result.message_id;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!isTelegramHtmlParseError(msg)) {\n throw err;\n }\n const result = await this.client.api.sendMessage(chatId, escapeTelegramHtml(text), {\n parse_mode: \"HTML\",\n });\n return result.message_id;\n }\n }\n\n async postPlainMessage(chatId: number, text: string): Promise<void> {\n await this.client.api.sendMessage(chatId, text);\n }\n\n async postReply(chatId: number, replyToMessageId: number, text: string): Promise<number> {\n try {\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 } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!isTelegramHtmlParseError(msg)) {\n throw err;\n }\n const result = await this.client.api.sendMessage(chatId, escapeTelegramHtml(text), {\n parse_mode: \"HTML\",\n reply_parameters: { message_id: replyToMessageId },\n });\n return result.message_id;\n }\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 extractMessageContext(msg: any): MessageContext | null {\n if (!msg) return null;\n if (msg.date * 1000 < this.startupTime) return null;\n if (msg.from?.is_bot) return null;\n\n const text = msg.text ?? msg.caption ?? \"\";\n if (!text && !msg.document && !msg.photo) return null;\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 const replyToId = msg.reply_to_message?.message_id;\n const threadTs = replyToId ? String(replyToId) : undefined;\n const conversationKind = chatType === \"private\" ? \"direct\" : \"shared\";\n\n // Private chats: single session per chat (no per-message splitting)\n // Groups: per-thread sessions (use reply chain or unique message id)\n const sessionKey = chatType === \"private\" ? chatId : `${chatId}:${threadTs ?? msgId}`;\n\n return {\n msg,\n text,\n chatId,\n chatType,\n conversationKind,\n userId,\n userName,\n msgId,\n threadTs,\n sessionKey,\n };\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.toLowerCase().includes(`@${this.botUsername.toLowerCase()}`);\n }\n\n private cleanText(text: string): string {\n if (!this.botUsername) return text.trim();\n return text.replace(new RegExp(`@${this.botUsername}`, \"gi\"), \"\").trim();\n }\n\n private isStopText(text: string): boolean {\n return /^\\/?stop(?:@\\w+)?$/i.test(text.trim());\n }\n\n private resolveStopTarget(mc: MessageContext): string | null {\n if (this.handler.isRunning(mc.sessionKey)) return mc.sessionKey;\n\n if (this.handler.isRunning(mc.chatId)) return mc.chatId;\n\n if (mc.chatType === \"private\") return null;\n\n const runningInChat = this.handler\n .getRunningSessions()\n .map((session) => session.sessionKey)\n .filter((sessionKey) => sessionKey === mc.chatId || sessionKey.startsWith(`${mc.chatId}:`));\n\n return runningInChat.length === 1 ? runningInChat[0] : null;\n }\n\n private setupEventHandlers(): void {\n // --- Slash commands (registered before catch-all so grammY intercepts them) ---\n\n this.client.command(\"start\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n await this.postMessageRaw(\n parseInt(mc.chatId),\n [\n \"<b>Welcome!</b>\",\n \"\",\n \"I'm an AI coding agent. Send me a message or use these commands:\",\n \"\",\n \"/new — Reset conversation history and start fresh\",\n \"/stop — Stop the current conversation\",\n \"/help — Show available commands\",\n \"/login — Store credentials in your private vault\",\n ].join(\"\\n\"),\n );\n });\n\n this.client.command(\"help\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n await this.postMessageRaw(\n parseInt(mc.chatId),\n [\n \"<b>Available commands:</b>\",\n \"\",\n \"/start — Welcome message\",\n \"/help — Show this help\",\n \"/login — Store credentials in your private vault\",\n \"/stop — Stop ongoing conversation\",\n \"/new — Reset conversation history and start fresh\",\n \"\",\n \"You can also send a regular message to chat with the agent.\",\n ].join(\"\\n\"),\n );\n });\n\n this.client.command(\"stop\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n const stopTarget = this.resolveStopTarget(mc);\n if (stopTarget) {\n await this.handler.handleStop(stopTarget, mc.chatId, this);\n } else {\n await this.postMessage(mc.chatId, formatNothingRunning(\"telegram\"));\n }\n });\n\n this.client.command(\"new\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n await this.handler.handleNew(mc.sessionKey, mc.chatId, this);\n });\n\n // --- Catch-all for regular (non-command) messages ---\n\n this.client.on(\"message\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n\n const cleanedText = this.cleanText(mc.text);\n const addressedToBot = this.isAddressedToBot(mc.text, mc.chatType);\n\n if (this.isStopText(cleanedText)) {\n this.logToFile(mc.chatId, {\n date: new Date(mc.msg.date * 1000).toISOString(),\n ts: mc.msgId,\n user: mc.userId,\n userName: mc.userName,\n text: cleanedText,\n attachments: [],\n isBot: false,\n });\n\n const stopTarget = this.resolveStopTarget(mc);\n if (stopTarget) {\n await this.handler.handleStop(stopTarget, mc.chatId, this);\n } else if (addressedToBot || mc.chatType === \"private\") {\n await this.postMessage(mc.chatId, formatNothingRunning(\"telegram\"));\n }\n return;\n }\n\n // In groups, only respond when addressed to bot\n if (!addressedToBot) return;\n\n // Process attachments\n const processedAttachments = await this.processAttachments(mc.chatId, mc.msg);\n\n const event: TelegramEvent = {\n type: \"message\",\n conversationId: mc.chatId,\n conversationKind: mc.conversationKind,\n ts: mc.msgId,\n thread_ts: mc.threadTs,\n sessionKey: mc.sessionKey,\n user: mc.userId,\n userName: mc.userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log the message\n this.logToFile(mc.chatId, {\n date: new Date(mc.msg.date * 1000).toISOString(),\n ts: mc.msgId,\n user: mc.userId,\n userName: mc.userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n if (this.handler.isRunning(mc.sessionKey)) {\n await this.postMessage(mc.chatId, formatAlreadyWorking(\"telegram\", \"/stop\"));\n } else {\n this.getQueue(mc.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;AA6BhF,MAAM,WAAW,aAAc,SAAQ,QAAQ;IAC7C,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAuBD,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,CAwB3B;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,CAwB5E;IAED,YAAY,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAerC;IAED,eAAe,IAAI,YAAY,CAQ9B;IAMK,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBlE;IAEK,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIlE;IAEK,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBvF;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,CAMjF;IAED,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE9C;IAED,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAE9D;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,qBAAqB;IAsC7B,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,kBAAkB;CAiI3B","sourcesContent":["import { 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 { resolveChatSessionKey } from \"../../session-policy.js\";\nimport { formatAlreadyWorking, formatNothingRunning } from \"../../ui-copy.js\";\nimport {\n appendBotResponseLog,\n appendChannelLog,\n ChannelQueue,\n resolveOnlyScopedStopTarget,\n resolveStopTarget,\n withRetry,\n} from \"../shared.js\";\nimport { createTelegramAdapters } from \"./context.js\";\nimport { escapeTelegramHtml } from \"./html.js\";\n\n// grammY surfaces Telegram errors as `GrammyError` with `error_code` mirroring\n// the Bot API. 429 is the rate-limit status; the response also includes\n// `parameters.retry_after` but exponential backoff is good enough here.\nfunction telegramIsRateLimited(err: Error): boolean {\n return (err as { error_code?: number }).error_code === 429;\n}\n\nconst telegramRetry = <T>(fn: () => Promise<T>): Promise<T> =>\n withRetry(fn, { isRateLimited: telegramIsRateLimited });\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface TelegramEvent extends BotEvent {\n type: \"message\" | \"command\";\n userName?: string;\n}\n\ninterface MessageContext {\n msg: any;\n text: string;\n chatId: string;\n chatType: string;\n conversationKind: \"direct\" | \"shared\";\n userId: string;\n userName: string;\n msgId: string;\n threadTs: string | undefined;\n sessionKey: string;\n}\n\n// ============================================================================\n// TelegramBot\n// ============================================================================\n\nfunction isTelegramHtmlParseError(message: string): boolean {\n return message.includes(\"can't parse entities\");\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 await this.client.api.setMyCommands([\n { command: \"start\", description: \"Welcome message\" },\n { command: \"help\", description: \"Show available commands\" },\n { command: \"login\", description: \"Store credentials in your private vault\" },\n { command: \"session\", description: \"Open the current session in the web viewer\" },\n { command: \"stop\", description: \"Stop ongoing conversation\" },\n { command: \"new\", description: \"Reset conversation history and start fresh\" },\n ]);\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 return telegramRetry(async () => {\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 return;\n }\n if (!isTelegramHtmlParseError(msg)) {\n throw err;\n }\n await this.client.api.editMessageText(\n parseInt(channel),\n parseInt(ts),\n escapeTelegramHtml(text),\n {\n parse_mode: \"HTML\",\n },\n );\n }\n });\n }\n\n enqueueEvent(event: BotEvent): boolean {\n const conversationId = event.conversationId;\n const queue = this.getQueue(conversationId);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${conversationId}: ${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 return telegramRetry(async () => {\n try {\n const result = await this.client.api.sendMessage(chatId, text, { parse_mode: \"HTML\" });\n return result.message_id;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!isTelegramHtmlParseError(msg)) {\n throw err;\n }\n const result = await this.client.api.sendMessage(chatId, escapeTelegramHtml(text), {\n parse_mode: \"HTML\",\n });\n return result.message_id;\n }\n });\n }\n\n async postPlainMessage(chatId: number, text: string): Promise<void> {\n return telegramRetry(async () => {\n await this.client.api.sendMessage(chatId, text);\n });\n }\n\n async postReply(chatId: number, replyToMessageId: number, text: string): Promise<number> {\n return telegramRetry(async () => {\n try {\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 } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!isTelegramHtmlParseError(msg)) {\n throw err;\n }\n const result = await this.client.api.sendMessage(chatId, escapeTelegramHtml(text), {\n parse_mode: \"HTML\",\n reply_parameters: { message_id: replyToMessageId },\n });\n return result.message_id;\n }\n });\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 return telegramRetry(async () => {\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\n logToFile(channel: string, entry: object): void {\n appendChannelLog(this.workingDir, channel, entry);\n }\n\n logBotResponse(channel: string, text: string, ts: string): void {\n appendBotResponseLog(this.workingDir, channel, text, ts);\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(\"Telegram\");\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private extractMessageContext(msg: any): MessageContext | null {\n if (!msg) return null;\n if (msg.date * 1000 < this.startupTime) return null;\n if (msg.from?.is_bot) return null;\n\n const text = msg.text ?? msg.caption ?? \"\";\n if (!text && !msg.document && !msg.photo) return null;\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 const replyToId = msg.reply_to_message?.message_id;\n const threadTs = replyToId ? String(replyToId) : undefined;\n const conversationKind = chatType === \"private\" ? \"direct\" : \"shared\";\n\n const sessionKey = resolveChatSessionKey({\n conversationId: chatId,\n conversationKind,\n messageId: msgId,\n threadTs,\n });\n\n return {\n msg,\n text,\n chatId,\n chatType,\n conversationKind,\n userId,\n userName,\n msgId,\n threadTs,\n sessionKey,\n };\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.toLowerCase().includes(`@${this.botUsername.toLowerCase()}`);\n }\n\n private cleanText(text: string): string {\n if (!this.botUsername) return text.trim();\n return text.replace(new RegExp(`@${this.botUsername}`, \"gi\"), \"\").trim();\n }\n\n private isStopText(text: string): boolean {\n return /^\\/?stop(?:@\\w+)?$/i.test(text.trim());\n }\n\n private resolveStopTarget(mc: MessageContext): string | null {\n const directTarget = resolveStopTarget({\n handler: this.handler,\n conversationId: mc.chatId,\n sessionKey: mc.sessionKey,\n });\n if (directTarget) return directTarget;\n return resolveOnlyScopedStopTarget(this.handler, mc.chatId);\n }\n\n private setupEventHandlers(): void {\n // --- Slash commands (registered before catch-all so grammY intercepts them) ---\n\n this.client.command(\"start\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n await this.postMessageRaw(\n parseInt(mc.chatId),\n [\n \"<b>Welcome!</b>\",\n \"\",\n \"I'm an AI coding agent. Send me a message or use these commands:\",\n \"\",\n \"/new — Reset conversation history and start fresh\",\n \"/stop — Stop the current conversation\",\n \"/session — Open the current session in the web viewer\",\n \"/help — Show available commands\",\n \"/login — Store credentials in your private vault\",\n ].join(\"\\n\"),\n );\n });\n\n this.client.command(\"help\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n await this.postMessageRaw(\n parseInt(mc.chatId),\n [\n \"<b>Available commands:</b>\",\n \"\",\n \"/start — Welcome message\",\n \"/help — Show this help\",\n \"/login — Store credentials in your private vault\",\n \"/session — Open the current session in the web viewer\",\n \"/stop — Stop ongoing conversation\",\n \"/new — Reset conversation history and start fresh\",\n \"\",\n \"You can also send a regular message to chat with the agent.\",\n ].join(\"\\n\"),\n );\n });\n\n this.client.command(\"stop\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n const stopTarget = this.resolveStopTarget(mc);\n if (stopTarget) {\n await this.handler.handleStop(stopTarget, mc.chatId, this);\n } else {\n await this.postMessage(mc.chatId, formatNothingRunning(\"telegram\"));\n }\n });\n\n this.client.command(\"new\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n await this.handler.handleNew(mc.sessionKey, mc.chatId, this);\n });\n\n // --- Catch-all for regular (non-command) messages ---\n\n this.client.on(\"message\", async (ctx) => {\n const mc = this.extractMessageContext(ctx.message);\n if (!mc) return;\n\n const cleanedText = this.cleanText(mc.text);\n const addressedToBot = this.isAddressedToBot(mc.text, mc.chatType);\n\n if (this.isStopText(cleanedText)) {\n this.logToFile(mc.chatId, {\n date: new Date(mc.msg.date * 1000).toISOString(),\n ts: mc.msgId,\n user: mc.userId,\n userName: mc.userName,\n text: cleanedText,\n attachments: [],\n isBot: false,\n });\n\n const stopTarget = this.resolveStopTarget(mc);\n if (stopTarget) {\n await this.handler.handleStop(stopTarget, mc.chatId, this);\n } else if (addressedToBot || mc.chatType === \"private\") {\n await this.postMessage(mc.chatId, formatNothingRunning(\"telegram\"));\n }\n return;\n }\n\n // In groups, only respond when addressed to bot\n if (!addressedToBot) return;\n\n // Process attachments\n const processedAttachments = await this.processAttachments(mc.chatId, mc.msg);\n\n const event: TelegramEvent = {\n type: \"message\",\n conversationId: mc.chatId,\n conversationKind: mc.conversationKind,\n ts: mc.msgId,\n thread_ts: mc.threadTs,\n sessionKey: mc.sessionKey,\n user: mc.userId,\n userName: mc.userName,\n text: cleanedText,\n attachments: processedAttachments,\n };\n\n // Log the message\n this.logToFile(mc.chatId, {\n date: new Date(mc.msg.date * 1000).toISOString(),\n ts: mc.msgId,\n ...(mc.conversationKind === \"shared\" && mc.threadTs ? { threadTs: mc.threadTs } : {}),\n user: mc.userId,\n userName: mc.userName,\n text: cleanedText,\n attachments: processedAttachments,\n isBot: false,\n });\n\n if (this.handler.isRunning(mc.sessionKey)) {\n await this.postMessage(mc.chatId, formatAlreadyWorking(\"telegram\", \"/stop\"));\n } else {\n this.getQueue(mc.sessionKey).enqueue(() => {\n const adapters = createTelegramAdapters(event, this, false);\n return this.handler.handleEvent(event, this, adapters, false);\n });\n }\n });\n }\n}\n"]}
|