From 1923ddab6eebbd5b817a88574deda7cb7f94e782 Mon Sep 17 00:00:00 2001 From: Dax Raad Date: Tue, 14 Oct 2025 02:53:55 -0400 Subject: feat: add Slack integration package with Bolt framework --- packages/slack/src/index.ts | 105 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 packages/slack/src/index.ts (limited to 'packages/slack/src') diff --git a/packages/slack/src/index.ts b/packages/slack/src/index.ts new file mode 100644 index 000000000..e86b8fec5 --- /dev/null +++ b/packages/slack/src/index.ts @@ -0,0 +1,105 @@ +import { App } from "@slack/bolt" +import { createOpencode } from "@opencode-ai/sdk" + +const app = new App({ + token: process.env.SLACK_BOT_TOKEN, + signingSecret: process.env.SLACK_SIGNING_SECRET, + socketMode: true, + appToken: process.env.SLACK_APP_TOKEN, +}) + +console.log("๐Ÿ”ง Bot configuration:") +console.log("- Bot token present:", !!process.env.SLACK_BOT_TOKEN) +console.log("- Signing secret present:", !!process.env.SLACK_SIGNING_SECRET) +console.log("- App token present:", !!process.env.SLACK_APP_TOKEN) + +console.log("๐Ÿš€ Starting opencode server...") +const opencode = await createOpencode({ + port: 0, +}) +console.log("โœ… Opencode server ready") + +const sessions = new Map() + +app.use(async ({ next, context }) => { + console.log("๐Ÿ“ก Raw Slack event:", JSON.stringify(context, null, 2)) + await next() +}) + +app.message(async ({ message, say }) => { + console.log("๐Ÿ“จ Received message event:", JSON.stringify(message, null, 2)) + + if (message.subtype || !("text" in message) || !message.text) { + console.log("โญ๏ธ Skipping message - no text or has subtype") + return + } + + console.log("โœ… Processing message:", message.text) + + const channel = message.channel + const thread = (message as any).thread_ts || message.ts + const sessionKey = `${channel}-${thread}` + + let session = sessions.get(sessionKey) + + if (!session) { + console.log("๐Ÿ†• Creating new opencode session...") + const { client, server } = opencode + + const createResult = await client.session.create({ + body: { title: `Slack thread ${thread}` }, + }) + + if (createResult.error) { + console.error("โŒ Failed to create session:", createResult.error) + await say({ text: "Sorry, I had trouble creating a session. Please try again.", thread_ts: thread }) + return + } + + console.log("โœ… Created opencode session:", createResult.data.id) + session = { client, server, sessionId: createResult.data.id, channel, thread } + sessions.set(sessionKey, session) + + const shareResult = await client.session.share({ path: { id: createResult.data.id } }) + if (!shareResult.error && shareResult.data) { + const sessionUrl = shareResult.data.share?.url! + console.log("๐Ÿ”— Session shared:", sessionUrl) + await app.client.chat.postMessage({ channel, thread_ts: thread, text: sessionUrl }) + } + } + + console.log("๐Ÿ“ Sending to opencode:", message.text) + const result = await session.client.session.prompt({ + path: { id: session.sessionId }, + body: { parts: [{ type: "text", text: message.text }] }, + }) + + console.log("๐Ÿ“ค Opencode response:", JSON.stringify(result, null, 2)) + + if (result.error) { + console.error("โŒ Failed to send message:", result.error) + await say({ text: "Sorry, I had trouble processing your message. Please try again.", thread_ts: thread }) + return + } + + const response = result.data + const responseText = + response.info?.content || + response.parts + ?.filter((p: any) => p.type === "text") + .map((p: any) => p.text) + .join("\n") || + "I received your message but didn't have a response." + + console.log("๐Ÿ’ฌ Sending response:", responseText) + await say({ text: responseText, thread_ts: thread }) +}) + +app.command("/test", async ({ command, ack, say }) => { + await ack() + console.log("๐Ÿงช Test command received:", JSON.stringify(command, null, 2)) + await say("๐Ÿค– Bot is working! I can hear you loud and clear.") +}) + +await app.start() +console.log("โšก๏ธ Slack bot is running!") -- cgit v1.2.3