Vendor opencode source for docker build
This commit is contained in:
3
opencode/packages/slack/.env.example
Normal file
3
opencode/packages/slack/.env.example
Normal 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
opencode/packages/slack/.gitignore
vendored
Normal file
4
opencode/packages/slack/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
.env
|
||||
.DS_Store
|
||||
27
opencode/packages/slack/README.md
Normal file
27
opencode/packages/slack/README.md
Normal 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.
|
||||
19
opencode/packages/slack/package.json
Normal file
19
opencode/packages/slack/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "@opencode-ai/slack",
|
||||
"version": "1.1.53",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "bun run src/index.ts",
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@slack/bolt": "^3.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"@typescript/native-preview": "catalog:"
|
||||
}
|
||||
}
|
||||
145
opencode/packages/slack/src/index.ts
Normal file
145
opencode/packages/slack/src/index.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { App } from "@slack/bolt"
|
||||
import { createOpencode, type ToolPart } 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 }>()
|
||||
;(async () => {
|
||||
const events = await opencode.client.event.subscribe()
|
||||
for await (const event of events.stream) {
|
||||
if (event.type === "message.part.updated") {
|
||||
const part = event.properties.part
|
||||
if (part.type === "tool") {
|
||||
// Find the session for this tool update
|
||||
for (const [sessionKey, session] of sessions.entries()) {
|
||||
if (session.sessionId === part.sessionID) {
|
||||
handleToolUpdate(part, session.channel, session.thread)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
async function handleToolUpdate(part: ToolPart, channel: string, thread: string) {
|
||||
if (part.state.status !== "completed") return
|
||||
const toolMessage = `*${part.tool}* - ${part.state.title}`
|
||||
await app.client.chat
|
||||
.postMessage({
|
||||
channel,
|
||||
thread_ts: thread,
|
||||
text: toolMessage,
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Build response text
|
||||
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)
|
||||
|
||||
// Send main response (tool updates will come via live events)
|
||||
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!")
|
||||
9
opencode/packages/slack/sst-env.d.ts
vendored
Normal file
9
opencode/packages/slack/sst-env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/* This file is auto-generated by SST. Do not edit. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/* deno-fmt-ignore-file */
|
||||
|
||||
/// <reference path="../../sst-env.d.ts" />
|
||||
|
||||
import "sst"
|
||||
export {}
|
||||
8
opencode/packages/slack/tsconfig.json
Normal file
8
opencode/packages/slack/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@tsconfig/bun/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"noUncheckedIndexedAccess": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user