@meetopenbot/slack 0.0.7 → 0.0.8

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/dist/agent.js ADDED
@@ -0,0 +1,21 @@
1
+ import { runMcpTurn } from "@meetopenbot/plugin-sdk";
2
+ const SYSTEM_PROMPT = `You are the OpenBot Slack specialist agent.
3
+ Use Slack MCP tools to post messages, list channels, read history, and perform other workspace actions when needed.
4
+ Summarize results clearly in plain text for the user. Be concise and friendly.`;
5
+ export async function runSlackAgent(args) {
6
+ return runMcpTurn({
7
+ mcp: {
8
+ command: "npx",
9
+ args: ["-y", "@modelcontextprotocol/server-slack"],
10
+ env: () => ({
11
+ SLACK_BOT_TOKEN: args.botToken,
12
+ SLACK_TEAM_ID: args.teamId,
13
+ }),
14
+ },
15
+ apiKey: args.botToken,
16
+ model: args.model ?? "openai/gpt-4o-mini",
17
+ system: SYSTEM_PROMPT,
18
+ prompt: args.prompt,
19
+ maxSteps: 10,
20
+ });
21
+ }
package/dist/config.js CHANGED
@@ -1,125 +1,86 @@
1
- import { resolveAuthMode } from "./cloud-mode.js";
2
- import { shouldUseCreditsAuth } from "./credits-auth.js";
1
+ import { lookupSecret, modelConfigField, trimmedString, } from "@meetopenbot/plugin-sdk";
3
2
  export const DEFAULT_OPENBOT_CHANNEL_ID = "general";
4
- /** Parse `C0123:engineering,C0456:support` into a lookup index. */
3
+ export const ROUTING_AGENT_ID = "system";
4
+ export const SLACK_USER_DISPLAY_NAME = "Slack user";
5
+ const modelField = await modelConfigField({
6
+ providers: ["openai"],
7
+ override: true,
8
+ defaultValue: "openai/gpt-4o-mini",
9
+ });
10
+ export const pluginConfigSchema = {
11
+ type: "object",
12
+ properties: {
13
+ model: {
14
+ ...modelField,
15
+ description: "Outbound — OpenAI model for direct Slack agent invocations",
16
+ },
17
+ channelMappings: {
18
+ type: "string",
19
+ description: "Inbound — webhook routing. Comma-separated slackChannelId:openbotChannelId pairs (e.g. C01234567:engineering,C89ABCDEF:support). Unmapped Slack channels use general.",
20
+ },
21
+ },
22
+ };
5
23
  export function buildSlackChannelMappingIndex(config) {
6
24
  const index = new Map();
7
- const raw = typeof config.channelMappings === "string"
8
- ? config.channelMappings.trim()
9
- : "";
25
+ const raw = trimmedString(config.channelMappings) ?? "";
10
26
  if (!raw)
11
27
  return index;
12
28
  for (const segment of raw.split(",")) {
13
29
  const trimmed = segment.trim();
14
- if (!trimmed)
15
- continue;
16
30
  const colon = trimmed.indexOf(":");
17
31
  if (colon <= 0 || colon === trimmed.length - 1)
18
32
  continue;
19
33
  const slackChannelId = trimmed.slice(0, colon).trim();
20
34
  const channelId = trimmed.slice(colon + 1).trim();
21
- if (!slackChannelId || !channelId)
22
- continue;
23
- index.set(slackChannelId, channelId);
35
+ if (slackChannelId && channelId)
36
+ index.set(slackChannelId, channelId);
24
37
  }
25
38
  return index;
26
39
  }
27
- /** Map a Slack channel id from webhook ingress to an OpenBot channel id. */
28
40
  export function resolveOpenBotChannelId(slackChannelId, config) {
29
41
  const normalized = slackChannelId.trim();
30
42
  if (!normalized)
31
43
  return DEFAULT_OPENBOT_CHANNEL_ID;
32
- const mapped = buildSlackChannelMappingIndex(config).get(normalized);
33
- return mapped ?? DEFAULT_OPENBOT_CHANNEL_ID;
34
- }
35
- function variableValue(variables, key) {
36
- const entry = variables[key];
37
- if (typeof entry === "string")
38
- return entry || undefined;
39
- return entry?.value || undefined;
44
+ return (buildSlackChannelMappingIndex(config).get(normalized) ??
45
+ DEFAULT_OPENBOT_CHANNEL_ID);
40
46
  }
41
47
  export function readSlackConfig(config) {
42
48
  return {
43
- signingSecret: typeof config.signingSecret === "string" && config.signingSecret.trim()
44
- ? config.signingSecret.trim()
45
- : undefined,
46
- botToken: typeof config.botToken === "string" && config.botToken.trim()
47
- ? config.botToken.trim()
48
- : undefined,
49
- teamId: typeof config.teamId === "string" && config.teamId.trim()
50
- ? config.teamId.trim()
51
- : undefined,
52
- channelMappings: typeof config.channelMappings === "string" &&
53
- config.channelMappings.trim()
54
- ? config.channelMappings.trim()
55
- : undefined,
56
- authMode: resolveAuthMode(config),
57
- model: typeof config.model === "string" && config.model.trim()
58
- ? config.model.trim()
59
- : undefined,
49
+ channelMappings: trimmedString(config.channelMappings),
50
+ model: trimmedString(config.model),
60
51
  };
61
52
  }
62
- export function formatMissingCredentials(missing, authMode) {
63
- const lines = [
64
- "Slack agent setup is incomplete. Configure the following in plugin config, workspace settings, or environment variables:",
65
- ];
53
+ export function resolveSlackSigningSecret() {
54
+ return lookupSecret({ envKeys: ["SLACK_SIGNING_SECRET"] });
55
+ }
56
+ export function formatMissingCredentials(missing) {
57
+ const lines = ["Slack agent setup is incomplete. Set these workspace variables:"];
66
58
  if (missing.includes("botToken")) {
67
- lines.push("- `botToken` / `SLACK_BOT_TOKEN` — Slack bot OAuth token (`xoxb-…`)");
59
+ lines.push("- `SLACK_BOT_TOKEN` — Slack bot OAuth token (`xoxb-…`)");
68
60
  }
69
61
  if (missing.includes("teamId")) {
70
- lines.push("- `teamId` / `SLACK_TEAM_ID` — workspace team id (`T…`)");
71
- }
72
- if (missing.includes("openaiApiKey")) {
73
- if (authMode === "credits") {
74
- lines.push("- OpenAI API key is required in BYOK mode — add `OPENAI_API_KEY` under workspace settings, or switch `authMode` to `credits` on cloud");
75
- }
76
- else {
77
- lines.push("- `OPENAI_API_KEY` — OpenAI API key for the agent loop (BYOK mode), or switch `authMode` to `credits` on cloud");
78
- }
62
+ lines.push("- `SLACK_TEAM_ID` — workspace team id (`T…`)");
79
63
  }
80
64
  return lines.join("\n");
81
65
  }
82
- export async function resolveSlackCredentials(config, storage, options) {
83
- const authMode = config.authMode ?? resolveAuthMode({});
84
- const useCredits = shouldUseCreditsAuth({ authMode });
85
- const requireOpenAi = options?.requireOpenAi ?? !useCredits;
86
- const variables = (await storage.getVariables().catch(() => ({})));
87
- const resolve = (configKey, envKey) => {
88
- const fromConfig = config[configKey];
89
- if (typeof fromConfig === "string" && fromConfig.trim()) {
90
- return fromConfig.trim();
91
- }
92
- if (process.env[envKey]?.trim())
93
- return process.env[envKey].trim();
94
- return variableValue(variables, envKey)?.trim();
95
- };
96
- const signingSecret = (typeof config.signingSecret === "string" && config.signingSecret.trim()) ||
97
- process.env.SLACK_SIGNING_SECRET?.trim() ||
98
- variableValue(variables, "SLACK_SIGNING_SECRET") ||
99
- "";
100
- const botToken = resolve("botToken", "SLACK_BOT_TOKEN");
101
- const teamId = resolve("teamId", "SLACK_TEAM_ID");
102
- const openaiApiKey = process.env.OPENAI_API_KEY?.trim() ||
103
- variableValue(variables, "OPENAI_API_KEY");
104
- const model = resolve("model", "OPENAI_MODEL") ?? "openai/gpt-4o-mini";
66
+ export function resolveSlackCredentials(config) {
67
+ const signingSecret = lookupSecret({ envKeys: ["SLACK_SIGNING_SECRET"] }) ?? "";
68
+ const botToken = lookupSecret({ envKeys: ["SLACK_BOT_TOKEN"] });
69
+ const teamId = lookupSecret({ envKeys: ["SLACK_TEAM_ID"] });
70
+ const model = trimmedString(config.model) ?? "openai/gpt-4o-mini";
105
71
  const missing = [];
106
72
  if (!botToken)
107
73
  missing.push("botToken");
108
74
  if (!teamId)
109
75
  missing.push("teamId");
110
- if (requireOpenAi && !openaiApiKey)
111
- missing.push("openaiApiKey");
112
- if (missing.length > 0) {
76
+ if (missing.length > 0)
113
77
  return { ok: false, missing };
114
- }
115
78
  return {
116
79
  ok: true,
117
80
  credentials: {
118
81
  signingSecret,
119
82
  botToken: botToken,
120
83
  teamId: teamId,
121
- authMode,
122
- openaiApiKey: openaiApiKey || undefined,
123
84
  model,
124
85
  },
125
86
  };
package/dist/index.js CHANGED
@@ -1,451 +1,34 @@
1
- import { randomUUID } from "node:crypto";
2
- import { agentOutput, definePlugin, decodeWebhookRawBody, getWebhookHeader, shouldHandleInvoke, webhookHttpResponse, } from "@meetopenbot/plugin-sdk";
3
- import { isCloudMode } from "./cloud-mode.js";
4
- import { CREDITS_NOT_CONFIGURED_MESSAGE, creditsErrorMessage, resolveCreditsAuthConfig, } from "./credits-auth.js";
5
- import { DEFAULT_OPENBOT_CHANNEL_ID, formatMissingCredentials, readSlackConfig, resolveOpenBotChannelId, resolveSlackCredentials, } from "./config.js";
6
- import { resolveModelConfigField } from "./model-registry.js";
7
- import { buildPromptWithSlackContext } from "./slack-context.js";
8
- import { postSlackMessage } from "./slack-post.js";
9
- import { runSlackAgent } from "./slack-agent.js";
10
- import { verifySlackSignature } from "./slack-verify.js";
11
- const ROUTING_AGENT_ID = "system";
12
- const SLACK_USER_DISPLAY_NAME = "Slack user";
13
- function isSlackWebhookIngress(meta) {
14
- return meta?.source === "slack";
15
- }
16
- const seenSlackEvents = new Map();
17
- const SLACK_EVENT_DEDUP_MS = 5 * 60 * 1000;
18
- function purgeStaleSlackEvents(now) {
19
- for (const [id, at] of seenSlackEvents) {
20
- if (now - at > SLACK_EVENT_DEDUP_MS)
21
- seenSlackEvents.delete(id);
22
- }
23
- }
24
- function markSlackEventSeen(dedupKey) {
25
- const now = Date.now();
26
- purgeStaleSlackEvents(now);
27
- if (seenSlackEvents.has(dedupKey))
28
- return true;
29
- seenSlackEvents.set(dedupKey, now);
30
- return false;
31
- }
32
- function slackEventDedupKey(payload, slackEvent) {
33
- if (payload.event_id)
34
- return payload.event_id;
35
- return `${slackEvent.channel}:${slackEvent.ts}`;
36
- }
37
- /** MVP: channel mentions via app_mention; DMs via message.im only. */
38
- function shouldProcessSlackEvent(slackEvent) {
39
- if (slackEvent.bot_id)
40
- return false;
41
- if (slackEvent.subtype)
42
- return false;
43
- if (slackEvent.type === "app_mention")
44
- return true;
45
- if (slackEvent.type === "message" && slackEvent.channel_type === "im") {
46
- return true;
47
- }
48
- return false;
49
- }
50
- function normalizeSlackText(text) {
51
- return text.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
52
- }
53
- function getSlackThreadMeta(meta) {
54
- const slack = meta?.slack;
55
- if (!slack || typeof slack !== "object")
56
- return undefined;
57
- return slack;
58
- }
59
- async function* bridgeSystemAgentRun(args) {
60
- const runId = `slack_${randomUUID()}`;
61
- const slackMeta = getSlackThreadMeta(args.meta);
62
- const eventQueue = [];
63
- let resolveNext = null;
64
- let isFinished = false;
65
- const runPromise = args.host
66
- .runAgent({
67
- runId,
68
- agentId: ROUTING_AGENT_ID,
69
- publicBaseUrl: args.publicBaseUrl,
70
- // Runtime's webhook ingress handles persistence.
71
- persistEvents: false,
72
- event: {
73
- type: "agent:invoke",
74
- data: {
75
- agentId: ROUTING_AGENT_ID,
76
- role: "user",
77
- content: args.userMessage,
78
- },
79
- meta: {
80
- channelId: args.channelId,
81
- threadId: args.threadId,
82
- source: "slack",
83
- slack: slackMeta,
84
- userName: SLACK_USER_DISPLAY_NAME,
85
- ...(typeof args.meta?.userId === "string"
86
- ? { userId: args.meta.userId }
87
- : {}),
88
- },
89
- },
90
- onEvent: async (outEvent) => {
91
- if (outEvent.type === "agent:output" &&
92
- !outEvent.meta?.parentToolCallId) {
93
- const output = outEvent;
94
- eventQueue.push({
95
- ...output,
96
- meta: {
97
- ...output.meta,
98
- agentId: ROUTING_AGENT_ID,
99
- threadId: args.threadId,
100
- source: "slack",
101
- slack: slackMeta,
102
- },
103
- });
104
- }
105
- if (resolveNext) {
106
- resolveNext();
107
- resolveNext = null;
108
- }
109
- },
110
- })
111
- .catch((error) => {
112
- const message = error instanceof Error ? error.message : String(error);
113
- eventQueue.push({
114
- type: "agent:output",
115
- data: { content: `Slack agent error: ${message}` },
116
- meta: {
117
- agentId: ROUTING_AGENT_ID,
118
- threadId: args.threadId,
119
- source: "slack",
120
- slack: slackMeta,
121
- },
122
- });
123
- })
124
- .finally(() => {
125
- isFinished = true;
126
- if (resolveNext) {
127
- resolveNext();
128
- resolveNext = null;
129
- }
130
- });
131
- while (!isFinished || eventQueue.length > 0) {
132
- if (eventQueue.length === 0) {
133
- await new Promise((resolve) => {
134
- resolveNext = resolve;
135
- });
136
- }
137
- while (eventQueue.length > 0) {
138
- yield eventQueue.shift();
139
- }
140
- }
141
- await runPromise;
142
- }
143
- const modelField = await resolveModelConfigField();
144
- const slackPluginConfigSchema = {
145
- type: "object",
146
- properties: {
147
- ...(isCloudMode()
148
- ? {
149
- authMode: {
150
- type: "string",
151
- description: "Outbound — Credits uses your workspace credit balance via OpenBot. BYOK uses `OPENAI_API_KEY` from workspace settings.",
152
- enum: ["credits", "byok"],
153
- default: "credits",
154
- },
155
- }
156
- : {}),
157
- model: {
158
- ...modelField,
159
- description: "Outbound — OpenAI model for direct Slack agent invocations",
160
- },
161
- botToken: {
162
- type: "string",
163
- description: "Outbound — Slack bot token (OAuth & Permissions → Bot User OAuth Token)",
164
- format: "password",
165
- },
166
- teamId: {
167
- type: "string",
168
- description: "Outbound — Slack workspace team id (starts with T)",
169
- },
170
- signingSecret: {
171
- type: "string",
172
- description: "Inbound — Slack app signing secret (Basic Information → App Credentials)",
173
- format: "password",
174
- },
175
- channelMappings: {
176
- type: "string",
177
- description: "Inbound — webhook routing. Comma-separated slackChannelId:openbotChannelId pairs (e.g. C01234567:engineering,C89ABCDEF:support). Unmapped Slack channels use general.",
178
- },
179
- },
180
- required: ["signingSecret"],
181
- };
182
- export default definePlugin({
1
+ import { definePlugin, } from "@meetopenbot/plugin-sdk";
2
+ import { pluginConfigSchema, readSlackConfig } from "./config.js";
3
+ import { handleSlackInvoke, handleSlackOutbound } from "./invoke.js";
4
+ import { handleSlackWebhook } from "./webhook.js";
5
+ export const plugin = definePlugin({
183
6
  name: "Slack",
184
7
  description: "Slack Events API ingress, local agent runtime, and system delegation for webhooks",
185
- configSchema: slackPluginConfigSchema,
8
+ configSchema: pluginConfigSchema,
186
9
  factory: (pluginContext) => (builder) => {
187
- const { agentId, config, storage } = pluginContext;
10
+ const { agentId, config, host, publicBaseUrl } = pluginContext;
188
11
  const slackConfig = readSlackConfig(config);
189
- const host = pluginContext.host;
190
- const publicBaseUrl = pluginContext.publicBaseUrl ?? "";
191
12
  builder.on("action:webhook", async function* (event, ctx) {
192
- const webhook = event;
193
- if (webhook.data.provider !== "slack")
194
- return;
195
- const auth = await resolveSlackCredentials(slackConfig, storage);
196
- const signingSecret = auth.ok
197
- ? auth.credentials.signingSecret
198
- : String(config.signingSecret ?? "").trim();
199
- const rawBody = decodeWebhookRawBody(webhook);
200
- let payload;
201
- try {
202
- payload = JSON.parse(rawBody.toString("utf8"));
203
- }
204
- catch {
205
- yield webhookHttpResponse({
206
- status: 400,
207
- body: { error: "invalid json" },
208
- });
209
- return;
210
- }
211
- if (payload.type === "url_verification") {
212
- yield webhookHttpResponse({
213
- status: 200,
214
- body: { challenge: payload.challenge },
215
- });
216
- return;
217
- }
218
- const timestamp = getWebhookHeader(webhook, "x-slack-request-timestamp");
219
- const signature = getWebhookHeader(webhook, "x-slack-signature");
220
- if (!verifySlackSignature(signingSecret, rawBody, timestamp, signature)) {
221
- yield webhookHttpResponse({
222
- status: 401,
223
- body: { error: "invalid signature" },
224
- });
225
- return;
226
- }
227
- const slackEvent = payload.type === "event_callback" ? payload.event : undefined;
228
- if (!slackEvent) {
229
- yield webhookHttpResponse({ status: 200, body: {} });
230
- return;
231
- }
232
- if (!shouldProcessSlackEvent(slackEvent)) {
233
- yield webhookHttpResponse({ status: 200, body: {} });
234
- return;
235
- }
236
- const dedupKey = slackEventDedupKey(payload, slackEvent);
237
- if (markSlackEventSeen(dedupKey)) {
238
- yield webhookHttpResponse({ status: 200, body: {} });
239
- return;
240
- }
241
- const text = normalizeSlackText(slackEvent.text ?? "");
242
- if (!text) {
243
- yield webhookHttpResponse({ status: 200, body: {} });
244
- return;
245
- }
246
- const threadId = slackEvent.thread_ts ?? slackEvent.ts;
247
- if (!threadId) {
248
- yield webhookHttpResponse({ status: 200, body: {} });
249
- return;
250
- }
251
- ctx.state.threadId = threadId;
252
- const slackMeta = {
253
- teamId: payload.team_id,
254
- channel: slackEvent.channel,
255
- user: slackEvent.user,
256
- ts: slackEvent.ts,
257
- threadTs: slackEvent.thread_ts,
258
- };
259
- const openBotChannelId = slackEvent.channel
260
- ? resolveOpenBotChannelId(slackEvent.channel, slackConfig)
261
- : DEFAULT_OPENBOT_CHANNEL_ID;
262
- ctx.state.channelId = openBotChannelId;
263
- yield webhookHttpResponse({ status: 200, body: {} });
264
- yield {
265
- type: "agent:invoke",
266
- data: {
267
- agentId: ROUTING_AGENT_ID,
268
- role: "user",
269
- content: text,
270
- },
271
- meta: {
272
- channelId: openBotChannelId,
273
- threadId,
274
- source: "slack",
275
- slack: slackMeta,
276
- userName: SLACK_USER_DISPLAY_NAME,
277
- ...(slackEvent.user ? { userId: slackEvent.user } : {}),
278
- },
279
- };
13
+ yield* handleSlackWebhook({
14
+ event: event,
15
+ ctx,
16
+ config: slackConfig,
17
+ });
280
18
  });
281
19
  builder.on("agent:invoke", async function* (event, ctx) {
282
- const fromWebhook = isSlackWebhookIngress(event.meta);
283
- if (fromWebhook) {
284
- if (!shouldHandleInvoke(event, ROUTING_AGENT_ID))
285
- return;
286
- }
287
- else {
288
- if (!shouldHandleInvoke(event, agentId))
289
- return;
290
- }
291
- if (event.meta?.threadId) {
292
- ctx.state.threadId = event.meta.threadId;
293
- }
294
- const threadId = event.meta?.threadId ?? ctx.state.threadId;
295
- const userMessage = (event.data.content ?? "").trim();
296
- if (!userMessage || !threadId)
297
- return;
298
- if (fromWebhook) {
299
- const auth = await resolveSlackCredentials(slackConfig, storage, {
300
- requireOpenAi: false,
301
- });
302
- if (!auth.ok) {
303
- yield {
304
- type: "agent:output",
305
- data: {
306
- content: formatMissingCredentials(auth.missing, slackConfig.authMode),
307
- },
308
- meta: {
309
- agentId: ROUTING_AGENT_ID,
310
- threadId,
311
- source: "slack",
312
- slack: getSlackThreadMeta(event.meta),
313
- },
314
- };
315
- return;
316
- }
317
- if (!host?.runAgent) {
318
- yield {
319
- type: "agent:output",
320
- data: {
321
- content: "Slack plugin requires host.runAgent to delegate to the system agent.",
322
- },
323
- meta: {
324
- agentId: ROUTING_AGENT_ID,
325
- threadId,
326
- source: "slack",
327
- slack: getSlackThreadMeta(event.meta),
328
- },
329
- };
330
- return;
331
- }
332
- const slackMeta = getSlackThreadMeta(event.meta);
333
- const channelId = slackMeta?.channel
334
- ? resolveOpenBotChannelId(slackMeta.channel, slackConfig)
335
- : ctx.state.channelId;
336
- if (!channelId)
337
- return;
338
- let prompt = userMessage;
339
- if (slackMeta?.channel && slackMeta.ts) {
340
- try {
341
- prompt = await buildPromptWithSlackContext({
342
- token: auth.credentials.botToken,
343
- channel: slackMeta.channel,
344
- messageTs: slackMeta.ts,
345
- threadTs: slackMeta.threadTs,
346
- userMessage,
347
- });
348
- }
349
- catch {
350
- // Fall back to the raw mention if Slack history is unavailable.
351
- }
352
- }
353
- try {
354
- yield* bridgeSystemAgentRun({
355
- host,
356
- channelId,
357
- threadId,
358
- userMessage: prompt,
359
- meta: event.meta,
360
- publicBaseUrl,
361
- });
362
- }
363
- catch (error) {
364
- const message = error instanceof Error ? error.message : String(error);
365
- yield {
366
- type: "agent:output",
367
- data: { content: `Slack agent error: ${message}` },
368
- meta: {
369
- agentId: ROUTING_AGENT_ID,
370
- threadId,
371
- source: "slack",
372
- slack: getSlackThreadMeta(event.meta),
373
- },
374
- };
375
- }
376
- return;
377
- }
378
- const auth = await resolveSlackCredentials(slackConfig, storage);
379
- if (!auth.ok) {
380
- yield agentOutput({
381
- agentId,
382
- content: formatMissingCredentials(auth.missing, slackConfig.authMode),
383
- threadId,
384
- meta: event.meta,
385
- });
386
- return;
387
- }
388
- if (auth.credentials.authMode === "credits" &&
389
- !resolveCreditsAuthConfig()) {
390
- yield agentOutput({
391
- agentId,
392
- content: CREDITS_NOT_CONFIGURED_MESSAGE,
393
- threadId,
394
- meta: event.meta,
395
- });
396
- return;
397
- }
398
- try {
399
- const reply = await runSlackAgent({
400
- prompt: userMessage,
401
- authMode: auth.credentials.authMode,
402
- openaiApiKey: auth.credentials.openaiApiKey,
403
- botToken: auth.credentials.botToken,
404
- teamId: auth.credentials.teamId,
405
- model: auth.credentials.model,
406
- });
407
- yield agentOutput({
408
- agentId,
409
- content: reply,
410
- threadId,
411
- meta: event.meta,
412
- });
413
- }
414
- catch (error) {
415
- const message = error instanceof Error ? error.message : String(error);
416
- const creditsMessage = auth.credentials.authMode === "credits"
417
- ? creditsErrorMessage(message)
418
- : undefined;
419
- yield agentOutput({
420
- agentId,
421
- content: creditsMessage ?? `Slack agent error: ${message}`,
422
- threadId,
423
- meta: event.meta,
424
- });
425
- }
20
+ yield* handleSlackInvoke({
21
+ event,
22
+ ctx,
23
+ agentId,
24
+ config: slackConfig,
25
+ host,
26
+ publicBaseUrl: publicBaseUrl ?? "",
27
+ });
426
28
  });
427
29
  builder.on("agent:output", async function* (event) {
428
- if (event.meta?.source !== "slack")
429
- return;
430
- if (event.meta?.agentId !== ROUTING_AGENT_ID)
431
- return;
432
- const slack = getSlackThreadMeta(event.meta);
433
- if (!slack?.channel)
434
- return;
435
- const auth = await resolveSlackCredentials(slackConfig, storage);
436
- if (!auth.ok)
437
- return;
438
- try {
439
- await postSlackMessage({
440
- token: auth.credentials.botToken,
441
- channel: slack.channel,
442
- text: event.data.content,
443
- threadTs: slack.threadTs ?? slack.ts,
444
- });
445
- }
446
- catch {
447
- // Avoid failing the bus run if Slack delivery fails.
448
- }
30
+ yield* handleSlackOutbound({ event, config: slackConfig });
449
31
  });
450
32
  },
451
33
  });
34
+ export default plugin;
package/dist/invoke.js ADDED
@@ -0,0 +1,246 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { agentOutput, creditsErrorMessage, hasLlmAuth, llmAuthNotConfiguredMessage, shouldHandleInvoke, } from "@meetopenbot/plugin-sdk";
3
+ import { runSlackAgent } from "./agent.js";
4
+ import { formatMissingCredentials, resolveOpenBotChannelId, resolveSlackCredentials, ROUTING_AGENT_ID, SLACK_USER_DISPLAY_NAME, } from "./config.js";
5
+ import { buildPromptWithSlackContext } from "./context.js";
6
+ import { postSlackMessage } from "./post.js";
7
+ import { getSlackThreadMeta, isSlackWebhookIngress } from "./webhook.js";
8
+ async function* bridgeSystemAgentRun(args) {
9
+ const runId = `slack_${randomUUID()}`;
10
+ const slackMeta = getSlackThreadMeta(args.meta);
11
+ const eventQueue = [];
12
+ let resolveNext = null;
13
+ let isFinished = false;
14
+ const runPromise = args.host
15
+ .runAgent({
16
+ runId,
17
+ agentId: ROUTING_AGENT_ID,
18
+ publicBaseUrl: args.publicBaseUrl,
19
+ persistEvents: false,
20
+ event: {
21
+ type: "agent:invoke",
22
+ data: {
23
+ agentId: ROUTING_AGENT_ID,
24
+ role: "user",
25
+ content: args.userMessage,
26
+ },
27
+ meta: {
28
+ channelId: args.channelId,
29
+ threadId: args.threadId,
30
+ source: "slack",
31
+ slack: slackMeta,
32
+ userName: SLACK_USER_DISPLAY_NAME,
33
+ ...(typeof args.meta?.userId === "string" ? { userId: args.meta.userId } : {}),
34
+ },
35
+ },
36
+ onEvent: async (outEvent) => {
37
+ if (outEvent.type === "agent:output" && !outEvent.meta?.parentToolCallId) {
38
+ const output = outEvent;
39
+ eventQueue.push({
40
+ ...output,
41
+ meta: {
42
+ ...output.meta,
43
+ agentId: ROUTING_AGENT_ID,
44
+ threadId: args.threadId,
45
+ source: "slack",
46
+ slack: slackMeta,
47
+ },
48
+ });
49
+ }
50
+ if (resolveNext) {
51
+ resolveNext();
52
+ resolveNext = null;
53
+ }
54
+ },
55
+ })
56
+ .catch((error) => {
57
+ const message = error instanceof Error ? error.message : String(error);
58
+ eventQueue.push({
59
+ type: "agent:output",
60
+ data: { content: `Slack agent error: ${message}` },
61
+ meta: {
62
+ agentId: ROUTING_AGENT_ID,
63
+ threadId: args.threadId,
64
+ source: "slack",
65
+ slack: slackMeta,
66
+ },
67
+ });
68
+ })
69
+ .finally(() => {
70
+ isFinished = true;
71
+ if (resolveNext) {
72
+ resolveNext();
73
+ resolveNext = null;
74
+ }
75
+ });
76
+ while (!isFinished || eventQueue.length > 0) {
77
+ if (eventQueue.length === 0) {
78
+ await new Promise((resolve) => {
79
+ resolveNext = resolve;
80
+ });
81
+ }
82
+ while (eventQueue.length > 0) {
83
+ yield eventQueue.shift();
84
+ }
85
+ }
86
+ await runPromise;
87
+ }
88
+ export async function* handleSlackInvoke(args) {
89
+ const fromWebhook = isSlackWebhookIngress(args.event.meta);
90
+ if (fromWebhook) {
91
+ if (!shouldHandleInvoke(args.event, ROUTING_AGENT_ID))
92
+ return;
93
+ }
94
+ else if (!shouldHandleInvoke(args.event, args.agentId)) {
95
+ return;
96
+ }
97
+ if (args.event.meta?.threadId) {
98
+ args.ctx.state.threadId = args.event.meta.threadId;
99
+ }
100
+ const threadId = args.event.meta?.threadId ?? args.ctx.state.threadId;
101
+ const userMessage = (args.event.data.content ?? "").trim();
102
+ if (!userMessage || !threadId)
103
+ return;
104
+ if (fromWebhook) {
105
+ const auth = resolveSlackCredentials(args.config);
106
+ if (!auth.ok) {
107
+ yield {
108
+ type: "agent:output",
109
+ data: { content: formatMissingCredentials(auth.missing) },
110
+ meta: {
111
+ agentId: ROUTING_AGENT_ID,
112
+ threadId,
113
+ source: "slack",
114
+ slack: getSlackThreadMeta(args.event.meta),
115
+ },
116
+ };
117
+ return;
118
+ }
119
+ if (!args.host?.runAgent) {
120
+ yield {
121
+ type: "agent:output",
122
+ data: {
123
+ content: "Slack plugin requires host.runAgent to delegate to the system agent.",
124
+ },
125
+ meta: {
126
+ agentId: ROUTING_AGENT_ID,
127
+ threadId,
128
+ source: "slack",
129
+ slack: getSlackThreadMeta(args.event.meta),
130
+ },
131
+ };
132
+ return;
133
+ }
134
+ const slackMeta = getSlackThreadMeta(args.event.meta);
135
+ const channelId = slackMeta?.channel
136
+ ? resolveOpenBotChannelId(slackMeta.channel, args.config)
137
+ : args.ctx.state.channelId;
138
+ if (!channelId)
139
+ return;
140
+ let prompt = userMessage;
141
+ if (slackMeta?.channel && slackMeta.ts) {
142
+ try {
143
+ prompt = await buildPromptWithSlackContext({
144
+ token: auth.credentials.botToken,
145
+ channel: slackMeta.channel,
146
+ messageTs: slackMeta.ts,
147
+ threadTs: slackMeta.threadTs,
148
+ userMessage,
149
+ });
150
+ }
151
+ catch {
152
+ // Fall back to the raw mention if Slack history is unavailable.
153
+ }
154
+ }
155
+ try {
156
+ yield* bridgeSystemAgentRun({
157
+ host: args.host,
158
+ channelId,
159
+ threadId,
160
+ userMessage: prompt,
161
+ meta: args.event.meta,
162
+ publicBaseUrl: args.publicBaseUrl,
163
+ });
164
+ }
165
+ catch (error) {
166
+ const message = error instanceof Error ? error.message : String(error);
167
+ yield {
168
+ type: "agent:output",
169
+ data: { content: `Slack agent error: ${message}` },
170
+ meta: {
171
+ agentId: ROUTING_AGENT_ID,
172
+ threadId,
173
+ source: "slack",
174
+ slack: getSlackThreadMeta(args.event.meta),
175
+ },
176
+ };
177
+ }
178
+ return;
179
+ }
180
+ const auth = resolveSlackCredentials(args.config);
181
+ if (!auth.ok) {
182
+ yield agentOutput({
183
+ agentId: args.agentId,
184
+ content: formatMissingCredentials(auth.missing),
185
+ threadId,
186
+ meta: args.event.meta,
187
+ });
188
+ return;
189
+ }
190
+ if (!hasLlmAuth("openai")) {
191
+ yield agentOutput({
192
+ agentId: args.agentId,
193
+ content: llmAuthNotConfiguredMessage(),
194
+ threadId,
195
+ meta: args.event.meta,
196
+ });
197
+ return;
198
+ }
199
+ try {
200
+ const reply = await runSlackAgent({
201
+ prompt: userMessage,
202
+ botToken: auth.credentials.botToken,
203
+ teamId: auth.credentials.teamId,
204
+ model: auth.credentials.model,
205
+ });
206
+ yield agentOutput({
207
+ agentId: args.agentId,
208
+ content: reply,
209
+ threadId,
210
+ meta: args.event.meta,
211
+ });
212
+ }
213
+ catch (error) {
214
+ const message = error instanceof Error ? error.message : String(error);
215
+ yield agentOutput({
216
+ agentId: args.agentId,
217
+ content: creditsErrorMessage(message, { agentName: "Slack" }) ??
218
+ `Slack agent error: ${message}`,
219
+ threadId,
220
+ meta: args.event.meta,
221
+ });
222
+ }
223
+ }
224
+ export async function* handleSlackOutbound(args) {
225
+ if (args.event.meta?.source !== "slack")
226
+ return;
227
+ if (args.event.meta?.agentId !== ROUTING_AGENT_ID)
228
+ return;
229
+ const slack = getSlackThreadMeta(args.event.meta);
230
+ if (!slack?.channel)
231
+ return;
232
+ const auth = resolveSlackCredentials(args.config);
233
+ if (!auth.ok)
234
+ return;
235
+ try {
236
+ await postSlackMessage({
237
+ token: auth.credentials.botToken,
238
+ channel: slack.channel,
239
+ text: args.event.data.content,
240
+ threadTs: slack.threadTs ?? slack.ts,
241
+ });
242
+ }
243
+ catch {
244
+ // Avoid failing the bus run if Slack delivery fails.
245
+ }
246
+ }
@@ -0,0 +1,112 @@
1
+ import { decodeWebhookRawBody, getWebhookHeader, webhookHttpResponse, } from "@meetopenbot/plugin-sdk";
2
+ import { DEFAULT_OPENBOT_CHANNEL_ID, ROUTING_AGENT_ID, SLACK_USER_DISPLAY_NAME, resolveOpenBotChannelId, resolveSlackSigningSecret, } from "./config.js";
3
+ import { verifySlackSignature } from "./verify.js";
4
+ const seenSlackEvents = new Map();
5
+ const SLACK_EVENT_DEDUP_MS = 5 * 60 * 1000;
6
+ function purgeStaleSlackEvents(now) {
7
+ for (const [id, at] of seenSlackEvents) {
8
+ if (now - at > SLACK_EVENT_DEDUP_MS)
9
+ seenSlackEvents.delete(id);
10
+ }
11
+ }
12
+ function markSlackEventSeen(dedupKey) {
13
+ const now = Date.now();
14
+ purgeStaleSlackEvents(now);
15
+ if (seenSlackEvents.has(dedupKey))
16
+ return true;
17
+ seenSlackEvents.set(dedupKey, now);
18
+ return false;
19
+ }
20
+ function slackEventDedupKey(payload, slackEvent) {
21
+ if (payload.event_id)
22
+ return payload.event_id;
23
+ return `${slackEvent.channel}:${slackEvent.ts}`;
24
+ }
25
+ function shouldProcessSlackEvent(slackEvent) {
26
+ if (slackEvent.bot_id || slackEvent.subtype)
27
+ return false;
28
+ if (slackEvent.type === "app_mention")
29
+ return true;
30
+ return slackEvent.type === "message" && slackEvent.channel_type === "im";
31
+ }
32
+ function normalizeSlackText(text) {
33
+ return text.replace(/<@[^>]+>/g, "").replace(/\s+/g, " ").trim();
34
+ }
35
+ export function isSlackWebhookIngress(meta) {
36
+ return meta?.source === "slack";
37
+ }
38
+ export function getSlackThreadMeta(meta) {
39
+ const slack = meta?.slack;
40
+ if (!slack || typeof slack !== "object")
41
+ return undefined;
42
+ return slack;
43
+ }
44
+ export async function* handleSlackWebhook(args) {
45
+ if (args.event.data.provider !== "slack")
46
+ return;
47
+ const signingSecret = resolveSlackSigningSecret() ?? "";
48
+ const rawBody = decodeWebhookRawBody(args.event);
49
+ let payload;
50
+ try {
51
+ payload = JSON.parse(rawBody.toString("utf8"));
52
+ }
53
+ catch {
54
+ yield webhookHttpResponse({ status: 400, body: { error: "invalid json" } });
55
+ return;
56
+ }
57
+ if (payload.type === "url_verification") {
58
+ yield webhookHttpResponse({ status: 200, body: { challenge: payload.challenge } });
59
+ return;
60
+ }
61
+ const timestamp = getWebhookHeader(args.event, "x-slack-request-timestamp");
62
+ const signature = getWebhookHeader(args.event, "x-slack-signature");
63
+ if (!verifySlackSignature(signingSecret, rawBody, timestamp, signature)) {
64
+ yield webhookHttpResponse({ status: 401, body: { error: "invalid signature" } });
65
+ return;
66
+ }
67
+ const slackEvent = payload.type === "event_callback" ? payload.event : undefined;
68
+ if (!slackEvent || !shouldProcessSlackEvent(slackEvent)) {
69
+ yield webhookHttpResponse({ status: 200, body: {} });
70
+ return;
71
+ }
72
+ const dedupKey = slackEventDedupKey(payload, slackEvent);
73
+ if (markSlackEventSeen(dedupKey)) {
74
+ yield webhookHttpResponse({ status: 200, body: {} });
75
+ return;
76
+ }
77
+ const text = normalizeSlackText(slackEvent.text ?? "");
78
+ const threadId = slackEvent.thread_ts ?? slackEvent.ts;
79
+ if (!text || !threadId) {
80
+ yield webhookHttpResponse({ status: 200, body: {} });
81
+ return;
82
+ }
83
+ args.ctx.state.threadId = threadId;
84
+ const slackMeta = {
85
+ teamId: payload.team_id,
86
+ channel: slackEvent.channel,
87
+ user: slackEvent.user,
88
+ ts: slackEvent.ts,
89
+ threadTs: slackEvent.thread_ts,
90
+ };
91
+ const openBotChannelId = slackEvent.channel
92
+ ? resolveOpenBotChannelId(slackEvent.channel, args.config)
93
+ : DEFAULT_OPENBOT_CHANNEL_ID;
94
+ args.ctx.state.channelId = openBotChannelId;
95
+ yield webhookHttpResponse({ status: 200, body: {} });
96
+ yield {
97
+ type: "agent:invoke",
98
+ data: {
99
+ agentId: ROUTING_AGENT_ID,
100
+ role: "user",
101
+ content: text,
102
+ },
103
+ meta: {
104
+ channelId: openBotChannelId,
105
+ threadId,
106
+ source: "slack",
107
+ slack: slackMeta,
108
+ userName: SLACK_USER_DISPLAY_NAME,
109
+ ...(slackEvent.user ? { userId: slackEvent.user } : {}),
110
+ },
111
+ };
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetopenbot/slack",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Slack Events API ingress, simple agent replies, and thread delivery for OpenBot",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -19,10 +19,7 @@
19
19
  "dist"
20
20
  ],
21
21
  "dependencies": {
22
- "@ai-sdk/mcp": "^2.0.14",
23
- "@ai-sdk/openai": "^4.0.15",
24
- "ai": "^7.0.29",
25
- "@meetopenbot/plugin-sdk": "^0.2.0"
22
+ "@meetopenbot/plugin-sdk": "^0.3.0"
26
23
  },
27
24
  "devDependencies": {
28
25
  "@types/node": "^20.10.1",
@@ -1,10 +0,0 @@
1
- /** True when this runtime is a platform-managed cloud deployment. */
2
- export const isCloudMode = () => process.env.OPENBOT_CLOUD_MODE === "1";
3
- /** Default auth mode: Credits on cloud, BYOK locally. */
4
- export const defaultAuthMode = () => isCloudMode() ? "credits" : "byok";
5
- export function resolveAuthMode(config) {
6
- if (config.authMode === "byok" || config.authMode === "credits") {
7
- return config.authMode;
8
- }
9
- return defaultAuthMode();
10
- }
@@ -1,53 +0,0 @@
1
- import { isCloudMode } from "./cloud-mode.js";
2
- export const INTEGRATIONS_TOKEN_HEADER = "x-openbot-integrations-token";
3
- export const CREDITS_API_KEY_PLACEHOLDER = "openbot-credits";
4
- /** Cloud host injects these when routing LLM calls through OpenBot Credits. */
5
- export function resolveCreditsAuthConfig() {
6
- const baseUrl = process.env.OPENBOT_INTEGRATIONS_BASE_URL?.trim();
7
- const token = process.env.OPENBOT_INTEGRATIONS_TOKEN?.trim();
8
- if (!baseUrl || !token)
9
- return undefined;
10
- return { baseUrl: baseUrl.replace(/\/$/, ""), token };
11
- }
12
- export function creditsProviderBaseUrl(config) {
13
- return `${config.baseUrl}/openai/v1`;
14
- }
15
- export function shouldUseCreditsAuth(options) {
16
- if (options?.authMode === "byok")
17
- return false;
18
- if (options?.authMode === "credits")
19
- return true;
20
- return isCloudMode() && resolveCreditsAuthConfig() !== undefined;
21
- }
22
- export function isCreditsErrorMessage(message) {
23
- const lower = message.toLowerCase();
24
- return (lower.includes("insufficient_credits") ||
25
- lower.includes("insufficient credits") ||
26
- lower.includes("402"));
27
- }
28
- export function isAuthErrorMessage(message) {
29
- const lower = message.toLowerCase();
30
- return (lower.includes("api key") ||
31
- lower.includes("401") ||
32
- lower.includes("unauthorized") ||
33
- lower.includes("authentication"));
34
- }
35
- export function isIntegrationsProviderError(message) {
36
- const lower = message.toLowerCase();
37
- return (lower.includes("provider api key not configured") ||
38
- (lower.includes("503") && lower.includes("provider")));
39
- }
40
- export const CREDITS_NOT_CONFIGURED_MESSAGE = "OpenBot Credits is not configured on this runtime. The cloud host must set OPENBOT_INTEGRATIONS_BASE_URL and OPENBOT_INTEGRATIONS_TOKEN (try redeploying the workspace).";
41
- export const CREDITS_PROVIDER_UNAVAILABLE_MESSAGE = "OpenBot Credits could not reach OpenAI — the platform provider API key is not configured yet. Try again later or switch this agent to BYOK mode.";
42
- export const CREDITS_AUTH_FAILED_MESSAGE = "Slack could not authenticate via OpenBot Credits. Check your workspace credit balance in settings, or switch this agent to BYOK mode.";
43
- export function creditsErrorMessage(message) {
44
- if (isIntegrationsProviderError(message)) {
45
- return CREDITS_PROVIDER_UNAVAILABLE_MESSAGE;
46
- }
47
- if (isCreditsErrorMessage(message)) {
48
- return "Insufficient workspace credits. Add credits in workspace settings or switch this agent to BYOK mode.";
49
- }
50
- if (isAuthErrorMessage(message))
51
- return CREDITS_AUTH_FAILED_MESSAGE;
52
- return undefined;
53
- }
@@ -1,40 +0,0 @@
1
- const REGISTRY_URL = "https://raw.githubusercontent.com/meetopenbot/openbot-registry/main/registry.json";
2
- const OPENAI_PROVIDER = "openai";
3
- const freeInputModelField = () => ({
4
- type: "string",
5
- override: true,
6
- description: "OpenAI model in provider/model-id format (e.g. openai/gpt-4o-mini).",
7
- default: "openai/gpt-4o-mini",
8
- });
9
- /** Registry-backed model field for plugin configSchema (`enum` + labeled `options`). */
10
- export async function resolveModelConfigField() {
11
- try {
12
- const res = await fetch(REGISTRY_URL, {
13
- headers: { Accept: "application/json" },
14
- signal: AbortSignal.timeout(15_000),
15
- });
16
- if (!res.ok)
17
- return freeInputModelField();
18
- const registry = (await res.json());
19
- const provider = registry.providers?.[OPENAI_PROVIDER];
20
- const models = provider?.models ?? [];
21
- if (models.length === 0)
22
- return freeInputModelField();
23
- const defaultModel = models.find((model) => model.id === "gpt-4o-mini")?.id ?? models[0].id;
24
- return {
25
- type: "string",
26
- override: true,
27
- description: "OpenAI model from the OpenBot registry.",
28
- default: `${OPENAI_PROVIDER}/${defaultModel}`,
29
- enum: models.map((model) => `${OPENAI_PROVIDER}/${model.id}`),
30
- options: models.map((model) => ({
31
- label: `${provider?.label ?? "OpenAI"} — ${model.label}`,
32
- value: `${OPENAI_PROVIDER}/${model.id}`,
33
- description: model.description,
34
- })),
35
- };
36
- }
37
- catch {
38
- return freeInputModelField();
39
- }
40
- }
package/dist/model.js DELETED
@@ -1,24 +0,0 @@
1
- import { createOpenAI } from "@ai-sdk/openai";
2
- import { CREDITS_API_KEY_PLACEHOLDER, INTEGRATIONS_TOKEN_HEADER, creditsProviderBaseUrl, resolveCreditsAuthConfig, shouldUseCreditsAuth, } from "./credits-auth.js";
3
- function normalizeOpenAiModelId(model) {
4
- return model.includes("/") ? model.split("/").slice(1).join("/") : model;
5
- }
6
- export function resolveOpenAiModel(model, options) {
7
- const modelId = normalizeOpenAiModelId(model);
8
- const useCredits = shouldUseCreditsAuth(options);
9
- if (useCredits) {
10
- const config = resolveCreditsAuthConfig();
11
- if (!config) {
12
- throw new Error("OpenBot Credits is not configured. The cloud host must set OPENBOT_INTEGRATIONS_BASE_URL and OPENBOT_INTEGRATIONS_TOKEN.");
13
- }
14
- const baseURL = creditsProviderBaseUrl(config);
15
- const headers = { [INTEGRATIONS_TOKEN_HEADER]: config.token };
16
- const apiKey = config.token || CREDITS_API_KEY_PLACEHOLDER;
17
- return createOpenAI({ baseURL, apiKey, headers })(modelId);
18
- }
19
- const apiKey = options?.openaiApiKey?.trim();
20
- if (!apiKey) {
21
- throw new Error("OpenAI API key is required in BYOK mode. Add `OPENAI_API_KEY` under workspace settings or switch `authMode` to `credits` on cloud.");
22
- }
23
- return createOpenAI({ apiKey })(modelId);
24
- }
@@ -1,30 +0,0 @@
1
- import { generateText, stepCountIs } from "ai";
2
- import { resolveOpenAiModel } from "./model.js";
3
- import { createSlackMcpClient } from "./slack-mcp.js";
4
- const SYSTEM_PROMPT = `You are the OpenBot Slack specialist agent.
5
- Use Slack MCP tools to post messages, list channels, read history, and perform other workspace actions when needed.
6
- Summarize results clearly in plain text for the user. Be concise and friendly.`;
7
- export async function runSlackAgent(args) {
8
- const model = resolveOpenAiModel(args.model ?? "openai/gpt-4o-mini", {
9
- authMode: args.authMode,
10
- openaiApiKey: args.openaiApiKey,
11
- });
12
- const mcpClient = await createSlackMcpClient({
13
- botToken: args.botToken,
14
- teamId: args.teamId,
15
- });
16
- try {
17
- const tools = await mcpClient.tools();
18
- const result = await generateText({
19
- model,
20
- system: SYSTEM_PROMPT,
21
- prompt: args.prompt,
22
- tools,
23
- stopWhen: stepCountIs(10),
24
- });
25
- return result.text.trim() || "Done.";
26
- }
27
- finally {
28
- await mcpClient.close();
29
- }
30
- }
package/dist/slack-mcp.js DELETED
@@ -1,15 +0,0 @@
1
- import { createMCPClient } from "@ai-sdk/mcp";
2
- import { Experimental_StdioMCPTransport } from "@ai-sdk/mcp/mcp-stdio";
3
- export async function createSlackMcpClient(args) {
4
- return createMCPClient({
5
- transport: new Experimental_StdioMCPTransport({
6
- command: "npx",
7
- args: ["-y", "@modelcontextprotocol/server-slack"],
8
- env: {
9
- ...process.env,
10
- SLACK_BOT_TOKEN: args.botToken,
11
- SLACK_TEAM_ID: args.teamId,
12
- },
13
- }),
14
- });
15
- }
File without changes
File without changes
File without changes