diff options
| author | Dax Raad <[email protected]> | 2025-10-14 02:53:55 -0400 |
|---|---|---|
| committer | Dax Raad <[email protected]> | 2025-10-14 02:53:55 -0400 |
| commit | 1923ddab6eebbd5b817a88574deda7cb7f94e782 (patch) | |
| tree | 10e1e54e8b106b67b64346cc5b23a12fa3775deb /packages | |
| parent | b8249cde4b1c5ed73daf31c002f9f5e2b2e3c89b (diff) | |
| download | opencode-1923ddab6eebbd5b817a88574deda7cb7f94e782.tar.gz opencode-1923ddab6eebbd5b817a88574deda7cb7f94e782.zip | |
feat: add Slack integration package with Bolt framework
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/opencode/src/tool/write.ts | 12 | ||||
| -rw-r--r-- | packages/slack/.env.example | 3 | ||||
| -rw-r--r-- | packages/slack/.gitignore | 4 | ||||
| -rw-r--r-- | packages/slack/README.md | 27 | ||||
| -rw-r--r-- | packages/slack/package.json | 17 | ||||
| -rw-r--r-- | packages/slack/src/index.ts | 105 | ||||
| -rw-r--r-- | packages/slack/sst-env.d.ts | 3 | ||||
| -rw-r--r-- | packages/slack/tsconfig.json | 5 |
8 files changed, 176 insertions, 0 deletions
diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts index aa79c9bfb..64f9feb20 100644 --- a/packages/opencode/src/tool/write.ts +++ b/packages/opencode/src/tool/write.ts @@ -10,6 +10,8 @@ import { FileTime } from "../file/time" import { Filesystem } from "../util/filesystem" import { Instance } from "../project/instance" import { Agent } from "../agent/agent" +import { createTwoFilesPatch } from "diff" +import { trimDiff } from "./edit" export const WriteTool = Tool.define("write", { description: DESCRIPTION, @@ -27,6 +29,13 @@ export const WriteTool = Tool.define("write", { const exists = await file.exists() if (exists) await FileTime.assert(ctx.sessionID, filepath) + let oldContent = "" + let diff = "" + + if (exists) { + oldContent = await file.text() + } + const agent = await Agent.get(ctx.agent) if (agent.permission.edit === "ask") await Permission.ask({ @@ -48,6 +57,9 @@ export const WriteTool = Tool.define("write", { }) FileTime.read(ctx.sessionID, filepath) + // Generate diff for the write operation + diff = trimDiff(createTwoFilesPatch(filepath, filepath, oldContent, params.content)) + let output = "" await LSP.touchFile(filepath, true) const diagnostics = await LSP.diagnostics() diff --git a/packages/slack/.env.example b/packages/slack/.env.example new file mode 100644 index 000000000..ff72adaf9 --- /dev/null +++ b/packages/slack/.env.example @@ -0,0 +1,3 @@ +SLACK_BOT_TOKEN=xoxb-your-bot-token +SLACK_SIGNING_SECRET=your-signing-secret +SLACK_APP_TOKEN=xapp-your-app-token
\ No newline at end of file diff --git a/packages/slack/.gitignore b/packages/slack/.gitignore new file mode 100644 index 000000000..1ef9d19ba --- /dev/null +++ b/packages/slack/.gitignore @@ -0,0 +1,4 @@ +node_modules +dist +.env +.DS_Store
\ No newline at end of file diff --git a/packages/slack/README.md b/packages/slack/README.md new file mode 100644 index 000000000..a12978d8a --- /dev/null +++ b/packages/slack/README.md @@ -0,0 +1,27 @@ +# @opencode-ai/slack + +Slack bot integration for opencode that creates threaded conversations. + +## Setup + +1. Create a Slack app at https://api.slack.com/apps +2. Enable Socket Mode +3. Add the following OAuth scopes: + - `chat:write` + - `app_mentions:read` + - `channels:history` + - `groups:history` +4. Install the app to your workspace +5. Set environment variables in `.env`: + - `SLACK_BOT_TOKEN` - Bot User OAuth Token + - `SLACK_SIGNING_SECRET` - Signing Secret from Basic Information + - `SLACK_APP_TOKEN` - App-Level Token from Basic Information + +## Usage + +```bash +# Edit .env with your Slack app credentials +bun dev +``` + +The bot will respond to messages in channels where it's added, creating separate opencode sessions for each thread. diff --git a/packages/slack/package.json b/packages/slack/package.json new file mode 100644 index 000000000..179782e23 --- /dev/null +++ b/packages/slack/package.json @@ -0,0 +1,17 @@ +{ + "name": "@opencode-ai/slack", + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "bun run src/index.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@opencode-ai/sdk": "workspace:*", + "@slack/bolt": "^3.17.1" + }, + "devDependencies": { + "@types/node": "catalog:", + "typescript": "catalog:" + } +} 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<string, { client: any; server: any; sessionId: string; channel: string; thread: string }>() + +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!") diff --git a/packages/slack/sst-env.d.ts b/packages/slack/sst-env.d.ts new file mode 100644 index 000000000..cde5429a6 --- /dev/null +++ b/packages/slack/sst-env.d.ts @@ -0,0 +1,3 @@ +/// <reference types="../../../sst-env.d.ts" /> + +export {} diff --git a/packages/slack/tsconfig.json b/packages/slack/tsconfig.json new file mode 100644 index 000000000..65fa6c7f3 --- /dev/null +++ b/packages/slack/tsconfig.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "@tsconfig/bun/tsconfig.json", + "compilerOptions": {} +} |
