@copilotkit/channels-slack 0.1.0
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 +353 -0
- package/dist/__tests__/assistant.test.d.ts +2 -0
- package/dist/__tests__/assistant.test.d.ts.map +1 -0
- package/dist/__tests__/assistant.test.js +153 -0
- package/dist/__tests__/auto-close-streaming.test.d.ts +2 -0
- package/dist/__tests__/auto-close-streaming.test.d.ts.map +1 -0
- package/dist/__tests__/auto-close-streaming.test.js +139 -0
- package/dist/__tests__/built-ins.test.d.ts +2 -0
- package/dist/__tests__/built-ins.test.d.ts.map +1 -0
- package/dist/__tests__/built-ins.test.js +67 -0
- package/dist/__tests__/chunked-message-stream.test.d.ts +2 -0
- package/dist/__tests__/chunked-message-stream.test.d.ts.map +1 -0
- package/dist/__tests__/chunked-message-stream.test.js +226 -0
- package/dist/__tests__/conversation-store.test.d.ts +2 -0
- package/dist/__tests__/conversation-store.test.d.ts.map +1 -0
- package/dist/__tests__/conversation-store.test.js +209 -0
- package/dist/__tests__/download-files.test.d.ts +2 -0
- package/dist/__tests__/download-files.test.d.ts.map +1 -0
- package/dist/__tests__/download-files.test.js +142 -0
- package/dist/__tests__/ephemeral.test.d.ts +2 -0
- package/dist/__tests__/ephemeral.test.d.ts.map +1 -0
- package/dist/__tests__/ephemeral.test.js +85 -0
- package/dist/__tests__/event-renderer.test.d.ts +2 -0
- package/dist/__tests__/event-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/event-renderer.test.js +521 -0
- package/dist/__tests__/markdown-to-mrkdwn.test.d.ts +2 -0
- package/dist/__tests__/markdown-to-mrkdwn.test.d.ts.map +1 -0
- package/dist/__tests__/markdown-to-mrkdwn.test.js +80 -0
- package/dist/__tests__/message-stream.test.d.ts +2 -0
- package/dist/__tests__/message-stream.test.d.ts.map +1 -0
- package/dist/__tests__/message-stream.test.js +116 -0
- package/dist/__tests__/native-renderer.test.d.ts +2 -0
- package/dist/__tests__/native-renderer.test.d.ts.map +1 -0
- package/dist/__tests__/native-renderer.test.js +236 -0
- package/dist/__tests__/native-stream.test.d.ts +2 -0
- package/dist/__tests__/native-stream.test.d.ts.map +1 -0
- package/dist/__tests__/native-stream.test.js +235 -0
- package/dist/__tests__/reactions.test.d.ts +2 -0
- package/dist/__tests__/reactions.test.d.ts.map +1 -0
- package/dist/__tests__/reactions.test.js +193 -0
- package/dist/__tests__/slack-listener.test.d.ts +2 -0
- package/dist/__tests__/slack-listener.test.d.ts.map +1 -0
- package/dist/__tests__/slack-listener.test.js +559 -0
- package/dist/__tests__/views.test.d.ts +2 -0
- package/dist/__tests__/views.test.d.ts.map +1 -0
- package/dist/__tests__/views.test.js +143 -0
- package/dist/adapter.d.ts +188 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +859 -0
- package/dist/adapter.test.d.ts +2 -0
- package/dist/adapter.test.d.ts.map +1 -0
- package/dist/adapter.test.js +368 -0
- package/dist/assistant.d.ts +19 -0
- package/dist/assistant.d.ts.map +1 -0
- package/dist/assistant.js +124 -0
- package/dist/auto-close-streaming.d.ts +62 -0
- package/dist/auto-close-streaming.d.ts.map +1 -0
- package/dist/auto-close-streaming.js +273 -0
- package/dist/built-in-context.d.ts +22 -0
- package/dist/built-in-context.d.ts.map +1 -0
- package/dist/built-in-context.js +65 -0
- package/dist/built-in-tools.d.ts +23 -0
- package/dist/built-in-tools.d.ts.map +1 -0
- package/dist/built-in-tools.js +45 -0
- package/dist/chunked-message-stream.d.ts +67 -0
- package/dist/chunked-message-stream.d.ts.map +1 -0
- package/dist/chunked-message-stream.js +134 -0
- package/dist/codec.d.ts +18 -0
- package/dist/codec.d.ts.map +1 -0
- package/dist/codec.js +18 -0
- package/dist/codec.test.d.ts +2 -0
- package/dist/codec.test.d.ts.map +1 -0
- package/dist/codec.test.js +19 -0
- package/dist/conversation-store.d.ts +92 -0
- package/dist/conversation-store.d.ts.map +1 -0
- package/dist/conversation-store.js +196 -0
- package/dist/download-files.d.ts +59 -0
- package/dist/download-files.d.ts.map +1 -0
- package/dist/download-files.js +116 -0
- package/dist/event-renderer.d.ts +98 -0
- package/dist/event-renderer.d.ts.map +1 -0
- package/dist/event-renderer.js +493 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/ingress-normalize.d.ts +75 -0
- package/dist/ingress-normalize.d.ts.map +1 -0
- package/dist/ingress-normalize.js +153 -0
- package/dist/ingress-normalize.test.d.ts +2 -0
- package/dist/ingress-normalize.test.d.ts.map +1 -0
- package/dist/ingress-normalize.test.js +187 -0
- package/dist/interaction.d.ts +30 -0
- package/dist/interaction.d.ts.map +1 -0
- package/dist/interaction.js +219 -0
- package/dist/interaction.test.d.ts +2 -0
- package/dist/interaction.test.d.ts.map +1 -0
- package/dist/interaction.test.js +137 -0
- package/dist/markdown-to-mrkdwn.d.ts +21 -0
- package/dist/markdown-to-mrkdwn.d.ts.map +1 -0
- package/dist/markdown-to-mrkdwn.js +99 -0
- package/dist/message-stream.d.ts +44 -0
- package/dist/message-stream.d.ts.map +1 -0
- package/dist/message-stream.js +70 -0
- package/dist/native-stream.d.ts +131 -0
- package/dist/native-stream.d.ts.map +1 -0
- package/dist/native-stream.js +208 -0
- package/dist/render/block-kit.d.ts +35 -0
- package/dist/render/block-kit.d.ts.map +1 -0
- package/dist/render/block-kit.js +399 -0
- package/dist/render/block-kit.test.d.ts +2 -0
- package/dist/render/block-kit.test.d.ts.map +1 -0
- package/dist/render/block-kit.test.js +289 -0
- package/dist/render/budget.d.ts +24 -0
- package/dist/render/budget.d.ts.map +1 -0
- package/dist/render/budget.js +30 -0
- package/dist/render/budget.test.d.ts +2 -0
- package/dist/render/budget.test.d.ts.map +1 -0
- package/dist/render/budget.test.js +49 -0
- package/dist/render/modal.d.ts +13 -0
- package/dist/render/modal.d.ts.map +1 -0
- package/dist/render/modal.js +108 -0
- package/dist/render/transport.d.ts +42 -0
- package/dist/render/transport.d.ts.map +1 -0
- package/dist/render/transport.js +1 -0
- package/dist/render.d.ts +23 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +20 -0
- package/dist/sanitizing-http-agent.d.ts +28 -0
- package/dist/sanitizing-http-agent.d.ts.map +1 -0
- package/dist/sanitizing-http-agent.js +51 -0
- package/dist/slack-listener.d.ts +73 -0
- package/dist/slack-listener.d.ts.map +1 -0
- package/dist/slack-listener.js +126 -0
- package/dist/slack-listener.test.d.ts +2 -0
- package/dist/slack-listener.test.d.ts.map +1 -0
- package/dist/slack-listener.test.js +115 -0
- package/dist/types.d.ts +153 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +20 -0
- package/package.json +73 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { HttpAgent, parseSSEStream, runHttpRequest } from "@ag-ui/client";
|
|
2
|
+
import { map } from "rxjs";
|
|
3
|
+
/**
|
|
4
|
+
* An `HttpAgent` that tolerates the AG-UI event streams real agents emit.
|
|
5
|
+
*
|
|
6
|
+
* `@ag-ui/client`'s stock transform re-validates every streamed event
|
|
7
|
+
* against a strict Zod schema. Some events that `@ag-ui/langgraph`
|
|
8
|
+
* legitimately emits fail it — notably a `TOOL_CALL_START` whose
|
|
9
|
+
* `parentMessageId` is `null` ("Expected string, received null") — and a
|
|
10
|
+
* single rejected event aborts the entire run. That breaks LangGraph
|
|
11
|
+
* interrupts / human-in-the-loop in Slack, where the tool call that
|
|
12
|
+
* triggers the interrupt carries exactly that shape.
|
|
13
|
+
*
|
|
14
|
+
* The bridge talks to a trusted runtime, so rather than re-validate its
|
|
15
|
+
* output we use the same SSE parse the stock path wraps
|
|
16
|
+
* (`parseSSEStream`) and coerce the known nullable-string fields. Note
|
|
17
|
+
* this deliberately drops the stock transform's *entire* strict Zod
|
|
18
|
+
* re-validation step (not just the offending field) — acceptable only
|
|
19
|
+
* because the runtime is trusted and `runHttpRequest` still throws on
|
|
20
|
+
* transport/HTTP errors. The first coercion logs a one-time breadcrumb so
|
|
21
|
+
* the workaround is visible in production. Revert to the stock transform
|
|
22
|
+
* (`transformHttpEventStream`) once upstream makes these fields nullable.
|
|
23
|
+
*/
|
|
24
|
+
export class SanitizingHttpAgent extends HttpAgent {
|
|
25
|
+
run(input) {
|
|
26
|
+
return parseSSEStream(runHttpRequest(() => this.fetch(this.url, this.requestInit(input))), this.debugLogger).pipe(map((event) => coerceNullStrings(event)));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** One-time breadcrumb so the workaround's use is visible in production. */
|
|
30
|
+
let coercionWarned = false;
|
|
31
|
+
/**
|
|
32
|
+
* Coerce known nullable-string event fields to `""`. Targeted on purpose:
|
|
33
|
+
* we only touch fields where a `null` is known to come through from
|
|
34
|
+
* `@ag-ui/langgraph` and would otherwise trip a downstream string check.
|
|
35
|
+
*/
|
|
36
|
+
function coerceNullStrings(event) {
|
|
37
|
+
if (!event || typeof event !== "object")
|
|
38
|
+
return event;
|
|
39
|
+
const e = event;
|
|
40
|
+
if (e["parentMessageId"] === null) {
|
|
41
|
+
e["parentMessageId"] = "";
|
|
42
|
+
if (!coercionWarned) {
|
|
43
|
+
coercionWarned = true;
|
|
44
|
+
console.warn('[SanitizingHttpAgent] coerced a null `parentMessageId` to "" and ' +
|
|
45
|
+
"bypassed @ag-ui/client strict event re-validation for this stream " +
|
|
46
|
+
"(known @ag-ui/langgraph quirk). Remove this agent once upstream " +
|
|
47
|
+
"makes the field nullable.");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return e;
|
|
51
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { App } from "@slack/bolt";
|
|
2
|
+
import type { WebClient } from "@slack/web-api";
|
|
3
|
+
import type { SlackConversationStore } from "./conversation-store.js";
|
|
4
|
+
import type { IncomingTurn, ResolvedSlackRespondToOptions } from "./types.js";
|
|
5
|
+
/**
|
|
6
|
+
* Handler the listener calls when a Slack event maps to a usable turn.
|
|
7
|
+
* The listener doesn't know what happens next — it just hands off.
|
|
8
|
+
*/
|
|
9
|
+
export type TurnHandler = (turn: IncomingTurn, client: WebClient) => Promise<void> | void;
|
|
10
|
+
/** A normalized slash-command invocation the listener hands off. */
|
|
11
|
+
export interface SlackCommand {
|
|
12
|
+
/** Command name as Slack sent it, including the leading slash (e.g. "/triage"). */
|
|
13
|
+
command: string;
|
|
14
|
+
/** The argument string after the command. */
|
|
15
|
+
text: string;
|
|
16
|
+
conversation: {
|
|
17
|
+
channelId: string;
|
|
18
|
+
scope: string;
|
|
19
|
+
};
|
|
20
|
+
replyTarget: {
|
|
21
|
+
channel: string;
|
|
22
|
+
};
|
|
23
|
+
senderUserId?: string;
|
|
24
|
+
/** Opaque platform trigger for opening a modal (Slack `trigger_id`). */
|
|
25
|
+
triggerId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Stable per-invocation id for inbound idempotency. Slash commands carry no
|
|
28
|
+
* Events API `event_id`, so this is derived from
|
|
29
|
+
* `${command}:${user_id}:${trigger_id}` — the closest stable-per-click value
|
|
30
|
+
* Slack provides.
|
|
31
|
+
*/
|
|
32
|
+
eventId?: string;
|
|
33
|
+
}
|
|
34
|
+
export type CommandHandler = (command: SlackCommand, client: WebClient) => Promise<void> | void;
|
|
35
|
+
export interface ListenerConfig {
|
|
36
|
+
app: App;
|
|
37
|
+
/** Conversation store — used to check whether a thread-reply is "ours". */
|
|
38
|
+
store: SlackConversationStore;
|
|
39
|
+
/** Bot user id, used to filter out our own messages (loop guard). */
|
|
40
|
+
botUserId: string | undefined;
|
|
41
|
+
/** Resolved response-routing policy for Slack ingress. */
|
|
42
|
+
respondTo?: ResolvedSlackRespondToOptions;
|
|
43
|
+
/** Where each accepted turn is dispatched. */
|
|
44
|
+
onTurn: TurnHandler;
|
|
45
|
+
/** Where each slash command is dispatched. */
|
|
46
|
+
onCommand: CommandHandler;
|
|
47
|
+
/**
|
|
48
|
+
* True if `(channel, threadTs)` is an assistant-pane thread (owned by the
|
|
49
|
+
* Assistant middleware). When provided, threaded `message.im` events for
|
|
50
|
+
* those threads are skipped here so each pane message becomes exactly one
|
|
51
|
+
* turn. Absent (or returning false) → messages flow as shipped.
|
|
52
|
+
*/
|
|
53
|
+
isAssistantThread?: (channel: string, threadTs: string) => boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Attach Slack event handlers to a Bolt app. After this returns, the listener
|
|
57
|
+
* is the sole writer of IncomingTurn events — anything downstream sees a
|
|
58
|
+
* stream of cleanly-normalised turns regardless of which Slack event fired.
|
|
59
|
+
*
|
|
60
|
+
* Triggers:
|
|
61
|
+
* 1. @mention in a channel/thread → start or continue a conversation.
|
|
62
|
+
* 2. DM to the bot → reply flat in the DM.
|
|
63
|
+
* 3. Plain reply in a tracked thread when explicitly configured.
|
|
64
|
+
*
|
|
65
|
+
* Filters out:
|
|
66
|
+
* - `subtype` events (edits, joins, channel_renames, …)
|
|
67
|
+
* - bot messages (any `bot_id`, including our own posts)
|
|
68
|
+
* - top-level channel chatter we weren't @-mentioned in
|
|
69
|
+
* - the `message.channels` event that arrives alongside every `app_mention`
|
|
70
|
+
* (we recognise it by the presence of `<@botUserId>` in the text)
|
|
71
|
+
*/
|
|
72
|
+
export declare function attachSlackListener(config: ListenerConfig): void;
|
|
73
|
+
//# sourceMappingURL=slack-listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-listener.d.ts","sourceRoot":"","sources":["../src/slack-listener.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,KAAK,EAAE,YAAY,EAAE,6BAA6B,EAAE,MAAM,YAAY,CAAC;AAQ9E;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,CACxB,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,SAAS,KACd,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1B,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,WAAW,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,cAAc,GAAG,CAC3B,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,SAAS,KACd,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE1B,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,GAAG,CAAC;IACT,2EAA2E;IAC3E,KAAK,EAAE,sBAAsB,CAAC;IAC9B,qEAAqE;IACrE,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,6BAA6B,CAAC;IAC1C,8CAA8C;IAC9C,MAAM,EAAE,WAAW,CAAC;IACpB,8CAA8C;IAC9C,SAAS,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACpE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAsIhE"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { DEFAULT_SLACK_RESPOND_TO_OPTIONS, DM_SCOPE } from "./types.js";
|
|
2
|
+
import { stripMentions, deriveEventId, isPlainUserMessage, } from "./ingress-normalize.js";
|
|
3
|
+
/**
|
|
4
|
+
* Attach Slack event handlers to a Bolt app. After this returns, the listener
|
|
5
|
+
* is the sole writer of IncomingTurn events — anything downstream sees a
|
|
6
|
+
* stream of cleanly-normalised turns regardless of which Slack event fired.
|
|
7
|
+
*
|
|
8
|
+
* Triggers:
|
|
9
|
+
* 1. @mention in a channel/thread → start or continue a conversation.
|
|
10
|
+
* 2. DM to the bot → reply flat in the DM.
|
|
11
|
+
* 3. Plain reply in a tracked thread when explicitly configured.
|
|
12
|
+
*
|
|
13
|
+
* Filters out:
|
|
14
|
+
* - `subtype` events (edits, joins, channel_renames, …)
|
|
15
|
+
* - bot messages (any `bot_id`, including our own posts)
|
|
16
|
+
* - top-level channel chatter we weren't @-mentioned in
|
|
17
|
+
* - the `message.channels` event that arrives alongside every `app_mention`
|
|
18
|
+
* (we recognise it by the presence of `<@botUserId>` in the text)
|
|
19
|
+
*/
|
|
20
|
+
export function attachSlackListener(config) {
|
|
21
|
+
const { app, store, onTurn, onCommand } = config;
|
|
22
|
+
const respondTo = config.respondTo ?? DEFAULT_SLACK_RESPOND_TO_OPTIONS;
|
|
23
|
+
// ── Slash commands ──────────────────────────────────────────────────
|
|
24
|
+
// Forward EVERY registered slash command to the engine, which routes it
|
|
25
|
+
// to the matching `bot.onCommand` handler (and ignores unregistered ones).
|
|
26
|
+
// We ack immediately (Slack's 3s deadline) and hand off; the handler does
|
|
27
|
+
// the slow work. Slack only delivers commands declared in the app config,
|
|
28
|
+
// so the `/.*/ ` matcher just catches whatever Slack sends.
|
|
29
|
+
// The command's args aren't posted to the channel, so we synthesise a
|
|
30
|
+
// stable per-(user) scope, letting a user re-run a command and continue
|
|
31
|
+
// the same conversation.
|
|
32
|
+
app.command(/.*/, async ({ command, ack, client }) => {
|
|
33
|
+
await ack();
|
|
34
|
+
await onCommand({
|
|
35
|
+
command: command.command,
|
|
36
|
+
text: (command.text ?? "").trim(),
|
|
37
|
+
conversation: {
|
|
38
|
+
channelId: command.channel_id,
|
|
39
|
+
scope: `slash::${command.user_id}`,
|
|
40
|
+
},
|
|
41
|
+
replyTarget: { channel: command.channel_id },
|
|
42
|
+
senderUserId: command.user_id,
|
|
43
|
+
triggerId: command.trigger_id,
|
|
44
|
+
// Slash commands carry no Events API event_id; trigger_id is the most
|
|
45
|
+
// stable per-invocation value Slack provides.
|
|
46
|
+
eventId: command.trigger_id
|
|
47
|
+
? `${command.command}:${command.user_id}:${command.trigger_id}`
|
|
48
|
+
: undefined,
|
|
49
|
+
}, client);
|
|
50
|
+
});
|
|
51
|
+
app.event("app_mention", async ({ event, body, client }) => {
|
|
52
|
+
if (respondTo.appMentions === false)
|
|
53
|
+
return;
|
|
54
|
+
const threadTs = event.thread_ts ?? event.ts;
|
|
55
|
+
const userText = stripMentions(event.text ?? "");
|
|
56
|
+
const hasFiles = Array.isArray(event.files) &&
|
|
57
|
+
event.files.length > 0;
|
|
58
|
+
// Fire on a mention with an attachment even if the only text is the
|
|
59
|
+
// mention itself (e.g. "@bot" + a CSV). The store reads the file.
|
|
60
|
+
if (!userText && !hasFiles)
|
|
61
|
+
return;
|
|
62
|
+
await onTurn({
|
|
63
|
+
conversation: { channelId: event.channel, scope: threadTs },
|
|
64
|
+
replyTarget: respondTo.appMentions.reply === "thread"
|
|
65
|
+
? { channel: event.channel, threadTs }
|
|
66
|
+
: { channel: event.channel },
|
|
67
|
+
userText,
|
|
68
|
+
senderUserId: event.user,
|
|
69
|
+
eventId: deriveEventId(body, event, event.channel),
|
|
70
|
+
}, client);
|
|
71
|
+
});
|
|
72
|
+
app.message(async ({ message, body, client }) => {
|
|
73
|
+
if (!isPlainUserMessage(message, config.botUserId))
|
|
74
|
+
return;
|
|
75
|
+
const text = (message.text ?? "").trim();
|
|
76
|
+
const hasFiles = Array.isArray(message.files) && message.files.length > 0;
|
|
77
|
+
// A bare file upload has empty text but is still a real turn.
|
|
78
|
+
if (!text && !hasFiles)
|
|
79
|
+
return;
|
|
80
|
+
const isDM = message.channel_type === "im";
|
|
81
|
+
// Pane messages are threaded DMs owned by the Assistant middleware — skip
|
|
82
|
+
// them here so each pane message becomes EXACTLY ONE turn. Gated per-THREAD
|
|
83
|
+
// (assistant threads tracked at runtime), never per-config: ordinary
|
|
84
|
+
// threaded DMs in apps without the Agents toggle keep flowing.
|
|
85
|
+
if (isDM &&
|
|
86
|
+
message.thread_ts &&
|
|
87
|
+
config.isAssistantThread?.(message.channel, message.thread_ts))
|
|
88
|
+
return;
|
|
89
|
+
if (!respondTo.directMessages)
|
|
90
|
+
return;
|
|
91
|
+
if (isDM) {
|
|
92
|
+
await onTurn({
|
|
93
|
+
conversation: { channelId: message.channel, scope: DM_SCOPE },
|
|
94
|
+
// Flat DM reply (no threadTs); carry the inbound ts so the renderer
|
|
95
|
+
// can anchor the native "is thinking…" status to a thread.
|
|
96
|
+
replyTarget: { channel: message.channel, statusTs: message.ts },
|
|
97
|
+
userText: text,
|
|
98
|
+
senderUserId: message.user,
|
|
99
|
+
eventId: deriveEventId(body, message, message.channel),
|
|
100
|
+
}, client);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!message.thread_ts)
|
|
104
|
+
return; // top-level channel chatter — ignore
|
|
105
|
+
// app_mention runs separately for these; skip the duplicate.
|
|
106
|
+
if (config.botUserId && text.includes(`<@${config.botUserId}>`))
|
|
107
|
+
return;
|
|
108
|
+
if (respondTo.threadReplies === "mentionsOnly")
|
|
109
|
+
return;
|
|
110
|
+
// Only continue threads we already own. `has` consults Slack itself,
|
|
111
|
+
// so a restarted bridge naturally recognises threads it replied to
|
|
112
|
+
// before the restart.
|
|
113
|
+
if (!(await store.has({
|
|
114
|
+
channelId: message.channel,
|
|
115
|
+
scope: message.thread_ts,
|
|
116
|
+
})))
|
|
117
|
+
return;
|
|
118
|
+
await onTurn({
|
|
119
|
+
conversation: { channelId: message.channel, scope: message.thread_ts },
|
|
120
|
+
replyTarget: { channel: message.channel, threadTs: message.thread_ts },
|
|
121
|
+
userText: stripMentions(text),
|
|
122
|
+
senderUserId: message.user,
|
|
123
|
+
eventId: deriveEventId(body, message, message.channel),
|
|
124
|
+
}, client);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-listener.test.d.ts","sourceRoot":"","sources":["../src/slack-listener.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { attachSlackListener } from "./slack-listener.js";
|
|
3
|
+
/**
|
|
4
|
+
* Capture the Bolt handlers `attachSlackListener` registers, without a real
|
|
5
|
+
* App. Each `app.command/event/message` records its handler so a test can
|
|
6
|
+
* invoke it with a representative Slack payload and assert on the emitted
|
|
7
|
+
* ingress object.
|
|
8
|
+
*/
|
|
9
|
+
function captureListener(opts) {
|
|
10
|
+
let commandHandler;
|
|
11
|
+
let mentionHandler;
|
|
12
|
+
let messageHandler;
|
|
13
|
+
const app = {
|
|
14
|
+
command: vi.fn((_m, h) => {
|
|
15
|
+
commandHandler = h;
|
|
16
|
+
}),
|
|
17
|
+
event: vi.fn((_name, h) => {
|
|
18
|
+
mentionHandler = h;
|
|
19
|
+
}),
|
|
20
|
+
message: vi.fn((h) => {
|
|
21
|
+
messageHandler = h;
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
const store = {
|
|
25
|
+
has: vi.fn(async () => opts?.storeHas ?? true),
|
|
26
|
+
};
|
|
27
|
+
const turns = [];
|
|
28
|
+
const commands = [];
|
|
29
|
+
attachSlackListener({
|
|
30
|
+
app,
|
|
31
|
+
store,
|
|
32
|
+
botUserId: "UBOT",
|
|
33
|
+
onTurn: (turn) => {
|
|
34
|
+
turns.push(turn);
|
|
35
|
+
},
|
|
36
|
+
onCommand: (cmd) => {
|
|
37
|
+
commands.push(cmd);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
turns,
|
|
42
|
+
commands,
|
|
43
|
+
command: (args) => commandHandler(args),
|
|
44
|
+
mention: (args) => mentionHandler(args),
|
|
45
|
+
message: (args) => messageHandler(args),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const client = {};
|
|
49
|
+
describe("attachSlackListener eventId (inbound dedup)", () => {
|
|
50
|
+
it("derives a turn eventId from the Events API envelope event_id for an app_mention", async () => {
|
|
51
|
+
const l = captureListener();
|
|
52
|
+
await l.mention({
|
|
53
|
+
event: {
|
|
54
|
+
channel: "C1",
|
|
55
|
+
user: "U1",
|
|
56
|
+
text: "<@UBOT> hi",
|
|
57
|
+
ts: "100.0",
|
|
58
|
+
client_msg_id: "cmid-1",
|
|
59
|
+
},
|
|
60
|
+
body: { event_id: "Ev123ABC" },
|
|
61
|
+
client,
|
|
62
|
+
});
|
|
63
|
+
expect(l.turns).toHaveLength(1);
|
|
64
|
+
// event_id wins over client_msg_id / ts.
|
|
65
|
+
expect(l.turns[0].eventId).toBe("Ev123ABC");
|
|
66
|
+
});
|
|
67
|
+
it("falls back to client_msg_id then ${channel}:${ts} when no envelope event_id", async () => {
|
|
68
|
+
const l = captureListener();
|
|
69
|
+
// DM message with client_msg_id but no envelope event_id.
|
|
70
|
+
await l.message({
|
|
71
|
+
message: {
|
|
72
|
+
channel: "D9",
|
|
73
|
+
channel_type: "im",
|
|
74
|
+
user: "U1",
|
|
75
|
+
text: "hello",
|
|
76
|
+
ts: "200.1",
|
|
77
|
+
client_msg_id: "cmid-2",
|
|
78
|
+
},
|
|
79
|
+
body: {},
|
|
80
|
+
client,
|
|
81
|
+
});
|
|
82
|
+
expect(l.turns).toHaveLength(1);
|
|
83
|
+
expect(l.turns[0].eventId).toBe("cmid-2");
|
|
84
|
+
// Without client_msg_id, falls back to channel:ts.
|
|
85
|
+
const l2 = captureListener();
|
|
86
|
+
await l2.message({
|
|
87
|
+
message: {
|
|
88
|
+
channel: "D9",
|
|
89
|
+
channel_type: "im",
|
|
90
|
+
user: "U1",
|
|
91
|
+
text: "hello",
|
|
92
|
+
ts: "200.2",
|
|
93
|
+
},
|
|
94
|
+
body: {},
|
|
95
|
+
client,
|
|
96
|
+
});
|
|
97
|
+
expect(l2.turns[0].eventId).toBe("D9:200.2");
|
|
98
|
+
});
|
|
99
|
+
it("derives a stable command eventId from command:user:trigger_id", async () => {
|
|
100
|
+
const l = captureListener();
|
|
101
|
+
await l.command({
|
|
102
|
+
command: {
|
|
103
|
+
command: "/triage",
|
|
104
|
+
text: "now",
|
|
105
|
+
channel_id: "C1",
|
|
106
|
+
user_id: "U1",
|
|
107
|
+
trigger_id: "trig-9",
|
|
108
|
+
},
|
|
109
|
+
ack: vi.fn(async () => { }),
|
|
110
|
+
client,
|
|
111
|
+
});
|
|
112
|
+
expect(l.commands).toHaveLength(1);
|
|
113
|
+
expect(l.commands[0].eventId).toBe("/triage:U1:trig-9");
|
|
114
|
+
});
|
|
115
|
+
});
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import type { PlatformUser } from "@copilotkit/channels";
|
|
2
|
+
/**
|
|
3
|
+
* Where to post a reply in Slack. Used by the renderer; constructed by the
|
|
4
|
+
* listener once per turn.
|
|
5
|
+
*/
|
|
6
|
+
export interface ReplyTarget {
|
|
7
|
+
channel: string;
|
|
8
|
+
/** Thread ts to post into. Omit for flat replies (DMs). */
|
|
9
|
+
threadTs?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Turn sender's Slack user id. `chat.startStream` requires it
|
|
12
|
+
* (`recipient_user_id`) when streaming into a channel; carried here so the
|
|
13
|
+
* native streamer can read it off the target.
|
|
14
|
+
*/
|
|
15
|
+
recipientUserId?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Inbound message ts used as the thread anchor for
|
|
18
|
+
* `assistant.threads.setStatus` in flat DMs (which have no `threadTs`).
|
|
19
|
+
* Replies still post flat; this only gives the native "is thinking…"
|
|
20
|
+
* indicator a thread to attach to.
|
|
21
|
+
*/
|
|
22
|
+
statusTs?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Assistant-pane behavior ("Agents & AI Apps"). The pane is ON by default —
|
|
26
|
+
* it activates whenever the Slack app config has the Agents toggle, and lies
|
|
27
|
+
* dormant otherwise. Pass an object to {@link SlackAdapterOptions.assistant} to
|
|
28
|
+
* customize, or `false` to disable pane handling entirely.
|
|
29
|
+
*/
|
|
30
|
+
export interface SlackAssistantOptions {
|
|
31
|
+
/** Posted when a user opens the pane. */
|
|
32
|
+
greeting?: string;
|
|
33
|
+
/** Up to 4 prompt chips shown on thread start. */
|
|
34
|
+
suggestedPrompts?: ReadonlyArray<{
|
|
35
|
+
title: string;
|
|
36
|
+
message: string;
|
|
37
|
+
}>;
|
|
38
|
+
/** "auto" (default): title the thread from the first user message. false: never title. */
|
|
39
|
+
title?: "auto" | false;
|
|
40
|
+
/** Native status shown under the composer while the agent runs. */
|
|
41
|
+
status?: {
|
|
42
|
+
/** Status while reasoning. Default "is thinking…". */
|
|
43
|
+
thinking?: string;
|
|
44
|
+
/** Up to 10 loading messages Slack rotates through. */
|
|
45
|
+
loadingMessages?: readonly string[];
|
|
46
|
+
/** Surface "is using `tool`…" per tool call. Default true. */
|
|
47
|
+
toolStatus?: boolean;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A 👍/👎 click on a streamed AI reply's native feedback row (Slack's
|
|
52
|
+
* `feedback_buttons` element, attached at `chat.stopStream`). Delivered to
|
|
53
|
+
* {@link SlackFeedbackOptions.onFeedback}.
|
|
54
|
+
*/
|
|
55
|
+
export interface SlackFeedback {
|
|
56
|
+
sentiment: "positive" | "negative";
|
|
57
|
+
/** The user who clicked, if Slack supplied their identity. */
|
|
58
|
+
user?: PlatformUser;
|
|
59
|
+
/** Channel (or DM) the reply lives in. */
|
|
60
|
+
channel: string;
|
|
61
|
+
/** Thread the reply belongs to, if any. */
|
|
62
|
+
threadTs?: string;
|
|
63
|
+
/** ts of the streamed reply message the feedback is about. */
|
|
64
|
+
messageTs: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Opt-in native AI feedback buttons. When provided, streamed replies on the
|
|
68
|
+
* native path finalize with a `context_actions` + `feedback_buttons` row, and
|
|
69
|
+
* clicks are routed to {@link onFeedback} (they never reach the engine's
|
|
70
|
+
* interaction dispatch). Omit to show no feedback row.
|
|
71
|
+
*/
|
|
72
|
+
export interface SlackFeedbackOptions {
|
|
73
|
+
/** Invoked when a user clicks 👍/👎 on a streamed reply. */
|
|
74
|
+
onFeedback: (feedback: SlackFeedback) => void | Promise<void>;
|
|
75
|
+
/** Positive button label. Default "Good response". */
|
|
76
|
+
positiveLabel?: string;
|
|
77
|
+
/** Negative button label. Default "Bad response". */
|
|
78
|
+
negativeLabel?: string;
|
|
79
|
+
}
|
|
80
|
+
export type SlackMentionReplyMode = "thread" | "channel";
|
|
81
|
+
export type SlackThreadReplyMode = "mentionsOnly" | "afterBotReply";
|
|
82
|
+
export interface SlackAppMentionOptions {
|
|
83
|
+
/**
|
|
84
|
+
* Where an app mention should reply. "thread" keeps channel noise down and is
|
|
85
|
+
* the default; "channel" posts a top-level channel reply.
|
|
86
|
+
*/
|
|
87
|
+
reply?: SlackMentionReplyMode;
|
|
88
|
+
}
|
|
89
|
+
export interface SlackRespondToOptions {
|
|
90
|
+
/** Respond to normal Slack DMs (`message.im`). Default true. */
|
|
91
|
+
directMessages?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Respond to Slack `app_mention` events in channels/private channels. Pass
|
|
94
|
+
* false to ignore app mentions entirely. Default: `{ reply: "thread" }`.
|
|
95
|
+
*/
|
|
96
|
+
appMentions?: false | SlackAppMentionOptions;
|
|
97
|
+
/**
|
|
98
|
+
* How to handle plain, non-mention replies in channel/private-channel threads.
|
|
99
|
+
* "mentionsOnly" requires every thread turn to explicitly @mention the bot.
|
|
100
|
+
* "afterBotReply" preserves the legacy behavior: once the bot has replied in a
|
|
101
|
+
* thread, future plain replies in that thread can trigger new turns.
|
|
102
|
+
*/
|
|
103
|
+
threadReplies?: SlackThreadReplyMode;
|
|
104
|
+
}
|
|
105
|
+
export interface ResolvedSlackRespondToOptions {
|
|
106
|
+
directMessages: boolean;
|
|
107
|
+
appMentions: false | {
|
|
108
|
+
reply: SlackMentionReplyMode;
|
|
109
|
+
};
|
|
110
|
+
threadReplies: SlackThreadReplyMode;
|
|
111
|
+
}
|
|
112
|
+
export declare const DEFAULT_SLACK_RESPOND_TO_OPTIONS: ResolvedSlackRespondToOptions;
|
|
113
|
+
export declare function resolveSlackRespondToOptions(respondTo?: SlackRespondToOptions): ResolvedSlackRespondToOptions;
|
|
114
|
+
/**
|
|
115
|
+
* Stable key identifying one ongoing conversation with the bot.
|
|
116
|
+
*
|
|
117
|
+
* - For a channel thread: `{ channelId, scope: <threadTs> }`
|
|
118
|
+
* - For a DM: `{ channelId, scope: "dm" }`
|
|
119
|
+
*
|
|
120
|
+
* The store uses the pair as a string key; conversations from different
|
|
121
|
+
* channels never collide.
|
|
122
|
+
*/
|
|
123
|
+
export interface ConversationKey {
|
|
124
|
+
channelId: string;
|
|
125
|
+
scope: string;
|
|
126
|
+
}
|
|
127
|
+
/** Sentinel scope used for DMs (DMs are flat — no thread). */
|
|
128
|
+
export declare const DM_SCOPE = "dm";
|
|
129
|
+
/**
|
|
130
|
+
* What the listener emits per turn. Everything downstream of the listener
|
|
131
|
+
* works in terms of these — they don't know about Slack event shapes.
|
|
132
|
+
*/
|
|
133
|
+
export interface IncomingTurn {
|
|
134
|
+
conversation: ConversationKey;
|
|
135
|
+
replyTarget: ReplyTarget;
|
|
136
|
+
userText: string;
|
|
137
|
+
/**
|
|
138
|
+
* Slack user id of the person who sent this message (the requester).
|
|
139
|
+
* Surfaced to the agent so it can act on behalf of the right person —
|
|
140
|
+
* e.g. scope "my issues" to them, or assign created work to them.
|
|
141
|
+
* Absent only if the originating event carried no user (rare).
|
|
142
|
+
*/
|
|
143
|
+
senderUserId?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Stable per-delivery id for inbound idempotency. Prefer the Events API
|
|
146
|
+
* envelope `event_id` (survives Slack's retries), falling back to
|
|
147
|
+
* `client_msg_id` or `${channel}:${ts}`. Undefined when no stable id is
|
|
148
|
+
* available — the engine simply skips dedup for that event (never fabricate
|
|
149
|
+
* a random id; that would defeat dedup).
|
|
150
|
+
*/
|
|
151
|
+
eventId?: string;
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const DEFAULT_SLACK_RESPOND_TO_OPTIONS = {
|
|
2
|
+
directMessages: true,
|
|
3
|
+
appMentions: { reply: "thread" },
|
|
4
|
+
threadReplies: "mentionsOnly",
|
|
5
|
+
};
|
|
6
|
+
export function resolveSlackRespondToOptions(respondTo) {
|
|
7
|
+
return {
|
|
8
|
+
directMessages: respondTo?.directMessages ??
|
|
9
|
+
DEFAULT_SLACK_RESPOND_TO_OPTIONS.directMessages,
|
|
10
|
+
appMentions: respondTo?.appMentions === false
|
|
11
|
+
? false
|
|
12
|
+
: {
|
|
13
|
+
reply: respondTo?.appMentions?.reply ?? "thread",
|
|
14
|
+
},
|
|
15
|
+
threadReplies: respondTo?.threadReplies ??
|
|
16
|
+
DEFAULT_SLACK_RESPOND_TO_OPTIONS.threadReplies,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Sentinel scope used for DMs (DMs are flat — no thread). */
|
|
20
|
+
export const DM_SCOPE = "dm";
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@copilotkit/channels-slack",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Slack platform adapter for CopilotKit JSX channels (@copilotkit/channels).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/CopilotKit/CopilotKit.git",
|
|
9
|
+
"directory": "packages/channels-slack"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/CopilotKit/CopilotKit",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"agent",
|
|
15
|
+
"bot",
|
|
16
|
+
"slack",
|
|
17
|
+
"block-kit",
|
|
18
|
+
"socket-mode",
|
|
19
|
+
"copilotkit",
|
|
20
|
+
"ag-ui"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./codec": {
|
|
37
|
+
"types": "./dist/codec.d.ts",
|
|
38
|
+
"import": "./dist/codec.js"
|
|
39
|
+
},
|
|
40
|
+
"./render": {
|
|
41
|
+
"types": "./dist/render.d.ts",
|
|
42
|
+
"import": "./dist/render.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@ag-ui/client": "0.0.57",
|
|
47
|
+
"@ag-ui/core": "0.0.57",
|
|
48
|
+
"@slack/bolt": "^4.2.0",
|
|
49
|
+
"@slack/types": "^2.21.1",
|
|
50
|
+
"@slack/web-api": "^7.16.0",
|
|
51
|
+
"rxjs": "^7.8.1",
|
|
52
|
+
"zod": "^3.25.76",
|
|
53
|
+
"zod-to-json-schema": "^3.25.1",
|
|
54
|
+
"@copilotkit/channels-ui": "~0.1.0",
|
|
55
|
+
"@copilotkit/core": "^1.62.3",
|
|
56
|
+
"@copilotkit/shared": "^1.62.3",
|
|
57
|
+
"@copilotkit/channels": "~0.1.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^22.10.0",
|
|
61
|
+
"typescript": "^5.6.3",
|
|
62
|
+
"vitest": "^4.1.3",
|
|
63
|
+
"@copilotkit/typescript-config": "^1.55.0-next.8"
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsc -p tsconfig.json",
|
|
67
|
+
"check-types": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.check.json",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"test:watch": "vitest",
|
|
70
|
+
"publint": "publint .",
|
|
71
|
+
"attw": "attw --pack . --profile esm-only"
|
|
72
|
+
}
|
|
73
|
+
}
|