@jonsoc/slack 1.1.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +3 -0
- package/README.md +27 -0
- package/package.json +22 -0
- package/src/index.ts +145 -0
- package/sst-env.d.ts +9 -0
- package/tsconfig.json +8 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @jonsoc/slack
|
|
2
|
+
|
|
3
|
+
Slack bot integration for jonsoc 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 jonsoc sessions for each thread.
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jonsoc/slack",
|
|
3
|
+
"version": "1.1.34",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "bun run src/index.ts",
|
|
8
|
+
"typecheck": "tsgo --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@jonsoc/sdk": "workspace:*",
|
|
12
|
+
"@slack/bolt": "^3.17.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "catalog:",
|
|
16
|
+
"typescript": "catalog:",
|
|
17
|
+
"@typescript/native-preview": "catalog:"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { App } from "@slack/bolt"
|
|
2
|
+
import { createOpencode, type ToolPart } from "@jonsoc/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 jonsoc server...")
|
|
17
|
+
const jonsoc = 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
|
+
;(async () => {
|
|
24
|
+
const events = await jonsoc.client.event.subscribe()
|
|
25
|
+
for await (const event of events.stream) {
|
|
26
|
+
if (event.type === "message.part.updated") {
|
|
27
|
+
const part = event.properties.part
|
|
28
|
+
if (part.type === "tool") {
|
|
29
|
+
// Find the session for this tool update
|
|
30
|
+
for (const [sessionKey, session] of sessions.entries()) {
|
|
31
|
+
if (session.sessionId === part.sessionID) {
|
|
32
|
+
handleToolUpdate(part, session.channel, session.thread)
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
})()
|
|
40
|
+
|
|
41
|
+
async function handleToolUpdate(part: ToolPart, channel: string, thread: string) {
|
|
42
|
+
if (part.state.status !== "completed") return
|
|
43
|
+
const toolMessage = `*${part.tool}* - ${part.state.title}`
|
|
44
|
+
await app.client.chat
|
|
45
|
+
.postMessage({
|
|
46
|
+
channel,
|
|
47
|
+
thread_ts: thread,
|
|
48
|
+
text: toolMessage,
|
|
49
|
+
})
|
|
50
|
+
.catch(() => {})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
app.use(async ({ next, context }) => {
|
|
54
|
+
console.log("๐ก Raw Slack event:", JSON.stringify(context, null, 2))
|
|
55
|
+
await next()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
app.message(async ({ message, say }) => {
|
|
59
|
+
console.log("๐จ Received message event:", JSON.stringify(message, null, 2))
|
|
60
|
+
|
|
61
|
+
if (message.subtype || !("text" in message) || !message.text) {
|
|
62
|
+
console.log("โญ๏ธ Skipping message - no text or has subtype")
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log("โ
Processing message:", message.text)
|
|
67
|
+
|
|
68
|
+
const channel = message.channel
|
|
69
|
+
const thread = (message as any).thread_ts || message.ts
|
|
70
|
+
const sessionKey = `${channel}-${thread}`
|
|
71
|
+
|
|
72
|
+
let session = sessions.get(sessionKey)
|
|
73
|
+
|
|
74
|
+
if (!session) {
|
|
75
|
+
console.log("๐ Creating new jonsoc session...")
|
|
76
|
+
const { client, server } = jonsoc
|
|
77
|
+
|
|
78
|
+
const createResult = await client.session.create({
|
|
79
|
+
body: { title: `Slack thread ${thread}` },
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (createResult.error) {
|
|
83
|
+
console.error("โ Failed to create session:", createResult.error)
|
|
84
|
+
await say({
|
|
85
|
+
text: "Sorry, I had trouble creating a session. Please try again.",
|
|
86
|
+
thread_ts: thread,
|
|
87
|
+
})
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log("โ
Created jonsoc session:", createResult.data.id)
|
|
92
|
+
|
|
93
|
+
session = { client, server, sessionId: createResult.data.id, channel, thread }
|
|
94
|
+
sessions.set(sessionKey, session)
|
|
95
|
+
|
|
96
|
+
const shareResult = await client.session.share({ path: { id: createResult.data.id } })
|
|
97
|
+
if (!shareResult.error && shareResult.data) {
|
|
98
|
+
const sessionUrl = shareResult.data.share?.url!
|
|
99
|
+
console.log("๐ Session shared:", sessionUrl)
|
|
100
|
+
await app.client.chat.postMessage({ channel, thread_ts: thread, text: sessionUrl })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log("๐ Sending to jonsoc:", message.text)
|
|
105
|
+
const result = await session.client.session.prompt({
|
|
106
|
+
path: { id: session.sessionId },
|
|
107
|
+
body: { parts: [{ type: "text", text: message.text }] },
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
console.log("๐ค Opencode response:", JSON.stringify(result, null, 2))
|
|
111
|
+
|
|
112
|
+
if (result.error) {
|
|
113
|
+
console.error("โ Failed to send message:", result.error)
|
|
114
|
+
await say({
|
|
115
|
+
text: "Sorry, I had trouble processing your message. Please try again.",
|
|
116
|
+
thread_ts: thread,
|
|
117
|
+
})
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const response = result.data
|
|
122
|
+
|
|
123
|
+
// Build response text
|
|
124
|
+
const responseText =
|
|
125
|
+
response.info?.content ||
|
|
126
|
+
response.parts
|
|
127
|
+
?.filter((p: any) => p.type === "text")
|
|
128
|
+
.map((p: any) => p.text)
|
|
129
|
+
.join("\n") ||
|
|
130
|
+
"I received your message but didn't have a response."
|
|
131
|
+
|
|
132
|
+
console.log("๐ฌ Sending response:", responseText)
|
|
133
|
+
|
|
134
|
+
// Send main response (tool updates will come via live events)
|
|
135
|
+
await say({ text: responseText, thread_ts: thread })
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
app.command("/test", async ({ command, ack, say }) => {
|
|
139
|
+
await ack()
|
|
140
|
+
console.log("๐งช Test command received:", JSON.stringify(command, null, 2))
|
|
141
|
+
await say("๐ค Bot is working! I can hear you loud and clear.")
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
await app.start()
|
|
145
|
+
console.log("โก๏ธ Slack bot is running!")
|
package/sst-env.d.ts
ADDED