index.ts 4.5 KB

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