@geminixiang/mama 0.2.0-beta.19 → 0.2.0-beta.20
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 +11 -17
- package/dist/adapter.d.ts +1 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +5 -5
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/context.d.ts +1 -1
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +27 -2
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/shared.d.ts +20 -0
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +29 -0
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +3 -3
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +99 -64
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/branch-manager.d.ts +6 -0
- package/dist/adapters/slack/branch-manager.d.ts.map +1 -1
- package/dist/adapters/slack/branch-manager.js +15 -5
- package/dist/adapters/slack/branch-manager.js.map +1 -1
- package/dist/adapters/slack/context.d.ts +4 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +36 -29
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/session.d.ts +35 -0
- package/dist/adapters/slack/session.d.ts.map +1 -1
- package/dist/adapters/slack/session.js +51 -1
- package/dist/adapters/slack/session.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +6 -6
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +27 -6
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +56 -10
- package/dist/agent.js.map +1 -1
- package/dist/commands/auto-reply.d.ts.map +1 -1
- package/dist/commands/auto-reply.js +4 -1
- package/dist/commands/auto-reply.js.map +1 -1
- package/dist/config.d.ts +0 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -39
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +37 -2
- package/dist/events.js.map +1 -1
- package/dist/execution-resolver.d.ts.map +1 -1
- package/dist/execution-resolver.js +40 -6
- package/dist/execution-resolver.js.map +1 -1
- package/dist/log.d.ts +0 -7
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +0 -106
- package/dist/log.js.map +1 -1
- package/dist/login/portal.d.ts.map +1 -1
- package/dist/login/portal.js +64 -0
- package/dist/login/portal.js.map +1 -1
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +14 -0
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/conversation-orchestrator.d.ts +1 -2
- package/dist/runtime/conversation-orchestrator.d.ts.map +1 -1
- package/dist/runtime/conversation-orchestrator.js +44 -12
- package/dist/runtime/conversation-orchestrator.js.map +1 -1
- package/dist/runtime/session-runtime.d.ts +0 -1
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/session-runtime.js +12 -11
- package/dist/runtime/session-runtime.js.map +1 -1
- package/dist/sentry.d.ts +20 -1
- package/dist/sentry.d.ts.map +1 -1
- package/dist/sentry.js +58 -8
- package/dist/sentry.js.map +1 -1
- package/dist/session-view/portal.d.ts.map +1 -1
- package/dist/session-view/portal.js +83 -3
- package/dist/session-view/portal.js.map +1 -1
- package/dist/vault.d.ts.map +1 -1
- package/dist/vault.js +8 -0
- package/dist/vault.js.map +1 -1
- package/package.json +2 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as log from "../../log.js";
|
|
2
2
|
import { resolveChatSessionKey } from "../../session-policy.js";
|
|
3
|
-
import { formatToolArgs, splitText } from "../shared.js";
|
|
3
|
+
import { createChatResponseErrorReporter, formatToolArgs, splitText } from "../shared.js";
|
|
4
4
|
export const DISCORD_FORMATTING_GUIDE = `## Discord Formatting (Markdown)
|
|
5
5
|
Bold: **text**, Italic: *text*, Code: \`code\`, Block: \`\`\`language\ncode\`\`\`
|
|
6
6
|
Links: [text](url), Spoiler: ||text||
|
|
@@ -23,7 +23,7 @@ function formatToolResult(result) {
|
|
|
23
23
|
text += `**Result:**\n\`\`\`\n${result.result}\n\`\`\``;
|
|
24
24
|
return text;
|
|
25
25
|
}
|
|
26
|
-
export function createDiscordAdapters(event, bot
|
|
26
|
+
export function createDiscordAdapters(event, bot) {
|
|
27
27
|
let messageId = null;
|
|
28
28
|
let accumulatedText = "";
|
|
29
29
|
let isWorking = true;
|
|
@@ -80,6 +80,17 @@ export function createDiscordAdapters(event, bot, _isSyntheticEvent) {
|
|
|
80
80
|
}
|
|
81
81
|
return bot.postMessage(channelId, text);
|
|
82
82
|
}
|
|
83
|
+
const reportResponseError = createChatResponseErrorReporter(() => ({
|
|
84
|
+
platform: "discord",
|
|
85
|
+
conversationId,
|
|
86
|
+
channelId,
|
|
87
|
+
messageId: message.id,
|
|
88
|
+
sessionKey: message.sessionKey,
|
|
89
|
+
responseMessageId: messageId,
|
|
90
|
+
threadTs: threadTargetId,
|
|
91
|
+
replyTargetId,
|
|
92
|
+
conversationKind: message.conversationKind,
|
|
93
|
+
}));
|
|
83
94
|
const responseCtx = {
|
|
84
95
|
respond: async (text) => {
|
|
85
96
|
updatePromise = updatePromise.then(async () => {
|
|
@@ -110,6 +121,11 @@ export function createDiscordAdapters(event, bot, _isSyntheticEvent) {
|
|
|
110
121
|
}
|
|
111
122
|
catch (err) {
|
|
112
123
|
log.logWarning("Discord respond error", err instanceof Error ? err.message : String(err));
|
|
124
|
+
reportResponseError(err, "respond", {
|
|
125
|
+
phase: messageId ? "update" : "initial_post",
|
|
126
|
+
textLength: text.length,
|
|
127
|
+
accumulatedLength: accumulatedText.length,
|
|
128
|
+
});
|
|
113
129
|
}
|
|
114
130
|
});
|
|
115
131
|
await updatePromise;
|
|
@@ -140,6 +156,10 @@ export function createDiscordAdapters(event, bot, _isSyntheticEvent) {
|
|
|
140
156
|
}
|
|
141
157
|
catch (err) {
|
|
142
158
|
log.logWarning("Discord replaceResponse error", err instanceof Error ? err.message : String(err));
|
|
159
|
+
reportResponseError(err, "replace_response", {
|
|
160
|
+
textLength: text.length,
|
|
161
|
+
hadExistingResponse: Boolean(messageId),
|
|
162
|
+
});
|
|
143
163
|
}
|
|
144
164
|
});
|
|
145
165
|
await updatePromise;
|
|
@@ -154,6 +174,10 @@ export function createDiscordAdapters(event, bot, _isSyntheticEvent) {
|
|
|
154
174
|
}
|
|
155
175
|
catch (err) {
|
|
156
176
|
log.logWarning("Discord respondDiagnostic error", err instanceof Error ? err.message : String(err));
|
|
177
|
+
reportResponseError(err, "respond_diagnostic", {
|
|
178
|
+
textLength: text.length,
|
|
179
|
+
style: options?.style,
|
|
180
|
+
});
|
|
157
181
|
}
|
|
158
182
|
});
|
|
159
183
|
await updatePromise;
|
|
@@ -192,6 +216,7 @@ export function createDiscordAdapters(event, bot, _isSyntheticEvent) {
|
|
|
192
216
|
}
|
|
193
217
|
catch (err) {
|
|
194
218
|
log.logWarning("Discord setWorking error", err instanceof Error ? err.message : String(err));
|
|
219
|
+
reportResponseError(err, "set_working", { working });
|
|
195
220
|
}
|
|
196
221
|
});
|
|
197
222
|
await updatePromise;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/discord/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 { resolveChatSessionKey } from \"../../session-policy.js\";\nimport { formatToolArgs, splitText } from \"../shared.js\";\nimport type { DiscordBot, DiscordEvent } from \"./bot.js\";\n\nexport const DISCORD_FORMATTING_GUIDE = `## Discord Formatting (Markdown)\nBold: **text**, Italic: *text*, Code: \\`code\\`, Block: \\`\\`\\`language\\ncode\\`\\`\\`\nLinks: [text](url), Spoiler: ||text||\nKeep messages under 2000 characters. Use code blocks for code.`;\n\n// Discord hard limit is 2000 chars; 1900 leaves headroom for working indicator.\nconst MAX_LENGTH = 1900;\n\nconst formatDiscordContinuation = (partNum: number): string => `*(continued ${partNum})*`;\n\nfunction isDiscordMessageReference(id: string | undefined): id is string {\n return typeof id === \"string\" && id !== \"\" && !id.startsWith(\"event:\");\n}\n\nfunction formatToolResult(result: ChatToolResult): string {\n const argsFormatted = formatToolArgs(result.args);\n const duration = (result.durationMs / 1000).toFixed(1);\n let text = `**${result.isError ? \"Error\" : \"Done\"} ${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 createDiscordAdapters(\n event: DiscordEvent,\n bot: DiscordBot,\n _isSyntheticEvent?: boolean,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageId: string | null = null;\n let accumulatedText = \"\";\n let isWorking = true;\n const workingIndicator = \" ...\";\n let updatePromise = Promise.resolve();\n let typingInterval: ReturnType<typeof setInterval> | null = null;\n let typingFailureWarned = false;\n\n function stopTyping(): void {\n if (typingInterval !== null) {\n clearInterval(typingInterval);\n typingInterval = null;\n }\n }\n\n const conversationId = event.conversationId;\n const channelId = conversationId;\n const threadTargetId = isDiscordMessageReference(event.thread_ts) ? event.thread_ts : undefined;\n const replyTargetId = isDiscordMessageReference(event.ts) ? event.ts : undefined;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey:\n event.sessionKey ??\n resolveChatSessionKey({\n conversationId,\n conversationKind: event.conversationKind,\n messageId: event.ts,\n persistentTopLevel: true,\n threadTs: event.thread_ts,\n }),\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"discord\",\n formattingGuide: DISCORD_FORMATTING_GUIDE,\n channels: bot.getAllChannels(),\n users: bot.getAllUsers(),\n diagnostics: {\n showUsageSummary: false,\n },\n };\n\n async function postDiagnosticMessage(text: string): Promise<string> {\n stopTyping();\n if (threadTargetId) {\n return bot.postInThread(channelId, threadTargetId, text);\n }\n if (replyTargetId) {\n return bot.postReply(channelId, replyTargetId, text);\n }\n if (messageId !== null) {\n return bot.postReply(channelId, messageId, text);\n }\n return bot.postMessage(channelId, text);\n }\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${text}` : text;\n const [displayText, ...extraParts] = splitText(\n isWorking ? accumulatedText + workingIndicator : accumulatedText,\n MAX_LENGTH,\n formatDiscordContinuation,\n );\n\n if (messageId !== null) {\n await bot.updateMessageRaw(channelId, messageId, displayText);\n } else {\n stopTyping();\n if (threadTargetId) {\n messageId = await bot.postInThread(channelId, threadTargetId, displayText);\n } else if (replyTargetId) {\n messageId = await bot.postReply(channelId, replyTargetId, displayText);\n } else {\n messageId = await bot.postMessage(channelId, displayText);\n }\n }\n for (const part of extraParts) {\n await postDiagnosticMessage(part);\n }\n\n if (messageId !== null) {\n bot.logBotResponse(channelId, text, messageId);\n }\n } catch (err) {\n log.logWarning(\"Discord 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 accumulatedText = text;\n const [displayText, ...extraParts] = splitText(\n accumulatedText,\n MAX_LENGTH,\n formatDiscordContinuation,\n );\n\n if (messageId !== null) {\n await bot.updateMessageRaw(channelId, messageId, displayText);\n } else {\n stopTyping();\n if (threadTargetId) {\n messageId = await bot.postInThread(channelId, threadTargetId, displayText);\n } else if (replyTargetId) {\n messageId = await bot.postReply(channelId, replyTargetId, displayText);\n } else {\n messageId = await bot.postMessage(channelId, displayText);\n }\n }\n for (const part of extraParts) {\n await postDiagnosticMessage(part);\n }\n } catch (err) {\n log.logWarning(\n \"Discord 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 const prefix = options?.style === \"error\" ? \"*Error:* \" : \"\";\n for (const part of splitText(`${prefix}${text}`, MAX_LENGTH, formatDiscordContinuation)) {\n await postDiagnosticMessage(part);\n }\n } catch (err) {\n log.logWarning(\n \"Discord 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(formatToolResult(result));\n },\n\n setTyping: async (isTyping: boolean) => {\n const onTypingError = (err: unknown): void => {\n if (typingFailureWarned) return;\n typingFailureWarned = true;\n log.logWarning(\n \"Discord sendTyping failed (further occurrences suppressed for this session)\",\n err instanceof Error ? err.message : String(err),\n );\n };\n if (isTyping && typingInterval === null) {\n // Send immediately and repeat every 8s (Discord clears indicator after ~10s)\n bot.sendTyping(channelId).catch(onTypingError);\n typingInterval = setInterval(() => {\n bot.sendTyping(channelId).catch(onTypingError);\n }, 8000);\n } else if (!isTyping) {\n stopTyping();\n }\n },\n\n setWorking: async (working: boolean) => {\n updatePromise = updatePromise.then(async () => {\n try {\n isWorking = working;\n if (!working) stopTyping();\n if (messageId !== null) {\n const [displayText] = splitText(\n isWorking ? accumulatedText + workingIndicator : accumulatedText,\n MAX_LENGTH,\n formatDiscordContinuation,\n );\n await bot.updateMessageRaw(channelId, messageId, displayText);\n }\n } catch (err) {\n log.logWarning(\n \"Discord setWorking error\",\n err instanceof Error ? err.message : String(err),\n );\n }\n });\n await updatePromise;\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await bot.uploadFile(channelId, filePath, title);\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n stopTyping();\n if (messageId !== null) {\n try {\n await bot.deleteMessageRaw(channelId, messageId);\n } catch {\n // Ignore errors\n }\n messageId = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/discord/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 { resolveChatSessionKey } from \"../../session-policy.js\";\nimport { createChatResponseErrorReporter, formatToolArgs, splitText } from \"../shared.js\";\nimport type { DiscordBot, DiscordEvent } from \"./bot.js\";\n\nexport const DISCORD_FORMATTING_GUIDE = `## Discord Formatting (Markdown)\nBold: **text**, Italic: *text*, Code: \\`code\\`, Block: \\`\\`\\`language\\ncode\\`\\`\\`\nLinks: [text](url), Spoiler: ||text||\nKeep messages under 2000 characters. Use code blocks for code.`;\n\n// Discord hard limit is 2000 chars; 1900 leaves headroom for working indicator.\nconst MAX_LENGTH = 1900;\n\nconst formatDiscordContinuation = (partNum: number): string => `*(continued ${partNum})*`;\n\nfunction isDiscordMessageReference(id: string | undefined): id is string {\n return typeof id === \"string\" && id !== \"\" && !id.startsWith(\"event:\");\n}\n\nfunction formatToolResult(result: ChatToolResult): string {\n const argsFormatted = formatToolArgs(result.args);\n const duration = (result.durationMs / 1000).toFixed(1);\n let text = `**${result.isError ? \"Error\" : \"Done\"} ${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 createDiscordAdapters(\n event: DiscordEvent,\n bot: DiscordBot,\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n let messageId: string | null = null;\n let accumulatedText = \"\";\n let isWorking = true;\n const workingIndicator = \" ...\";\n let updatePromise = Promise.resolve();\n let typingInterval: ReturnType<typeof setInterval> | null = null;\n let typingFailureWarned = false;\n\n function stopTyping(): void {\n if (typingInterval !== null) {\n clearInterval(typingInterval);\n typingInterval = null;\n }\n }\n\n const conversationId = event.conversationId;\n const channelId = conversationId;\n const threadTargetId = isDiscordMessageReference(event.thread_ts) ? event.thread_ts : undefined;\n const replyTargetId = isDiscordMessageReference(event.ts) ? event.ts : undefined;\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey:\n event.sessionKey ??\n resolveChatSessionKey({\n conversationId,\n conversationKind: event.conversationKind,\n messageId: event.ts,\n persistentTopLevel: true,\n threadTs: event.thread_ts,\n }),\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n };\n\n const platform: PlatformInfo = {\n name: \"discord\",\n formattingGuide: DISCORD_FORMATTING_GUIDE,\n channels: bot.getAllChannels(),\n users: bot.getAllUsers(),\n diagnostics: {\n showUsageSummary: false,\n },\n };\n\n async function postDiagnosticMessage(text: string): Promise<string> {\n stopTyping();\n if (threadTargetId) {\n return bot.postInThread(channelId, threadTargetId, text);\n }\n if (replyTargetId) {\n return bot.postReply(channelId, replyTargetId, text);\n }\n if (messageId !== null) {\n return bot.postReply(channelId, messageId, text);\n }\n return bot.postMessage(channelId, text);\n }\n\n const reportResponseError = createChatResponseErrorReporter(() => ({\n platform: \"discord\",\n conversationId,\n channelId,\n messageId: message.id,\n sessionKey: message.sessionKey,\n responseMessageId: messageId,\n threadTs: threadTargetId,\n replyTargetId,\n conversationKind: message.conversationKind,\n }));\n\n const responseCtx: ChatResponseContext = {\n respond: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${text}` : text;\n const [displayText, ...extraParts] = splitText(\n isWorking ? accumulatedText + workingIndicator : accumulatedText,\n MAX_LENGTH,\n formatDiscordContinuation,\n );\n\n if (messageId !== null) {\n await bot.updateMessageRaw(channelId, messageId, displayText);\n } else {\n stopTyping();\n if (threadTargetId) {\n messageId = await bot.postInThread(channelId, threadTargetId, displayText);\n } else if (replyTargetId) {\n messageId = await bot.postReply(channelId, replyTargetId, displayText);\n } else {\n messageId = await bot.postMessage(channelId, displayText);\n }\n }\n for (const part of extraParts) {\n await postDiagnosticMessage(part);\n }\n\n if (messageId !== null) {\n bot.logBotResponse(channelId, text, messageId);\n }\n } catch (err) {\n log.logWarning(\"Discord respond error\", err instanceof Error ? err.message : String(err));\n reportResponseError(err, \"respond\", {\n phase: messageId ? \"update\" : \"initial_post\",\n textLength: text.length,\n accumulatedLength: accumulatedText.length,\n });\n }\n });\n await updatePromise;\n },\n\n replaceResponse: async (text: string) => {\n updatePromise = updatePromise.then(async () => {\n try {\n accumulatedText = text;\n const [displayText, ...extraParts] = splitText(\n accumulatedText,\n MAX_LENGTH,\n formatDiscordContinuation,\n );\n\n if (messageId !== null) {\n await bot.updateMessageRaw(channelId, messageId, displayText);\n } else {\n stopTyping();\n if (threadTargetId) {\n messageId = await bot.postInThread(channelId, threadTargetId, displayText);\n } else if (replyTargetId) {\n messageId = await bot.postReply(channelId, replyTargetId, displayText);\n } else {\n messageId = await bot.postMessage(channelId, displayText);\n }\n }\n for (const part of extraParts) {\n await postDiagnosticMessage(part);\n }\n } catch (err) {\n log.logWarning(\n \"Discord replaceResponse error\",\n err instanceof Error ? err.message : String(err),\n );\n reportResponseError(err, \"replace_response\", {\n textLength: text.length,\n hadExistingResponse: Boolean(messageId),\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 const prefix = options?.style === \"error\" ? \"*Error:* \" : \"\";\n for (const part of splitText(`${prefix}${text}`, MAX_LENGTH, formatDiscordContinuation)) {\n await postDiagnosticMessage(part);\n }\n } catch (err) {\n log.logWarning(\n \"Discord respondDiagnostic error\",\n err instanceof Error ? err.message : String(err),\n );\n reportResponseError(err, \"respond_diagnostic\", {\n textLength: text.length,\n style: options?.style,\n });\n }\n });\n await updatePromise;\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatToolResult(result));\n },\n\n setTyping: async (isTyping: boolean) => {\n const onTypingError = (err: unknown): void => {\n if (typingFailureWarned) return;\n typingFailureWarned = true;\n log.logWarning(\n \"Discord sendTyping failed (further occurrences suppressed for this session)\",\n err instanceof Error ? err.message : String(err),\n );\n };\n if (isTyping && typingInterval === null) {\n // Send immediately and repeat every 8s (Discord clears indicator after ~10s)\n bot.sendTyping(channelId).catch(onTypingError);\n typingInterval = setInterval(() => {\n bot.sendTyping(channelId).catch(onTypingError);\n }, 8000);\n } else if (!isTyping) {\n stopTyping();\n }\n },\n\n setWorking: async (working: boolean) => {\n updatePromise = updatePromise.then(async () => {\n try {\n isWorking = working;\n if (!working) stopTyping();\n if (messageId !== null) {\n const [displayText] = splitText(\n isWorking ? accumulatedText + workingIndicator : accumulatedText,\n MAX_LENGTH,\n formatDiscordContinuation,\n );\n await bot.updateMessageRaw(channelId, messageId, displayText);\n }\n } catch (err) {\n log.logWarning(\n \"Discord setWorking error\",\n err instanceof Error ? err.message : String(err),\n );\n reportResponseError(err, \"set_working\", { working });\n }\n });\n await updatePromise;\n },\n\n uploadFile: async (filePath: string, title?: string) => {\n await bot.uploadFile(channelId, filePath, title);\n },\n\n deleteResponse: async () => {\n updatePromise = updatePromise.then(async () => {\n stopTyping();\n if (messageId !== null) {\n try {\n await bot.deleteMessageRaw(channelId, messageId);\n } catch {\n // Ignore errors\n }\n messageId = null;\n }\n });\n await updatePromise;\n },\n };\n\n return { message, responseCtx, platform };\n}\n"]}
|
|
@@ -7,6 +7,26 @@
|
|
|
7
7
|
* markup wrappers — so it lives here once.
|
|
8
8
|
*/
|
|
9
9
|
import type { BotHandler } from "../adapter.js";
|
|
10
|
+
export type ChatResponseErrorOperation = "respond" | "replace_response" | "respond_diagnostic" | "set_working";
|
|
11
|
+
export interface ChatResponseErrorContext {
|
|
12
|
+
platform: string;
|
|
13
|
+
conversationId: string;
|
|
14
|
+
messageId: string;
|
|
15
|
+
sessionKey: string;
|
|
16
|
+
conversationKind: string;
|
|
17
|
+
operation: ChatResponseErrorOperation;
|
|
18
|
+
channelId?: string;
|
|
19
|
+
chatId?: number;
|
|
20
|
+
responseMessageId?: string | number | null;
|
|
21
|
+
threadTs?: string;
|
|
22
|
+
replyTargetId?: string;
|
|
23
|
+
replyToId?: number | null;
|
|
24
|
+
isThreaded?: boolean;
|
|
25
|
+
extra?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export type ChatResponseErrorReporter = (err: unknown, operation: ChatResponseErrorOperation, extra?: Record<string, unknown>) => void;
|
|
28
|
+
export declare function createChatResponseErrorReporter(resolve: () => Omit<ChatResponseErrorContext, "operation" | "extra">): ChatResponseErrorReporter;
|
|
29
|
+
export declare function reportChatResponseError(err: unknown, context: ChatResponseErrorContext): void;
|
|
10
30
|
export declare class ChannelQueue {
|
|
11
31
|
private readonly name;
|
|
12
32
|
private queue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/adapters/shared.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAGhD,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,KAAK,CAAkC;IAC/C,OAAO,CAAC,UAAU,CAAS;IAE3B,YAA6B,IAAI,GAAE,MAAW,EAAI;IAElD,OAAO,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAGvC;IAED,IAAI,IAAI,MAAM,CAEb;YAEa,WAAW;CAe1B;AAED,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,aAAa,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,OAAO,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC,CAAC,CAwBvF;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAC9C,MAAM,EAAE,CAeV;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAIzF;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAUN;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,UAAU,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,MAAM,GAAG,IAAI,CAM9E;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,UAAU,EACnB,cAAc,EAAE,MAAM,GACrB,MAAM,GAAG,IAAI,CAOf;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,CAsBhF","sourcesContent":["/**\n * Helpers shared across platform adapters.\n *\n * The agent runner is platform-agnostic: it hands strings and structured tool\n * results to each adapter, which decides how to split, format, and route them.\n * The split/normalize logic itself doesn't differ across platforms — only the\n * markup wrappers — so it lives here once.\n */\n\nimport { appendFileSync, existsSync, mkdirSync } from \"fs\";\nimport { join } from \"path\";\nimport type { BotHandler } from \"../adapter.js\";\nimport * as log from \"../log.js\";\n\nexport class ChannelQueue {\n private queue: Array<() => Promise<void>> = [];\n private processing = false;\n\n constructor(private readonly name: string = \"\") {}\n\n enqueue(work: () => Promise<void>): 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(\n `${this.name ? this.name + \" \" : \"\"}queue error`,\n err instanceof Error ? err.message : String(err),\n );\n }\n this.processing = false;\n this.processNext();\n }\n}\n\nexport interface RetryOptions {\n /** Predicate that returns true when an error indicates a platform-side rate limit. */\n isRateLimited: (err: Error) => boolean;\n maxRetries?: number;\n baseDelayMs?: number;\n}\n\n/**\n * Run `fn` and retry with exponential backoff when its error matches\n * `isRateLimited`. Other errors propagate immediately. Each platform supplies\n * its own predicate so we don't have to know every SDK's error shape here.\n */\nexport async function withRetry<T>(fn: () => Promise<T>, opts: RetryOptions): Promise<T> {\n const maxRetries = opts.maxRetries ?? 3;\n const baseDelayMs = opts.baseDelayMs ?? 1000;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (opts.isRateLimited(lastError)) {\n const delay = baseDelayMs * Math.pow(2, attempt);\n log.logWarning(\n `Rate limited, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n throw lastError;\n }\n }\n throw lastError;\n}\n\n/**\n * Split `text` into chunks no larger than `limit`, appending a continuation\n * marker (e.g. `_(continued 1)_`) at the end of every part except the last.\n *\n * Each adapter passes its own `formatContinuation` so the marker uses the\n * platform's italic / emphasis convention.\n */\nexport function splitText(\n text: string,\n limit: number,\n formatContinuation: (partNum: number) => string,\n): string[] {\n if (text.length <= limit) return [text];\n const parts: string[] = [];\n let remaining = text;\n let partNum = 1;\n while (remaining.length > 0) {\n const suffixReserve = formatContinuation(partNum).length + 8;\n const chunkLimit = Math.max(1, limit - suffixReserve);\n const chunk = remaining.slice(0, chunkLimit);\n remaining = remaining.slice(chunkLimit);\n const suffix = remaining.length > 0 ? `\\n${formatContinuation(partNum)}` : \"\";\n parts.push(chunk + suffix);\n partNum++;\n }\n return parts;\n}\n\n/**\n * Append a JSON-serializable entry to `${workingDir}/${channel}/log.jsonl`,\n * creating the directory on first use. This is the single write path every\n * adapter uses for human-readable message history.\n */\nexport function appendChannelLog(workingDir: string, channel: string, entry: object): void {\n const dir = join(workingDir, channel);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, \"log.jsonl\"), `${JSON.stringify(entry)}\\n`);\n}\n\n/** Convenience for appending the bot's own outbound message. */\nexport function appendBotResponseLog(\n workingDir: string,\n channel: string,\n text: string,\n ts: string,\n threadTs?: string,\n): void {\n appendChannelLog(workingDir, channel, {\n date: new Date().toISOString(),\n ts,\n ...(threadTs ? { threadTs } : {}),\n user: \"bot\",\n text,\n attachments: [],\n isBot: true,\n });\n}\n\nexport interface ResolveStopTargetInput {\n handler: BotHandler;\n conversationId: string;\n /** Session key derived from the current message; checked first when present. */\n sessionKey?: string;\n}\n\n/**\n * Pick which session key a `/stop` should target without applying any\n * platform-specific fallback policy. Order:\n * 1. The provided sessionKey, if running.\n * 2. The bare conversationId, if running.\n */\nexport function resolveStopTarget(input: ResolveStopTargetInput): string | null {\n const { handler, conversationId, sessionKey } = input;\n\n if (sessionKey && handler.isRunning(sessionKey)) return sessionKey;\n if (handler.isRunning(conversationId)) return conversationId;\n return null;\n}\n\n/**\n * Return the single running scoped session for this conversation, or null when\n * there are zero or multiple matches.\n */\nexport function resolveOnlyScopedStopTarget(\n handler: BotHandler,\n conversationId: string,\n): string | null {\n const runningScopes = handler\n .getRunningSessions()\n .map((s) => s.sessionKey)\n .filter((k) => k.startsWith(`${conversationId}:`));\n\n return runningScopes.length === 1 ? runningScopes[0] : null;\n}\n\n/**\n * Render tool-call args for human display. Drops `label` (already in the\n * heading) and folds `path` + `offset`/`limit` into a single `path:start-end`\n * line. Pure data normalization with no platform-specific markup.\n */\nexport function formatToolArgs(args: Record<string, unknown> | undefined): string {\n if (!args) return \"\";\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(args)) {\n if (key === \"label\" || key === \"offset\" || key === \"limit\") continue;\n\n if (key === \"path\" && typeof value === \"string\") {\n const offset = args.offset as number | undefined;\n const limit = args.limit as number | undefined;\n lines.push(\n offset !== undefined && limit !== undefined\n ? `${value}:${offset}-${offset + limit}`\n : value,\n );\n continue;\n }\n\n lines.push(typeof value === \"string\" ? value : JSON.stringify(value));\n }\n\n return lines.join(\"\\n\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/adapters/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Helpers shared across platform adapters.\n *\n * The agent runner is platform-agnostic: it hands strings and structured tool\n * results to each adapter, which decides how to split, format, and route them.\n * The split/normalize logic itself doesn't differ across platforms — only the\n * markup wrappers — so it lives here once.\n */\n\nimport { appendFileSync, existsSync, mkdirSync } from \"fs\";\nimport { join } from \"path\";\nimport type { BotHandler } from \"../adapter.js\";\nimport * as log from \"../log.js\";\nimport { reportUserFacingError } from \"../sentry.js\";\n\nexport type ChatResponseErrorOperation =\n | \"respond\"\n | \"replace_response\"\n | \"respond_diagnostic\"\n | \"set_working\";\n\nexport interface ChatResponseErrorContext {\n platform: string;\n conversationId: string;\n messageId: string;\n sessionKey: string;\n conversationKind: string;\n operation: ChatResponseErrorOperation;\n channelId?: string;\n chatId?: number;\n responseMessageId?: string | number | null;\n threadTs?: string;\n replyTargetId?: string;\n replyToId?: number | null;\n isThreaded?: boolean;\n extra?: Record<string, unknown>;\n}\n\nexport type ChatResponseErrorReporter = (\n err: unknown,\n operation: ChatResponseErrorOperation,\n extra?: Record<string, unknown>,\n) => void;\n\nexport function createChatResponseErrorReporter(\n resolve: () => Omit<ChatResponseErrorContext, \"operation\" | \"extra\">,\n): ChatResponseErrorReporter {\n return (err, operation, extra) => {\n reportChatResponseError(err, { ...resolve(), operation, extra });\n };\n}\n\nexport function reportChatResponseError(err: unknown, context: ChatResponseErrorContext): void {\n reportUserFacingError(err, {\n domain: \"chat_platform\",\n surface: \"chat_response\",\n operation: context.operation,\n severity: context.operation === \"set_working\" ? \"warning\" : \"error\",\n platform: context.platform,\n context: {\n conversationId: context.conversationId,\n channelId: context.channelId,\n chatId: context.chatId,\n messageId: context.messageId,\n sessionKey: context.sessionKey,\n responseMessageId: context.responseMessageId,\n threadTs: context.threadTs,\n replyTargetId: context.replyTargetId,\n replyToId: context.replyToId,\n conversationKind: context.conversationKind,\n isThreaded: context.isThreaded,\n ...context.extra,\n },\n });\n}\n\nexport class ChannelQueue {\n private queue: Array<() => Promise<void>> = [];\n private processing = false;\n\n constructor(private readonly name: string = \"\") {}\n\n enqueue(work: () => Promise<void>): 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(\n `${this.name ? this.name + \" \" : \"\"}queue error`,\n err instanceof Error ? err.message : String(err),\n );\n }\n this.processing = false;\n this.processNext();\n }\n}\n\nexport interface RetryOptions {\n /** Predicate that returns true when an error indicates a platform-side rate limit. */\n isRateLimited: (err: Error) => boolean;\n maxRetries?: number;\n baseDelayMs?: number;\n}\n\n/**\n * Run `fn` and retry with exponential backoff when its error matches\n * `isRateLimited`. Other errors propagate immediately. Each platform supplies\n * its own predicate so we don't have to know every SDK's error shape here.\n */\nexport async function withRetry<T>(fn: () => Promise<T>, opts: RetryOptions): Promise<T> {\n const maxRetries = opts.maxRetries ?? 3;\n const baseDelayMs = opts.baseDelayMs ?? 1000;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (opts.isRateLimited(lastError)) {\n const delay = baseDelayMs * Math.pow(2, attempt);\n log.logWarning(\n `Rate limited, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n throw lastError;\n }\n }\n throw lastError;\n}\n\n/**\n * Split `text` into chunks no larger than `limit`, appending a continuation\n * marker (e.g. `_(continued 1)_`) at the end of every part except the last.\n *\n * Each adapter passes its own `formatContinuation` so the marker uses the\n * platform's italic / emphasis convention.\n */\nexport function splitText(\n text: string,\n limit: number,\n formatContinuation: (partNum: number) => string,\n): string[] {\n if (text.length <= limit) return [text];\n const parts: string[] = [];\n let remaining = text;\n let partNum = 1;\n while (remaining.length > 0) {\n const suffixReserve = formatContinuation(partNum).length + 8;\n const chunkLimit = Math.max(1, limit - suffixReserve);\n const chunk = remaining.slice(0, chunkLimit);\n remaining = remaining.slice(chunkLimit);\n const suffix = remaining.length > 0 ? `\\n${formatContinuation(partNum)}` : \"\";\n parts.push(chunk + suffix);\n partNum++;\n }\n return parts;\n}\n\n/**\n * Append a JSON-serializable entry to `${workingDir}/${channel}/log.jsonl`,\n * creating the directory on first use. This is the single write path every\n * adapter uses for human-readable message history.\n */\nexport function appendChannelLog(workingDir: string, channel: string, entry: object): void {\n const dir = join(workingDir, channel);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, \"log.jsonl\"), `${JSON.stringify(entry)}\\n`);\n}\n\n/** Convenience for appending the bot's own outbound message. */\nexport function appendBotResponseLog(\n workingDir: string,\n channel: string,\n text: string,\n ts: string,\n threadTs?: string,\n): void {\n appendChannelLog(workingDir, channel, {\n date: new Date().toISOString(),\n ts,\n ...(threadTs ? { threadTs } : {}),\n user: \"bot\",\n text,\n attachments: [],\n isBot: true,\n });\n}\n\nexport interface ResolveStopTargetInput {\n handler: BotHandler;\n conversationId: string;\n /** Session key derived from the current message; checked first when present. */\n sessionKey?: string;\n}\n\n/**\n * Pick which session key a `/stop` should target without applying any\n * platform-specific fallback policy. Order:\n * 1. The provided sessionKey, if running.\n * 2. The bare conversationId, if running.\n */\nexport function resolveStopTarget(input: ResolveStopTargetInput): string | null {\n const { handler, conversationId, sessionKey } = input;\n\n if (sessionKey && handler.isRunning(sessionKey)) return sessionKey;\n if (handler.isRunning(conversationId)) return conversationId;\n return null;\n}\n\n/**\n * Return the single running scoped session for this conversation, or null when\n * there are zero or multiple matches.\n */\nexport function resolveOnlyScopedStopTarget(\n handler: BotHandler,\n conversationId: string,\n): string | null {\n const runningScopes = handler\n .getRunningSessions()\n .map((s) => s.sessionKey)\n .filter((k) => k.startsWith(`${conversationId}:`));\n\n return runningScopes.length === 1 ? runningScopes[0] : null;\n}\n\n/**\n * Render tool-call args for human display. Drops `label` (already in the\n * heading) and folds `path` + `offset`/`limit` into a single `path:start-end`\n * line. Pure data normalization with no platform-specific markup.\n */\nexport function formatToolArgs(args: Record<string, unknown> | undefined): string {\n if (!args) return \"\";\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(args)) {\n if (key === \"label\" || key === \"offset\" || key === \"limit\") continue;\n\n if (key === \"path\" && typeof value === \"string\") {\n const offset = args.offset as number | undefined;\n const limit = args.limit as number | undefined;\n lines.push(\n offset !== undefined && limit !== undefined\n ? `${value}:${offset}-${offset + limit}`\n : value,\n );\n continue;\n }\n\n lines.push(typeof value === \"string\" ? value : JSON.stringify(value));\n }\n\n return lines.join(\"\\n\");\n}\n"]}
|
package/dist/adapters/shared.js
CHANGED
|
@@ -9,6 +9,35 @@
|
|
|
9
9
|
import { appendFileSync, existsSync, mkdirSync } from "fs";
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import * as log from "../log.js";
|
|
12
|
+
import { reportUserFacingError } from "../sentry.js";
|
|
13
|
+
export function createChatResponseErrorReporter(resolve) {
|
|
14
|
+
return (err, operation, extra) => {
|
|
15
|
+
reportChatResponseError(err, { ...resolve(), operation, extra });
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function reportChatResponseError(err, context) {
|
|
19
|
+
reportUserFacingError(err, {
|
|
20
|
+
domain: "chat_platform",
|
|
21
|
+
surface: "chat_response",
|
|
22
|
+
operation: context.operation,
|
|
23
|
+
severity: context.operation === "set_working" ? "warning" : "error",
|
|
24
|
+
platform: context.platform,
|
|
25
|
+
context: {
|
|
26
|
+
conversationId: context.conversationId,
|
|
27
|
+
channelId: context.channelId,
|
|
28
|
+
chatId: context.chatId,
|
|
29
|
+
messageId: context.messageId,
|
|
30
|
+
sessionKey: context.sessionKey,
|
|
31
|
+
responseMessageId: context.responseMessageId,
|
|
32
|
+
threadTs: context.threadTs,
|
|
33
|
+
replyTargetId: context.replyTargetId,
|
|
34
|
+
replyToId: context.replyToId,
|
|
35
|
+
conversationKind: context.conversationKind,
|
|
36
|
+
isThreaded: context.isThreaded,
|
|
37
|
+
...context.extra,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
12
41
|
export class ChannelQueue {
|
|
13
42
|
constructor(name = "") {
|
|
14
43
|
this.name = name;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/adapters/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Helpers shared across platform adapters.\n *\n * The agent runner is platform-agnostic: it hands strings and structured tool\n * results to each adapter, which decides how to split, format, and route them.\n * The split/normalize logic itself doesn't differ across platforms — only the\n * markup wrappers — so it lives here once.\n */\n\nimport { appendFileSync, existsSync, mkdirSync } from \"fs\";\nimport { join } from \"path\";\nimport type { BotHandler } from \"../adapter.js\";\nimport * as log from \"../log.js\";\n\nexport class ChannelQueue {\n private queue: Array<() => Promise<void>> = [];\n private processing = false;\n\n constructor(private readonly name: string = \"\") {}\n\n enqueue(work: () => Promise<void>): 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(\n `${this.name ? this.name + \" \" : \"\"}queue error`,\n err instanceof Error ? err.message : String(err),\n );\n }\n this.processing = false;\n this.processNext();\n }\n}\n\nexport interface RetryOptions {\n /** Predicate that returns true when an error indicates a platform-side rate limit. */\n isRateLimited: (err: Error) => boolean;\n maxRetries?: number;\n baseDelayMs?: number;\n}\n\n/**\n * Run `fn` and retry with exponential backoff when its error matches\n * `isRateLimited`. Other errors propagate immediately. Each platform supplies\n * its own predicate so we don't have to know every SDK's error shape here.\n */\nexport async function withRetry<T>(fn: () => Promise<T>, opts: RetryOptions): Promise<T> {\n const maxRetries = opts.maxRetries ?? 3;\n const baseDelayMs = opts.baseDelayMs ?? 1000;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (opts.isRateLimited(lastError)) {\n const delay = baseDelayMs * Math.pow(2, attempt);\n log.logWarning(\n `Rate limited, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n throw lastError;\n }\n }\n throw lastError;\n}\n\n/**\n * Split `text` into chunks no larger than `limit`, appending a continuation\n * marker (e.g. `_(continued 1)_`) at the end of every part except the last.\n *\n * Each adapter passes its own `formatContinuation` so the marker uses the\n * platform's italic / emphasis convention.\n */\nexport function splitText(\n text: string,\n limit: number,\n formatContinuation: (partNum: number) => string,\n): string[] {\n if (text.length <= limit) return [text];\n const parts: string[] = [];\n let remaining = text;\n let partNum = 1;\n while (remaining.length > 0) {\n const suffixReserve = formatContinuation(partNum).length + 8;\n const chunkLimit = Math.max(1, limit - suffixReserve);\n const chunk = remaining.slice(0, chunkLimit);\n remaining = remaining.slice(chunkLimit);\n const suffix = remaining.length > 0 ? `\\n${formatContinuation(partNum)}` : \"\";\n parts.push(chunk + suffix);\n partNum++;\n }\n return parts;\n}\n\n/**\n * Append a JSON-serializable entry to `${workingDir}/${channel}/log.jsonl`,\n * creating the directory on first use. This is the single write path every\n * adapter uses for human-readable message history.\n */\nexport function appendChannelLog(workingDir: string, channel: string, entry: object): void {\n const dir = join(workingDir, channel);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, \"log.jsonl\"), `${JSON.stringify(entry)}\\n`);\n}\n\n/** Convenience for appending the bot's own outbound message. */\nexport function appendBotResponseLog(\n workingDir: string,\n channel: string,\n text: string,\n ts: string,\n threadTs?: string,\n): void {\n appendChannelLog(workingDir, channel, {\n date: new Date().toISOString(),\n ts,\n ...(threadTs ? { threadTs } : {}),\n user: \"bot\",\n text,\n attachments: [],\n isBot: true,\n });\n}\n\nexport interface ResolveStopTargetInput {\n handler: BotHandler;\n conversationId: string;\n /** Session key derived from the current message; checked first when present. */\n sessionKey?: string;\n}\n\n/**\n * Pick which session key a `/stop` should target without applying any\n * platform-specific fallback policy. Order:\n * 1. The provided sessionKey, if running.\n * 2. The bare conversationId, if running.\n */\nexport function resolveStopTarget(input: ResolveStopTargetInput): string | null {\n const { handler, conversationId, sessionKey } = input;\n\n if (sessionKey && handler.isRunning(sessionKey)) return sessionKey;\n if (handler.isRunning(conversationId)) return conversationId;\n return null;\n}\n\n/**\n * Return the single running scoped session for this conversation, or null when\n * there are zero or multiple matches.\n */\nexport function resolveOnlyScopedStopTarget(\n handler: BotHandler,\n conversationId: string,\n): string | null {\n const runningScopes = handler\n .getRunningSessions()\n .map((s) => s.sessionKey)\n .filter((k) => k.startsWith(`${conversationId}:`));\n\n return runningScopes.length === 1 ? runningScopes[0] : null;\n}\n\n/**\n * Render tool-call args for human display. Drops `label` (already in the\n * heading) and folds `path` + `offset`/`limit` into a single `path:start-end`\n * line. Pure data normalization with no platform-specific markup.\n */\nexport function formatToolArgs(args: Record<string, unknown> | undefined): string {\n if (!args) return \"\";\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(args)) {\n if (key === \"label\" || key === \"offset\" || key === \"limit\") continue;\n\n if (key === \"path\" && typeof value === \"string\") {\n const offset = args.offset as number | undefined;\n const limit = args.limit as number | undefined;\n lines.push(\n offset !== undefined && limit !== undefined\n ? `${value}:${offset}-${offset + limit}`\n : value,\n );\n continue;\n }\n\n lines.push(typeof value === \"string\" ? value : JSON.stringify(value));\n }\n\n return lines.join(\"\\n\");\n}\n"]}
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/adapters/shared.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Helpers shared across platform adapters.\n *\n * The agent runner is platform-agnostic: it hands strings and structured tool\n * results to each adapter, which decides how to split, format, and route them.\n * The split/normalize logic itself doesn't differ across platforms — only the\n * markup wrappers — so it lives here once.\n */\n\nimport { appendFileSync, existsSync, mkdirSync } from \"fs\";\nimport { join } from \"path\";\nimport type { BotHandler } from \"../adapter.js\";\nimport * as log from \"../log.js\";\nimport { reportUserFacingError } from \"../sentry.js\";\n\nexport type ChatResponseErrorOperation =\n | \"respond\"\n | \"replace_response\"\n | \"respond_diagnostic\"\n | \"set_working\";\n\nexport interface ChatResponseErrorContext {\n platform: string;\n conversationId: string;\n messageId: string;\n sessionKey: string;\n conversationKind: string;\n operation: ChatResponseErrorOperation;\n channelId?: string;\n chatId?: number;\n responseMessageId?: string | number | null;\n threadTs?: string;\n replyTargetId?: string;\n replyToId?: number | null;\n isThreaded?: boolean;\n extra?: Record<string, unknown>;\n}\n\nexport type ChatResponseErrorReporter = (\n err: unknown,\n operation: ChatResponseErrorOperation,\n extra?: Record<string, unknown>,\n) => void;\n\nexport function createChatResponseErrorReporter(\n resolve: () => Omit<ChatResponseErrorContext, \"operation\" | \"extra\">,\n): ChatResponseErrorReporter {\n return (err, operation, extra) => {\n reportChatResponseError(err, { ...resolve(), operation, extra });\n };\n}\n\nexport function reportChatResponseError(err: unknown, context: ChatResponseErrorContext): void {\n reportUserFacingError(err, {\n domain: \"chat_platform\",\n surface: \"chat_response\",\n operation: context.operation,\n severity: context.operation === \"set_working\" ? \"warning\" : \"error\",\n platform: context.platform,\n context: {\n conversationId: context.conversationId,\n channelId: context.channelId,\n chatId: context.chatId,\n messageId: context.messageId,\n sessionKey: context.sessionKey,\n responseMessageId: context.responseMessageId,\n threadTs: context.threadTs,\n replyTargetId: context.replyTargetId,\n replyToId: context.replyToId,\n conversationKind: context.conversationKind,\n isThreaded: context.isThreaded,\n ...context.extra,\n },\n });\n}\n\nexport class ChannelQueue {\n private queue: Array<() => Promise<void>> = [];\n private processing = false;\n\n constructor(private readonly name: string = \"\") {}\n\n enqueue(work: () => Promise<void>): 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(\n `${this.name ? this.name + \" \" : \"\"}queue error`,\n err instanceof Error ? err.message : String(err),\n );\n }\n this.processing = false;\n this.processNext();\n }\n}\n\nexport interface RetryOptions {\n /** Predicate that returns true when an error indicates a platform-side rate limit. */\n isRateLimited: (err: Error) => boolean;\n maxRetries?: number;\n baseDelayMs?: number;\n}\n\n/**\n * Run `fn` and retry with exponential backoff when its error matches\n * `isRateLimited`. Other errors propagate immediately. Each platform supplies\n * its own predicate so we don't have to know every SDK's error shape here.\n */\nexport async function withRetry<T>(fn: () => Promise<T>, opts: RetryOptions): Promise<T> {\n const maxRetries = opts.maxRetries ?? 3;\n const baseDelayMs = opts.baseDelayMs ?? 1000;\n let lastError: Error | undefined;\n\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n return await fn();\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n if (opts.isRateLimited(lastError)) {\n const delay = baseDelayMs * Math.pow(2, attempt);\n log.logWarning(\n `Rate limited, retrying in ${delay}ms (attempt ${attempt + 1}/${maxRetries})`,\n );\n await new Promise((resolve) => setTimeout(resolve, delay));\n continue;\n }\n\n throw lastError;\n }\n }\n throw lastError;\n}\n\n/**\n * Split `text` into chunks no larger than `limit`, appending a continuation\n * marker (e.g. `_(continued 1)_`) at the end of every part except the last.\n *\n * Each adapter passes its own `formatContinuation` so the marker uses the\n * platform's italic / emphasis convention.\n */\nexport function splitText(\n text: string,\n limit: number,\n formatContinuation: (partNum: number) => string,\n): string[] {\n if (text.length <= limit) return [text];\n const parts: string[] = [];\n let remaining = text;\n let partNum = 1;\n while (remaining.length > 0) {\n const suffixReserve = formatContinuation(partNum).length + 8;\n const chunkLimit = Math.max(1, limit - suffixReserve);\n const chunk = remaining.slice(0, chunkLimit);\n remaining = remaining.slice(chunkLimit);\n const suffix = remaining.length > 0 ? `\\n${formatContinuation(partNum)}` : \"\";\n parts.push(chunk + suffix);\n partNum++;\n }\n return parts;\n}\n\n/**\n * Append a JSON-serializable entry to `${workingDir}/${channel}/log.jsonl`,\n * creating the directory on first use. This is the single write path every\n * adapter uses for human-readable message history.\n */\nexport function appendChannelLog(workingDir: string, channel: string, entry: object): void {\n const dir = join(workingDir, channel);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n appendFileSync(join(dir, \"log.jsonl\"), `${JSON.stringify(entry)}\\n`);\n}\n\n/** Convenience for appending the bot's own outbound message. */\nexport function appendBotResponseLog(\n workingDir: string,\n channel: string,\n text: string,\n ts: string,\n threadTs?: string,\n): void {\n appendChannelLog(workingDir, channel, {\n date: new Date().toISOString(),\n ts,\n ...(threadTs ? { threadTs } : {}),\n user: \"bot\",\n text,\n attachments: [],\n isBot: true,\n });\n}\n\nexport interface ResolveStopTargetInput {\n handler: BotHandler;\n conversationId: string;\n /** Session key derived from the current message; checked first when present. */\n sessionKey?: string;\n}\n\n/**\n * Pick which session key a `/stop` should target without applying any\n * platform-specific fallback policy. Order:\n * 1. The provided sessionKey, if running.\n * 2. The bare conversationId, if running.\n */\nexport function resolveStopTarget(input: ResolveStopTargetInput): string | null {\n const { handler, conversationId, sessionKey } = input;\n\n if (sessionKey && handler.isRunning(sessionKey)) return sessionKey;\n if (handler.isRunning(conversationId)) return conversationId;\n return null;\n}\n\n/**\n * Return the single running scoped session for this conversation, or null when\n * there are zero or multiple matches.\n */\nexport function resolveOnlyScopedStopTarget(\n handler: BotHandler,\n conversationId: string,\n): string | null {\n const runningScopes = handler\n .getRunningSessions()\n .map((s) => s.sessionKey)\n .filter((k) => k.startsWith(`${conversationId}:`));\n\n return runningScopes.length === 1 ? runningScopes[0] : null;\n}\n\n/**\n * Render tool-call args for human display. Drops `label` (already in the\n * heading) and folds `path` + `offset`/`limit` into a single `path:start-end`\n * line. Pure data normalization with no platform-specific markup.\n */\nexport function formatToolArgs(args: Record<string, unknown> | undefined): string {\n if (!args) return \"\";\n const lines: string[] = [];\n\n for (const [key, value] of Object.entries(args)) {\n if (key === \"label\" || key === \"offset\" || key === \"limit\") continue;\n\n if (key === \"path\" && typeof value === \"string\") {\n const offset = args.offset as number | undefined;\n const limit = args.limit as number | undefined;\n lines.push(\n offset !== undefined && limit !== undefined\n ? `${value}:${offset}-${offset + limit}`\n : value,\n );\n continue;\n }\n\n lines.push(typeof value === \"string\" ? value : JSON.stringify(value));\n }\n\n return lines.join(\"\\n\");\n}\n"]}
|
|
@@ -89,8 +89,8 @@ export declare class SlackBot implements Bot {
|
|
|
89
89
|
getAllChannels(): SlackChannel[];
|
|
90
90
|
private stripOwnMention;
|
|
91
91
|
postMessage(channel: string, text: string): Promise<string>;
|
|
92
|
-
postEphemeral(channel: string, user: string, text: string): Promise<void>;
|
|
93
|
-
postEphemeralBlocks(channel: string, user: string, text: string, blocks: object[]): Promise<void>;
|
|
92
|
+
postEphemeral(channel: string, user: string, text: string, threadTs?: string): Promise<void>;
|
|
93
|
+
postEphemeralBlocks(channel: string, user: string, text: string, blocks: object[], threadTs?: string): Promise<void>;
|
|
94
94
|
postMessageBlocks(channel: string, text: string, blocks: object[]): Promise<string>;
|
|
95
95
|
postPrivate(conversationId: string, userId: string, text: string): Promise<void>;
|
|
96
96
|
postPrivateDiagnostic(conversationId: string, userId: string, text: string, options?: {
|
|
@@ -106,7 +106,6 @@ export declare class SlackBot implements Bot {
|
|
|
106
106
|
uploadFile(channel: string, filePath: string, title?: string, threadTs?: string): Promise<void>;
|
|
107
107
|
logToFile(channel: string, entry: object): void;
|
|
108
108
|
logBotResponse(channel: string, text: string, ts: string, threadTs?: string): void;
|
|
109
|
-
aliasSyntheticEventThread(channel: string, threadTs: string, eventTs: string): void;
|
|
110
109
|
getPlatformInfo(): PlatformInfo;
|
|
111
110
|
/**
|
|
112
111
|
* Enqueue an event for processing. Always queues (no "already working" rejection).
|
|
@@ -115,6 +114,7 @@ export declare class SlackBot implements Bot {
|
|
|
115
114
|
enqueueEvent(event: BotEvent): boolean;
|
|
116
115
|
private getQueue;
|
|
117
116
|
private resolveQueueKey;
|
|
117
|
+
private hasKnownForkSession;
|
|
118
118
|
private shouldTriggerSharedThreadReply;
|
|
119
119
|
private buildHomeView;
|
|
120
120
|
private resolveStopTarget;
|