Skip to content

Commit 1923dda

Browse files
committed
feat: add Slack integration package with Bolt framework
1 parent b8249cd commit 1923dda

10 files changed

Lines changed: 566 additions & 25 deletions

File tree

bun.lock

Lines changed: 388 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"packages": [
1414
"packages/*",
1515
"packages/console/*",
16-
"packages/sdk/js"
16+
"packages/sdk/js",
17+
"packages/slack"
1718
],
1819
"catalog": {
1920
"@types/bun": "1.3.0",

packages/opencode/src/tool/write.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { FileTime } from "../file/time"
1010
import { Filesystem } from "../util/filesystem"
1111
import { Instance } from "../project/instance"
1212
import { Agent } from "../agent/agent"
13+
import { createTwoFilesPatch } from "diff"
14+
import { trimDiff } from "./edit"
1315

1416
export const WriteTool = Tool.define("write", {
1517
description: DESCRIPTION,
@@ -27,6 +29,13 @@ export const WriteTool = Tool.define("write", {
2729
const exists = await file.exists()
2830
if (exists) await FileTime.assert(ctx.sessionID, filepath)
2931

32+
let oldContent = ""
33+
let diff = ""
34+
35+
if (exists) {
36+
oldContent = await file.text()
37+
}
38+
3039
const agent = await Agent.get(ctx.agent)
3140
if (agent.permission.edit === "ask")
3241
await Permission.ask({
@@ -48,6 +57,9 @@ export const WriteTool = Tool.define("write", {
4857
})
4958
FileTime.read(ctx.sessionID, filepath)
5059

60+
// Generate diff for the write operation
61+
diff = trimDiff(createTwoFilesPatch(filepath, filepath, oldContent, params.content))
62+
5163
let output = ""
5264
await LSP.touchFile(filepath, true)
5365
const diagnostics = await LSP.diagnostics()

packages/slack/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
SLACK_BOT_TOKEN=xoxb-your-bot-token
2+
SLACK_SIGNING_SECRET=your-signing-secret
3+
SLACK_APP_TOKEN=xapp-your-app-token

packages/slack/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
dist
3+
.env
4+
.DS_Store

packages/slack/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# @opencode-ai/slack
2+
3+
Slack bot integration for opencode that creates threaded conversations.
4+
5+
## Setup
6+
7+
1. Create a Slack app at https://api.slack.com/apps
8+
2. Enable Socket Mode
9+
3. Add the following OAuth scopes:
10+
- `chat:write`
11+
- `app_mentions:read`
12+
- `channels:history`
13+
- `groups:history`
14+
4. Install the app to your workspace
15+
5. Set environment variables in `.env`:
16+
- `SLACK_BOT_TOKEN` - Bot User OAuth Token
17+
- `SLACK_SIGNING_SECRET` - Signing Secret from Basic Information
18+
- `SLACK_APP_TOKEN` - App-Level Token from Basic Information
19+
20+
## Usage
21+
22+
```bash
23+
# Edit .env with your Slack app credentials
24+
bun dev
25+
```
26+
27+
The bot will respond to messages in channels where it's added, creating separate opencode sessions for each thread.

packages/slack/package.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@opencode-ai/slack",
3+
"version": "0.1.0",
4+
"type": "module",
5+
"scripts": {
6+
"dev": "bun run src/index.ts",
7+
"typecheck": "tsc --noEmit"
8+
},
9+
"dependencies": {
10+
"@opencode-ai/sdk": "workspace:*",
11+
"@slack/bolt": "^3.17.1"
12+
},
13+
"devDependencies": {
14+
"@types/node": "catalog:",
15+
"typescript": "catalog:"
16+
}
17+
}

packages/slack/src/index.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { App } from "@slack/bolt"
2+
import { createOpencode } from "@opencode-ai/sdk"
3+
4+
const app = new App({
5+
token: process.env.SLACK_BOT_TOKEN,
6+
signingSecret: process.env.SLACK_SIGNING_SECRET,
7+
socketMode: true,
8+
appToken: process.env.SLACK_APP_TOKEN,
9+
})
10+
11+
console.log("🔧 Bot configuration:")
12+
console.log("- Bot token present:", !!process.env.SLACK_BOT_TOKEN)
13+
console.log("- Signing secret present:", !!process.env.SLACK_SIGNING_SECRET)
14+
console.log("- App token present:", !!process.env.SLACK_APP_TOKEN)
15+
16+
console.log("🚀 Starting opencode server...")
17+
const opencode = await createOpencode({
18+
port: 0,
19+
})
20+
console.log("✅ Opencode server ready")
21+
22+
const sessions = new Map<string, { client: any; server: any; sessionId: string; channel: string; thread: string }>()
23+
24+
app.use(async ({ next, context }) => {
25+
console.log("📡 Raw Slack event:", JSON.stringify(context, null, 2))
26+
await next()
27+
})
28+
29+
app.message(async ({ message, say }) => {
30+
console.log("📨 Received message event:", JSON.stringify(message, null, 2))
31+
32+
if (message.subtype || !("text" in message) || !message.text) {
33+
console.log("⏭️ Skipping message - no text or has subtype")
34+
return
35+
}
36+
37+
console.log("✅ Processing message:", message.text)
38+
39+
const channel = message.channel
40+
const thread = (message as any).thread_ts || message.ts
41+
const sessionKey = `${channel}-${thread}`
42+
43+
let session = sessions.get(sessionKey)
44+
45+
if (!session) {
46+
console.log("🆕 Creating new opencode session...")
47+
const { client, server } = opencode
48+
49+
const createResult = await client.session.create({
50+
body: { title: `Slack thread ${thread}` },
51+
})
52+
53+
if (createResult.error) {
54+
console.error("❌ Failed to create session:", createResult.error)
55+
await say({ text: "Sorry, I had trouble creating a session. Please try again.", thread_ts: thread })
56+
return
57+
}
58+
59+
console.log("✅ Created opencode session:", createResult.data.id)
60+
session = { client, server, sessionId: createResult.data.id, channel, thread }
61+
sessions.set(sessionKey, session)
62+
63+
const shareResult = await client.session.share({ path: { id: createResult.data.id } })
64+
if (!shareResult.error && shareResult.data) {
65+
const sessionUrl = shareResult.data.share?.url!
66+
console.log("🔗 Session shared:", sessionUrl)
67+
await app.client.chat.postMessage({ channel, thread_ts: thread, text: sessionUrl })
68+
}
69+
}
70+
71+
console.log("📝 Sending to opencode:", message.text)
72+
const result = await session.client.session.prompt({
73+
path: { id: session.sessionId },
74+
body: { parts: [{ type: "text", text: message.text }] },
75+
})
76+
77+
console.log("📤 Opencode response:", JSON.stringify(result, null, 2))
78+
79+
if (result.error) {
80+
console.error("❌ Failed to send message:", result.error)
81+
await say({ text: "Sorry, I had trouble processing your message. Please try again.", thread_ts: thread })
82+
return
83+
}
84+
85+
const response = result.data
86+
const responseText =
87+
response.info?.content ||
88+
response.parts
89+
?.filter((p: any) => p.type === "text")
90+
.map((p: any) => p.text)
91+
.join("\n") ||
92+
"I received your message but didn't have a response."
93+
94+
console.log("💬 Sending response:", responseText)
95+
await say({ text: responseText, thread_ts: thread })
96+
})
97+
98+
app.command("/test", async ({ command, ack, say }) => {
99+
await ack()
100+
console.log("🧪 Test command received:", JSON.stringify(command, null, 2))
101+
await say("🤖 Bot is working! I can hear you loud and clear.")
102+
})
103+
104+
await app.start()
105+
console.log("⚡️ Slack bot is running!")

packages/slack/sst-env.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/// <reference types="../../../sst-env.d.ts" />
2+
3+
export {}

packages/slack/tsconfig.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"$schema": "https://json.schemastore.org/tsconfig",
3+
"extends": "@tsconfig/bun/tsconfig.json",
4+
"compilerOptions": {}
5+
}

0 commit comments

Comments
 (0)