@geminixiang/mikan 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +7 -422
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/response-lifecycle.d.ts +11 -0
- package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -0
- package/dist/adapters/slack/response-lifecycle.js +426 -0
- package/dist/adapters/slack/response-lifecycle.js.map +1 -0
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
- package/dist/adapters/slack/tools/attach.js +3 -4
- package/dist/adapters/slack/tools/attach.js.map +1 -1
- package/dist/agent.d.ts +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +19 -11
- package/dist/agent.js.map +1 -1
- package/dist/log.d.ts +1 -0
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +5 -0
- package/dist/log.js.map +1 -1
- package/dist/model-registry.d.ts +4 -0
- package/dist/model-registry.d.ts.map +1 -0
- package/dist/model-registry.js +7 -0
- package/dist/model-registry.js.map +1 -0
- package/dist/trigger.d.ts.map +1 -1
- package/dist/trigger.js +12 -4
- package/dist/trigger.js.map +1 -1
- package/dist/web/admin/portal.d.ts.map +1 -1
- package/dist/web/admin/portal.js +18 -6
- package/dist/web/admin/portal.js.map +1 -1
- package/dist/web/admin/provider-models.d.ts +10 -0
- package/dist/web/admin/provider-models.d.ts.map +1 -0
- package/dist/web/admin/provider-models.js +104 -0
- package/dist/web/admin/provider-models.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,20 @@ any release.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.4.3] - 2026-06-12
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Group admin model selectors by verified availability for OpenAI and Anthropic keys.
|
|
17
|
+
- Log the active provider and model when each agent run starts to simplify server-side debugging.
|
|
18
|
+
|
|
19
|
+
## [0.4.2] - 2026-06-11
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- Resolve custom providers through pi's model registry so conversation settings can select models from `models.json`.
|
|
24
|
+
- Resolve attached file paths from the sandbox runtime workspace so image sandboxes upload generated files from the correct host path.
|
|
25
|
+
|
|
12
26
|
## [0.4.1] - 2026-06-08
|
|
13
27
|
|
|
14
28
|
### Changed
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAEX,mBAAmB,EAEnB,YAAY,EACb,MAAM,kBAAkB,CAAC;AAS1B,OAAO,EAA2B,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAEnF,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAmEtD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,QAAQ,EACf,cAAc,GAAE,mBAAwB,GACvC;IACD,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,mBAAmB,CAAC;IACjC,QAAQ,EAAE,YAAY,CAAC;CACxB,CAkeA","sourcesContent":["import type {\n ChatMessage,\n ChatResponseBlockKit,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport {\n createChatResponseErrorReporter,\n formatToolArgs,\n splitText,\n type ChatResponseErrorOperation,\n} from \"../shared.js\";\nimport { BufferedResponseStream } from \"../streaming.js\";\nimport { buildMrkdwnContextBlock, type SlackBot, type SlackEvent } from \"./bot.js\";\nimport { planSlackAdapterSession } from \"./session.js\";\nexport type { SlackAdapterOptions } from \"./types.js\";\nimport type { SlackAdapterOptions } from \"./types.js\";\n\nconst 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; // Best-effort streaming cap; final responses use Slack error-driven fallback.\nconst MAX_THREAD_LENGTH = 20000;\nconst FALLBACK_MAIN_LENGTH = 3000;\nconst WORKING_INDICATOR = \" ...\";\nconst TRUNCATION_NOTE_INCREMENTAL =\n \"\\n\\n_(message truncated, ask me to elaborate on specific parts)_\";\n\nconst formatSlackContinuation = (partNum: number): string => `_(continued ${partNum})_`;\n\nfunction isSlackMsgTooLong(err: unknown): boolean {\n const data = (err as { data?: { error?: string } } | undefined)?.data;\n const message = err instanceof Error ? err.message : String(err);\n return data?.error === \"msg_too_long\" || message.includes(\"msg_too_long\");\n}\n\nfunction fallbackLongSlackText(\n text: string,\n overflowLink?: string,\n prefixLength = FALLBACK_MAIN_LENGTH,\n): string {\n const suffix = overflowLink\n ? `\\n\\n_(message too long for Slack; continued in thread; session view: <${overflowLink}|open>)_`\n : \"\\n\\n_(message too long for Slack; continued in thread)_\";\n return `${text.slice(0, prefixLength)}${suffix}`;\n}\n\nasync function postSlackTextWithFallback(\n post: (text: string) => Promise<string | void>,\n text: string,\n overflowLink?: string,\n): Promise<{ result: string | void; text: string; prefixLength: number }> {\n let prefixLength = FALLBACK_MAIN_LENGTH;\n let lastErr: unknown;\n\n for (;;) {\n const fallbackText = fallbackLongSlackText(text, overflowLink, prefixLength);\n try {\n const result = await post(fallbackText);\n return { result, text: fallbackText, prefixLength };\n } catch (err) {\n if (!isSlackMsgTooLong(err)) throw err;\n lastErr = err;\n if (prefixLength === 0) {\n throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));\n }\n prefixLength = Math.max(0, Math.floor(prefixLength / 2));\n }\n }\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 adapterOptions: SlackAdapterOptions = {},\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n const sessionPlan = planSlackAdapterSession(event, {\n initialMessageTs: adapterOptions.initialMessageTs,\n });\n let messageTs: string | null = sessionPlan.initialMessageTs ?? null;\n let assistantStatusFailureWarned = false;\n const onAssistantStatusError = (label: string, err: unknown): void => {\n if (assistantStatusFailureWarned) return;\n assistantStatusFailureWarned = true;\n log.logWarning(\n `Slack setAssistantStatus failed (${label}; further occurrences suppressed for this session)`,\n err instanceof Error ? err.message : String(err),\n );\n };\n const threadMessageTs: string[] = [];\n let accumulatedText = \"\";\n let isWorking = true;\n let blockKitFinalized = false;\n let streamActive = false;\n let streamUnavailable = false;\n let streamedText = \"\";\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 // Slack message timestamps are numeric; event-file triggers use `event:<filename>`.\n const eventFilename = event.ts.match(/^event:([^:]+(?:\\.json)?)/)?.[1];\n\n const { rootTs, isThreaded } = sessionPlan;\n const replyMode = adapterOptions.replyMode ?? \"top-level\";\n const replyInThread = Boolean(rootTs && (isThreaded || replyMode === \"thread\"));\n\n /**\n * Post the first visible reply.\n * Default Slack behavior is now top-level channel replies.\n * If the triggering message is already inside a thread, stay in that thread.\n */\n const postFirstMessage = async (text: string): Promise<string> => {\n if (replyInThread && rootTs) {\n return slack.postInThread(channelId, rootTs, text);\n }\n return slack.postMessage(channelId, text);\n };\n\n const postDiagnosticDirect = async (\n text: string,\n options?: { style?: \"muted\" | \"error\" },\n ): Promise<void> => {\n const threadAnchor = messageTs ?? rootTs;\n if (!threadAnchor) return;\n\n for (const part of splitText(text, MAX_THREAD_LENGTH, formatSlackContinuation)) {\n if (options?.style === \"muted\") {\n const ts = await slack.postInThreadBlocks(channelId, threadAnchor, part, [\n buildMrkdwnContextBlock(part),\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: sessionPlan.sessionKey,\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 reportResponseError = createChatResponseErrorReporter(() => ({\n platform: \"slack\",\n conversationId,\n channelId,\n messageId: message.id,\n sessionKey: message.sessionKey,\n responseMessageId: messageTs,\n threadTs: rootTs,\n conversationKind: message.conversationKind,\n isThreaded,\n }));\n\n const postOrUpdateMain = async (body: string, handleMsgTooLong = true): Promise<void> => {\n const writeMain = async (text: string): Promise<string | void> => {\n if (messageTs) {\n await slack.updateMessage(channelId, messageTs, text);\n return;\n }\n if (replyInThread && rootTs) {\n messageTs = await slack.postInThread(channelId, rootTs, text);\n return messageTs;\n }\n messageTs = await postFirstMessage(text);\n return messageTs;\n };\n\n try {\n await writeMain(body);\n } catch (err) {\n if (!handleMsgTooLong || !isSlackMsgTooLong(err)) throw err;\n const fallback = await postSlackTextWithFallback(writeMain, body);\n accumulatedText = fallback.text;\n stream.setText(accumulatedText);\n }\n };\n\n const abandonActiveStream = async (): Promise<void> => {\n if (!streamActive || !messageTs) return;\n const failedStreamTs = messageTs;\n streamActive = false;\n streamedText = \"\";\n await slack.stopMessageStream(channelId, failedStreamTs).catch(() => undefined);\n if (messageTs === failedStreamTs) messageTs = null;\n };\n\n const startOrAppendStream = async (text: string, displayText: string): Promise<void> => {\n if (streamUnavailable) {\n await postOrUpdateMain(displayText);\n return;\n }\n\n try {\n if (messageTs && streamActive) {\n if (!text.startsWith(streamedText)) {\n await abandonActiveStream();\n await postOrUpdateMain(displayText);\n return;\n }\n const delta = text.slice(streamedText.length);\n if (delta) {\n await slack.appendMessageStream(channelId, messageTs, delta);\n streamedText = text;\n }\n return;\n }\n if (!replyInThread || !rootTs) {\n streamUnavailable = true;\n await postOrUpdateMain(displayText);\n return;\n }\n messageTs = await slack.startMessageStream(channelId, text, rootTs, event.user);\n streamActive = true;\n streamedText = text;\n } catch (err) {\n streamUnavailable = true;\n log.logWarning(\n \"Slack streaming unavailable; falling back to chat.update\",\n err instanceof Error ? err.message : String(err),\n );\n await abandonActiveStream();\n await postOrUpdateMain(displayText);\n }\n };\n\n const stream = new BufferedResponseStream({\n flush: async (text) => {\n accumulatedText = text;\n const mainLimit = isWorking ? MAX_MAIN_LENGTH - WORKING_INDICATOR.length : MAX_MAIN_LENGTH;\n if (accumulatedText.length > mainLimit) {\n accumulatedText =\n accumulatedText.substring(0, mainLimit - TRUNCATION_NOTE_INCREMENTAL.length) +\n TRUNCATION_NOTE_INCREMENTAL;\n stream.setText(accumulatedText);\n }\n const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;\n await startOrAppendStream(text, displayText);\n },\n finish: async (text) => {\n accumulatedText = text;\n isWorking = false;\n if (streamActive && messageTs) {\n try {\n if (!accumulatedText.startsWith(streamedText)) {\n await abandonActiveStream();\n await postOrUpdateMain(accumulatedText);\n return;\n }\n const delta = accumulatedText.slice(streamedText.length);\n if (delta) {\n await slack.appendMessageStream(channelId, messageTs, delta);\n streamedText = accumulatedText;\n }\n await slack.stopMessageStream(channelId, messageTs);\n streamActive = false;\n streamedText = \"\";\n return;\n } catch (err) {\n streamUnavailable = true;\n log.logWarning(\n \"Slack streaming unavailable; falling back to chat.update\",\n err instanceof Error ? err.message : String(err),\n );\n await abandonActiveStream();\n }\n }\n if (messageTs || accumulatedText) {\n await postOrUpdateMain(accumulatedText);\n }\n },\n });\n\n const queueResponseOperation = async (\n label: string,\n operation: ChatResponseErrorOperation,\n work: () => Promise<void>,\n context: (err: unknown) => Record<string, unknown>,\n ): Promise<void> => {\n updatePromise = updatePromise.then(async () => {\n try {\n await work();\n } catch (err) {\n log.logWarning(`Slack ${label} error`, err instanceof Error ? err.message : String(err));\n reportResponseError(err, operation, context(err));\n }\n });\n await updatePromise;\n };\n\n const responseCtx = {\n respond: async (text: string) => {\n await queueResponseOperation(\n \"respond\",\n \"respond\",\n async () => {\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${text}` : text;\n\n const mainLimit = isWorking\n ? MAX_MAIN_LENGTH - WORKING_INDICATOR.length\n : MAX_MAIN_LENGTH;\n if (accumulatedText.length > mainLimit) {\n accumulatedText =\n accumulatedText.substring(0, mainLimit - TRUNCATION_NOTE_INCREMENTAL.length) +\n TRUNCATION_NOTE_INCREMENTAL;\n }\n\n stream.setText(accumulatedText);\n const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;\n await startOrAppendStream(text, displayText);\n\n if (messageTs) {\n slack.logBotResponse(channelId, text, messageTs, replyInThread ? rootTs : undefined);\n }\n },\n () => ({\n phase: messageTs ? \"update\" : \"initial_post\",\n textLength: text.length,\n accumulatedLength: accumulatedText.length,\n }),\n );\n },\n\n appendResponseDelta: async (delta: string) => {\n await queueResponseOperation(\n \"appendResponseDelta\",\n \"respond\",\n async () => {\n await stream.append(delta);\n if (messageTs) {\n slack.logBotResponse(channelId, delta, messageTs, replyInThread ? rootTs : undefined);\n }\n },\n () => ({ textLength: delta.length, accumulatedLength: stream.getText().length }),\n );\n },\n\n finishResponse: async (finalText?: string) => {\n await queueResponseOperation(\n \"finishResponse\",\n \"set_working\",\n async () => {\n await stream.finish(finalText);\n accumulatedText = stream.getText();\n if (!rootTs) return;\n await slack\n .setAssistantStatus(channelId, rootTs, \"\")\n .catch((err) => onAssistantStatusError(\"clear-on-idle\", err));\n },\n () => ({ finalTextLength: finalText?.length }),\n );\n },\n\n replaceResponse: async (text: string, options?: { createOverflowLink?: () => string }) => {\n await queueResponseOperation(\n \"replaceResponse\",\n \"replace_response\",\n async () => {\n // Lazy: only mint a token if Slack actually rejects the message.\n let overflowLink: string | undefined;\n const resolveOverflowLink = (): string | undefined => {\n if (overflowLink === undefined && options?.createOverflowLink) {\n overflowLink = options.createOverflowLink();\n }\n return overflowLink;\n };\n\n accumulatedText = text;\n stream.setText(accumulatedText);\n const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;\n\n try {\n if (streamActive && messageTs) {\n await slack.stopMessageStream(channelId, messageTs);\n streamActive = false;\n streamedText = \"\";\n }\n await postOrUpdateMain(displayText, false);\n } catch (err) {\n if (!isSlackMsgTooLong(err)) throw err;\n const link = resolveOverflowLink();\n const fallback = await postSlackTextWithFallback(\n async (body) => {\n await postOrUpdateMain(body);\n },\n text,\n link,\n );\n accumulatedText = fallback.text;\n const continuation = text.slice(fallback.prefixLength).trimStart();\n if (continuation) {\n await postDiagnosticDirect(`_(continued from truncated message)_\\n\\n${continuation}`);\n }\n }\n },\n () => ({\n textLength: text.length,\n hadExistingResponse: Boolean(messageTs),\n }),\n );\n },\n\n respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n await queueResponseOperation(\n \"respondDiagnostic\",\n \"respond_diagnostic\",\n async () => {\n await postDiagnosticDirect(text, options);\n },\n () => ({\n textLength: text.length,\n style: options?.style,\n }),\n );\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatSlackToolResult(result));\n },\n\n respondBlockKit: async (response: ChatResponseBlockKit) => {\n updatePromise = updatePromise.then(async () => {\n isWorking = false;\n accumulatedText = response.text;\n if (replyInThread && rootTs) {\n messageTs = await slack.postInThreadBlocks(\n channelId,\n rootTs,\n response.text,\n response.blocks,\n );\n } else {\n messageTs = await slack.postBlocks(channelId, response.text, response.blocks);\n }\n blockKitFinalized = true;\n slack.logBotResponse(\n channelId,\n response.text,\n messageTs,\n replyInThread ? rootTs : undefined,\n response.blocks,\n );\n });\n await updatePromise;\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 (err) {\n // Assistant API not available — first respond() call will create the message.\n onAssistantStatusError(\"typing\", err);\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 await queueResponseOperation(\n \"setWorking\",\n \"set_working\",\n async () => {\n isWorking = working;\n if (blockKitFinalized) {\n if (!working && rootTs) {\n await slack\n .setAssistantStatus(channelId, rootTs, \"\")\n .catch((err) => onAssistantStatusError(\"clear-on-idle\", err));\n }\n return;\n }\n if (messageTs) {\n const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;\n const updates: Promise<void>[] =\n streamActive && !isWorking\n ? [\n slack.stopMessageStream(channelId, messageTs).then(() => {\n streamActive = false;\n }),\n ]\n : [slack.updateMessage(channelId, messageTs, displayText)];\n if (!working && rootTs) {\n updates.push(\n slack\n .setAssistantStatus(channelId, rootTs, \"\")\n .catch((err) => onAssistantStatusError(\"clear-on-idle\", err)),\n );\n }\n await Promise.all(updates);\n }\n },\n () => ({ working }),\n );\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"]}
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/adapters/slack/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAG1D,YAAY,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAMtD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,QAAQ,EACf,cAAc,GAAE,mBAAwB,GACvC;IACD,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,EAAE,mBAAmB,CAAC;IACjC,QAAQ,EAAE,YAAY,CAAC;CACxB,CAyCA","sourcesContent":["import type { ChatMessage, ChatResponseContext, PlatformInfo } from \"../../adapter.js\";\nimport { type SlackBot, type SlackEvent } from \"./bot.js\";\nimport { createSlackResponseContext } from \"./response-lifecycle.js\";\nimport { planSlackAdapterSession } from \"./session.js\";\nexport type { SlackAdapterOptions } from \"./types.js\";\nimport type { SlackAdapterOptions } from \"./types.js\";\n\nconst 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 adapterOptions: SlackAdapterOptions = {},\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n const sessionPlan = planSlackAdapterSession(event, {\n initialMessageTs: adapterOptions.initialMessageTs,\n });\n const user = slack.getUser(event.user);\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: sessionPlan.sessionKey,\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 = createSlackResponseContext({\n event,\n slack,\n sessionPlan,\n replyMode: adapterOptions.replyMode ?? \"top-level\",\n message,\n });\n\n return { message, responseCtx, platform };\n}\n"]}
|
|
@@ -1,117 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { createChatResponseErrorReporter, formatToolArgs, splitText, } from "../shared.js";
|
|
3
|
-
import { BufferedResponseStream } from "../streaming.js";
|
|
4
|
-
import { buildMrkdwnContextBlock } from "./bot.js";
|
|
1
|
+
import { createSlackResponseContext } from "./response-lifecycle.js";
|
|
5
2
|
import { planSlackAdapterSession } from "./session.js";
|
|
6
3
|
const SLACK_FORMATTING_GUIDE = `## Slack Formatting (mrkdwn, NOT Markdown)
|
|
7
4
|
Bold: *text*, Italic: _text_, Code: \`code\`, Block: \`\`\`code\`\`\`, Links: <url|text>
|
|
8
5
|
Do NOT use **double asterisks** or [markdown](links).`;
|
|
9
|
-
const MAX_MAIN_LENGTH = 35000; // Best-effort streaming cap; final responses use Slack error-driven fallback.
|
|
10
|
-
const MAX_THREAD_LENGTH = 20000;
|
|
11
|
-
const FALLBACK_MAIN_LENGTH = 3000;
|
|
12
|
-
const WORKING_INDICATOR = " ...";
|
|
13
|
-
const TRUNCATION_NOTE_INCREMENTAL = "\n\n_(message truncated, ask me to elaborate on specific parts)_";
|
|
14
|
-
const formatSlackContinuation = (partNum) => `_(continued ${partNum})_`;
|
|
15
|
-
function isSlackMsgTooLong(err) {
|
|
16
|
-
const data = err?.data;
|
|
17
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
18
|
-
return data?.error === "msg_too_long" || message.includes("msg_too_long");
|
|
19
|
-
}
|
|
20
|
-
function fallbackLongSlackText(text, overflowLink, prefixLength = FALLBACK_MAIN_LENGTH) {
|
|
21
|
-
const suffix = overflowLink
|
|
22
|
-
? `\n\n_(message too long for Slack; continued in thread; session view: <${overflowLink}|open>)_`
|
|
23
|
-
: "\n\n_(message too long for Slack; continued in thread)_";
|
|
24
|
-
return `${text.slice(0, prefixLength)}${suffix}`;
|
|
25
|
-
}
|
|
26
|
-
async function postSlackTextWithFallback(post, text, overflowLink) {
|
|
27
|
-
let prefixLength = FALLBACK_MAIN_LENGTH;
|
|
28
|
-
let lastErr;
|
|
29
|
-
for (;;) {
|
|
30
|
-
const fallbackText = fallbackLongSlackText(text, overflowLink, prefixLength);
|
|
31
|
-
try {
|
|
32
|
-
const result = await post(fallbackText);
|
|
33
|
-
return { result, text: fallbackText, prefixLength };
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
if (!isSlackMsgTooLong(err))
|
|
37
|
-
throw err;
|
|
38
|
-
lastErr = err;
|
|
39
|
-
if (prefixLength === 0) {
|
|
40
|
-
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
41
|
-
}
|
|
42
|
-
prefixLength = Math.max(0, Math.floor(prefixLength / 2));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function formatSlackToolResult(result) {
|
|
47
|
-
const argsFormatted = formatToolArgs(result.args);
|
|
48
|
-
const duration = (result.durationMs / 1000).toFixed(1);
|
|
49
|
-
let text = `*${result.isError ? "✗" : "✓"} ${result.toolName}*`;
|
|
50
|
-
if (result.label)
|
|
51
|
-
text += `: ${result.label}`;
|
|
52
|
-
text += ` (${duration}s)\n`;
|
|
53
|
-
if (argsFormatted)
|
|
54
|
-
text += `\`\`\`\n${argsFormatted}\n\`\`\`\n`;
|
|
55
|
-
text += `*Result:*\n\`\`\`\n${result.result}\n\`\`\``;
|
|
56
|
-
return text;
|
|
57
|
-
}
|
|
58
6
|
export function createSlackAdapters(event, slack, adapterOptions = {}) {
|
|
59
7
|
const sessionPlan = planSlackAdapterSession(event, {
|
|
60
8
|
initialMessageTs: adapterOptions.initialMessageTs,
|
|
61
9
|
});
|
|
62
|
-
let messageTs = sessionPlan.initialMessageTs ?? null;
|
|
63
|
-
let assistantStatusFailureWarned = false;
|
|
64
|
-
const onAssistantStatusError = (label, err) => {
|
|
65
|
-
if (assistantStatusFailureWarned)
|
|
66
|
-
return;
|
|
67
|
-
assistantStatusFailureWarned = true;
|
|
68
|
-
log.logWarning(`Slack setAssistantStatus failed (${label}; further occurrences suppressed for this session)`, err instanceof Error ? err.message : String(err));
|
|
69
|
-
};
|
|
70
|
-
const threadMessageTs = [];
|
|
71
|
-
let accumulatedText = "";
|
|
72
|
-
let isWorking = true;
|
|
73
|
-
let blockKitFinalized = false;
|
|
74
|
-
let streamActive = false;
|
|
75
|
-
let streamUnavailable = false;
|
|
76
|
-
let streamedText = "";
|
|
77
|
-
let updatePromise = Promise.resolve();
|
|
78
|
-
const channelId = event.channel;
|
|
79
|
-
const conversationId = event.conversationId;
|
|
80
10
|
const user = slack.getUser(event.user);
|
|
81
|
-
// Slack message timestamps are numeric; event-file triggers use `event:<filename>`.
|
|
82
|
-
const eventFilename = event.ts.match(/^event:([^:]+(?:\.json)?)/)?.[1];
|
|
83
|
-
const { rootTs, isThreaded } = sessionPlan;
|
|
84
|
-
const replyMode = adapterOptions.replyMode ?? "top-level";
|
|
85
|
-
const replyInThread = Boolean(rootTs && (isThreaded || replyMode === "thread"));
|
|
86
|
-
/**
|
|
87
|
-
* Post the first visible reply.
|
|
88
|
-
* Default Slack behavior is now top-level channel replies.
|
|
89
|
-
* If the triggering message is already inside a thread, stay in that thread.
|
|
90
|
-
*/
|
|
91
|
-
const postFirstMessage = async (text) => {
|
|
92
|
-
if (replyInThread && rootTs) {
|
|
93
|
-
return slack.postInThread(channelId, rootTs, text);
|
|
94
|
-
}
|
|
95
|
-
return slack.postMessage(channelId, text);
|
|
96
|
-
};
|
|
97
|
-
const postDiagnosticDirect = async (text, options) => {
|
|
98
|
-
const threadAnchor = messageTs ?? rootTs;
|
|
99
|
-
if (!threadAnchor)
|
|
100
|
-
return;
|
|
101
|
-
for (const part of splitText(text, MAX_THREAD_LENGTH, formatSlackContinuation)) {
|
|
102
|
-
if (options?.style === "muted") {
|
|
103
|
-
const ts = await slack.postInThreadBlocks(channelId, threadAnchor, part, [
|
|
104
|
-
buildMrkdwnContextBlock(part),
|
|
105
|
-
]);
|
|
106
|
-
threadMessageTs.push(ts);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
const diagnosticText = options?.style === "error" ? `_${part}_` : part;
|
|
110
|
-
const ts = await slack.postInThread(channelId, threadAnchor, diagnosticText);
|
|
111
|
-
threadMessageTs.push(ts);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
11
|
const message = {
|
|
116
12
|
id: event.ts,
|
|
117
13
|
sessionKey: sessionPlan.sessionKey,
|
|
@@ -136,324 +32,13 @@ export function createSlackAdapters(event, slack, adapterOptions = {}) {
|
|
|
136
32
|
showUsageSummary: true,
|
|
137
33
|
},
|
|
138
34
|
};
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
responseMessageId: messageTs,
|
|
146
|
-
threadTs: rootTs,
|
|
147
|
-
conversationKind: message.conversationKind,
|
|
148
|
-
isThreaded,
|
|
149
|
-
}));
|
|
150
|
-
const postOrUpdateMain = async (body, handleMsgTooLong = true) => {
|
|
151
|
-
const writeMain = async (text) => {
|
|
152
|
-
if (messageTs) {
|
|
153
|
-
await slack.updateMessage(channelId, messageTs, text);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (replyInThread && rootTs) {
|
|
157
|
-
messageTs = await slack.postInThread(channelId, rootTs, text);
|
|
158
|
-
return messageTs;
|
|
159
|
-
}
|
|
160
|
-
messageTs = await postFirstMessage(text);
|
|
161
|
-
return messageTs;
|
|
162
|
-
};
|
|
163
|
-
try {
|
|
164
|
-
await writeMain(body);
|
|
165
|
-
}
|
|
166
|
-
catch (err) {
|
|
167
|
-
if (!handleMsgTooLong || !isSlackMsgTooLong(err))
|
|
168
|
-
throw err;
|
|
169
|
-
const fallback = await postSlackTextWithFallback(writeMain, body);
|
|
170
|
-
accumulatedText = fallback.text;
|
|
171
|
-
stream.setText(accumulatedText);
|
|
172
|
-
}
|
|
173
|
-
};
|
|
174
|
-
const abandonActiveStream = async () => {
|
|
175
|
-
if (!streamActive || !messageTs)
|
|
176
|
-
return;
|
|
177
|
-
const failedStreamTs = messageTs;
|
|
178
|
-
streamActive = false;
|
|
179
|
-
streamedText = "";
|
|
180
|
-
await slack.stopMessageStream(channelId, failedStreamTs).catch(() => undefined);
|
|
181
|
-
if (messageTs === failedStreamTs)
|
|
182
|
-
messageTs = null;
|
|
183
|
-
};
|
|
184
|
-
const startOrAppendStream = async (text, displayText) => {
|
|
185
|
-
if (streamUnavailable) {
|
|
186
|
-
await postOrUpdateMain(displayText);
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
try {
|
|
190
|
-
if (messageTs && streamActive) {
|
|
191
|
-
if (!text.startsWith(streamedText)) {
|
|
192
|
-
await abandonActiveStream();
|
|
193
|
-
await postOrUpdateMain(displayText);
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
const delta = text.slice(streamedText.length);
|
|
197
|
-
if (delta) {
|
|
198
|
-
await slack.appendMessageStream(channelId, messageTs, delta);
|
|
199
|
-
streamedText = text;
|
|
200
|
-
}
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (!replyInThread || !rootTs) {
|
|
204
|
-
streamUnavailable = true;
|
|
205
|
-
await postOrUpdateMain(displayText);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
messageTs = await slack.startMessageStream(channelId, text, rootTs, event.user);
|
|
209
|
-
streamActive = true;
|
|
210
|
-
streamedText = text;
|
|
211
|
-
}
|
|
212
|
-
catch (err) {
|
|
213
|
-
streamUnavailable = true;
|
|
214
|
-
log.logWarning("Slack streaming unavailable; falling back to chat.update", err instanceof Error ? err.message : String(err));
|
|
215
|
-
await abandonActiveStream();
|
|
216
|
-
await postOrUpdateMain(displayText);
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
const stream = new BufferedResponseStream({
|
|
220
|
-
flush: async (text) => {
|
|
221
|
-
accumulatedText = text;
|
|
222
|
-
const mainLimit = isWorking ? MAX_MAIN_LENGTH - WORKING_INDICATOR.length : MAX_MAIN_LENGTH;
|
|
223
|
-
if (accumulatedText.length > mainLimit) {
|
|
224
|
-
accumulatedText =
|
|
225
|
-
accumulatedText.substring(0, mainLimit - TRUNCATION_NOTE_INCREMENTAL.length) +
|
|
226
|
-
TRUNCATION_NOTE_INCREMENTAL;
|
|
227
|
-
stream.setText(accumulatedText);
|
|
228
|
-
}
|
|
229
|
-
const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;
|
|
230
|
-
await startOrAppendStream(text, displayText);
|
|
231
|
-
},
|
|
232
|
-
finish: async (text) => {
|
|
233
|
-
accumulatedText = text;
|
|
234
|
-
isWorking = false;
|
|
235
|
-
if (streamActive && messageTs) {
|
|
236
|
-
try {
|
|
237
|
-
if (!accumulatedText.startsWith(streamedText)) {
|
|
238
|
-
await abandonActiveStream();
|
|
239
|
-
await postOrUpdateMain(accumulatedText);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const delta = accumulatedText.slice(streamedText.length);
|
|
243
|
-
if (delta) {
|
|
244
|
-
await slack.appendMessageStream(channelId, messageTs, delta);
|
|
245
|
-
streamedText = accumulatedText;
|
|
246
|
-
}
|
|
247
|
-
await slack.stopMessageStream(channelId, messageTs);
|
|
248
|
-
streamActive = false;
|
|
249
|
-
streamedText = "";
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
catch (err) {
|
|
253
|
-
streamUnavailable = true;
|
|
254
|
-
log.logWarning("Slack streaming unavailable; falling back to chat.update", err instanceof Error ? err.message : String(err));
|
|
255
|
-
await abandonActiveStream();
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (messageTs || accumulatedText) {
|
|
259
|
-
await postOrUpdateMain(accumulatedText);
|
|
260
|
-
}
|
|
261
|
-
},
|
|
35
|
+
const responseCtx = createSlackResponseContext({
|
|
36
|
+
event,
|
|
37
|
+
slack,
|
|
38
|
+
sessionPlan,
|
|
39
|
+
replyMode: adapterOptions.replyMode ?? "top-level",
|
|
40
|
+
message,
|
|
262
41
|
});
|
|
263
|
-
const queueResponseOperation = async (label, operation, work, context) => {
|
|
264
|
-
updatePromise = updatePromise.then(async () => {
|
|
265
|
-
try {
|
|
266
|
-
await work();
|
|
267
|
-
}
|
|
268
|
-
catch (err) {
|
|
269
|
-
log.logWarning(`Slack ${label} error`, err instanceof Error ? err.message : String(err));
|
|
270
|
-
reportResponseError(err, operation, context(err));
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
await updatePromise;
|
|
274
|
-
};
|
|
275
|
-
const responseCtx = {
|
|
276
|
-
respond: async (text) => {
|
|
277
|
-
await queueResponseOperation("respond", "respond", async () => {
|
|
278
|
-
accumulatedText = accumulatedText ? `${accumulatedText}\n${text}` : text;
|
|
279
|
-
const mainLimit = isWorking
|
|
280
|
-
? MAX_MAIN_LENGTH - WORKING_INDICATOR.length
|
|
281
|
-
: MAX_MAIN_LENGTH;
|
|
282
|
-
if (accumulatedText.length > mainLimit) {
|
|
283
|
-
accumulatedText =
|
|
284
|
-
accumulatedText.substring(0, mainLimit - TRUNCATION_NOTE_INCREMENTAL.length) +
|
|
285
|
-
TRUNCATION_NOTE_INCREMENTAL;
|
|
286
|
-
}
|
|
287
|
-
stream.setText(accumulatedText);
|
|
288
|
-
const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;
|
|
289
|
-
await startOrAppendStream(text, displayText);
|
|
290
|
-
if (messageTs) {
|
|
291
|
-
slack.logBotResponse(channelId, text, messageTs, replyInThread ? rootTs : undefined);
|
|
292
|
-
}
|
|
293
|
-
}, () => ({
|
|
294
|
-
phase: messageTs ? "update" : "initial_post",
|
|
295
|
-
textLength: text.length,
|
|
296
|
-
accumulatedLength: accumulatedText.length,
|
|
297
|
-
}));
|
|
298
|
-
},
|
|
299
|
-
appendResponseDelta: async (delta) => {
|
|
300
|
-
await queueResponseOperation("appendResponseDelta", "respond", async () => {
|
|
301
|
-
await stream.append(delta);
|
|
302
|
-
if (messageTs) {
|
|
303
|
-
slack.logBotResponse(channelId, delta, messageTs, replyInThread ? rootTs : undefined);
|
|
304
|
-
}
|
|
305
|
-
}, () => ({ textLength: delta.length, accumulatedLength: stream.getText().length }));
|
|
306
|
-
},
|
|
307
|
-
finishResponse: async (finalText) => {
|
|
308
|
-
await queueResponseOperation("finishResponse", "set_working", async () => {
|
|
309
|
-
await stream.finish(finalText);
|
|
310
|
-
accumulatedText = stream.getText();
|
|
311
|
-
if (!rootTs)
|
|
312
|
-
return;
|
|
313
|
-
await slack
|
|
314
|
-
.setAssistantStatus(channelId, rootTs, "")
|
|
315
|
-
.catch((err) => onAssistantStatusError("clear-on-idle", err));
|
|
316
|
-
}, () => ({ finalTextLength: finalText?.length }));
|
|
317
|
-
},
|
|
318
|
-
replaceResponse: async (text, options) => {
|
|
319
|
-
await queueResponseOperation("replaceResponse", "replace_response", async () => {
|
|
320
|
-
// Lazy: only mint a token if Slack actually rejects the message.
|
|
321
|
-
let overflowLink;
|
|
322
|
-
const resolveOverflowLink = () => {
|
|
323
|
-
if (overflowLink === undefined && options?.createOverflowLink) {
|
|
324
|
-
overflowLink = options.createOverflowLink();
|
|
325
|
-
}
|
|
326
|
-
return overflowLink;
|
|
327
|
-
};
|
|
328
|
-
accumulatedText = text;
|
|
329
|
-
stream.setText(accumulatedText);
|
|
330
|
-
const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;
|
|
331
|
-
try {
|
|
332
|
-
if (streamActive && messageTs) {
|
|
333
|
-
await slack.stopMessageStream(channelId, messageTs);
|
|
334
|
-
streamActive = false;
|
|
335
|
-
streamedText = "";
|
|
336
|
-
}
|
|
337
|
-
await postOrUpdateMain(displayText, false);
|
|
338
|
-
}
|
|
339
|
-
catch (err) {
|
|
340
|
-
if (!isSlackMsgTooLong(err))
|
|
341
|
-
throw err;
|
|
342
|
-
const link = resolveOverflowLink();
|
|
343
|
-
const fallback = await postSlackTextWithFallback(async (body) => {
|
|
344
|
-
await postOrUpdateMain(body);
|
|
345
|
-
}, text, link);
|
|
346
|
-
accumulatedText = fallback.text;
|
|
347
|
-
const continuation = text.slice(fallback.prefixLength).trimStart();
|
|
348
|
-
if (continuation) {
|
|
349
|
-
await postDiagnosticDirect(`_(continued from truncated message)_\n\n${continuation}`);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}, () => ({
|
|
353
|
-
textLength: text.length,
|
|
354
|
-
hadExistingResponse: Boolean(messageTs),
|
|
355
|
-
}));
|
|
356
|
-
},
|
|
357
|
-
respondDiagnostic: async (text, options) => {
|
|
358
|
-
await queueResponseOperation("respondDiagnostic", "respond_diagnostic", async () => {
|
|
359
|
-
await postDiagnosticDirect(text, options);
|
|
360
|
-
}, () => ({
|
|
361
|
-
textLength: text.length,
|
|
362
|
-
style: options?.style,
|
|
363
|
-
}));
|
|
364
|
-
},
|
|
365
|
-
respondToolResult: async (result) => {
|
|
366
|
-
await responseCtx.respondDiagnostic(formatSlackToolResult(result));
|
|
367
|
-
},
|
|
368
|
-
respondBlockKit: async (response) => {
|
|
369
|
-
updatePromise = updatePromise.then(async () => {
|
|
370
|
-
isWorking = false;
|
|
371
|
-
accumulatedText = response.text;
|
|
372
|
-
if (replyInThread && rootTs) {
|
|
373
|
-
messageTs = await slack.postInThreadBlocks(channelId, rootTs, response.text, response.blocks);
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
messageTs = await slack.postBlocks(channelId, response.text, response.blocks);
|
|
377
|
-
}
|
|
378
|
-
blockKitFinalized = true;
|
|
379
|
-
slack.logBotResponse(channelId, response.text, messageTs, replyInThread ? rootTs : undefined, response.blocks);
|
|
380
|
-
});
|
|
381
|
-
await updatePromise;
|
|
382
|
-
},
|
|
383
|
-
setTyping: async (isTyping) => {
|
|
384
|
-
if (isTyping && !messageTs && rootTs) {
|
|
385
|
-
try {
|
|
386
|
-
const statusText = eventFilename ? `Starting event: ${eventFilename}` : "Thinking";
|
|
387
|
-
await slack.setAssistantStatus(channelId, rootTs, statusText);
|
|
388
|
-
}
|
|
389
|
-
catch (err) {
|
|
390
|
-
// Assistant API not available — first respond() call will create the message.
|
|
391
|
-
onAssistantStatusError("typing", err);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
},
|
|
395
|
-
uploadFile: async (filePath, title) => {
|
|
396
|
-
await slack.uploadFile(channelId, filePath, title, rootTs);
|
|
397
|
-
},
|
|
398
|
-
setWorking: async (working) => {
|
|
399
|
-
await queueResponseOperation("setWorking", "set_working", async () => {
|
|
400
|
-
isWorking = working;
|
|
401
|
-
if (blockKitFinalized) {
|
|
402
|
-
if (!working && rootTs) {
|
|
403
|
-
await slack
|
|
404
|
-
.setAssistantStatus(channelId, rootTs, "")
|
|
405
|
-
.catch((err) => onAssistantStatusError("clear-on-idle", err));
|
|
406
|
-
}
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
if (messageTs) {
|
|
410
|
-
const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;
|
|
411
|
-
const updates = streamActive && !isWorking
|
|
412
|
-
? [
|
|
413
|
-
slack.stopMessageStream(channelId, messageTs).then(() => {
|
|
414
|
-
streamActive = false;
|
|
415
|
-
}),
|
|
416
|
-
]
|
|
417
|
-
: [slack.updateMessage(channelId, messageTs, displayText)];
|
|
418
|
-
if (!working && rootTs) {
|
|
419
|
-
updates.push(slack
|
|
420
|
-
.setAssistantStatus(channelId, rootTs, "")
|
|
421
|
-
.catch((err) => onAssistantStatusError("clear-on-idle", err)));
|
|
422
|
-
}
|
|
423
|
-
await Promise.all(updates);
|
|
424
|
-
}
|
|
425
|
-
}, () => ({ working }));
|
|
426
|
-
},
|
|
427
|
-
deleteResponse: async () => {
|
|
428
|
-
updatePromise = updatePromise.then(async () => {
|
|
429
|
-
// Clear assistant status first
|
|
430
|
-
if (rootTs) {
|
|
431
|
-
try {
|
|
432
|
-
await slack.setAssistantStatus(channelId, rootTs, "");
|
|
433
|
-
}
|
|
434
|
-
catch {
|
|
435
|
-
// Ignore errors clearing status
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
// Delete thread messages first (in reverse order)
|
|
439
|
-
for (let i = threadMessageTs.length - 1; i >= 0; i--) {
|
|
440
|
-
try {
|
|
441
|
-
await slack.deleteMessage(channelId, threadMessageTs[i]);
|
|
442
|
-
}
|
|
443
|
-
catch {
|
|
444
|
-
// Ignore errors deleting thread messages
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
threadMessageTs.length = 0;
|
|
448
|
-
// Then delete main message
|
|
449
|
-
if (messageTs) {
|
|
450
|
-
await slack.deleteMessage(channelId, messageTs);
|
|
451
|
-
messageTs = null;
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
await updatePromise;
|
|
455
|
-
},
|
|
456
|
-
};
|
|
457
42
|
return { message, responseCtx, platform };
|
|
458
43
|
}
|
|
459
44
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
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 ChatResponseBlockKit,\n ChatResponseContext,\n ChatToolResult,\n PlatformInfo,\n} from \"../../adapter.js\";\nimport * as log from \"../../log.js\";\nimport {\n createChatResponseErrorReporter,\n formatToolArgs,\n splitText,\n type ChatResponseErrorOperation,\n} from \"../shared.js\";\nimport { BufferedResponseStream } from \"../streaming.js\";\nimport { buildMrkdwnContextBlock, type SlackBot, type SlackEvent } from \"./bot.js\";\nimport { planSlackAdapterSession } from \"./session.js\";\nexport type { SlackAdapterOptions } from \"./types.js\";\nimport type { SlackAdapterOptions } from \"./types.js\";\n\nconst 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; // Best-effort streaming cap; final responses use Slack error-driven fallback.\nconst MAX_THREAD_LENGTH = 20000;\nconst FALLBACK_MAIN_LENGTH = 3000;\nconst WORKING_INDICATOR = \" ...\";\nconst TRUNCATION_NOTE_INCREMENTAL =\n \"\\n\\n_(message truncated, ask me to elaborate on specific parts)_\";\n\nconst formatSlackContinuation = (partNum: number): string => `_(continued ${partNum})_`;\n\nfunction isSlackMsgTooLong(err: unknown): boolean {\n const data = (err as { data?: { error?: string } } | undefined)?.data;\n const message = err instanceof Error ? err.message : String(err);\n return data?.error === \"msg_too_long\" || message.includes(\"msg_too_long\");\n}\n\nfunction fallbackLongSlackText(\n text: string,\n overflowLink?: string,\n prefixLength = FALLBACK_MAIN_LENGTH,\n): string {\n const suffix = overflowLink\n ? `\\n\\n_(message too long for Slack; continued in thread; session view: <${overflowLink}|open>)_`\n : \"\\n\\n_(message too long for Slack; continued in thread)_\";\n return `${text.slice(0, prefixLength)}${suffix}`;\n}\n\nasync function postSlackTextWithFallback(\n post: (text: string) => Promise<string | void>,\n text: string,\n overflowLink?: string,\n): Promise<{ result: string | void; text: string; prefixLength: number }> {\n let prefixLength = FALLBACK_MAIN_LENGTH;\n let lastErr: unknown;\n\n for (;;) {\n const fallbackText = fallbackLongSlackText(text, overflowLink, prefixLength);\n try {\n const result = await post(fallbackText);\n return { result, text: fallbackText, prefixLength };\n } catch (err) {\n if (!isSlackMsgTooLong(err)) throw err;\n lastErr = err;\n if (prefixLength === 0) {\n throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));\n }\n prefixLength = Math.max(0, Math.floor(prefixLength / 2));\n }\n }\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 adapterOptions: SlackAdapterOptions = {},\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n const sessionPlan = planSlackAdapterSession(event, {\n initialMessageTs: adapterOptions.initialMessageTs,\n });\n let messageTs: string | null = sessionPlan.initialMessageTs ?? null;\n let assistantStatusFailureWarned = false;\n const onAssistantStatusError = (label: string, err: unknown): void => {\n if (assistantStatusFailureWarned) return;\n assistantStatusFailureWarned = true;\n log.logWarning(\n `Slack setAssistantStatus failed (${label}; further occurrences suppressed for this session)`,\n err instanceof Error ? err.message : String(err),\n );\n };\n const threadMessageTs: string[] = [];\n let accumulatedText = \"\";\n let isWorking = true;\n let blockKitFinalized = false;\n let streamActive = false;\n let streamUnavailable = false;\n let streamedText = \"\";\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 // Slack message timestamps are numeric; event-file triggers use `event:<filename>`.\n const eventFilename = event.ts.match(/^event:([^:]+(?:\\.json)?)/)?.[1];\n\n const { rootTs, isThreaded } = sessionPlan;\n const replyMode = adapterOptions.replyMode ?? \"top-level\";\n const replyInThread = Boolean(rootTs && (isThreaded || replyMode === \"thread\"));\n\n /**\n * Post the first visible reply.\n * Default Slack behavior is now top-level channel replies.\n * If the triggering message is already inside a thread, stay in that thread.\n */\n const postFirstMessage = async (text: string): Promise<string> => {\n if (replyInThread && rootTs) {\n return slack.postInThread(channelId, rootTs, text);\n }\n return slack.postMessage(channelId, text);\n };\n\n const postDiagnosticDirect = async (\n text: string,\n options?: { style?: \"muted\" | \"error\" },\n ): Promise<void> => {\n const threadAnchor = messageTs ?? rootTs;\n if (!threadAnchor) return;\n\n for (const part of splitText(text, MAX_THREAD_LENGTH, formatSlackContinuation)) {\n if (options?.style === \"muted\") {\n const ts = await slack.postInThreadBlocks(channelId, threadAnchor, part, [\n buildMrkdwnContextBlock(part),\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: sessionPlan.sessionKey,\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 reportResponseError = createChatResponseErrorReporter(() => ({\n platform: \"slack\",\n conversationId,\n channelId,\n messageId: message.id,\n sessionKey: message.sessionKey,\n responseMessageId: messageTs,\n threadTs: rootTs,\n conversationKind: message.conversationKind,\n isThreaded,\n }));\n\n const postOrUpdateMain = async (body: string, handleMsgTooLong = true): Promise<void> => {\n const writeMain = async (text: string): Promise<string | void> => {\n if (messageTs) {\n await slack.updateMessage(channelId, messageTs, text);\n return;\n }\n if (replyInThread && rootTs) {\n messageTs = await slack.postInThread(channelId, rootTs, text);\n return messageTs;\n }\n messageTs = await postFirstMessage(text);\n return messageTs;\n };\n\n try {\n await writeMain(body);\n } catch (err) {\n if (!handleMsgTooLong || !isSlackMsgTooLong(err)) throw err;\n const fallback = await postSlackTextWithFallback(writeMain, body);\n accumulatedText = fallback.text;\n stream.setText(accumulatedText);\n }\n };\n\n const abandonActiveStream = async (): Promise<void> => {\n if (!streamActive || !messageTs) return;\n const failedStreamTs = messageTs;\n streamActive = false;\n streamedText = \"\";\n await slack.stopMessageStream(channelId, failedStreamTs).catch(() => undefined);\n if (messageTs === failedStreamTs) messageTs = null;\n };\n\n const startOrAppendStream = async (text: string, displayText: string): Promise<void> => {\n if (streamUnavailable) {\n await postOrUpdateMain(displayText);\n return;\n }\n\n try {\n if (messageTs && streamActive) {\n if (!text.startsWith(streamedText)) {\n await abandonActiveStream();\n await postOrUpdateMain(displayText);\n return;\n }\n const delta = text.slice(streamedText.length);\n if (delta) {\n await slack.appendMessageStream(channelId, messageTs, delta);\n streamedText = text;\n }\n return;\n }\n if (!replyInThread || !rootTs) {\n streamUnavailable = true;\n await postOrUpdateMain(displayText);\n return;\n }\n messageTs = await slack.startMessageStream(channelId, text, rootTs, event.user);\n streamActive = true;\n streamedText = text;\n } catch (err) {\n streamUnavailable = true;\n log.logWarning(\n \"Slack streaming unavailable; falling back to chat.update\",\n err instanceof Error ? err.message : String(err),\n );\n await abandonActiveStream();\n await postOrUpdateMain(displayText);\n }\n };\n\n const stream = new BufferedResponseStream({\n flush: async (text) => {\n accumulatedText = text;\n const mainLimit = isWorking ? MAX_MAIN_LENGTH - WORKING_INDICATOR.length : MAX_MAIN_LENGTH;\n if (accumulatedText.length > mainLimit) {\n accumulatedText =\n accumulatedText.substring(0, mainLimit - TRUNCATION_NOTE_INCREMENTAL.length) +\n TRUNCATION_NOTE_INCREMENTAL;\n stream.setText(accumulatedText);\n }\n const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;\n await startOrAppendStream(text, displayText);\n },\n finish: async (text) => {\n accumulatedText = text;\n isWorking = false;\n if (streamActive && messageTs) {\n try {\n if (!accumulatedText.startsWith(streamedText)) {\n await abandonActiveStream();\n await postOrUpdateMain(accumulatedText);\n return;\n }\n const delta = accumulatedText.slice(streamedText.length);\n if (delta) {\n await slack.appendMessageStream(channelId, messageTs, delta);\n streamedText = accumulatedText;\n }\n await slack.stopMessageStream(channelId, messageTs);\n streamActive = false;\n streamedText = \"\";\n return;\n } catch (err) {\n streamUnavailable = true;\n log.logWarning(\n \"Slack streaming unavailable; falling back to chat.update\",\n err instanceof Error ? err.message : String(err),\n );\n await abandonActiveStream();\n }\n }\n if (messageTs || accumulatedText) {\n await postOrUpdateMain(accumulatedText);\n }\n },\n });\n\n const queueResponseOperation = async (\n label: string,\n operation: ChatResponseErrorOperation,\n work: () => Promise<void>,\n context: (err: unknown) => Record<string, unknown>,\n ): Promise<void> => {\n updatePromise = updatePromise.then(async () => {\n try {\n await work();\n } catch (err) {\n log.logWarning(`Slack ${label} error`, err instanceof Error ? err.message : String(err));\n reportResponseError(err, operation, context(err));\n }\n });\n await updatePromise;\n };\n\n const responseCtx = {\n respond: async (text: string) => {\n await queueResponseOperation(\n \"respond\",\n \"respond\",\n async () => {\n accumulatedText = accumulatedText ? `${accumulatedText}\\n${text}` : text;\n\n const mainLimit = isWorking\n ? MAX_MAIN_LENGTH - WORKING_INDICATOR.length\n : MAX_MAIN_LENGTH;\n if (accumulatedText.length > mainLimit) {\n accumulatedText =\n accumulatedText.substring(0, mainLimit - TRUNCATION_NOTE_INCREMENTAL.length) +\n TRUNCATION_NOTE_INCREMENTAL;\n }\n\n stream.setText(accumulatedText);\n const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;\n await startOrAppendStream(text, displayText);\n\n if (messageTs) {\n slack.logBotResponse(channelId, text, messageTs, replyInThread ? rootTs : undefined);\n }\n },\n () => ({\n phase: messageTs ? \"update\" : \"initial_post\",\n textLength: text.length,\n accumulatedLength: accumulatedText.length,\n }),\n );\n },\n\n appendResponseDelta: async (delta: string) => {\n await queueResponseOperation(\n \"appendResponseDelta\",\n \"respond\",\n async () => {\n await stream.append(delta);\n if (messageTs) {\n slack.logBotResponse(channelId, delta, messageTs, replyInThread ? rootTs : undefined);\n }\n },\n () => ({ textLength: delta.length, accumulatedLength: stream.getText().length }),\n );\n },\n\n finishResponse: async (finalText?: string) => {\n await queueResponseOperation(\n \"finishResponse\",\n \"set_working\",\n async () => {\n await stream.finish(finalText);\n accumulatedText = stream.getText();\n if (!rootTs) return;\n await slack\n .setAssistantStatus(channelId, rootTs, \"\")\n .catch((err) => onAssistantStatusError(\"clear-on-idle\", err));\n },\n () => ({ finalTextLength: finalText?.length }),\n );\n },\n\n replaceResponse: async (text: string, options?: { createOverflowLink?: () => string }) => {\n await queueResponseOperation(\n \"replaceResponse\",\n \"replace_response\",\n async () => {\n // Lazy: only mint a token if Slack actually rejects the message.\n let overflowLink: string | undefined;\n const resolveOverflowLink = (): string | undefined => {\n if (overflowLink === undefined && options?.createOverflowLink) {\n overflowLink = options.createOverflowLink();\n }\n return overflowLink;\n };\n\n accumulatedText = text;\n stream.setText(accumulatedText);\n const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;\n\n try {\n if (streamActive && messageTs) {\n await slack.stopMessageStream(channelId, messageTs);\n streamActive = false;\n streamedText = \"\";\n }\n await postOrUpdateMain(displayText, false);\n } catch (err) {\n if (!isSlackMsgTooLong(err)) throw err;\n const link = resolveOverflowLink();\n const fallback = await postSlackTextWithFallback(\n async (body) => {\n await postOrUpdateMain(body);\n },\n text,\n link,\n );\n accumulatedText = fallback.text;\n const continuation = text.slice(fallback.prefixLength).trimStart();\n if (continuation) {\n await postDiagnosticDirect(`_(continued from truncated message)_\\n\\n${continuation}`);\n }\n }\n },\n () => ({\n textLength: text.length,\n hadExistingResponse: Boolean(messageTs),\n }),\n );\n },\n\n respondDiagnostic: async (text: string, options?: { style?: \"muted\" | \"error\" }) => {\n await queueResponseOperation(\n \"respondDiagnostic\",\n \"respond_diagnostic\",\n async () => {\n await postDiagnosticDirect(text, options);\n },\n () => ({\n textLength: text.length,\n style: options?.style,\n }),\n );\n },\n\n respondToolResult: async (result: ChatToolResult) => {\n await responseCtx.respondDiagnostic(formatSlackToolResult(result));\n },\n\n respondBlockKit: async (response: ChatResponseBlockKit) => {\n updatePromise = updatePromise.then(async () => {\n isWorking = false;\n accumulatedText = response.text;\n if (replyInThread && rootTs) {\n messageTs = await slack.postInThreadBlocks(\n channelId,\n rootTs,\n response.text,\n response.blocks,\n );\n } else {\n messageTs = await slack.postBlocks(channelId, response.text, response.blocks);\n }\n blockKitFinalized = true;\n slack.logBotResponse(\n channelId,\n response.text,\n messageTs,\n replyInThread ? rootTs : undefined,\n response.blocks,\n );\n });\n await updatePromise;\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 (err) {\n // Assistant API not available — first respond() call will create the message.\n onAssistantStatusError(\"typing\", err);\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 await queueResponseOperation(\n \"setWorking\",\n \"set_working\",\n async () => {\n isWorking = working;\n if (blockKitFinalized) {\n if (!working && rootTs) {\n await slack\n .setAssistantStatus(channelId, rootTs, \"\")\n .catch((err) => onAssistantStatusError(\"clear-on-idle\", err));\n }\n return;\n }\n if (messageTs) {\n const displayText = isWorking ? accumulatedText + WORKING_INDICATOR : accumulatedText;\n const updates: Promise<void>[] =\n streamActive && !isWorking\n ? [\n slack.stopMessageStream(channelId, messageTs).then(() => {\n streamActive = false;\n }),\n ]\n : [slack.updateMessage(channelId, messageTs, displayText)];\n if (!working && rootTs) {\n updates.push(\n slack\n .setAssistantStatus(channelId, rootTs, \"\")\n .catch((err) => onAssistantStatusError(\"clear-on-idle\", err)),\n );\n }\n await Promise.all(updates);\n }\n },\n () => ({ working }),\n );\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"]}
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/slack/context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AAIvD,MAAM,sBAAsB,GAAG;;sDAEuB,CAAC;AAEvD,MAAM,UAAU,mBAAmB,CACjC,KAAiB,EACjB,KAAe,EACf,cAAc,GAAwB,EAAE;IAMxC,MAAM,WAAW,GAAG,uBAAuB,CAAC,KAAK,EAAE;QACjD,gBAAgB,EAAE,cAAc,CAAC,gBAAgB;KAClD,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAEvC,MAAM,OAAO,GAAgB;QAC3B,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,UAAU,EAAE,WAAW,CAAC,UAAU;QAClC,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,MAAM,EAAE,KAAK,CAAC,IAAI;QAClB,QAAQ,EAAE,IAAI,EAAE,QAAQ;QACxB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,WAAW,EAAE,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACjD,IAAI,EAAE,CAAC,CAAC,QAAQ;YAChB,SAAS,EAAE,CAAC,CAAC,SAAS;SACvB,CAAC,CAAC;QACH,QAAQ,EAAE,KAAK,CAAC,SAAS;KAC1B,CAAC;IAEF,MAAM,QAAQ,GAAiB;QAC7B,IAAI,EAAE,OAAO;QACb,eAAe,EAAE,sBAAsB;QACvC,QAAQ,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACzE,KAAK,EAAE,KAAK;aACT,WAAW,EAAE;aACb,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/E,WAAW,EAAE;YACX,gBAAgB,EAAE,IAAI;SACvB;KACF,CAAC;IAEF,MAAM,WAAW,GAAG,0BAA0B,CAAC;QAC7C,KAAK;QACL,KAAK;QACL,WAAW;QACX,SAAS,EAAE,cAAc,CAAC,SAAS,IAAI,WAAW;QAClD,OAAO;KACR,CAAC,CAAC;IAEH,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AAC5C,CAAC","sourcesContent":["import type { ChatMessage, ChatResponseContext, PlatformInfo } from \"../../adapter.js\";\nimport { type SlackBot, type SlackEvent } from \"./bot.js\";\nimport { createSlackResponseContext } from \"./response-lifecycle.js\";\nimport { planSlackAdapterSession } from \"./session.js\";\nexport type { SlackAdapterOptions } from \"./types.js\";\nimport type { SlackAdapterOptions } from \"./types.js\";\n\nconst 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 adapterOptions: SlackAdapterOptions = {},\n): {\n message: ChatMessage;\n responseCtx: ChatResponseContext;\n platform: PlatformInfo;\n} {\n const sessionPlan = planSlackAdapterSession(event, {\n initialMessageTs: adapterOptions.initialMessageTs,\n });\n const user = slack.getUser(event.user);\n\n const message: ChatMessage = {\n id: event.ts,\n sessionKey: sessionPlan.sessionKey,\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 = createSlackResponseContext({\n event,\n slack,\n sessionPlan,\n replyMode: adapterOptions.replyMode ?? \"top-level\",\n message,\n });\n\n return { message, responseCtx, platform };\n}\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ChatMessage, ChatResponseContext } from "../../adapter.js";
|
|
2
|
+
import { type SlackBot, type SlackEvent } from "./bot.js";
|
|
3
|
+
import type { SlackAdapterSessionPlan } from "./types.js";
|
|
4
|
+
export declare function createSlackResponseContext({ event, slack, sessionPlan, replyMode, message }: {
|
|
5
|
+
event: SlackEvent;
|
|
6
|
+
slack: SlackBot;
|
|
7
|
+
sessionPlan: SlackAdapterSessionPlan;
|
|
8
|
+
replyMode: "top-level" | "thread";
|
|
9
|
+
message: ChatMessage;
|
|
10
|
+
}): ChatResponseContext;
|
|
11
|
+
//# sourceMappingURL=response-lifecycle.d.ts.map
|