opensentinel 2.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/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/bot-KJ26BG56.js +15 -0
- package/dist/bot-KJ26BG56.js.map +1 -0
- package/dist/charts-MMXM6BWW.js +241 -0
- package/dist/charts-MMXM6BWW.js.map +1 -0
- package/dist/chunk-4LVWXUNC.js +1079 -0
- package/dist/chunk-4LVWXUNC.js.map +1 -0
- package/dist/chunk-4TG2IG5K.js +5249 -0
- package/dist/chunk-4TG2IG5K.js.map +1 -0
- package/dist/chunk-6DRDKB45.js +251 -0
- package/dist/chunk-6DRDKB45.js.map +1 -0
- package/dist/chunk-6SNHU3CY.js +123 -0
- package/dist/chunk-6SNHU3CY.js.map +1 -0
- package/dist/chunk-CI6Q63MM.js +1613 -0
- package/dist/chunk-CI6Q63MM.js.map +1 -0
- package/dist/chunk-CQ4JURG7.js +57 -0
- package/dist/chunk-CQ4JURG7.js.map +1 -0
- package/dist/chunk-F6QUZQGI.js +51 -0
- package/dist/chunk-F6QUZQGI.js.map +1 -0
- package/dist/chunk-GK3E2I7A.js +216 -0
- package/dist/chunk-GK3E2I7A.js.map +1 -0
- package/dist/chunk-GUBEEYDW.js +211 -0
- package/dist/chunk-GUBEEYDW.js.map +1 -0
- package/dist/chunk-GVJVEWHI.js +29 -0
- package/dist/chunk-GVJVEWHI.js.map +1 -0
- package/dist/chunk-HH2HBTQM.js +806 -0
- package/dist/chunk-HH2HBTQM.js.map +1 -0
- package/dist/chunk-JXUP2X7V.js +129 -0
- package/dist/chunk-JXUP2X7V.js.map +1 -0
- package/dist/chunk-KHNYJY2Z.js +178 -0
- package/dist/chunk-KHNYJY2Z.js.map +1 -0
- package/dist/chunk-L3F43VPB.js +652 -0
- package/dist/chunk-L3F43VPB.js.map +1 -0
- package/dist/chunk-L3PDU3XN.js +803 -0
- package/dist/chunk-L3PDU3XN.js.map +1 -0
- package/dist/chunk-NSBPE2FW.js +17 -0
- package/dist/chunk-NSBPE2FW.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/setup.d.ts +9 -0
- package/dist/commands/setup.js +374 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/start.d.ts +8 -0
- package/dist/commands/start.js +27 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +8 -0
- package/dist/commands/status.js +57 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +8 -0
- package/dist/commands/stop.js +37 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/utils.d.ts +50 -0
- package/dist/commands/utils.js +36 -0
- package/dist/commands/utils.js.map +1 -0
- package/dist/discord-ZOJFTVTB.js +49 -0
- package/dist/discord-ZOJFTVTB.js.map +1 -0
- package/dist/imessage-JFRB6EJ7.js +14 -0
- package/dist/imessage-JFRB6EJ7.js.map +1 -0
- package/dist/lib.d.ts +855 -0
- package/dist/lib.js +274 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp-LS7Q3Z5W.js +30 -0
- package/dist/mcp-LS7Q3Z5W.js.map +1 -0
- package/dist/scheduler-EZ7CZMCS.js +42 -0
- package/dist/scheduler-EZ7CZMCS.js.map +1 -0
- package/dist/signal-T3MCSULM.js +14 -0
- package/dist/signal-T3MCSULM.js.map +1 -0
- package/dist/slack-N2M4FHAJ.js +54 -0
- package/dist/slack-N2M4FHAJ.js.map +1 -0
- package/dist/src-K7GASHRH.js +430 -0
- package/dist/src-K7GASHRH.js.map +1 -0
- package/dist/tools-24GZHYRF.js +16 -0
- package/dist/tools-24GZHYRF.js.map +1 -0
- package/dist/whatsapp-VCRUPAO5.js +14 -0
- package/dist/whatsapp-VCRUPAO5.js.map +1 -0
- package/drizzle/0000_chilly_shinobi_shaw.sql +75 -0
- package/drizzle/0001_freezing_shape.sql +274 -0
- package/drizzle/meta/0000_snapshot.json +529 -0
- package/drizzle/meta/0001_snapshot.json +2576 -0
- package/drizzle/meta/_journal.json +20 -0
- package/package.json +98 -0
|
@@ -0,0 +1,803 @@
|
|
|
1
|
+
import {
|
|
2
|
+
transcribeAudio
|
|
3
|
+
} from "./chunk-GVJVEWHI.js";
|
|
4
|
+
import {
|
|
5
|
+
scheduleReminder
|
|
6
|
+
} from "./chunk-4LVWXUNC.js";
|
|
7
|
+
import {
|
|
8
|
+
chatWithTools
|
|
9
|
+
} from "./chunk-CI6Q63MM.js";
|
|
10
|
+
|
|
11
|
+
// src/inputs/slack/index.ts
|
|
12
|
+
import { createRequire } from "module";
|
|
13
|
+
|
|
14
|
+
// src/inputs/slack/commands.ts
|
|
15
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
16
|
+
var MAX_HISTORY = 20;
|
|
17
|
+
function getSession(userId) {
|
|
18
|
+
if (!sessions.has(userId)) {
|
|
19
|
+
sessions.set(userId, []);
|
|
20
|
+
}
|
|
21
|
+
return sessions.get(userId);
|
|
22
|
+
}
|
|
23
|
+
function addToSession(userId, message) {
|
|
24
|
+
const session = getSession(userId);
|
|
25
|
+
session.push(message);
|
|
26
|
+
if (session.length > MAX_HISTORY) {
|
|
27
|
+
sessions.set(userId, session.slice(-MAX_HISTORY));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function clearSession(userId) {
|
|
31
|
+
sessions.set(userId, []);
|
|
32
|
+
}
|
|
33
|
+
var askCommand = {
|
|
34
|
+
command: "/opensentinel-ask",
|
|
35
|
+
description: "Ask OpenSentinel a question",
|
|
36
|
+
usage: "/opensentinel-ask <question>",
|
|
37
|
+
async handler({ command, ack, respond, client }) {
|
|
38
|
+
await ack();
|
|
39
|
+
const question = command.text.trim();
|
|
40
|
+
const userId = command.user_id;
|
|
41
|
+
if (!question) {
|
|
42
|
+
await respond({
|
|
43
|
+
response_type: "ephemeral",
|
|
44
|
+
text: "Please provide a question. Usage: `/opensentinel-ask <question>`"
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
addToSession(userId, { role: "user", content: question });
|
|
50
|
+
const response = await chatWithTools(
|
|
51
|
+
getSession(userId),
|
|
52
|
+
`slack:${userId}`
|
|
53
|
+
);
|
|
54
|
+
addToSession(userId, { role: "assistant", content: response.content });
|
|
55
|
+
let finalResponse = response.content;
|
|
56
|
+
if (response.toolsUsed && response.toolsUsed.length > 0) {
|
|
57
|
+
const toolList = [...new Set(response.toolsUsed)].join(", ");
|
|
58
|
+
finalResponse = `_Used: ${toolList}_
|
|
59
|
+
|
|
60
|
+
${response.content}`;
|
|
61
|
+
}
|
|
62
|
+
await respond({
|
|
63
|
+
response_type: "in_channel",
|
|
64
|
+
text: finalResponse,
|
|
65
|
+
mrkdwn: true
|
|
66
|
+
});
|
|
67
|
+
console.log(
|
|
68
|
+
`[Slack] Processed /opensentinel-ask from ${userId}. Tokens: ${response.inputTokens}/${response.outputTokens}`
|
|
69
|
+
);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error("[Slack] Error processing /opensentinel-ask:", error);
|
|
72
|
+
await respond({
|
|
73
|
+
response_type: "ephemeral",
|
|
74
|
+
text: "Sorry, I encountered an error processing your question. Please try again."
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var chatCommand = {
|
|
80
|
+
command: "/opensentinel-chat",
|
|
81
|
+
description: "Continue a conversation with OpenSentinel",
|
|
82
|
+
usage: "/opensentinel-chat <message>",
|
|
83
|
+
async handler({ command, ack, respond }) {
|
|
84
|
+
await ack();
|
|
85
|
+
const message = command.text.trim();
|
|
86
|
+
const userId = command.user_id;
|
|
87
|
+
if (!message) {
|
|
88
|
+
await respond({
|
|
89
|
+
response_type: "ephemeral",
|
|
90
|
+
text: "Please provide a message. Usage: `/opensentinel-chat <message>`"
|
|
91
|
+
});
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
addToSession(userId, { role: "user", content: message });
|
|
96
|
+
const response = await chatWithTools(
|
|
97
|
+
getSession(userId),
|
|
98
|
+
`slack:${userId}`
|
|
99
|
+
);
|
|
100
|
+
addToSession(userId, { role: "assistant", content: response.content });
|
|
101
|
+
let finalResponse = response.content;
|
|
102
|
+
if (response.toolsUsed && response.toolsUsed.length > 0) {
|
|
103
|
+
const toolList = [...new Set(response.toolsUsed)].join(", ");
|
|
104
|
+
finalResponse = `_Used: ${toolList}_
|
|
105
|
+
|
|
106
|
+
${response.content}`;
|
|
107
|
+
}
|
|
108
|
+
await respond({
|
|
109
|
+
response_type: "in_channel",
|
|
110
|
+
text: finalResponse,
|
|
111
|
+
mrkdwn: true
|
|
112
|
+
});
|
|
113
|
+
console.log(
|
|
114
|
+
`[Slack] Processed /opensentinel-chat from ${userId}. Tokens: ${response.inputTokens}/${response.outputTokens}`
|
|
115
|
+
);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error("[Slack] Error processing /opensentinel-chat:", error);
|
|
118
|
+
await respond({
|
|
119
|
+
response_type: "ephemeral",
|
|
120
|
+
text: "Sorry, I encountered an error processing your message. Please try again."
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var clearCommand = {
|
|
126
|
+
command: "/opensentinel-clear",
|
|
127
|
+
description: "Clear your conversation history with OpenSentinel",
|
|
128
|
+
usage: "/opensentinel-clear",
|
|
129
|
+
async handler({ command, ack, respond }) {
|
|
130
|
+
await ack();
|
|
131
|
+
clearSession(command.user_id);
|
|
132
|
+
await respond({
|
|
133
|
+
response_type: "ephemeral",
|
|
134
|
+
text: "Conversation history cleared."
|
|
135
|
+
});
|
|
136
|
+
console.log(`[Slack] Cleared history for ${command.user_id}`);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
var remindCommand = {
|
|
140
|
+
command: "/opensentinel-remind",
|
|
141
|
+
description: "Set a reminder",
|
|
142
|
+
usage: "/opensentinel-remind <time><s/m/h> <message>",
|
|
143
|
+
async handler({ command, ack, respond }) {
|
|
144
|
+
await ack();
|
|
145
|
+
const text = command.text.trim();
|
|
146
|
+
const match = text.match(/^(\d+)(s|m|h)\s+(.+)$/i);
|
|
147
|
+
if (!match) {
|
|
148
|
+
await respond({
|
|
149
|
+
response_type: "ephemeral",
|
|
150
|
+
text: "Invalid format. Use: `/opensentinel-remind <number><s/m/h> <message>`\n\nExamples:\n\u2022 `/opensentinel-remind 5m Check the oven`\n\u2022 `/opensentinel-remind 1h Call mom`\n\u2022 `/opensentinel-remind 30s Test reminder`"
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const [, amount, unit, message] = match;
|
|
155
|
+
const multipliers = {
|
|
156
|
+
s: 1e3,
|
|
157
|
+
m: 60 * 1e3,
|
|
158
|
+
h: 60 * 60 * 1e3
|
|
159
|
+
};
|
|
160
|
+
const delayMs = parseInt(amount) * multipliers[unit.toLowerCase()];
|
|
161
|
+
try {
|
|
162
|
+
await scheduleReminder(
|
|
163
|
+
message,
|
|
164
|
+
delayMs,
|
|
165
|
+
`slack:${command.channel_id}:${command.user_id}`
|
|
166
|
+
);
|
|
167
|
+
const timeStr = unit === "s" ? "seconds" : unit === "m" ? "minutes" : "hours";
|
|
168
|
+
await respond({
|
|
169
|
+
response_type: "ephemeral",
|
|
170
|
+
text: `Reminder set for ${amount} ${timeStr}: "${message}"`
|
|
171
|
+
});
|
|
172
|
+
console.log(
|
|
173
|
+
`[Slack] Reminder set by ${command.user_id}: ${amount}${unit} - "${message}"`
|
|
174
|
+
);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error("[Slack] Error setting reminder:", error);
|
|
177
|
+
await respond({
|
|
178
|
+
response_type: "ephemeral",
|
|
179
|
+
text: "Sorry, I couldn't set the reminder. Please try again."
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var statusCommand = {
|
|
185
|
+
command: "/opensentinel-status",
|
|
186
|
+
description: "Check OpenSentinel status and capabilities",
|
|
187
|
+
usage: "/opensentinel-status",
|
|
188
|
+
async handler({ command, ack, respond }) {
|
|
189
|
+
await ack();
|
|
190
|
+
const session = getSession(command.user_id);
|
|
191
|
+
const historyCount = session.length;
|
|
192
|
+
await respond({
|
|
193
|
+
response_type: "ephemeral",
|
|
194
|
+
text: `*OpenSentinel Status*
|
|
195
|
+
|
|
196
|
+
Bot: Online
|
|
197
|
+
Your conversation history: ${historyCount} messages
|
|
198
|
+
|
|
199
|
+
*Capabilities:*
|
|
200
|
+
\u2022 Chat and answer questions using Claude AI
|
|
201
|
+
\u2022 Execute shell commands
|
|
202
|
+
\u2022 Read and write files
|
|
203
|
+
\u2022 Search the web
|
|
204
|
+
\u2022 Remember important information
|
|
205
|
+
\u2022 Set reminders
|
|
206
|
+
|
|
207
|
+
Use \`/opensentinel-help\` for available commands.`
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
var helpCommand = {
|
|
212
|
+
command: "/opensentinel-help",
|
|
213
|
+
description: "Show available OpenSentinel commands",
|
|
214
|
+
usage: "/opensentinel-help",
|
|
215
|
+
async handler({ ack, respond }) {
|
|
216
|
+
await ack();
|
|
217
|
+
await respond({
|
|
218
|
+
response_type: "ephemeral",
|
|
219
|
+
text: "*OpenSentinel Commands*\n\n`/opensentinel-ask <question>` - Ask a single question\n`/opensentinel-chat <message>` - Continue a conversation\n`/opensentinel-clear` - Clear your conversation history\n`/opensentinel-remind <time> <message>` - Set a reminder\n`/opensentinel-status` - Check bot status\n`/opensentinel-help` - Show this help message\n\n*Tips:*\n\u2022 Use `/opensentinel-chat` for multi-turn conversations with context\n\u2022 Use `/opensentinel-ask` for quick one-off questions\n\u2022 Mention @OpenSentinel in any channel to chat directly\n\u2022 DM the bot for private conversations"
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
var mainCommand = {
|
|
224
|
+
command: "/opensentinel",
|
|
225
|
+
description: "OpenSentinel AI assistant",
|
|
226
|
+
usage: "/opensentinel <ask|chat|clear|remind|status|help> [args]",
|
|
227
|
+
async handler({ command, ack, respond }) {
|
|
228
|
+
await ack();
|
|
229
|
+
const [subcommand, ...args] = command.text.trim().split(/\s+/);
|
|
230
|
+
const argsText = args.join(" ");
|
|
231
|
+
switch (subcommand?.toLowerCase()) {
|
|
232
|
+
case "ask":
|
|
233
|
+
if (!argsText) {
|
|
234
|
+
await respond({
|
|
235
|
+
response_type: "ephemeral",
|
|
236
|
+
text: "Please provide a question. Usage: `/opensentinel ask <question>`"
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
command.text = argsText;
|
|
241
|
+
await askCommand.handler({ command, ack, respond });
|
|
242
|
+
break;
|
|
243
|
+
case "chat":
|
|
244
|
+
if (!argsText) {
|
|
245
|
+
await respond({
|
|
246
|
+
response_type: "ephemeral",
|
|
247
|
+
text: "Please provide a message. Usage: `/opensentinel chat <message>`"
|
|
248
|
+
});
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
command.text = argsText;
|
|
252
|
+
await chatCommand.handler({ command, ack, respond });
|
|
253
|
+
break;
|
|
254
|
+
case "clear":
|
|
255
|
+
await clearCommand.handler({ command, ack, respond });
|
|
256
|
+
break;
|
|
257
|
+
case "remind":
|
|
258
|
+
command.text = argsText;
|
|
259
|
+
await remindCommand.handler({ command, ack, respond });
|
|
260
|
+
break;
|
|
261
|
+
case "status":
|
|
262
|
+
await statusCommand.handler({ command, ack, respond });
|
|
263
|
+
break;
|
|
264
|
+
case "help":
|
|
265
|
+
default:
|
|
266
|
+
await respond({
|
|
267
|
+
response_type: "ephemeral",
|
|
268
|
+
text: "*OpenSentinel Commands*\n\n`/opensentinel ask <question>` - Ask a single question\n`/opensentinel chat <message>` - Continue a conversation\n`/opensentinel clear` - Clear your conversation history\n`/opensentinel remind <time> <message>` - Set a reminder\n`/opensentinel status` - Check bot status\n`/opensentinel help` - Show this help message\n\n*Or use individual commands:*\n`/opensentinel-ask`, `/opensentinel-chat`, `/opensentinel-clear`, `/opensentinel-remind`, `/opensentinel-status`, `/opensentinel-help`"
|
|
269
|
+
});
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var slashCommands = [
|
|
275
|
+
mainCommand,
|
|
276
|
+
askCommand,
|
|
277
|
+
chatCommand,
|
|
278
|
+
clearCommand,
|
|
279
|
+
remindCommand,
|
|
280
|
+
statusCommand,
|
|
281
|
+
helpCommand
|
|
282
|
+
];
|
|
283
|
+
function getCommand(name) {
|
|
284
|
+
return slashCommands.find((cmd) => cmd.command === name);
|
|
285
|
+
}
|
|
286
|
+
function getCommandNames() {
|
|
287
|
+
return slashCommands.map((cmd) => cmd.command);
|
|
288
|
+
}
|
|
289
|
+
function splitMessage(text, maxLength = 3e3) {
|
|
290
|
+
const chunks = [];
|
|
291
|
+
let remaining = text;
|
|
292
|
+
while (remaining.length > 0) {
|
|
293
|
+
if (remaining.length <= maxLength) {
|
|
294
|
+
chunks.push(remaining);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
let breakPoint = remaining.lastIndexOf("\n", maxLength);
|
|
298
|
+
if (breakPoint === -1 || breakPoint < maxLength / 2) {
|
|
299
|
+
breakPoint = remaining.lastIndexOf(" ", maxLength);
|
|
300
|
+
}
|
|
301
|
+
if (breakPoint === -1 || breakPoint < maxLength / 2) {
|
|
302
|
+
breakPoint = maxLength;
|
|
303
|
+
}
|
|
304
|
+
chunks.push(remaining.slice(0, breakPoint));
|
|
305
|
+
remaining = remaining.slice(breakPoint).trim();
|
|
306
|
+
}
|
|
307
|
+
return chunks;
|
|
308
|
+
}
|
|
309
|
+
function formatAsBlocks(text) {
|
|
310
|
+
return [
|
|
311
|
+
{
|
|
312
|
+
type: "section",
|
|
313
|
+
text: {
|
|
314
|
+
type: "mrkdwn",
|
|
315
|
+
text
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
function createErrorBlocks(message) {
|
|
321
|
+
return [
|
|
322
|
+
{
|
|
323
|
+
type: "section",
|
|
324
|
+
text: {
|
|
325
|
+
type: "mrkdwn",
|
|
326
|
+
text: `:warning: *Error*
|
|
327
|
+
${message}`
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
function createSuccessBlocks(message) {
|
|
333
|
+
return [
|
|
334
|
+
{
|
|
335
|
+
type: "section",
|
|
336
|
+
text: {
|
|
337
|
+
type: "mrkdwn",
|
|
338
|
+
text: `:white_check_mark: ${message}`
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/inputs/slack/index.ts
|
|
345
|
+
var require2 = createRequire(import.meta.url);
|
|
346
|
+
var { App, ExpressReceiver } = require2("@slack/bolt");
|
|
347
|
+
var { WebClient } = require2("@slack/web-api");
|
|
348
|
+
var SlackBot = class {
|
|
349
|
+
app;
|
|
350
|
+
config;
|
|
351
|
+
client;
|
|
352
|
+
receiver;
|
|
353
|
+
isRunning = false;
|
|
354
|
+
constructor(config) {
|
|
355
|
+
this.config = {
|
|
356
|
+
allowDMs: true,
|
|
357
|
+
allowMentions: true,
|
|
358
|
+
allowThreadReplies: true,
|
|
359
|
+
...config
|
|
360
|
+
};
|
|
361
|
+
if (!config.socketMode) {
|
|
362
|
+
this.receiver = new ExpressReceiver({
|
|
363
|
+
signingSecret: config.signingSecret
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
this.app = new App({
|
|
367
|
+
token: config.token,
|
|
368
|
+
signingSecret: config.signingSecret,
|
|
369
|
+
socketMode: config.socketMode,
|
|
370
|
+
appToken: config.appToken,
|
|
371
|
+
receiver: this.receiver
|
|
372
|
+
});
|
|
373
|
+
this.client = new WebClient(config.token);
|
|
374
|
+
this.setupEventHandlers();
|
|
375
|
+
this.setupSlashCommands();
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Set up Slack event handlers
|
|
379
|
+
*/
|
|
380
|
+
setupEventHandlers() {
|
|
381
|
+
if (this.config.allowMentions) {
|
|
382
|
+
this.app.event("app_mention", async (args) => {
|
|
383
|
+
await this.handleAppMention(args);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
if (this.config.allowDMs) {
|
|
387
|
+
this.app.message(async (args) => {
|
|
388
|
+
await this.handleMessage(args);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
this.app.event("file_shared", async (args) => {
|
|
392
|
+
await this.handleFileShared(args);
|
|
393
|
+
});
|
|
394
|
+
this.app.error(async (error) => {
|
|
395
|
+
console.error("[Slack] App error:", error);
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Set up slash command handlers
|
|
400
|
+
*/
|
|
401
|
+
setupSlashCommands() {
|
|
402
|
+
for (const cmd of slashCommands) {
|
|
403
|
+
this.app.command(cmd.command, async (args) => {
|
|
404
|
+
if (!this.isUserAuthorized(args.command.user_id, args.command.channel_id)) {
|
|
405
|
+
await args.ack();
|
|
406
|
+
await args.respond({
|
|
407
|
+
response_type: "ephemeral",
|
|
408
|
+
text: "You are not authorized to use this bot."
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
await cmd.handler(args);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Handle app mentions
|
|
418
|
+
*/
|
|
419
|
+
async handleAppMention(args) {
|
|
420
|
+
const { event, say, client } = args;
|
|
421
|
+
if (!this.isUserAuthorized(event.user, event.channel)) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const text = event.text.replace(/<@[A-Z0-9]+>/gi, "").trim();
|
|
425
|
+
if (!text) {
|
|
426
|
+
await say({
|
|
427
|
+
text: "Hi! How can I help you? Mention me with a question or use `/sentinel help` for available commands.",
|
|
428
|
+
thread_ts: event.thread_ts || event.ts
|
|
429
|
+
});
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const userId = event.user;
|
|
433
|
+
const sessionKey = event.thread_ts ? `${userId}:thread:${event.thread_ts}` : userId;
|
|
434
|
+
try {
|
|
435
|
+
await client.reactions.add({
|
|
436
|
+
channel: event.channel,
|
|
437
|
+
timestamp: event.ts,
|
|
438
|
+
name: "hourglass_flowing_sand"
|
|
439
|
+
});
|
|
440
|
+
addToSession(sessionKey, { role: "user", content: text });
|
|
441
|
+
const response = await chatWithTools(
|
|
442
|
+
getSession(sessionKey),
|
|
443
|
+
`slack:${userId}`
|
|
444
|
+
);
|
|
445
|
+
addToSession(sessionKey, { role: "assistant", content: response.content });
|
|
446
|
+
let finalResponse = response.content;
|
|
447
|
+
if (response.toolsUsed && response.toolsUsed.length > 0) {
|
|
448
|
+
const toolList = [...new Set(response.toolsUsed)].join(", ");
|
|
449
|
+
finalResponse = `_Used: ${toolList}_
|
|
450
|
+
|
|
451
|
+
${response.content}`;
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
await client.reactions.remove({
|
|
455
|
+
channel: event.channel,
|
|
456
|
+
timestamp: event.ts,
|
|
457
|
+
name: "hourglass_flowing_sand"
|
|
458
|
+
});
|
|
459
|
+
} catch {
|
|
460
|
+
}
|
|
461
|
+
if (finalResponse.length > 3e3) {
|
|
462
|
+
const chunks = splitMessage(finalResponse, 3e3);
|
|
463
|
+
for (const chunk of chunks) {
|
|
464
|
+
await say({
|
|
465
|
+
text: chunk,
|
|
466
|
+
thread_ts: event.thread_ts || event.ts,
|
|
467
|
+
mrkdwn: true
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
await say({
|
|
472
|
+
text: finalResponse,
|
|
473
|
+
thread_ts: event.thread_ts || event.ts,
|
|
474
|
+
mrkdwn: true
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
console.log(
|
|
478
|
+
`[Slack] Processed mention from ${userId}. Tokens: ${response.inputTokens}/${response.outputTokens}` + (response.toolsUsed ? ` Tools: ${response.toolsUsed.join(", ")}` : "")
|
|
479
|
+
);
|
|
480
|
+
} catch (error) {
|
|
481
|
+
console.error("[Slack] Error processing mention:", error);
|
|
482
|
+
try {
|
|
483
|
+
await client.reactions.remove({
|
|
484
|
+
channel: event.channel,
|
|
485
|
+
timestamp: event.ts,
|
|
486
|
+
name: "hourglass_flowing_sand"
|
|
487
|
+
});
|
|
488
|
+
await client.reactions.add({
|
|
489
|
+
channel: event.channel,
|
|
490
|
+
timestamp: event.ts,
|
|
491
|
+
name: "x"
|
|
492
|
+
});
|
|
493
|
+
} catch {
|
|
494
|
+
}
|
|
495
|
+
await say({
|
|
496
|
+
text: "Sorry, I encountered an error processing your message. Please try again.",
|
|
497
|
+
thread_ts: event.thread_ts || event.ts
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Handle direct messages
|
|
503
|
+
*/
|
|
504
|
+
async handleMessage(args) {
|
|
505
|
+
const { event, say, client, message } = args;
|
|
506
|
+
const genericMessage = message;
|
|
507
|
+
if (genericMessage.subtype) return;
|
|
508
|
+
if ("bot_id" in event) return;
|
|
509
|
+
const channelType = event.channel_type;
|
|
510
|
+
if (channelType !== "im" && channelType !== "mpim") return;
|
|
511
|
+
const userId = message.user;
|
|
512
|
+
if (!userId) return;
|
|
513
|
+
if (!this.isUserAuthorized(userId, event.channel)) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
const text = message.text || "";
|
|
517
|
+
if (!text.trim()) return;
|
|
518
|
+
const threadedMessage = message;
|
|
519
|
+
const sessionKey = threadedMessage.thread_ts ? `${userId}:thread:${threadedMessage.thread_ts}` : userId;
|
|
520
|
+
try {
|
|
521
|
+
await client.reactions.add({
|
|
522
|
+
channel: event.channel,
|
|
523
|
+
timestamp: message.ts,
|
|
524
|
+
name: "hourglass_flowing_sand"
|
|
525
|
+
});
|
|
526
|
+
addToSession(sessionKey, { role: "user", content: text });
|
|
527
|
+
const response = await chatWithTools(
|
|
528
|
+
getSession(sessionKey),
|
|
529
|
+
`slack:${userId}`
|
|
530
|
+
);
|
|
531
|
+
addToSession(sessionKey, { role: "assistant", content: response.content });
|
|
532
|
+
let finalResponse = response.content;
|
|
533
|
+
if (response.toolsUsed && response.toolsUsed.length > 0) {
|
|
534
|
+
const toolList = [...new Set(response.toolsUsed)].join(", ");
|
|
535
|
+
finalResponse = `_Used: ${toolList}_
|
|
536
|
+
|
|
537
|
+
${response.content}`;
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
await client.reactions.remove({
|
|
541
|
+
channel: event.channel,
|
|
542
|
+
timestamp: message.ts,
|
|
543
|
+
name: "hourglass_flowing_sand"
|
|
544
|
+
});
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
const replyTs = this.config.allowThreadReplies && threadedMessage.thread_ts ? threadedMessage.thread_ts : void 0;
|
|
548
|
+
if (finalResponse.length > 3e3) {
|
|
549
|
+
const chunks = splitMessage(finalResponse, 3e3);
|
|
550
|
+
for (const chunk of chunks) {
|
|
551
|
+
await say({
|
|
552
|
+
text: chunk,
|
|
553
|
+
thread_ts: replyTs,
|
|
554
|
+
mrkdwn: true
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
} else {
|
|
558
|
+
await say({
|
|
559
|
+
text: finalResponse,
|
|
560
|
+
thread_ts: replyTs,
|
|
561
|
+
mrkdwn: true
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
console.log(
|
|
565
|
+
`[Slack] Processed DM from ${userId}. Tokens: ${response.inputTokens}/${response.outputTokens}` + (response.toolsUsed ? ` Tools: ${response.toolsUsed.join(", ")}` : "")
|
|
566
|
+
);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
console.error("[Slack] Error processing message:", error);
|
|
569
|
+
try {
|
|
570
|
+
await client.reactions.remove({
|
|
571
|
+
channel: event.channel,
|
|
572
|
+
timestamp: message.ts,
|
|
573
|
+
name: "hourglass_flowing_sand"
|
|
574
|
+
});
|
|
575
|
+
await client.reactions.add({
|
|
576
|
+
channel: event.channel,
|
|
577
|
+
timestamp: message.ts,
|
|
578
|
+
name: "x"
|
|
579
|
+
});
|
|
580
|
+
} catch {
|
|
581
|
+
}
|
|
582
|
+
await say({
|
|
583
|
+
text: "Sorry, I encountered an error processing your message. Please try again."
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Handle file shared events
|
|
589
|
+
*/
|
|
590
|
+
async handleFileShared(args) {
|
|
591
|
+
const { event, client } = args;
|
|
592
|
+
try {
|
|
593
|
+
const fileInfo = await client.files.info({ file: event.file_id });
|
|
594
|
+
const file = fileInfo.file;
|
|
595
|
+
if (!file) return;
|
|
596
|
+
const mimetype = file.mimetype || "";
|
|
597
|
+
if (mimetype.startsWith("audio/")) {
|
|
598
|
+
if (file.url_private_download) {
|
|
599
|
+
const response = await fetch(file.url_private_download, {
|
|
600
|
+
headers: {
|
|
601
|
+
Authorization: `Bearer ${this.config.token}`
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
if (response.ok) {
|
|
605
|
+
const audioBuffer = await response.arrayBuffer();
|
|
606
|
+
const transcription = await transcribeAudio(Buffer.from(audioBuffer));
|
|
607
|
+
if (transcription) {
|
|
608
|
+
const channelId = file.channels && file.channels[0] || file.ims && file.ims[0];
|
|
609
|
+
if (channelId) {
|
|
610
|
+
await client.chat.postMessage({
|
|
611
|
+
channel: channelId,
|
|
612
|
+
text: `Audio transcription: "${transcription}"`,
|
|
613
|
+
mrkdwn: true
|
|
614
|
+
});
|
|
615
|
+
console.log(
|
|
616
|
+
`[Slack] Transcribed audio file: ${file.name}`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
} catch (error) {
|
|
624
|
+
console.error("[Slack] Error processing file:", error);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Check if a user is authorized to use the bot
|
|
629
|
+
*/
|
|
630
|
+
isUserAuthorized(userId, channelId) {
|
|
631
|
+
if (!this.config.allowedUserIds?.length && !this.config.allowedChannelIds?.length) {
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
if (this.config.allowedUserIds?.includes(userId)) {
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
if (this.config.allowedChannelIds?.includes(channelId)) {
|
|
638
|
+
return true;
|
|
639
|
+
}
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Start the Slack bot
|
|
644
|
+
*/
|
|
645
|
+
async start() {
|
|
646
|
+
const port = this.config.port || 3e3;
|
|
647
|
+
console.log("[Slack] Starting bot...");
|
|
648
|
+
await this.app.start(port);
|
|
649
|
+
this.isRunning = true;
|
|
650
|
+
if (this.config.socketMode) {
|
|
651
|
+
console.log("[Slack] Bot started in Socket Mode");
|
|
652
|
+
} else {
|
|
653
|
+
console.log(`[Slack] Bot started on port ${port}`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Stop the Slack bot
|
|
658
|
+
*/
|
|
659
|
+
async stop() {
|
|
660
|
+
console.log("[Slack] Stopping bot...");
|
|
661
|
+
await this.app.stop();
|
|
662
|
+
this.isRunning = false;
|
|
663
|
+
console.log("[Slack] Bot stopped");
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Get the Slack app instance
|
|
667
|
+
*/
|
|
668
|
+
getApp() {
|
|
669
|
+
return this.app;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Get the Slack Web API client
|
|
673
|
+
*/
|
|
674
|
+
getClient() {
|
|
675
|
+
return this.client;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Check if bot is running
|
|
679
|
+
*/
|
|
680
|
+
running() {
|
|
681
|
+
return this.isRunning;
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Send a message to a channel
|
|
685
|
+
*/
|
|
686
|
+
async sendMessage(channelId, text, options) {
|
|
687
|
+
return this.client.chat.postMessage({
|
|
688
|
+
channel: channelId,
|
|
689
|
+
text,
|
|
690
|
+
thread_ts: options?.threadTs,
|
|
691
|
+
mrkdwn: options?.mrkdwn ?? true,
|
|
692
|
+
blocks: options?.blocks
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Send a message with blocks
|
|
697
|
+
*/
|
|
698
|
+
async sendBlocks(channelId, blocks, text, threadTs) {
|
|
699
|
+
return this.client.chat.postMessage({
|
|
700
|
+
channel: channelId,
|
|
701
|
+
text: text || "Message",
|
|
702
|
+
blocks,
|
|
703
|
+
thread_ts: threadTs
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Send a file to a channel
|
|
708
|
+
*/
|
|
709
|
+
async sendFile(channelId, content, filename, options) {
|
|
710
|
+
await this.client.files.uploadV2({
|
|
711
|
+
channel_id: channelId,
|
|
712
|
+
file: content,
|
|
713
|
+
filename,
|
|
714
|
+
title: options?.title,
|
|
715
|
+
initial_comment: options?.initialComment,
|
|
716
|
+
thread_ts: options?.threadTs
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Reply to a thread
|
|
721
|
+
*/
|
|
722
|
+
async replyToThread(channelId, threadTs, text, mrkdwn) {
|
|
723
|
+
return this.client.chat.postMessage({
|
|
724
|
+
channel: channelId,
|
|
725
|
+
text,
|
|
726
|
+
thread_ts: threadTs,
|
|
727
|
+
mrkdwn: mrkdwn ?? true
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Add a reaction to a message
|
|
732
|
+
*/
|
|
733
|
+
async addReaction(channelId, timestamp, emoji) {
|
|
734
|
+
await this.client.reactions.add({
|
|
735
|
+
channel: channelId,
|
|
736
|
+
timestamp,
|
|
737
|
+
name: emoji
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Remove a reaction from a message
|
|
742
|
+
*/
|
|
743
|
+
async removeReaction(channelId, timestamp, emoji) {
|
|
744
|
+
await this.client.reactions.remove({
|
|
745
|
+
channel: channelId,
|
|
746
|
+
timestamp,
|
|
747
|
+
name: emoji
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Get user info
|
|
752
|
+
*/
|
|
753
|
+
async getUserInfo(userId) {
|
|
754
|
+
const result = await this.client.users.info({ user: userId });
|
|
755
|
+
return result.user;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Get channel info
|
|
759
|
+
*/
|
|
760
|
+
async getChannelInfo(channelId) {
|
|
761
|
+
const result = await this.client.conversations.info({ channel: channelId });
|
|
762
|
+
return result.channel;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Get the Express receiver (for adding custom routes)
|
|
766
|
+
*/
|
|
767
|
+
getReceiver() {
|
|
768
|
+
return this.receiver;
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
function createSlackBot(config) {
|
|
772
|
+
return new SlackBot(config);
|
|
773
|
+
}
|
|
774
|
+
var slack_default = {
|
|
775
|
+
createSlackBot,
|
|
776
|
+
SlackBot,
|
|
777
|
+
slashCommands
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
export {
|
|
781
|
+
sessions,
|
|
782
|
+
getSession,
|
|
783
|
+
addToSession,
|
|
784
|
+
clearSession,
|
|
785
|
+
askCommand,
|
|
786
|
+
chatCommand,
|
|
787
|
+
clearCommand,
|
|
788
|
+
remindCommand,
|
|
789
|
+
statusCommand,
|
|
790
|
+
helpCommand,
|
|
791
|
+
mainCommand,
|
|
792
|
+
slashCommands,
|
|
793
|
+
getCommand,
|
|
794
|
+
getCommandNames,
|
|
795
|
+
splitMessage,
|
|
796
|
+
formatAsBlocks,
|
|
797
|
+
createErrorBlocks,
|
|
798
|
+
createSuccessBlocks,
|
|
799
|
+
SlackBot,
|
|
800
|
+
createSlackBot,
|
|
801
|
+
slack_default
|
|
802
|
+
};
|
|
803
|
+
//# sourceMappingURL=chunk-L3PDU3XN.js.map
|