diff --git a/apps/dbagent/package.json b/apps/dbagent/package.json index 6897dfe5..0c97dae0 100644 --- a/apps/dbagent/package.json +++ b/apps/dbagent/package.json @@ -26,6 +26,7 @@ "@fluentui/react-icons": "^2.0.274", "@internal/components": "workspace:*", "@internal/theme": "workspace:*", + "@slack/web-api": "^7.8.0", "@tailwindcss/postcss": "^4.0.9", "@tanstack/react-query": "^5.67.1", "@vercel/functions": "^2.0.0", diff --git a/apps/dbagent/src/app/api/chat/route.ts b/apps/dbagent/src/app/api/chat/route.ts index b9d985bb..be998402 100644 --- a/apps/dbagent/src/app/api/chat/route.ts +++ b/apps/dbagent/src/app/api/chat/route.ts @@ -33,24 +33,16 @@ export async function POST(req: Request) { } const targetClient = await getTargetDbConnection(connection.connectionString); try { - const context = chatSystemPrompt; - - console.log(context); - - const modelInstance = getModelInstance(model); - const result = streamText({ - model: modelInstance, + model: getModelInstance(model), messages, - system: context, + system: chatSystemPrompt, tools: await getTools(connection, targetClient), maxSteps: 20, toolCallStreaming: true }); - return result.toDataStreamResponse({ - getErrorMessage: errorHandler - }); + return result.toDataStreamResponse({ getErrorMessage: errorHandler }); } finally { await targetClient.end(); } diff --git a/apps/dbagent/src/app/api/priv/slack/route.ts b/apps/dbagent/src/app/api/priv/slack/route.ts new file mode 100644 index 00000000..358b6734 --- /dev/null +++ b/apps/dbagent/src/app/api/priv/slack/route.ts @@ -0,0 +1,51 @@ +import type { SlackEvent } from '@slack/web-api'; +import { waitUntil } from '@vercel/functions'; +import { handleNewAppMention } from '~/lib/slack/handle-app-mentions'; +import { assistantThreadMessage, handleNewAssistantMessage } from '~/lib/slack/handle-messages'; +import { getBotId, verifyRequest } from '~/lib/slack/utils'; + +export const runtime = 'nodejs'; +export const maxDuration = 30; + +export async function POST(request: Request) { + const rawBody = await request.text(); + const payload = JSON.parse(rawBody); + const requestType = payload.type as 'url_verification' | 'event_callback'; + + // See https://api.slack.com/events/url_verification + if (requestType === 'url_verification') { + return new Response(payload.challenge, { status: 200 }); + } + + await verifyRequest({ requestType, request, rawBody }); + + try { + const botUserId = await getBotId(); + + const event = payload.event as SlackEvent; + + if (event.type === 'app_mention') { + waitUntil(handleNewAppMention(event, botUserId)); + } + + if (event.type === 'assistant_thread_started') { + waitUntil(assistantThreadMessage(event)); + } + + if ( + event.type === 'message' && + !event.subtype && + event.channel_type === 'im' && + !event.bot_id && + !event.bot_profile && + event.bot_id !== botUserId + ) { + waitUntil(handleNewAssistantMessage(event, botUserId)); + } + + return new Response('Success!', { status: 200 }); + } catch (error) { + console.error('Error generating response', error); + return new Response('Error generating response', { status: 500 }); + } +} diff --git a/apps/dbagent/src/lib/env/server.ts b/apps/dbagent/src/lib/env/server.ts index 70379ded..31bd3838 100644 --- a/apps/dbagent/src/lib/env/server.ts +++ b/apps/dbagent/src/lib/env/server.ts @@ -13,6 +13,12 @@ const schema = z.object({ AUTH_OPENID_SECRET: z.string().optional(), AUTH_OPENID_ISSUER: z.string().optional(), + // Slack OAuth settings + SLACK_CLIENT_ID: z.string().optional(), + SLACK_CLIENT_SECRET: z.string().optional(), + SLACK_SIGNING_SECRET: z.string().optional(), + SLACK_BOT_TOKEN: z.string().optional(), + // LLM API credentials OPENAI_API_KEY: z.string(), DEEPSEEK_API_KEY: z.string().optional(), diff --git a/apps/dbagent/src/lib/slack/generate-response.ts b/apps/dbagent/src/lib/slack/generate-response.ts new file mode 100644 index 00000000..de6b1d17 --- /dev/null +++ b/apps/dbagent/src/lib/slack/generate-response.ts @@ -0,0 +1,23 @@ +import { CoreMessage, generateText } from 'ai'; +import { chatSystemPrompt, getModelInstance, getTools } from '../ai/aidba'; +import { getConnection } from '../db/connections'; +import { getTargetDbConnection } from '../targetdb/db'; + +export const generateResponse = async (messages: CoreMessage[]) => { + const connection = await getConnection(connectionId); + if (!connection) { + throw new Error('Connection not found'); + } + const targetClient = await getTargetDbConnection(connection.connectionString); + + const { text } = await generateText({ + model: getModelInstance('openai-gpt-4o'), + messages, + system: chatSystemPrompt, + tools: await getTools(connection, targetClient), + maxSteps: 20 + }); + + // Convert markdown to Slack mrkdwn format + return text.replace(/\[(.*?)\]\((.*?)\)/g, '<$2|$1>').replace(/\*\*/g, '*'); +}; diff --git a/apps/dbagent/src/lib/slack/handle-app-mentions.ts b/apps/dbagent/src/lib/slack/handle-app-mentions.ts new file mode 100644 index 00000000..ca8f1771 --- /dev/null +++ b/apps/dbagent/src/lib/slack/handle-app-mentions.ts @@ -0,0 +1,42 @@ +import { AppMentionEvent } from '@slack/web-api'; +import { generateResponse } from './generate-response'; +import { client, getThread } from './utils'; + +const updateStatusUtil = async (initialStatus: string, event: AppMentionEvent) => { + const initialMessage = await client.chat.postMessage({ + channel: event.channel, + thread_ts: event.thread_ts ?? event.ts, + text: initialStatus + }); + + if (!initialMessage || !initialMessage.ts) throw new Error('Failed to post initial message'); + + const updateMessage = async (status: string) => { + await client.chat.update({ + channel: event.channel, + ts: initialMessage.ts as string, + text: status + }); + }; + return updateMessage; +}; + +export async function handleNewAppMention(event: AppMentionEvent, botUserId: string) { + console.log('Handling app mention'); + if (event.bot_id || event.bot_id === botUserId || event.bot_profile) { + console.log('Skipping app mention'); + return; + } + + const { thread_ts, channel } = event; + const updateMessage = await updateStatusUtil('is thinking...', event); + + if (thread_ts) { + const messages = await getThread(channel, thread_ts, botUserId); + const result = await generateResponse(messages); + updateMessage(result); + } else { + const result = await generateResponse([{ role: 'user', content: event.text }]); + updateMessage(result); + } +} diff --git a/apps/dbagent/src/lib/slack/handle-messages.ts b/apps/dbagent/src/lib/slack/handle-messages.ts new file mode 100644 index 00000000..1a4779ee --- /dev/null +++ b/apps/dbagent/src/lib/slack/handle-messages.ts @@ -0,0 +1,62 @@ +import type { AssistantThreadStartedEvent, GenericMessageEvent } from '@slack/web-api'; +import { generateResponse } from './generate-response'; +import { client, getThread, updateStatusUtil } from './utils'; + +export async function assistantThreadMessage(event: AssistantThreadStartedEvent) { + const { channel_id, thread_ts } = event.assistant_thread; + console.log(`Thread started: ${channel_id} ${thread_ts}`); + + await client.chat.postMessage({ + channel: channel_id, + thread_ts: thread_ts, + text: "Hello! I'm your AI database expert. I can help you manage and optimize your PostgreSQL database. Which project would you like to work with?" + }); + + await client.assistant.threads.setSuggestedPrompts({ + channel_id: channel_id, + thread_ts: thread_ts, + prompts: [ + { + title: 'List my projects', + message: 'Show me my available projects' + }, + { + title: 'Check database health', + message: 'Check the health of my database' + }, + { + title: 'Optimize queries', + message: 'Help me optimize my slow queries' + } + ] + }); +} + +export async function handleNewAssistantMessage(event: GenericMessageEvent, botUserId: string) { + if (event.bot_id || event.bot_id === botUserId || event.bot_profile || !event.thread_ts) return; + + const { thread_ts, channel } = event; + const updateStatus = updateStatusUtil(channel, thread_ts); + updateStatus('is thinking...'); + + const messages = await getThread(channel, thread_ts, botUserId); + const result = await generateResponse(messages); + + await client.chat.postMessage({ + channel: channel, + thread_ts: thread_ts, + text: result, + unfurl_links: false, + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text: result + } + } + ] + }); + + updateStatus(''); +} diff --git a/apps/dbagent/src/lib/slack/utils.ts b/apps/dbagent/src/lib/slack/utils.ts new file mode 100644 index 00000000..458d9b6c --- /dev/null +++ b/apps/dbagent/src/lib/slack/utils.ts @@ -0,0 +1,100 @@ +import { WebClient } from '@slack/web-api'; +import { CoreMessage } from 'ai'; +import crypto from 'crypto'; + +const signingSecret = process.env.SLACK_SIGNING_SECRET!; + +export const client = new WebClient(process.env.SLACK_BOT_TOKEN); + +// See https://api.slack.com/authentication/verifying-requests-from-slack +export async function isValidSlackRequest({ request, rawBody }: { request: Request; rawBody: string }) { + // console.log('Validating Slack request') + const timestamp = request.headers.get('X-Slack-Request-Timestamp'); + const slackSignature = request.headers.get('X-Slack-Signature'); + // console.log(timestamp, slackSignature) + + if (!timestamp || !slackSignature) { + console.log('Missing timestamp or signature'); + return false; + } + + // Prevent replay attacks on the order of 5 minutes + if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 60 * 5) { + console.log('Timestamp out of range'); + return false; + } + + const base = `v0:${timestamp}:${rawBody}`; + const hmac = crypto.createHmac('sha256', signingSecret).update(base).digest('hex'); + const computedSignature = `v0=${hmac}`; + + // Prevent timing attacks + return crypto.timingSafeEqual(Buffer.from(computedSignature), Buffer.from(slackSignature)); +} + +export const verifyRequest = async ({ + requestType, + request, + rawBody +}: { + requestType: string; + request: Request; + rawBody: string; +}) => { + const validRequest = await isValidSlackRequest({ request, rawBody }); + if (!validRequest || requestType !== 'event_callback') { + return new Response('Invalid request', { status: 400 }); + } +}; + +export const updateStatusUtil = (channel: string, thread_ts: string) => { + return async (status: string) => { + await client.assistant.threads.setStatus({ + channel_id: channel, + thread_ts: thread_ts, + status: status + }); + }; +}; + +export async function getThread(channel_id: string, thread_ts: string, botUserId: string): Promise { + const { messages } = await client.conversations.replies({ + channel: channel_id, + ts: thread_ts, + limit: 50 + }); + + // Ensure we have messages + + if (!messages) throw new Error('No messages found in thread'); + + const result = messages + .map((message) => { + const isBot = !!message.bot_id; + if (!message.text) return null; + + // For app mentions, remove the mention prefix + // For IM messages, keep the full text + let content = message.text; + if (!isBot && content.includes(`<@${botUserId}>`)) { + content = content.replace(`<@${botUserId}> `, ''); + } + + return { + role: isBot ? 'assistant' : 'user', + content: content + } as CoreMessage; + }) + .filter((msg): msg is CoreMessage => msg !== null); + + return result; +} + +export const getBotId = async () => { + const { user_id: botUserId } = await client.auth.test(); + + if (!botUserId) { + throw new Error('botUserId is undefined'); + } + return botUserId; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1181a4c9..d3209fce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: '@internal/theme': specifier: workspace:* version: link:../../packages/theme + '@slack/web-api': + specifier: ^7.8.0 + version: 7.8.0 '@tailwindcss/postcss': specifier: ^4.0.9 version: 4.0.9 @@ -1879,6 +1882,18 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@slack/logger@4.0.0': + resolution: {integrity: sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + + '@slack/types@2.14.0': + resolution: {integrity: sha512-n0EGm7ENQRxlXbgKSrQZL69grzg1gHLAVd+GlRVQJ1NSORo0FrApR7wql/gaKdu2n4TO83Sq/AmeUOqD60aXUA==} + engines: {node: '>= 12.13.0', npm: '>= 6.12.0'} + + '@slack/web-api@7.8.0': + resolution: {integrity: sha512-d4SdG+6UmGdzWw38a4sN3lF/nTEzsDxhzU13wm10ejOpPehtmRoqBKnPztQUfFiWbNvSb4czkWYJD4kt+5+Fuw==} + engines: {node: '>= 18', npm: '>= 8.6.0'} + '@smithy/abort-controller@4.0.1': resolution: {integrity: sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==} engines: {node: '>=18.0.0'} @@ -2308,6 +2323,9 @@ packages: '@types/react@19.0.10': resolution: {integrity: sha512-JuRQ9KXLEjaUNjTWpzuR231Z2WpIwczOkBEIvbHNCzQefFIT0L8IqE6NV6ULLyC1SI/i234JnDoMkfg+RjQj2g==} + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -2505,6 +2523,9 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -2520,6 +2541,9 @@ packages: resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==} engines: {node: '>=4'} + axios@1.8.1: + resolution: {integrity: sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -2698,6 +2722,10 @@ packages: colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + comma-separated-tokens@1.0.8: resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} @@ -2860,6 +2888,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -3256,9 +3288,22 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + form-data@4.0.2: + resolution: {integrity: sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==} + engines: {node: '>= 6'} + format@0.2.2: resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} engines: {node: '>=0.4.x'} @@ -3507,6 +3552,9 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-electron@2.2.2: + resolution: {integrity: sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3569,6 +3617,10 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + is-stream@3.0.0: resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3902,6 +3954,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -4049,6 +4109,10 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4065,6 +4129,18 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -4325,6 +4401,9 @@ packages: property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -4483,6 +4562,10 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -6751,6 +6834,29 @@ snapshots: '@rtsao/scc@1.1.0': {} + '@slack/logger@4.0.0': + dependencies: + '@types/node': 22.13.9 + + '@slack/types@2.14.0': {} + + '@slack/web-api@7.8.0': + dependencies: + '@slack/logger': 4.0.0 + '@slack/types': 2.14.0 + '@types/node': 22.13.9 + '@types/retry': 0.12.0 + axios: 1.8.1 + eventemitter3: 5.0.1 + form-data: 4.0.2 + is-electron: 2.2.2 + is-stream: 2.0.1 + p-queue: 6.6.2 + p-retry: 4.6.2 + retry: 0.13.1 + transitivePeerDependencies: + - debug + '@smithy/abort-controller@4.0.1': dependencies: '@smithy/types': 4.1.0 @@ -7301,6 +7407,8 @@ snapshots: dependencies: csstype: 3.1.3 + '@types/retry@0.12.0': {} + '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -7552,6 +7660,8 @@ snapshots: ast-types-flow@0.0.8: {} + asynckit@0.4.0: {} + autoprefixer@10.4.20(postcss@8.5.3): dependencies: browserslist: 4.24.4 @@ -7568,6 +7678,14 @@ snapshots: axe-core@4.10.2: {} + axios@1.8.1: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.2 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.1.0: {} bail@2.0.2: {} @@ -7750,6 +7868,10 @@ snapshots: colorette@2.0.20: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + comma-separated-tokens@1.0.8: {} comma-separated-tokens@2.0.3: {} @@ -7895,6 +8017,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + dequal@2.0.3: {} detect-libc@1.0.3: {} @@ -8362,10 +8486,19 @@ snapshots: flatted@3.3.1: {} + follow-redirects@1.15.9: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 + form-data@4.0.2: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + mime-types: 2.1.35 + format@0.2.2: {} fraction.js@4.3.7: {} @@ -8620,6 +8753,8 @@ snapshots: is-decimal@2.0.1: {} + is-electron@2.2.2: {} + is-extglob@2.1.1: {} is-finalizationregistry@1.1.1: @@ -8670,6 +8805,8 @@ snapshots: dependencies: call-bound: 1.0.3 + is-stream@2.0.1: {} + is-stream@3.0.0: {} is-string@1.1.1: @@ -9121,6 +9258,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mimic-fn@4.0.0: {} mimic-function@5.0.1: {} @@ -9264,6 +9407,8 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-finally@1.0.0: {} + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -9280,6 +9425,20 @@ snapshots: dependencies: p-limit: 3.1.0 + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + p-try@2.2.0: {} parent-module@1.0.1: @@ -9465,6 +9624,8 @@ snapshots: property-information@6.5.0: {} + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -9668,6 +9829,8 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 + retry@0.13.1: {} + reusify@1.0.4: {} rfdc@1.4.1: {}