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