File size: 4,601 Bytes
89a2873 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | 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 }>()
void (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) {
void 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!")
|