@agent-native/core 0.7.69 → 0.7.70
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/adapters/slack.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,aAAa,CAAC;AASrB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,IAAI,eAAe,
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/adapters/slack.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EAKhB,MAAM,aAAa,CAAC;AASrB;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,IAAI,eAAe,CA4U9C"}
|
|
@@ -180,8 +180,15 @@ export function slackAdapter() {
|
|
|
180
180
|
// Block-rich path: split text into chunks but render the FIRST chunk as
|
|
181
181
|
// blocks (so we keep the in-place edit + button) and any overflow as
|
|
182
182
|
// plain follow-up posts. The vast majority of replies fit in one block.
|
|
183
|
-
const chunks =
|
|
184
|
-
const
|
|
183
|
+
const chunks = splitNonEmptyMessage(message.text, SLACK_MAX_LENGTH);
|
|
184
|
+
const hasProvidedBlocks = Array.isArray(blocks) && blocks.length > 0;
|
|
185
|
+
const firstChunk = chunks[0] ?? (hasProvidedBlocks ? "Response" : "");
|
|
186
|
+
if (!firstChunk) {
|
|
187
|
+
if (threadTs) {
|
|
188
|
+
setSlackAssistantStatus(token, channelId, threadTs, "");
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
185
192
|
const restChunks = chunks.slice(1);
|
|
186
193
|
const finalBlocks = blocks ??
|
|
187
194
|
buildResponseBlocks(firstChunk, {
|
|
@@ -244,7 +251,9 @@ export function slackAdapter() {
|
|
|
244
251
|
console.error("[slack] SLACK_BOT_TOKEN not configured");
|
|
245
252
|
return;
|
|
246
253
|
}
|
|
247
|
-
const chunks =
|
|
254
|
+
const chunks = splitNonEmptyMessage(message.text, SLACK_MAX_LENGTH);
|
|
255
|
+
if (chunks.length === 0)
|
|
256
|
+
return;
|
|
248
257
|
for (const chunk of chunks) {
|
|
249
258
|
const body = {
|
|
250
259
|
channel: target.destination,
|
|
@@ -444,6 +453,10 @@ function splitMessage(text, maxLength) {
|
|
|
444
453
|
}
|
|
445
454
|
return chunks;
|
|
446
455
|
}
|
|
456
|
+
/** Split a message and drop chunks Slack would render as blank messages. */
|
|
457
|
+
function splitNonEmptyMessage(text, maxLength) {
|
|
458
|
+
return splitMessage(text, maxLength).filter((chunk) => chunk.trim().length > 0);
|
|
459
|
+
}
|
|
447
460
|
/** Hard cap on input length we feed to the regex-based mrkdwn converter.
|
|
448
461
|
* L2 in the webhook audit: `\*\*(.+?)\*\*` with the `s` flag on a long
|
|
449
462
|
* string of asterisks can exhibit super-linear backtracking. Slack
|
|
@@ -526,6 +539,12 @@ function buildResponseBlocks(text, opts) {
|
|
|
526
539
|
* (e.g. when chat.update fails) and for follow-up overflow chunks.
|
|
527
540
|
*/
|
|
528
541
|
async function postFresh(token, channelId, threadTs, body) {
|
|
542
|
+
const hasBlocks = Array.isArray(body.blocks) && body.blocks.length > 0;
|
|
543
|
+
if (typeof body.text === "string" &&
|
|
544
|
+
body.text.trim().length === 0 &&
|
|
545
|
+
!hasBlocks) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
529
548
|
const payload = {
|
|
530
549
|
...body,
|
|
531
550
|
channel: channelId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../src/integrations/adapters/slack.ts"],"names":[],"mappings":"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","sourcesContent":["import type { H3Event } from \"h3\";\nimport { createError, getHeader, readRawBody } from \"h3\";\nimport type {\n PlatformAdapter,\n IncomingMessage,\n OutgoingMessage,\n IntegrationStatus,\n OutboundTarget,\n} from \"../types.js\";\nimport type { EnvKeyConfig } from \"../../server/create-server.js\";\nimport { getIntegrationConfig } from \"../config-store.js\";\n\n/** Slack's max message length */\nconst SLACK_MAX_LENGTH = 4000;\nconst SLACK_SECTION_TEXT_MAX_LENGTH = 3000;\nconst SLACK_API_TIMEOUT_MS = 10_000;\n\n/**\n * Create a Slack platform adapter.\n *\n * Required env vars:\n * - SLACK_BOT_TOKEN — Bot user OAuth token (xoxb-...)\n * - SLACK_SIGNING_SECRET — Used to verify webhook signatures\n *\n * Optional env vars:\n * - SLACK_ALLOWED_TEAM_IDS — Comma-separated list of Slack workspace\n * `team_id` values (e.g. \"T012ABCDEF,T034GHIJKL\") that this deployment\n * accepts events from. Required in production and strongly recommended\n * to prevent cross-workspace event injection (H1 in the webhook audit):\n * the global `SLACK_SIGNING_SECRET` is the same key for every workspace\n * the app is installed to, so without an allowlist any installed\n * workspace can drive the agent. When unset the adapter accepts events\n * from any workspace in development, but rejects events in production.\n * - SLACK_ALLOWED_API_APP_IDS — Comma-separated list of Slack app IDs\n * (`api_app_id`) to additionally pin events to. Useful when the same\n * signing secret rotation surfaces multiple app installs.\n */\nexport function slackAdapter(): PlatformAdapter {\n return {\n platform: \"slack\",\n label: \"Slack\",\n\n getRequiredEnvKeys(): EnvKeyConfig[] {\n return [\n {\n key: \"SLACK_BOT_TOKEN\",\n label: \"Slack Bot Token\",\n required: true,\n helpText:\n \"In your Slack app's left nav: OAuth & Permissions → Bot User OAuth Token (starts with `xoxb-`).\",\n },\n {\n key: \"SLACK_SIGNING_SECRET\",\n label: \"Slack Signing Secret\",\n required: true,\n helpText:\n \"In your Slack app's left nav: Basic Information → App Credentials → Signing Secret.\",\n },\n ];\n },\n\n async handleVerification(\n event: H3Event,\n ): Promise<{ handled: boolean; response?: unknown }> {\n // Slack sends url_verification when first setting up the webhook.\n // readRawBodyCached caches the raw bytes on event.context.__rawBody so\n // subsequent verifyWebhook + parseIncomingMessage calls re-use them\n // without re-stringifying a parsed body (M2 in the audit).\n const body = await readRawBodyCached(event);\n try {\n const parsed = JSON.parse(body);\n if (parsed.type === \"url_verification\") {\n // Slack's URL verifier expects the raw challenge value in the\n // response body. Returning JSON works for some clients but the app\n // settings verifier rejects it as not matching the challenge.\n return { handled: true, response: parsed.challenge };\n }\n } catch {}\n return { handled: false };\n },\n\n async verifyWebhook(event: H3Event): Promise<boolean> {\n const signingSecret = process.env.SLACK_SIGNING_SECRET;\n if (!signingSecret) return false;\n\n const signature = getHeader(event, \"x-slack-signature\");\n const timestamp = getHeader(event, \"x-slack-request-timestamp\");\n if (!signature || !timestamp) return false;\n\n // Reject requests older than 5 minutes (replay protection)\n const ts = parseInt(timestamp, 10);\n if (Math.abs(Date.now() / 1000 - ts) > 300) return false;\n\n const body = await readRawBodyCached(event);\n const crypto = await import(\"node:crypto\");\n const basestring = `v0:${timestamp}:${body}`;\n const expectedSignature =\n \"v0=\" +\n crypto\n .createHmac(\"sha256\", signingSecret)\n .update(basestring)\n .digest(\"hex\");\n\n // Timing-safe comparison\n try {\n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature),\n );\n } catch {\n return false;\n }\n },\n\n async parseIncomingMessage(\n event: H3Event,\n ): Promise<IncomingMessage | null> {\n const raw = await readRawBodyCached(event);\n let payload: any;\n try {\n payload = JSON.parse(raw);\n } catch {\n return null;\n }\n\n // H1 (webhook audit): cross-workspace event injection. The global\n // SLACK_SIGNING_SECRET is the same key for every workspace this Slack\n // app is installed to — without a per-tenant allowlist any installed\n // workspace can drive the agent. We enforce SLACK_ALLOWED_TEAM_IDS\n // here AFTER the signature has already been verified by the webhook\n // handler, so this is purely a tenant-isolation gate (not a forgery\n // defense). When unset in production we surface a one-time warning\n // recommending it be configured.\n enforceWorkspaceAllowlist(payload);\n\n // Handle Events API wrapper\n if (payload.type === \"event_callback\") {\n const e = payload.event;\n if (!e) return null;\n\n // Ignore bot messages\n if (e.bot_id || e.subtype === \"bot_message\") return null;\n // Ignore message edits and deletes\n if (e.subtype === \"message_changed\" || e.subtype === \"message_deleted\")\n return null;\n\n // Handle both direct messages and app_mentions\n const text = e.text?.trim();\n if (!text) return null;\n\n // Remove bot mention from text (e.g., \"<@U123> do something\" → \"do something\")\n const cleanText = text.replace(/<@[A-Z0-9]+>/g, \"\").trim();\n if (!cleanText) return null;\n\n // Thread ID: use thread_ts if in a thread, otherwise message ts\n const threadTs = e.thread_ts || e.ts;\n const externalThreadId = `${e.channel}:${threadTs}`;\n\n return {\n platform: \"slack\",\n externalThreadId,\n text: cleanText,\n senderName: e.user,\n senderId: e.user,\n platformContext: {\n channelId: e.channel,\n threadTs: threadTs,\n messageTs: e.ts,\n teamId: payload.team_id,\n eventId: payload.event_id,\n },\n timestamp: Math.floor(parseFloat(e.ts) * 1000),\n };\n }\n\n return null;\n },\n\n async postProcessingPlaceholder(\n incoming: IncomingMessage,\n ): Promise<{ placeholderRef: string } | null> {\n // No placeholder reply in the thread — Slack's native assistant\n // status bar (\"agent-native is thinking…\", below the composer) is the\n // loading affordance. A second visible \"Working on it…\" reply was\n // redundant and added an extra chunk that we then had to overwrite.\n // We just set the native status and return null so sendResponse posts\n // the final reply as a fresh message.\n const token = process.env.SLACK_BOT_TOKEN;\n if (!token) return null;\n\n const channelId = incoming.platformContext.channelId as string;\n const threadTs = incoming.platformContext.threadTs as string;\n if (!channelId || !threadTs) return null;\n\n // Best-effort: flip the native AI-assistant \"is thinking…\" status bar\n // in the channel input area. Requires `assistant:write` scope on the\n // app — otherwise silently no-ops.\n setSlackAssistantStatus(token, channelId, threadTs, \"is thinking…\");\n return null;\n },\n\n async sendResponse(\n message: OutgoingMessage,\n context: IncomingMessage,\n opts?: { placeholderRef?: string },\n ): Promise<void> {\n const token = process.env.SLACK_BOT_TOKEN;\n if (!token) {\n console.error(\"[slack] SLACK_BOT_TOKEN not configured\");\n return;\n }\n\n const channelId = context.platformContext.channelId as string;\n const threadTs = context.platformContext.threadTs as string;\n const blocks = (message.platformContext as any)?.blocks as\n | unknown[]\n | undefined;\n const placeholderRef = opts?.placeholderRef;\n\n // Block-rich path: split text into chunks but render the FIRST chunk as\n // blocks (so we keep the in-place edit + button) and any overflow as\n // plain follow-up posts. The vast majority of replies fit in one block.\n const chunks = splitMessage(message.text, SLACK_MAX_LENGTH);\n const firstChunk = chunks[0] ?? \"\";\n const restChunks = chunks.slice(1);\n\n const finalBlocks =\n blocks ??\n buildResponseBlocks(firstChunk, {\n threadDeepLinkUrl: (message.platformContext as any)\n ?.threadDeepLinkUrl,\n });\n\n const baseBody: Record<string, unknown> = {\n channel: channelId,\n text: firstChunk,\n blocks: finalBlocks,\n unfurl_links: false,\n unfurl_media: false,\n mrkdwn: true,\n };\n\n try {\n if (placeholderRef) {\n // Replace the \"thinking…\" placeholder in place.\n const res = await slackApiFetch(\"https://slack.com/api/chat.update\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ ...baseBody, ts: placeholderRef }),\n });\n const data = (await res.json()) as {\n ok: boolean;\n error?: string;\n };\n if (!data.ok) {\n console.error(\"[slack] chat.update error:\", data.error);\n // Fall back to a fresh post so the user still sees a reply\n await postFresh(token, channelId, threadTs, baseBody);\n }\n } else {\n await postFresh(token, channelId, threadTs, baseBody);\n }\n\n // Clear the AI-assistant \"is thinking…\" status now that we've\n // delivered the final answer. Empty status clears it.\n if (threadTs) {\n setSlackAssistantStatus(token, channelId, threadTs, \"\");\n }\n\n // Overflow chunks (rare) — post as plain follow-ups in the same thread\n for (const chunk of restChunks) {\n await postFresh(token, channelId, threadTs, {\n channel: channelId,\n text: chunk,\n unfurl_links: false,\n unfurl_media: false,\n mrkdwn: true,\n });\n }\n } catch (err) {\n console.error(\"[slack] Failed to send message:\", err);\n throw err;\n }\n },\n\n async sendMessageToTarget(\n message: OutgoingMessage,\n target: OutboundTarget,\n ): Promise<void> {\n const token = process.env.SLACK_BOT_TOKEN;\n if (!token) {\n console.error(\"[slack] SLACK_BOT_TOKEN not configured\");\n return;\n }\n\n const chunks = splitMessage(message.text, SLACK_MAX_LENGTH);\n for (const chunk of chunks) {\n const body: Record<string, unknown> = {\n channel: target.destination,\n text: chunk,\n };\n if (target.threadRef) body.thread_ts = target.threadRef;\n\n try {\n const res = await slackApiFetch(\n \"https://slack.com/api/chat.postMessage\",\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n },\n );\n const data = (await res.json()) as { ok: boolean; error?: string };\n if (!data.ok) {\n throw new Error(data.error || \"chat.postMessage failed\");\n }\n } catch (err) {\n console.error(\"[slack] Failed to send proactive message:\", err);\n throw err;\n }\n }\n },\n\n formatAgentResponse(\n text: string,\n opts?: { threadDeepLinkUrl?: string },\n ): OutgoingMessage {\n return {\n text: markdownToSlackMrkdwn(text),\n platformContext: opts?.threadDeepLinkUrl\n ? { threadDeepLinkUrl: opts.threadDeepLinkUrl }\n : {},\n };\n },\n\n async getStatus(baseUrl?: string): Promise<IntegrationStatus> {\n const hasToken = !!process.env.SLACK_BOT_TOKEN;\n const hasSecret = !!process.env.SLACK_SIGNING_SECRET;\n const configured = hasToken && hasSecret;\n\n return {\n platform: \"slack\",\n label: \"Slack\",\n enabled: false, // overridden by plugin\n configured,\n details: {\n hasToken,\n hasSecret,\n },\n error: !configured\n ? \"Set SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET in your environment\"\n : undefined,\n };\n },\n };\n}\n\n/**\n * Parse a comma-separated env var into a Set of trimmed, non-empty values.\n * Returns null when the env var is unset or empty (so callers can\n * distinguish \"no allowlist configured\" from \"empty allowlist\").\n */\nfunction parseAllowlistEnv(name: string): Set<string> | null {\n const raw = process.env[name];\n if (!raw) return null;\n const values = raw\n .split(\",\")\n .map((v) => v.trim())\n .filter((v) => v.length > 0);\n if (values.length === 0) return null;\n return new Set(values);\n}\n\nlet _missingAllowlistWarned = false;\n\n/**\n * Enforce that an incoming Slack event comes from an allowlisted workspace.\n *\n * H1 in the webhook audit: the framework uses a SINGLE global\n * SLACK_SIGNING_SECRET for every workspace the Slack app is installed to,\n * so a valid signature alone doesn't prove the request belongs to the\n * tenant the deployment intends to serve. This helper layers a per-tenant\n * allowlist on top of signature verification.\n *\n * Behavior:\n * - If `SLACK_ALLOWED_TEAM_IDS` is set: reject any payload whose\n * `team_id` isn't in the list.\n * - If `SLACK_ALLOWED_API_APP_IDS` is set: also reject payloads whose\n * `api_app_id` isn't in the list (bot apps can be installed under the\n * same Slack app id across multiple workspaces — pinning both keeps\n * the surface tight when team_id allows multiple workspaces).\n * - If `SLACK_ALLOWED_TEAM_IDS` is unset/empty in production: reject the\n * event. Production must fail closed so any workspace with the shared\n * signing secret cannot drive the agent.\n * - If `SLACK_ALLOWED_TEAM_IDS` is unset/empty in dev / single-tenant: log a\n * one-time warning and accept (current local setup behavior).\n *\n * Throws an h3 401 error when an allowlisted-but-mismatched payload is\n * received, which the integrations plugin surfaces to the caller as\n * \"Unrecognized Slack workspace\" without enqueuing the event.\n */\nfunction enforceWorkspaceAllowlist(payload: any): void {\n const teamId =\n typeof payload?.team_id === \"string\" ? payload.team_id : undefined;\n const apiAppId =\n typeof payload?.api_app_id === \"string\" ? payload.api_app_id : undefined;\n\n const allowedTeamIds = parseAllowlistEnv(\"SLACK_ALLOWED_TEAM_IDS\");\n const allowedAppIds = parseAllowlistEnv(\"SLACK_ALLOWED_API_APP_IDS\");\n\n if (!allowedTeamIds) {\n if (process.env.NODE_ENV === \"production\") {\n throw createError({\n statusCode: 401,\n statusMessage: \"Slack workspace allowlist is not configured\",\n });\n }\n if (!_missingAllowlistWarned) {\n _missingAllowlistWarned = true;\n console.warn(\n \"[slack] SLACK_ALLOWED_TEAM_IDS not set — accepting events from any workspace whose signature matches SLACK_SIGNING_SECRET. \" +\n \"Set SLACK_ALLOWED_TEAM_IDS to a comma-separated list of allowed team_id values before deploying to production.\",\n );\n }\n }\n\n if (allowedTeamIds) {\n if (!teamId || !allowedTeamIds.has(teamId)) {\n throw createError({\n statusCode: 401,\n statusMessage: \"Unrecognized Slack workspace\",\n });\n }\n }\n\n if (allowedAppIds) {\n if (!apiAppId || !allowedAppIds.has(apiAppId)) {\n throw createError({\n statusCode: 401,\n statusMessage: \"Unrecognized Slack workspace\",\n });\n }\n }\n}\n\n/**\n * Read the raw request body as a string and cache on the event context.\n *\n * This MUST read raw bytes from the request stream — never `JSON.stringify`\n * a parsed body, because Slack's HMAC is computed over the exact bytes Slack\n * sent. Re-stringifying a parsed object loses key ordering, whitespace, and\n * Unicode-escape choices, so the signature check would silently fail for\n * legitimate requests (M2 in the webhook security audit).\n *\n * h3 v2's body stream is consume-once, so we cache the raw string on the\n * event context after the first read. All call sites (handleVerification,\n * verifyWebhook, parseIncomingMessage) MUST go through this helper.\n */\nasync function readRawBodyCached(event: H3Event): Promise<string> {\n const cached = event.context.__rawBody;\n if (typeof cached === \"string\") return cached;\n // h3's readRawBody returns the bytes Slack actually sent, defaulting to\n // utf8-decoded. Returns undefined for empty bodies — we coerce to \"\" so\n // the HMAC check can proceed deterministically.\n const raw = (await readRawBody(event)) ?? \"\";\n event.context.__rawBody = raw;\n return raw;\n}\n\nfunction utf8ByteLength(text: string): number {\n return new TextEncoder().encode(text).length;\n}\n\nfunction prefixWithinUtf8ByteLimit(text: string, maxLength: number): string {\n let bytes = 0;\n let end = 0;\n for (const char of text) {\n const nextBytes = utf8ByteLength(char);\n if (bytes + nextBytes > maxLength) break;\n bytes += nextBytes;\n end += char.length;\n }\n return text.slice(0, end || 1);\n}\n\n/** Split a message into chunks that fit within the platform's byte limit. */\nfunction splitMessage(text: string, maxLength: number): string[] {\n if (utf8ByteLength(text) <= maxLength) return [text];\n const chunks: string[] = [];\n let remaining = text;\n while (remaining.length > 0) {\n if (utf8ByteLength(remaining) <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n const prefix = prefixWithinUtf8ByteLimit(remaining, maxLength);\n\n // Try to split at a newline\n let splitIdx = prefix.lastIndexOf(\"\\n\");\n if (splitIdx <= 0) {\n // Try to split at a space\n splitIdx = prefix.lastIndexOf(\" \");\n }\n if (splitIdx <= 0) {\n splitIdx = prefix.length;\n }\n chunks.push(remaining.slice(0, splitIdx));\n remaining = remaining.slice(splitIdx).trimStart();\n }\n return chunks;\n}\n\n/** Hard cap on input length we feed to the regex-based mrkdwn converter.\n * L2 in the webhook audit: `\\*\\*(.+?)\\*\\*` with the `s` flag on a long\n * string of asterisks can exhibit super-linear backtracking. Slack\n * itself caps message bodies at 4000 chars (SLACK_MAX_LENGTH); we cap\n * the input here at 10x that as a defensive bound for any caller that\n * passes a longer rendering source through this helper before chunking. */\nconst MRKDWN_MAX_LENGTH = 40_000;\n\n/**\n * Convert standard markdown to Slack's mrkdwn dialect.\n * - `[text](url)` → `<url|text>`\n * - `**bold**` → `*bold*` (Slack uses single asterisks for bold)\n *\n * Inputs longer than MRKDWN_MAX_LENGTH are truncated before the regex\n * pass to bound worst-case backtracking on pathological input (L2 in the\n * webhook audit).\n */\nfunction markdownToSlackMrkdwn(text: string): string {\n const bounded =\n text.length > MRKDWN_MAX_LENGTH ? text.slice(0, MRKDWN_MAX_LENGTH) : text;\n return (\n bounded\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, \"<$2|$1>\")\n // Do not wrap bare URLs in Slack bold markers. Slack's autolinker can\n // treat the trailing `*` as part of the URL, producing a broken link.\n .replace(/\\*\\*<?(https?:\\/\\/[^\\s>*]+)>?\\*\\*/g, \"<$1>\")\n // Bounded character class instead of `.+?` with the `s` flag — caps\n // each bold span at 5000 chars so an attacker can't construct a\n // pathological \"**\" sequence that exhibits super-linear backtracking.\n // Newlines are allowed because `[^*]` excludes only the asterisk\n // itself, so multi-line bold spans still match.\n .replace(/\\*\\*([^*]{1,5000})\\*\\*/g, \"*$1*\")\n );\n}\n\n/**\n * Optionally set Slack's native AI-assistant status indicator (the small\n * \"is thinking…\" line under the message composer) for an app configured\n * with the `assistant:write` scope. Pure best-effort — fails silently for\n * apps that aren't set up as AI assistants.\n */\nfunction setSlackAssistantStatus(\n token: string,\n channelId: string,\n threadTs: string,\n status: string,\n): void {\n slackApiFetch(\"https://slack.com/api/assistant.threads.setStatus\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n channel_id: channelId,\n thread_ts: threadTs,\n status,\n }),\n }).catch(() => {});\n}\n\n/**\n * Block Kit payload for the final answer. We avoid auto-unfurl previews by\n * separating the deep-link out into a button instead of inlining it as a\n * `<url|text>` markdown link in the section body — that's what was producing\n * the giant \"Agent-Native Dispatch\" card in every thread reply.\n */\nfunction buildResponseBlocks(\n text: string,\n opts: { threadDeepLinkUrl?: string },\n): unknown[] {\n const sectionChunks = splitMessage(\n text || \"_(no response)_\",\n SLACK_SECTION_TEXT_MAX_LENGTH,\n );\n const blocks: any[] = sectionChunks.map((chunk) => ({\n type: \"section\",\n text: { type: \"mrkdwn\", text: chunk },\n }));\n if (opts.threadDeepLinkUrl) {\n blocks.push({\n type: \"actions\",\n elements: [\n {\n type: \"button\",\n text: { type: \"plain_text\", text: \"Open thread\", emoji: true },\n url: opts.threadDeepLinkUrl,\n action_id: \"open_dispatch_thread\",\n },\n ],\n });\n }\n return blocks;\n}\n\n/**\n * Post a fresh message to a thread. Used as the placeholder-fallback path\n * (e.g. when chat.update fails) and for follow-up overflow chunks.\n */\nasync function postFresh(\n token: string,\n channelId: string,\n threadTs: string | undefined,\n body: Record<string, unknown>,\n): Promise<void> {\n const payload: Record<string, unknown> = {\n ...body,\n channel: channelId,\n };\n if (threadTs && !payload.thread_ts) payload.thread_ts = threadTs;\n const res = await slackApiFetch(\"https://slack.com/api/chat.postMessage\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n const data = (await res.json()) as { ok: boolean; error?: string };\n if (!data.ok) {\n console.error(\"[slack] chat.postMessage error:\", data.error);\n throw new Error(data.error || \"chat.postMessage failed\");\n }\n}\n\nasync function slackApiFetch(\n url: string,\n init: RequestInit,\n): Promise<Response> {\n const controller =\n typeof AbortController !== \"undefined\" ? new AbortController() : undefined;\n const timer = controller\n ? setTimeout(() => controller.abort(), SLACK_API_TIMEOUT_MS)\n : undefined;\n try {\n return await fetch(url, {\n ...init,\n signal: controller?.signal ?? init.signal,\n });\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../../src/integrations/adapters/slack.ts"],"names":[],"mappings":"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","sourcesContent":["import type { H3Event } from \"h3\";\nimport { createError, getHeader, readRawBody } from \"h3\";\nimport type {\n PlatformAdapter,\n IncomingMessage,\n OutgoingMessage,\n IntegrationStatus,\n OutboundTarget,\n} from \"../types.js\";\nimport type { EnvKeyConfig } from \"../../server/create-server.js\";\nimport { getIntegrationConfig } from \"../config-store.js\";\n\n/** Slack's max message length */\nconst SLACK_MAX_LENGTH = 4000;\nconst SLACK_SECTION_TEXT_MAX_LENGTH = 3000;\nconst SLACK_API_TIMEOUT_MS = 10_000;\n\n/**\n * Create a Slack platform adapter.\n *\n * Required env vars:\n * - SLACK_BOT_TOKEN — Bot user OAuth token (xoxb-...)\n * - SLACK_SIGNING_SECRET — Used to verify webhook signatures\n *\n * Optional env vars:\n * - SLACK_ALLOWED_TEAM_IDS — Comma-separated list of Slack workspace\n * `team_id` values (e.g. \"T012ABCDEF,T034GHIJKL\") that this deployment\n * accepts events from. Required in production and strongly recommended\n * to prevent cross-workspace event injection (H1 in the webhook audit):\n * the global `SLACK_SIGNING_SECRET` is the same key for every workspace\n * the app is installed to, so without an allowlist any installed\n * workspace can drive the agent. When unset the adapter accepts events\n * from any workspace in development, but rejects events in production.\n * - SLACK_ALLOWED_API_APP_IDS — Comma-separated list of Slack app IDs\n * (`api_app_id`) to additionally pin events to. Useful when the same\n * signing secret rotation surfaces multiple app installs.\n */\nexport function slackAdapter(): PlatformAdapter {\n return {\n platform: \"slack\",\n label: \"Slack\",\n\n getRequiredEnvKeys(): EnvKeyConfig[] {\n return [\n {\n key: \"SLACK_BOT_TOKEN\",\n label: \"Slack Bot Token\",\n required: true,\n helpText:\n \"In your Slack app's left nav: OAuth & Permissions → Bot User OAuth Token (starts with `xoxb-`).\",\n },\n {\n key: \"SLACK_SIGNING_SECRET\",\n label: \"Slack Signing Secret\",\n required: true,\n helpText:\n \"In your Slack app's left nav: Basic Information → App Credentials → Signing Secret.\",\n },\n ];\n },\n\n async handleVerification(\n event: H3Event,\n ): Promise<{ handled: boolean; response?: unknown }> {\n // Slack sends url_verification when first setting up the webhook.\n // readRawBodyCached caches the raw bytes on event.context.__rawBody so\n // subsequent verifyWebhook + parseIncomingMessage calls re-use them\n // without re-stringifying a parsed body (M2 in the audit).\n const body = await readRawBodyCached(event);\n try {\n const parsed = JSON.parse(body);\n if (parsed.type === \"url_verification\") {\n // Slack's URL verifier expects the raw challenge value in the\n // response body. Returning JSON works for some clients but the app\n // settings verifier rejects it as not matching the challenge.\n return { handled: true, response: parsed.challenge };\n }\n } catch {}\n return { handled: false };\n },\n\n async verifyWebhook(event: H3Event): Promise<boolean> {\n const signingSecret = process.env.SLACK_SIGNING_SECRET;\n if (!signingSecret) return false;\n\n const signature = getHeader(event, \"x-slack-signature\");\n const timestamp = getHeader(event, \"x-slack-request-timestamp\");\n if (!signature || !timestamp) return false;\n\n // Reject requests older than 5 minutes (replay protection)\n const ts = parseInt(timestamp, 10);\n if (Math.abs(Date.now() / 1000 - ts) > 300) return false;\n\n const body = await readRawBodyCached(event);\n const crypto = await import(\"node:crypto\");\n const basestring = `v0:${timestamp}:${body}`;\n const expectedSignature =\n \"v0=\" +\n crypto\n .createHmac(\"sha256\", signingSecret)\n .update(basestring)\n .digest(\"hex\");\n\n // Timing-safe comparison\n try {\n return crypto.timingSafeEqual(\n Buffer.from(signature),\n Buffer.from(expectedSignature),\n );\n } catch {\n return false;\n }\n },\n\n async parseIncomingMessage(\n event: H3Event,\n ): Promise<IncomingMessage | null> {\n const raw = await readRawBodyCached(event);\n let payload: any;\n try {\n payload = JSON.parse(raw);\n } catch {\n return null;\n }\n\n // H1 (webhook audit): cross-workspace event injection. The global\n // SLACK_SIGNING_SECRET is the same key for every workspace this Slack\n // app is installed to — without a per-tenant allowlist any installed\n // workspace can drive the agent. We enforce SLACK_ALLOWED_TEAM_IDS\n // here AFTER the signature has already been verified by the webhook\n // handler, so this is purely a tenant-isolation gate (not a forgery\n // defense). When unset in production we surface a one-time warning\n // recommending it be configured.\n enforceWorkspaceAllowlist(payload);\n\n // Handle Events API wrapper\n if (payload.type === \"event_callback\") {\n const e = payload.event;\n if (!e) return null;\n\n // Ignore bot messages\n if (e.bot_id || e.subtype === \"bot_message\") return null;\n // Ignore message edits and deletes\n if (e.subtype === \"message_changed\" || e.subtype === \"message_deleted\")\n return null;\n\n // Handle both direct messages and app_mentions\n const text = e.text?.trim();\n if (!text) return null;\n\n // Remove bot mention from text (e.g., \"<@U123> do something\" → \"do something\")\n const cleanText = text.replace(/<@[A-Z0-9]+>/g, \"\").trim();\n if (!cleanText) return null;\n\n // Thread ID: use thread_ts if in a thread, otherwise message ts\n const threadTs = e.thread_ts || e.ts;\n const externalThreadId = `${e.channel}:${threadTs}`;\n\n return {\n platform: \"slack\",\n externalThreadId,\n text: cleanText,\n senderName: e.user,\n senderId: e.user,\n platformContext: {\n channelId: e.channel,\n threadTs: threadTs,\n messageTs: e.ts,\n teamId: payload.team_id,\n eventId: payload.event_id,\n },\n timestamp: Math.floor(parseFloat(e.ts) * 1000),\n };\n }\n\n return null;\n },\n\n async postProcessingPlaceholder(\n incoming: IncomingMessage,\n ): Promise<{ placeholderRef: string } | null> {\n // No placeholder reply in the thread — Slack's native assistant\n // status bar (\"agent-native is thinking…\", below the composer) is the\n // loading affordance. A second visible \"Working on it…\" reply was\n // redundant and added an extra chunk that we then had to overwrite.\n // We just set the native status and return null so sendResponse posts\n // the final reply as a fresh message.\n const token = process.env.SLACK_BOT_TOKEN;\n if (!token) return null;\n\n const channelId = incoming.platformContext.channelId as string;\n const threadTs = incoming.platformContext.threadTs as string;\n if (!channelId || !threadTs) return null;\n\n // Best-effort: flip the native AI-assistant \"is thinking…\" status bar\n // in the channel input area. Requires `assistant:write` scope on the\n // app — otherwise silently no-ops.\n setSlackAssistantStatus(token, channelId, threadTs, \"is thinking…\");\n return null;\n },\n\n async sendResponse(\n message: OutgoingMessage,\n context: IncomingMessage,\n opts?: { placeholderRef?: string },\n ): Promise<void> {\n const token = process.env.SLACK_BOT_TOKEN;\n if (!token) {\n console.error(\"[slack] SLACK_BOT_TOKEN not configured\");\n return;\n }\n\n const channelId = context.platformContext.channelId as string;\n const threadTs = context.platformContext.threadTs as string;\n const blocks = (message.platformContext as any)?.blocks as\n | unknown[]\n | undefined;\n const placeholderRef = opts?.placeholderRef;\n\n // Block-rich path: split text into chunks but render the FIRST chunk as\n // blocks (so we keep the in-place edit + button) and any overflow as\n // plain follow-up posts. The vast majority of replies fit in one block.\n const chunks = splitNonEmptyMessage(message.text, SLACK_MAX_LENGTH);\n const hasProvidedBlocks = Array.isArray(blocks) && blocks.length > 0;\n const firstChunk = chunks[0] ?? (hasProvidedBlocks ? \"Response\" : \"\");\n if (!firstChunk) {\n if (threadTs) {\n setSlackAssistantStatus(token, channelId, threadTs, \"\");\n }\n return;\n }\n const restChunks = chunks.slice(1);\n\n const finalBlocks =\n blocks ??\n buildResponseBlocks(firstChunk, {\n threadDeepLinkUrl: (message.platformContext as any)\n ?.threadDeepLinkUrl,\n });\n\n const baseBody: Record<string, unknown> = {\n channel: channelId,\n text: firstChunk,\n blocks: finalBlocks,\n unfurl_links: false,\n unfurl_media: false,\n mrkdwn: true,\n };\n\n try {\n if (placeholderRef) {\n // Replace the \"thinking…\" placeholder in place.\n const res = await slackApiFetch(\"https://slack.com/api/chat.update\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ ...baseBody, ts: placeholderRef }),\n });\n const data = (await res.json()) as {\n ok: boolean;\n error?: string;\n };\n if (!data.ok) {\n console.error(\"[slack] chat.update error:\", data.error);\n // Fall back to a fresh post so the user still sees a reply\n await postFresh(token, channelId, threadTs, baseBody);\n }\n } else {\n await postFresh(token, channelId, threadTs, baseBody);\n }\n\n // Clear the AI-assistant \"is thinking…\" status now that we've\n // delivered the final answer. Empty status clears it.\n if (threadTs) {\n setSlackAssistantStatus(token, channelId, threadTs, \"\");\n }\n\n // Overflow chunks (rare) — post as plain follow-ups in the same thread\n for (const chunk of restChunks) {\n await postFresh(token, channelId, threadTs, {\n channel: channelId,\n text: chunk,\n unfurl_links: false,\n unfurl_media: false,\n mrkdwn: true,\n });\n }\n } catch (err) {\n console.error(\"[slack] Failed to send message:\", err);\n throw err;\n }\n },\n\n async sendMessageToTarget(\n message: OutgoingMessage,\n target: OutboundTarget,\n ): Promise<void> {\n const token = process.env.SLACK_BOT_TOKEN;\n if (!token) {\n console.error(\"[slack] SLACK_BOT_TOKEN not configured\");\n return;\n }\n\n const chunks = splitNonEmptyMessage(message.text, SLACK_MAX_LENGTH);\n if (chunks.length === 0) return;\n for (const chunk of chunks) {\n const body: Record<string, unknown> = {\n channel: target.destination,\n text: chunk,\n };\n if (target.threadRef) body.thread_ts = target.threadRef;\n\n try {\n const res = await slackApiFetch(\n \"https://slack.com/api/chat.postMessage\",\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n },\n );\n const data = (await res.json()) as { ok: boolean; error?: string };\n if (!data.ok) {\n throw new Error(data.error || \"chat.postMessage failed\");\n }\n } catch (err) {\n console.error(\"[slack] Failed to send proactive message:\", err);\n throw err;\n }\n }\n },\n\n formatAgentResponse(\n text: string,\n opts?: { threadDeepLinkUrl?: string },\n ): OutgoingMessage {\n return {\n text: markdownToSlackMrkdwn(text),\n platformContext: opts?.threadDeepLinkUrl\n ? { threadDeepLinkUrl: opts.threadDeepLinkUrl }\n : {},\n };\n },\n\n async getStatus(baseUrl?: string): Promise<IntegrationStatus> {\n const hasToken = !!process.env.SLACK_BOT_TOKEN;\n const hasSecret = !!process.env.SLACK_SIGNING_SECRET;\n const configured = hasToken && hasSecret;\n\n return {\n platform: \"slack\",\n label: \"Slack\",\n enabled: false, // overridden by plugin\n configured,\n details: {\n hasToken,\n hasSecret,\n },\n error: !configured\n ? \"Set SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET in your environment\"\n : undefined,\n };\n },\n };\n}\n\n/**\n * Parse a comma-separated env var into a Set of trimmed, non-empty values.\n * Returns null when the env var is unset or empty (so callers can\n * distinguish \"no allowlist configured\" from \"empty allowlist\").\n */\nfunction parseAllowlistEnv(name: string): Set<string> | null {\n const raw = process.env[name];\n if (!raw) return null;\n const values = raw\n .split(\",\")\n .map((v) => v.trim())\n .filter((v) => v.length > 0);\n if (values.length === 0) return null;\n return new Set(values);\n}\n\nlet _missingAllowlistWarned = false;\n\n/**\n * Enforce that an incoming Slack event comes from an allowlisted workspace.\n *\n * H1 in the webhook audit: the framework uses a SINGLE global\n * SLACK_SIGNING_SECRET for every workspace the Slack app is installed to,\n * so a valid signature alone doesn't prove the request belongs to the\n * tenant the deployment intends to serve. This helper layers a per-tenant\n * allowlist on top of signature verification.\n *\n * Behavior:\n * - If `SLACK_ALLOWED_TEAM_IDS` is set: reject any payload whose\n * `team_id` isn't in the list.\n * - If `SLACK_ALLOWED_API_APP_IDS` is set: also reject payloads whose\n * `api_app_id` isn't in the list (bot apps can be installed under the\n * same Slack app id across multiple workspaces — pinning both keeps\n * the surface tight when team_id allows multiple workspaces).\n * - If `SLACK_ALLOWED_TEAM_IDS` is unset/empty in production: reject the\n * event. Production must fail closed so any workspace with the shared\n * signing secret cannot drive the agent.\n * - If `SLACK_ALLOWED_TEAM_IDS` is unset/empty in dev / single-tenant: log a\n * one-time warning and accept (current local setup behavior).\n *\n * Throws an h3 401 error when an allowlisted-but-mismatched payload is\n * received, which the integrations plugin surfaces to the caller as\n * \"Unrecognized Slack workspace\" without enqueuing the event.\n */\nfunction enforceWorkspaceAllowlist(payload: any): void {\n const teamId =\n typeof payload?.team_id === \"string\" ? payload.team_id : undefined;\n const apiAppId =\n typeof payload?.api_app_id === \"string\" ? payload.api_app_id : undefined;\n\n const allowedTeamIds = parseAllowlistEnv(\"SLACK_ALLOWED_TEAM_IDS\");\n const allowedAppIds = parseAllowlistEnv(\"SLACK_ALLOWED_API_APP_IDS\");\n\n if (!allowedTeamIds) {\n if (process.env.NODE_ENV === \"production\") {\n throw createError({\n statusCode: 401,\n statusMessage: \"Slack workspace allowlist is not configured\",\n });\n }\n if (!_missingAllowlistWarned) {\n _missingAllowlistWarned = true;\n console.warn(\n \"[slack] SLACK_ALLOWED_TEAM_IDS not set — accepting events from any workspace whose signature matches SLACK_SIGNING_SECRET. \" +\n \"Set SLACK_ALLOWED_TEAM_IDS to a comma-separated list of allowed team_id values before deploying to production.\",\n );\n }\n }\n\n if (allowedTeamIds) {\n if (!teamId || !allowedTeamIds.has(teamId)) {\n throw createError({\n statusCode: 401,\n statusMessage: \"Unrecognized Slack workspace\",\n });\n }\n }\n\n if (allowedAppIds) {\n if (!apiAppId || !allowedAppIds.has(apiAppId)) {\n throw createError({\n statusCode: 401,\n statusMessage: \"Unrecognized Slack workspace\",\n });\n }\n }\n}\n\n/**\n * Read the raw request body as a string and cache on the event context.\n *\n * This MUST read raw bytes from the request stream — never `JSON.stringify`\n * a parsed body, because Slack's HMAC is computed over the exact bytes Slack\n * sent. Re-stringifying a parsed object loses key ordering, whitespace, and\n * Unicode-escape choices, so the signature check would silently fail for\n * legitimate requests (M2 in the webhook security audit).\n *\n * h3 v2's body stream is consume-once, so we cache the raw string on the\n * event context after the first read. All call sites (handleVerification,\n * verifyWebhook, parseIncomingMessage) MUST go through this helper.\n */\nasync function readRawBodyCached(event: H3Event): Promise<string> {\n const cached = event.context.__rawBody;\n if (typeof cached === \"string\") return cached;\n // h3's readRawBody returns the bytes Slack actually sent, defaulting to\n // utf8-decoded. Returns undefined for empty bodies — we coerce to \"\" so\n // the HMAC check can proceed deterministically.\n const raw = (await readRawBody(event)) ?? \"\";\n event.context.__rawBody = raw;\n return raw;\n}\n\nfunction utf8ByteLength(text: string): number {\n return new TextEncoder().encode(text).length;\n}\n\nfunction prefixWithinUtf8ByteLimit(text: string, maxLength: number): string {\n let bytes = 0;\n let end = 0;\n for (const char of text) {\n const nextBytes = utf8ByteLength(char);\n if (bytes + nextBytes > maxLength) break;\n bytes += nextBytes;\n end += char.length;\n }\n return text.slice(0, end || 1);\n}\n\n/** Split a message into chunks that fit within the platform's byte limit. */\nfunction splitMessage(text: string, maxLength: number): string[] {\n if (utf8ByteLength(text) <= maxLength) return [text];\n const chunks: string[] = [];\n let remaining = text;\n while (remaining.length > 0) {\n if (utf8ByteLength(remaining) <= maxLength) {\n chunks.push(remaining);\n break;\n }\n\n const prefix = prefixWithinUtf8ByteLimit(remaining, maxLength);\n\n // Try to split at a newline\n let splitIdx = prefix.lastIndexOf(\"\\n\");\n if (splitIdx <= 0) {\n // Try to split at a space\n splitIdx = prefix.lastIndexOf(\" \");\n }\n if (splitIdx <= 0) {\n splitIdx = prefix.length;\n }\n chunks.push(remaining.slice(0, splitIdx));\n remaining = remaining.slice(splitIdx).trimStart();\n }\n return chunks;\n}\n\n/** Split a message and drop chunks Slack would render as blank messages. */\nfunction splitNonEmptyMessage(text: string, maxLength: number): string[] {\n return splitMessage(text, maxLength).filter(\n (chunk) => chunk.trim().length > 0,\n );\n}\n\n/** Hard cap on input length we feed to the regex-based mrkdwn converter.\n * L2 in the webhook audit: `\\*\\*(.+?)\\*\\*` with the `s` flag on a long\n * string of asterisks can exhibit super-linear backtracking. Slack\n * itself caps message bodies at 4000 chars (SLACK_MAX_LENGTH); we cap\n * the input here at 10x that as a defensive bound for any caller that\n * passes a longer rendering source through this helper before chunking. */\nconst MRKDWN_MAX_LENGTH = 40_000;\n\n/**\n * Convert standard markdown to Slack's mrkdwn dialect.\n * - `[text](url)` → `<url|text>`\n * - `**bold**` → `*bold*` (Slack uses single asterisks for bold)\n *\n * Inputs longer than MRKDWN_MAX_LENGTH are truncated before the regex\n * pass to bound worst-case backtracking on pathological input (L2 in the\n * webhook audit).\n */\nfunction markdownToSlackMrkdwn(text: string): string {\n const bounded =\n text.length > MRKDWN_MAX_LENGTH ? text.slice(0, MRKDWN_MAX_LENGTH) : text;\n return (\n bounded\n .replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, \"<$2|$1>\")\n // Do not wrap bare URLs in Slack bold markers. Slack's autolinker can\n // treat the trailing `*` as part of the URL, producing a broken link.\n .replace(/\\*\\*<?(https?:\\/\\/[^\\s>*]+)>?\\*\\*/g, \"<$1>\")\n // Bounded character class instead of `.+?` with the `s` flag — caps\n // each bold span at 5000 chars so an attacker can't construct a\n // pathological \"**\" sequence that exhibits super-linear backtracking.\n // Newlines are allowed because `[^*]` excludes only the asterisk\n // itself, so multi-line bold spans still match.\n .replace(/\\*\\*([^*]{1,5000})\\*\\*/g, \"*$1*\")\n );\n}\n\n/**\n * Optionally set Slack's native AI-assistant status indicator (the small\n * \"is thinking…\" line under the message composer) for an app configured\n * with the `assistant:write` scope. Pure best-effort — fails silently for\n * apps that aren't set up as AI assistants.\n */\nfunction setSlackAssistantStatus(\n token: string,\n channelId: string,\n threadTs: string,\n status: string,\n): void {\n slackApiFetch(\"https://slack.com/api/assistant.threads.setStatus\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n channel_id: channelId,\n thread_ts: threadTs,\n status,\n }),\n }).catch(() => {});\n}\n\n/**\n * Block Kit payload for the final answer. We avoid auto-unfurl previews by\n * separating the deep-link out into a button instead of inlining it as a\n * `<url|text>` markdown link in the section body — that's what was producing\n * the giant \"Agent-Native Dispatch\" card in every thread reply.\n */\nfunction buildResponseBlocks(\n text: string,\n opts: { threadDeepLinkUrl?: string },\n): unknown[] {\n const sectionChunks = splitMessage(\n text || \"_(no response)_\",\n SLACK_SECTION_TEXT_MAX_LENGTH,\n );\n const blocks: any[] = sectionChunks.map((chunk) => ({\n type: \"section\",\n text: { type: \"mrkdwn\", text: chunk },\n }));\n if (opts.threadDeepLinkUrl) {\n blocks.push({\n type: \"actions\",\n elements: [\n {\n type: \"button\",\n text: { type: \"plain_text\", text: \"Open thread\", emoji: true },\n url: opts.threadDeepLinkUrl,\n action_id: \"open_dispatch_thread\",\n },\n ],\n });\n }\n return blocks;\n}\n\n/**\n * Post a fresh message to a thread. Used as the placeholder-fallback path\n * (e.g. when chat.update fails) and for follow-up overflow chunks.\n */\nasync function postFresh(\n token: string,\n channelId: string,\n threadTs: string | undefined,\n body: Record<string, unknown>,\n): Promise<void> {\n const hasBlocks =\n Array.isArray(body.blocks) && (body.blocks as unknown[]).length > 0;\n if (\n typeof body.text === \"string\" &&\n body.text.trim().length === 0 &&\n !hasBlocks\n ) {\n return;\n }\n\n const payload: Record<string, unknown> = {\n ...body,\n channel: channelId,\n };\n if (threadTs && !payload.thread_ts) payload.thread_ts = threadTs;\n const res = await slackApiFetch(\"https://slack.com/api/chat.postMessage\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n const data = (await res.json()) as { ok: boolean; error?: string };\n if (!data.ok) {\n console.error(\"[slack] chat.postMessage error:\", data.error);\n throw new Error(data.error || \"chat.postMessage failed\");\n }\n}\n\nasync function slackApiFetch(\n url: string,\n init: RequestInit,\n): Promise<Response> {\n const controller =\n typeof AbortController !== \"undefined\" ? new AbortController() : undefined;\n const timer = controller\n ? setTimeout(() => controller.abort(), SLACK_API_TIMEOUT_MS)\n : undefined;\n try {\n return await fetch(url, {\n ...init,\n signal: controller?.signal ?? init.signal,\n });\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n"]}
|