@neocode-ai/slack 1.1.1
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 +19 -0
- package/src/index.ts +145 -0
- package/tsconfig.json +8 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @neocode-ai/slack
|
|
2
|
+
|
|
3
|
+
Slack bot integration for neocode 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 neocode sessions for each thread.
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neocode-ai/slack",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "bun run src/index.ts",
|
|
8
|
+
"typecheck": "tsgo --noEmit"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@neocode-ai/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
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { App } from "@slack/bolt"
|
|
2
|
+
import { createNeocode, type ToolPart } from "@neocode-ai/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 neocode server...")
|
|
17
|
+
const neocode = await createNeocode({
|
|
18
|
+
port: 0,
|
|
19
|
+
})
|
|
20
|
+
console.log("โ
Neocode 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 neocode.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 neocode session...")
|
|
76
|
+
const { client, server } = neocode
|
|
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 neocode 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 neocode:", 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("๐ค Neocode 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!")
|