feat: add Slack integration package with Bolt framework

This commit is contained in:
Dax Raad
2025-10-14 02:53:55 -04:00
parent b8249cde4b
commit 1923ddab6e
10 changed files with 566 additions and 25 deletions

View File

@@ -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()

View File

@@ -0,0 +1,3 @@
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret
SLACK_APP_TOKEN=xapp-your-app-token

4
packages/slack/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules
dist
.env
.DS_Store

27
packages/slack/README.md Normal file
View File

@@ -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.

View File

@@ -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:"
}
}

105
packages/slack/src/index.ts Normal file
View File

@@ -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!")

3
packages/slack/sst-env.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/// <reference types="../../../sst-env.d.ts" />
export {}

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@tsconfig/bun/tsconfig.json",
"compilerOptions": {}
}