@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,493 @@
|
|
|
1
|
+
import { ChunkedMessageStream } from "./chunked-message-stream.js";
|
|
2
|
+
import { markdownToMrkdwn } from "./markdown-to-mrkdwn.js";
|
|
3
|
+
import { autoCloseOpenMarkdown } from "./auto-close-streaming.js";
|
|
4
|
+
import { NativeMessageStream } from "./native-stream.js";
|
|
5
|
+
const DEFAULT_THINKING_STATUS = "is thinking…";
|
|
6
|
+
/**
|
|
7
|
+
* The display-transform applied to every streaming chunk before it hits
|
|
8
|
+
* `chat.update` (the LEGACY transport only — the native path streams raw
|
|
9
|
+
* markdown). Composed of two pure functions:
|
|
10
|
+
*
|
|
11
|
+
* 1. `autoCloseOpenMarkdown` — closes dangling fenced code, inline
|
|
12
|
+
* backticks, bold/italic/strike so a mid-stream buffer renders as
|
|
13
|
+
* valid mrkdwn (instead of leaking code-styling through the rest of
|
|
14
|
+
* the Slack message). When the agent eventually emits the real
|
|
15
|
+
* close marker the buffer becomes balanced and this step adds
|
|
16
|
+
* nothing, so the committed Slack message is never double-closed.
|
|
17
|
+
*
|
|
18
|
+
* 2. `markdownToMrkdwn` — translates the (now-balanced) markdown into
|
|
19
|
+
* Slack mrkdwn.
|
|
20
|
+
*/
|
|
21
|
+
const displayTransform = (text) => markdownToMrkdwn(autoCloseOpenMarkdown(text));
|
|
22
|
+
const INTERRUPTED_SUFFIX = "\n_(interrupted)_";
|
|
23
|
+
/**
|
|
24
|
+
* Construct a {@link RunRenderer} for a single agent run in Slack.
|
|
25
|
+
*
|
|
26
|
+
* The `subscriber` is passed to `runAgent`. After the run resolves, the
|
|
27
|
+
* run-loop reads `getCapturedToolCalls()` (filtering by which tools are
|
|
28
|
+
* registered) and `getPendingInterrupt()`, then calls `finish()` once at
|
|
29
|
+
* turn end. `markInterrupted()` is called when a new turn arrives for the
|
|
30
|
+
* same conversation while this run is still streaming — it appends a
|
|
31
|
+
* `_(interrupted)_` marker to any partial reply so the user sees a clear
|
|
32
|
+
* visual cue that the bot stopped.
|
|
33
|
+
*
|
|
34
|
+
* ## Native vs legacy text streaming
|
|
35
|
+
*
|
|
36
|
+
* When `nativeStreaming` is set, the run uses a SINGLE turn-scoped
|
|
37
|
+
* `chat.startStream` message for the whole turn: text from every AG-UI
|
|
38
|
+
* message accumulates into it (separated by blank lines), and tool calls
|
|
39
|
+
* surface as native `task_update` chunks INSIDE that message. The message is
|
|
40
|
+
* finalized once, at `finish()`, optionally carrying a feedback row. The
|
|
41
|
+
* legacy path keeps the prior behavior — one `chat.update` message per AG-UI
|
|
42
|
+
* text message, plus separate `:wrench:` tool-status rows.
|
|
43
|
+
*/
|
|
44
|
+
export function createRunRenderer(args) {
|
|
45
|
+
const { transport, target } = args;
|
|
46
|
+
const interruptEventNames = args.interruptEventNames ?? new Set(["on_interrupt"]);
|
|
47
|
+
const showToolStatus = args.showToolStatus ?? true;
|
|
48
|
+
const nativeMode = args.nativeStreaming !== undefined;
|
|
49
|
+
// ── Native status mode ──────────────────────────────────────────────
|
|
50
|
+
// Whenever the reply is anchored to a thread, the run lifecycle drives
|
|
51
|
+
// Slack's native `assistant.threads.setStatus` ("is thinking…") instead of a
|
|
52
|
+
// placeholder message. `isPane` only selects how *tool* progress is shown.
|
|
53
|
+
const status = args.status;
|
|
54
|
+
const statusMode = status !== undefined;
|
|
55
|
+
const isPane = status?.isPane ?? false;
|
|
56
|
+
const paneToolStatus = status?.config?.toolStatus ?? true;
|
|
57
|
+
const setStatus = async (text) => {
|
|
58
|
+
if (!status)
|
|
59
|
+
return;
|
|
60
|
+
try {
|
|
61
|
+
await transport.setStatus({
|
|
62
|
+
channel_id: target.channel,
|
|
63
|
+
thread_ts: status.threadTs,
|
|
64
|
+
status: text,
|
|
65
|
+
...(text && status.config?.loadingMessages
|
|
66
|
+
? { loading_messages: [...status.config.loadingMessages] }
|
|
67
|
+
: {}),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error("[slack-renderer] setStatus failed:", err);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/** Clear the native status (best-effort). */
|
|
75
|
+
const clearStatus = async () => {
|
|
76
|
+
if (!statusMode)
|
|
77
|
+
return;
|
|
78
|
+
await setStatus("");
|
|
79
|
+
};
|
|
80
|
+
/** Whether this run has posted any visible reply yet (drives status clear). */
|
|
81
|
+
let postedReply = false;
|
|
82
|
+
const onFirstReply = async () => {
|
|
83
|
+
if (postedReply)
|
|
84
|
+
return;
|
|
85
|
+
postedReply = true;
|
|
86
|
+
// Slack auto-clears status when the app replies; clear explicitly too so
|
|
87
|
+
// there's no lingering indicator on slow/edge paths.
|
|
88
|
+
await clearStatus();
|
|
89
|
+
};
|
|
90
|
+
// ── Legacy per-message streaming state ──────────────────────────────
|
|
91
|
+
/** Per-AG-UI-message accumulated text (legacy path; native uses one buffer). */
|
|
92
|
+
const buffers = new Map();
|
|
93
|
+
/** Per-AG-UI-message text stream (legacy). Lazily created on first content. */
|
|
94
|
+
const streams = new Map();
|
|
95
|
+
/** Per-tool-call Slack message ts so we can edit it on END (legacy/degraded). */
|
|
96
|
+
const toolStatusTs = new Map();
|
|
97
|
+
/**
|
|
98
|
+
* Once a stream has been "finalised" (either via TEXT_MESSAGE_END or via
|
|
99
|
+
* markInterrupted), we drop it. Late-arriving events for the same
|
|
100
|
+
* messageId are ignored — they'd otherwise reopen a closed stream and
|
|
101
|
+
* post a fresh placeholder *after* the user already saw the message
|
|
102
|
+
* was over.
|
|
103
|
+
*/
|
|
104
|
+
const finalised = new Set();
|
|
105
|
+
// ── Native turn-scoped streaming state ──────────────────────────────
|
|
106
|
+
/** The single native stream for the whole turn (lazily created). */
|
|
107
|
+
let turnStream;
|
|
108
|
+
/** All text streamed this turn (every AG-UI message concatenated). */
|
|
109
|
+
let turnText = "";
|
|
110
|
+
/** Set after a TEXT_MESSAGE_END so the next message inserts a blank-line gap. */
|
|
111
|
+
let pendingSeparator = false;
|
|
112
|
+
/** True once the turn stream has been finalized (interrupt / error / finish). */
|
|
113
|
+
let turnFinalised = false;
|
|
114
|
+
/**
|
|
115
|
+
* Whether native structured chunks (`task_update`) are usable. Flipped off
|
|
116
|
+
* the first time a chunk append fails (old workspace / missing scope), after
|
|
117
|
+
* which tool progress degrades to `:wrench:` rows.
|
|
118
|
+
*/
|
|
119
|
+
let taskChunksOk = nativeMode && (args.nativeStreaming?.taskChunks ?? true);
|
|
120
|
+
/**
|
|
121
|
+
* Set when the caller intentionally aborted the run (i.e. a new turn
|
|
122
|
+
* arrived for the same conversation). Suppresses the RUN_ERROR /
|
|
123
|
+
* AbortError warning and stops accepting further AG-UI events — the
|
|
124
|
+
* `_(interrupted)_` marker conveys the state visually.
|
|
125
|
+
*/
|
|
126
|
+
let aborted = false;
|
|
127
|
+
/** Tool calls observed in this run, in event order. */
|
|
128
|
+
const capturedToolCalls = [];
|
|
129
|
+
/**
|
|
130
|
+
* Interrupt observed via a matching `onCustomEvent`. The React
|
|
131
|
+
* `useInterrupt` pattern buffers locally and commits on run-finalize,
|
|
132
|
+
* which we mirror via this single-slot variable — the run-loop reads it
|
|
133
|
+
* after `runAgent` resolves.
|
|
134
|
+
*/
|
|
135
|
+
let pendingInterrupt;
|
|
136
|
+
// The shipped chat.update streamer (mrkdwn-translated). Also the automatic
|
|
137
|
+
// fallback for the native transport.
|
|
138
|
+
const makeLegacyStream = () => new ChunkedMessageStream({
|
|
139
|
+
postPlaceholder: async (text) => {
|
|
140
|
+
const posted = await transport.postMessage({
|
|
141
|
+
channel: target.channel,
|
|
142
|
+
thread_ts: target.threadTs,
|
|
143
|
+
text,
|
|
144
|
+
});
|
|
145
|
+
if (!posted.ts)
|
|
146
|
+
throw new Error("postMessage returned no ts");
|
|
147
|
+
await onFirstReply();
|
|
148
|
+
return posted.ts;
|
|
149
|
+
},
|
|
150
|
+
updateAt: async (ts, text) => {
|
|
151
|
+
await transport.updateMessage({ channel: target.channel, ts, text });
|
|
152
|
+
},
|
|
153
|
+
transform: displayTransform,
|
|
154
|
+
});
|
|
155
|
+
// The single native turn stream — raw markdown, no mrkdwn translation, with
|
|
156
|
+
// interleaved `task_update` chunks.
|
|
157
|
+
const ensureTurnStream = () => {
|
|
158
|
+
if (turnStream)
|
|
159
|
+
return turnStream;
|
|
160
|
+
const ns = args.nativeStreaming;
|
|
161
|
+
turnStream = new NativeMessageStream({
|
|
162
|
+
transport: {
|
|
163
|
+
startStream: async () => {
|
|
164
|
+
const ts = await ns.transport.startStream();
|
|
165
|
+
// The native message owns the bubble now; clear the native status
|
|
166
|
+
// (the reply is visible).
|
|
167
|
+
await onFirstReply();
|
|
168
|
+
return ts;
|
|
169
|
+
},
|
|
170
|
+
appendText: (ts, md) => ns.transport.appendText(ts, md),
|
|
171
|
+
appendChunks: (ts, chunks) => ns.transport.appendChunks(ts, chunks),
|
|
172
|
+
stopStream: (ts, blocks) => ns.transport.stopStream(ts, blocks),
|
|
173
|
+
},
|
|
174
|
+
fallback: makeLegacyStream,
|
|
175
|
+
onStartFailure: ns.onStartFailure,
|
|
176
|
+
onChunkFailure: () => {
|
|
177
|
+
// Structured chunks unsupported on this workspace — degrade tool
|
|
178
|
+
// progress to `:wrench:` rows for the rest of the run, and let the
|
|
179
|
+
// adapter persist it so later turns skip chunks entirely.
|
|
180
|
+
taskChunksOk = false;
|
|
181
|
+
ns.onChunkFailure?.();
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
return turnStream;
|
|
185
|
+
};
|
|
186
|
+
const ensureLegacyStream = (messageId) => {
|
|
187
|
+
if (finalised.has(messageId))
|
|
188
|
+
return undefined;
|
|
189
|
+
let s = streams.get(messageId);
|
|
190
|
+
if (!s) {
|
|
191
|
+
s = makeLegacyStream();
|
|
192
|
+
streams.set(messageId, s);
|
|
193
|
+
}
|
|
194
|
+
return s;
|
|
195
|
+
};
|
|
196
|
+
const captureToolCall = (toolCallId, toolCallName, toolCallArgs) => {
|
|
197
|
+
const existing = capturedToolCalls.find((c) => c.toolCallId === toolCallId);
|
|
198
|
+
if (existing) {
|
|
199
|
+
existing.toolCallName = toolCallName;
|
|
200
|
+
existing.toolCallArgs = toolCallArgs;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
capturedToolCalls.push({ toolCallId, toolCallName, toolCallArgs });
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
/** Post a `:wrench:` tool-start row (legacy path / native degradation). */
|
|
207
|
+
const postToolStartRow = async (toolCallId, toolCallName) => {
|
|
208
|
+
if (!showToolStatus)
|
|
209
|
+
return;
|
|
210
|
+
if (toolStatusTs.has(toolCallId))
|
|
211
|
+
return; // dedup
|
|
212
|
+
try {
|
|
213
|
+
const posted = await transport.postMessage({
|
|
214
|
+
channel: target.channel,
|
|
215
|
+
thread_ts: target.threadTs,
|
|
216
|
+
text: `:wrench: Calling \`${toolCallName}\`…`,
|
|
217
|
+
});
|
|
218
|
+
if (posted.ts)
|
|
219
|
+
toolStatusTs.set(toolCallId, posted.ts);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
console.error("[slack-renderer] tool-start post failed:", err);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
/** Edit the `:wrench:` row to a checkmark on END (legacy path / native degradation). */
|
|
226
|
+
const finishToolStatusRow = async (toolCallId, toolCallName) => {
|
|
227
|
+
if (!showToolStatus)
|
|
228
|
+
return;
|
|
229
|
+
const ts = toolStatusTs.get(toolCallId);
|
|
230
|
+
if (!ts)
|
|
231
|
+
return;
|
|
232
|
+
try {
|
|
233
|
+
await transport.updateMessage({
|
|
234
|
+
channel: target.channel,
|
|
235
|
+
ts,
|
|
236
|
+
text: `:white_check_mark: \`${toolCallName}\``,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
console.error("[slack-renderer] tool-end update failed:", err);
|
|
241
|
+
}
|
|
242
|
+
toolStatusTs.delete(toolCallId);
|
|
243
|
+
};
|
|
244
|
+
const subscriber = {
|
|
245
|
+
// ── 0. Run lifecycle: thinking indicator ───────────────────────────
|
|
246
|
+
async onRunStartedEvent() {
|
|
247
|
+
if (aborted)
|
|
248
|
+
return;
|
|
249
|
+
// Native "is thinking…" status on the thread. `runAgent` fires once per
|
|
250
|
+
// tool-loop iteration, so we (re)set it on each RUN_STARTED — Slack also
|
|
251
|
+
// resets its 2-minute status timeout on each call.
|
|
252
|
+
if (statusMode) {
|
|
253
|
+
await setStatus(status?.config?.thinking || DEFAULT_THINKING_STATUS);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
async onRunFinishedEvent() {
|
|
257
|
+
// The NATIVE turn stream is NOT finalized here: the run-loop may
|
|
258
|
+
// re-invoke for tool results, so it stays open and is finalized in
|
|
259
|
+
// `finish()`. For the LEGACY per-message path, defensively finalize any
|
|
260
|
+
// stream still open at the end of THIS run so its text is fully posted
|
|
261
|
+
// before the run-loop executes tool handlers that post out-of-band
|
|
262
|
+
// content (images/cards). In native mode `streams` is empty, so this
|
|
263
|
+
// loop is a no-op.
|
|
264
|
+
const drains = [];
|
|
265
|
+
for (const [id, stream] of Array.from(streams.entries())) {
|
|
266
|
+
drains.push(stream.finish());
|
|
267
|
+
streams.delete(id);
|
|
268
|
+
finalised.add(id);
|
|
269
|
+
buffers.delete(id);
|
|
270
|
+
}
|
|
271
|
+
if (drains.length > 0)
|
|
272
|
+
await Promise.all(drains);
|
|
273
|
+
// A posted reply auto-clears Slack's status; clear it explicitly when the
|
|
274
|
+
// run produced no visible reply yet (tool/component/HITL output, or none).
|
|
275
|
+
if (statusMode && !postedReply)
|
|
276
|
+
await clearStatus();
|
|
277
|
+
},
|
|
278
|
+
// ── 1. Text streaming ──────────────────────────────────────────────
|
|
279
|
+
onTextMessageStartEvent({ event }) {
|
|
280
|
+
if (aborted)
|
|
281
|
+
return;
|
|
282
|
+
if (nativeMode) {
|
|
283
|
+
// A new message after prior text → blank-line gap in the one bubble.
|
|
284
|
+
if (turnText.length > 0)
|
|
285
|
+
pendingSeparator = true;
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
buffers.set(event.messageId, "");
|
|
289
|
+
},
|
|
290
|
+
onTextMessageContentEvent({ event }) {
|
|
291
|
+
if (aborted)
|
|
292
|
+
return;
|
|
293
|
+
const delta = event.delta ?? "";
|
|
294
|
+
if (nativeMode) {
|
|
295
|
+
if (turnFinalised)
|
|
296
|
+
return;
|
|
297
|
+
if (pendingSeparator && turnText.length > 0) {
|
|
298
|
+
turnText += "\n\n";
|
|
299
|
+
pendingSeparator = false;
|
|
300
|
+
}
|
|
301
|
+
turnText += delta;
|
|
302
|
+
ensureTurnStream().append(turnText);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const next = (buffers.get(event.messageId) ?? "") + delta;
|
|
306
|
+
buffers.set(event.messageId, next);
|
|
307
|
+
ensureLegacyStream(event.messageId)?.append(next);
|
|
308
|
+
},
|
|
309
|
+
async onTextMessageEndEvent({ event }) {
|
|
310
|
+
if (aborted)
|
|
311
|
+
return;
|
|
312
|
+
if (nativeMode) {
|
|
313
|
+
// Do NOT finish the turn stream — more messages / tools may follow.
|
|
314
|
+
// The blank-line separator is applied on the next message's first
|
|
315
|
+
// content. Finalization happens in `finish()`.
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const stream = streams.get(event.messageId);
|
|
319
|
+
if (stream) {
|
|
320
|
+
await stream.finish();
|
|
321
|
+
streams.delete(event.messageId);
|
|
322
|
+
}
|
|
323
|
+
buffers.delete(event.messageId);
|
|
324
|
+
finalised.add(event.messageId);
|
|
325
|
+
},
|
|
326
|
+
// ── 2. Tool-call surfacing + capture ──────────────────────────────
|
|
327
|
+
async onToolCallStartEvent({ event }) {
|
|
328
|
+
if (aborted)
|
|
329
|
+
return;
|
|
330
|
+
// Composer "is using `tool`…" status on ANY thread anchor (not just
|
|
331
|
+
// panes) — the native status API works on every surface. Each setStatus
|
|
332
|
+
// also resets Slack's status timeout. The pane's `toolStatus` config knob
|
|
333
|
+
// still gates panes; every other surface shows it when tool status is on.
|
|
334
|
+
if (statusMode && showToolStatus && (isPane ? paneToolStatus : true)) {
|
|
335
|
+
await setStatus(`is using \`${event.toolCallName}\`…`);
|
|
336
|
+
}
|
|
337
|
+
// Panes surface tool activity ONLY as composer status — no in-thread rows.
|
|
338
|
+
if (isPane)
|
|
339
|
+
return;
|
|
340
|
+
// Native path: also surface the call as an in-message `task_update` chunk.
|
|
341
|
+
if (nativeMode && taskChunksOk) {
|
|
342
|
+
if (showToolStatus) {
|
|
343
|
+
ensureTurnStream().appendChunk({
|
|
344
|
+
type: "task_update",
|
|
345
|
+
id: event.toolCallId,
|
|
346
|
+
title: `Using \`${event.toolCallName}\``,
|
|
347
|
+
status: "in_progress",
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// Legacy path (or native degraded): a `:wrench:` status row.
|
|
353
|
+
await postToolStartRow(event.toolCallId, event.toolCallName);
|
|
354
|
+
},
|
|
355
|
+
onToolCallArgsEvent({ event, toolCallName, partialToolCallArgs }) {
|
|
356
|
+
if (aborted)
|
|
357
|
+
return;
|
|
358
|
+
// Accumulate partial args so the captured entry tracks the latest
|
|
359
|
+
// parsed shape as args stream in. We keep a single entry per
|
|
360
|
+
// toolCallId, overwriting `toolCallArgs`.
|
|
361
|
+
captureToolCall(event.toolCallId, toolCallName, (partialToolCallArgs ?? {}));
|
|
362
|
+
},
|
|
363
|
+
async onToolCallEndEvent({ event, toolCallName, toolCallArgs }) {
|
|
364
|
+
if (aborted)
|
|
365
|
+
return;
|
|
366
|
+
// Capture EVERY tool call — the run-loop filters by which tools are
|
|
367
|
+
// registered. A tool with no `args` events still lands here.
|
|
368
|
+
captureToolCall(event.toolCallId, toolCallName, (toolCallArgs ?? {}));
|
|
369
|
+
// Pane threads use live status (set on START); no per-call rows to edit.
|
|
370
|
+
if (isPane)
|
|
371
|
+
return;
|
|
372
|
+
// Native path: complete the in-message `task_update`.
|
|
373
|
+
if (nativeMode && taskChunksOk) {
|
|
374
|
+
if (showToolStatus) {
|
|
375
|
+
ensureTurnStream().appendChunk({
|
|
376
|
+
type: "task_update",
|
|
377
|
+
id: event.toolCallId,
|
|
378
|
+
title: `Used \`${toolCallName}\``,
|
|
379
|
+
status: "complete",
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
// Legacy path (or native degraded): edit the `:wrench:` row to a check.
|
|
385
|
+
await finishToolStatusRow(event.toolCallId, toolCallName);
|
|
386
|
+
},
|
|
387
|
+
// ── 3. Interrupts (LangGraph `interrupt()` → AG-UI custom event) ─
|
|
388
|
+
onCustomEvent({ event }) {
|
|
389
|
+
if (aborted)
|
|
390
|
+
return;
|
|
391
|
+
const e = event;
|
|
392
|
+
if (!e.name || !interruptEventNames.has(e.name))
|
|
393
|
+
return;
|
|
394
|
+
// LangGraph's AG-UI adapter ships the interrupt value as a JSON
|
|
395
|
+
// string in some shapes (and as an object in others). Normalize
|
|
396
|
+
// here so downstream schema validation always sees the parsed shape.
|
|
397
|
+
let value = e.value;
|
|
398
|
+
if (typeof value === "string") {
|
|
399
|
+
try {
|
|
400
|
+
value = JSON.parse(value);
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
// Leave it as a string — the handler's schema will reject it
|
|
404
|
+
// explicitly with a clearer error.
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
pendingInterrupt = { eventName: e.name, value };
|
|
408
|
+
},
|
|
409
|
+
// ── 4. Errors ─────────────────────────────────────────────────────
|
|
410
|
+
async onRunErrorEvent({ event }) {
|
|
411
|
+
// Don't post a warning if we're the ones aborting the run; the
|
|
412
|
+
// `_(interrupted)_` marker on the partial reply is the user-visible
|
|
413
|
+
// signal in that case.
|
|
414
|
+
if (statusMode)
|
|
415
|
+
await clearStatus();
|
|
416
|
+
// Close any open native turn stream so the partial reply is committed.
|
|
417
|
+
await finalizeTurnStream();
|
|
418
|
+
if (aborted)
|
|
419
|
+
return;
|
|
420
|
+
try {
|
|
421
|
+
await transport.postMessage({
|
|
422
|
+
channel: target.channel,
|
|
423
|
+
thread_ts: target.threadTs,
|
|
424
|
+
text: `:warning: Agent error: ${event.message ?? "unknown error"}`,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
console.error("[slack-renderer] error notice failed:", err);
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
/** Finalize the native turn stream once (idempotent), attaching feedback if any. */
|
|
433
|
+
const finalizeTurnStream = async () => {
|
|
434
|
+
if (turnFinalised)
|
|
435
|
+
return;
|
|
436
|
+
turnFinalised = true;
|
|
437
|
+
if (turnStream) {
|
|
438
|
+
// Attach the feedback row only to a COMPLETE reply that streamed text —
|
|
439
|
+
// never to an interrupted/aborted partial (no point rating a half answer).
|
|
440
|
+
const blocks = !aborted && turnText.length > 0 ? args.feedbackBlocks : undefined;
|
|
441
|
+
await turnStream.finish(blocks);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
return {
|
|
445
|
+
subscriber,
|
|
446
|
+
getCapturedToolCalls: () => capturedToolCalls,
|
|
447
|
+
getPendingInterrupt: () => pendingInterrupt,
|
|
448
|
+
clearPendingInterrupt: () => {
|
|
449
|
+
pendingInterrupt = undefined;
|
|
450
|
+
},
|
|
451
|
+
async finish() {
|
|
452
|
+
// Turn-end hook (called by the engine after the run-loop resolves).
|
|
453
|
+
// No-op if the run was interrupted (markInterrupted already drained).
|
|
454
|
+
if (aborted)
|
|
455
|
+
return;
|
|
456
|
+
await finalizeTurnStream();
|
|
457
|
+
},
|
|
458
|
+
async markInterrupted() {
|
|
459
|
+
// Idempotent. Mark BEFORE any await so subsequent subscriber
|
|
460
|
+
// callbacks (including the RUN_ERROR that AG-UI fires when we
|
|
461
|
+
// abort) see the flag and bail.
|
|
462
|
+
if (aborted)
|
|
463
|
+
return;
|
|
464
|
+
aborted = true;
|
|
465
|
+
// Clear the native status; the interrupted marker (or the next turn) is
|
|
466
|
+
// the user-visible signal now.
|
|
467
|
+
if (statusMode)
|
|
468
|
+
await clearStatus();
|
|
469
|
+
// Native turn stream: append the interrupted marker to the partial reply
|
|
470
|
+
// and finalize it.
|
|
471
|
+
if (nativeMode) {
|
|
472
|
+
if (turnStream && turnText.length > 0 && !turnFinalised) {
|
|
473
|
+
turnStream.append(turnText + INTERRUPTED_SUFFIX);
|
|
474
|
+
}
|
|
475
|
+
await finalizeTurnStream();
|
|
476
|
+
}
|
|
477
|
+
// Legacy per-message streams: append the marker and drain. Streams with
|
|
478
|
+
// no content yet are silently dropped (the bot never posted anything).
|
|
479
|
+
const tasks = [];
|
|
480
|
+
for (const [id, stream] of Array.from(streams.entries())) {
|
|
481
|
+
const buf = buffers.get(id) ?? "";
|
|
482
|
+
if (buf.length > 0) {
|
|
483
|
+
stream.append(buf + INTERRUPTED_SUFFIX);
|
|
484
|
+
tasks.push(stream.finish());
|
|
485
|
+
}
|
|
486
|
+
streams.delete(id);
|
|
487
|
+
finalised.add(id);
|
|
488
|
+
}
|
|
489
|
+
buffers.clear();
|
|
490
|
+
await Promise.all(tasks);
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export { SlackConversationStore } from "./conversation-store.js";
|
|
2
|
+
export type { AgentSession } from "./conversation-store.js";
|
|
3
|
+
export { slackCodec } from "./codec.js";
|
|
4
|
+
export { normalizeSlackEvent, stripMentions, deriveEventId, isPlainUserMessage, } from "./ingress-normalize.js";
|
|
5
|
+
export type { SlackNeutralEvent, PlainUserMessage, } from "./ingress-normalize.js";
|
|
6
|
+
export type { ConversationKey, IncomingTurn, ResolvedSlackRespondToOptions, ReplyTarget, SlackAssistantOptions, SlackAppMentionOptions, SlackFeedback, SlackFeedbackOptions, SlackMentionReplyMode, SlackRespondToOptions, SlackThreadReplyMode, } from "./types.js";
|
|
7
|
+
export { DEFAULT_SLACK_RESPOND_TO_OPTIONS, DM_SCOPE, resolveSlackRespondToOptions, } from "./types.js";
|
|
8
|
+
export { MessageStream } from "./message-stream.js";
|
|
9
|
+
export type { MessageStreamConfig } from "./message-stream.js";
|
|
10
|
+
export { ChunkedMessageStream } from "./chunked-message-stream.js";
|
|
11
|
+
export type { ChunkedMessageStreamConfig } from "./chunked-message-stream.js";
|
|
12
|
+
export { NativeMessageStream } from "./native-stream.js";
|
|
13
|
+
export type { NativeMessageStreamConfig, NativeStreamTransport, TextStream, } from "./native-stream.js";
|
|
14
|
+
export { attachAssistant } from "./assistant.js";
|
|
15
|
+
export type { AttachAssistantConfig, AssistantHandle } from "./assistant.js";
|
|
16
|
+
export { autoCloseOpenMarkdown, detectOpenContext, renderContextOpener, } from "./auto-close-streaming.js";
|
|
17
|
+
export type { OpenMarkdownContext } from "./auto-close-streaming.js";
|
|
18
|
+
export { markdownToMrkdwn } from "./markdown-to-mrkdwn.js";
|
|
19
|
+
export { attachSlackListener } from "./slack-listener.js";
|
|
20
|
+
export type { ListenerConfig, TurnHandler } from "./slack-listener.js";
|
|
21
|
+
export { slackTaggingContext, slackFormattingContext, slackConversationModelContext, defaultSlackContext, } from "./built-in-context.js";
|
|
22
|
+
export { SanitizingHttpAgent } from "./sanitizing-http-agent.js";
|
|
23
|
+
export { buildFileContentParts } from "./download-files.js";
|
|
24
|
+
export type { SlackFileRef, AgentContentPart, FileDeliveryConfig, } from "./download-files.js";
|
|
25
|
+
export { lookupSlackUserTool, defaultSlackTools } from "./built-in-tools.js";
|
|
26
|
+
export { slack, SlackAdapter } from "./adapter.js";
|
|
27
|
+
export type { SlackAdapterOptions } from "./adapter.js";
|
|
28
|
+
export { createRunRenderer } from "./event-renderer.js";
|
|
29
|
+
export type { SlackRenderTransport } from "./render/transport.js";
|
|
30
|
+
export { decodeInteraction, conversationKeyOf } from "./interaction.js";
|
|
31
|
+
export { renderBlockKit, renderSlackMessage, buildFeedbackBlocks, FEEDBACK_ACTION_ID, } from "./render/block-kit.js";
|
|
32
|
+
export { renderSlackModal } from "./render/modal.js";
|
|
33
|
+
export { SLACK_LIMITS } from "./render/budget.js";
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAI5D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAEhC,YAAY,EACV,eAAe,EACf,YAAY,EACZ,6BAA6B,EAC7B,WAAW,EACX,qBAAqB,EACrB,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gCAAgC,EAChC,QAAQ,EACR,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,YAAY,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAE9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,YAAY,EACV,yBAAyB,EACzB,qBAAqB,EACrB,UAAU,GACX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAErE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvE,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,6BAA6B,EAC7B,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAE7E,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAExE,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { SlackConversationStore } from "./conversation-store.js";
|
|
2
|
+
// Pure Slack codec (render + normalize) shared with the managed/Connector-Outbox
|
|
3
|
+
// and webhook-ingress paths.
|
|
4
|
+
export { slackCodec } from "./codec.js";
|
|
5
|
+
export { normalizeSlackEvent, stripMentions, deriveEventId, isPlainUserMessage, } from "./ingress-normalize.js";
|
|
6
|
+
export { DEFAULT_SLACK_RESPOND_TO_OPTIONS, DM_SCOPE, resolveSlackRespondToOptions, } from "./types.js";
|
|
7
|
+
export { MessageStream } from "./message-stream.js";
|
|
8
|
+
export { ChunkedMessageStream } from "./chunked-message-stream.js";
|
|
9
|
+
export { NativeMessageStream } from "./native-stream.js";
|
|
10
|
+
export { attachAssistant } from "./assistant.js";
|
|
11
|
+
export { autoCloseOpenMarkdown, detectOpenContext, renderContextOpener, } from "./auto-close-streaming.js";
|
|
12
|
+
export { markdownToMrkdwn } from "./markdown-to-mrkdwn.js";
|
|
13
|
+
export { attachSlackListener } from "./slack-listener.js";
|
|
14
|
+
export { slackTaggingContext, slackFormattingContext, slackConversationModelContext, defaultSlackContext, } from "./built-in-context.js";
|
|
15
|
+
export { SanitizingHttpAgent } from "./sanitizing-http-agent.js";
|
|
16
|
+
export { buildFileContentParts } from "./download-files.js";
|
|
17
|
+
export { lookupSlackUserTool, defaultSlackTools } from "./built-in-tools.js";
|
|
18
|
+
export { slack, SlackAdapter } from "./adapter.js";
|
|
19
|
+
export { createRunRenderer } from "./event-renderer.js";
|
|
20
|
+
export { decodeInteraction, conversationKeyOf } from "./interaction.js";
|
|
21
|
+
export { renderBlockKit, renderSlackMessage, buildFeedbackBlocks, FEEDBACK_ACTION_ID, } from "./render/block-kit.js";
|
|
22
|
+
export { renderSlackModal } from "./render/modal.js";
|
|
23
|
+
export { SLACK_LIMITS } from "./render/budget.js";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Strip `<@U…>` / `<@U…|handle>` mention tokens and collapse whitespace. */
|
|
2
|
+
export declare const stripMentions: (text: string) => string;
|
|
3
|
+
/**
|
|
4
|
+
* Derive a stable per-delivery id for inbound idempotency. Prefer the Events
|
|
5
|
+
* API envelope `event_id` (stable across Slack's automatic retries), then the
|
|
6
|
+
* message's `client_msg_id`, then a synthesized `${channel}:${ts}`. Returns
|
|
7
|
+
* undefined only when none is available — never fabricate a random id (that
|
|
8
|
+
* would defeat dedup).
|
|
9
|
+
*/
|
|
10
|
+
export declare function deriveEventId(body: unknown, event: {
|
|
11
|
+
client_msg_id?: string;
|
|
12
|
+
ts?: string;
|
|
13
|
+
channel?: string;
|
|
14
|
+
}, channel: string): string | undefined;
|
|
15
|
+
/** Minimal shape of a plain Slack message we care about, after filtering. */
|
|
16
|
+
export interface PlainUserMessage {
|
|
17
|
+
channel: string;
|
|
18
|
+
text: string;
|
|
19
|
+
user?: string;
|
|
20
|
+
thread_ts?: string;
|
|
21
|
+
channel_type?: string;
|
|
22
|
+
files?: unknown[];
|
|
23
|
+
/** Slack's client-generated message id; a per-delivery dedup fallback. */
|
|
24
|
+
client_msg_id?: string;
|
|
25
|
+
/** Message ts; last-resort dedup key as `${channel}:${ts}`. */
|
|
26
|
+
ts?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Narrow to plain, user-authored, non-bot, non-subtyped messages with text.
|
|
30
|
+
* Rejects subtypes (edits/joins/…) except `file_share`, bot posts, and
|
|
31
|
+
* app-only messages. Anything that fails this is uninteresting to a bot.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isPlainUserMessage(message: unknown, botUserId: string | undefined): message is PlainUserMessage;
|
|
34
|
+
/** Platform-neutral ingress event the closed Intelligence side maps to its envelope. */
|
|
35
|
+
export type SlackNeutralEvent = {
|
|
36
|
+
kind: "turn";
|
|
37
|
+
/** Which Slack trigger produced this turn. */
|
|
38
|
+
source: "app_mention" | "direct_message" | "thread_reply";
|
|
39
|
+
channel: string;
|
|
40
|
+
/** Thread anchor: present for mentions and thread replies. */
|
|
41
|
+
threadTs?: string;
|
|
42
|
+
/** Inbound message ts (lets a renderer anchor a "thinking…" status on DMs). */
|
|
43
|
+
ts?: string;
|
|
44
|
+
userText: string;
|
|
45
|
+
senderUserId?: string;
|
|
46
|
+
eventId?: string;
|
|
47
|
+
hasFiles: boolean;
|
|
48
|
+
} | {
|
|
49
|
+
kind: "command";
|
|
50
|
+
command: string;
|
|
51
|
+
text: string;
|
|
52
|
+
channel: string;
|
|
53
|
+
senderUserId?: string;
|
|
54
|
+
triggerId?: string;
|
|
55
|
+
eventId?: string;
|
|
56
|
+
};
|
|
57
|
+
interface RawSlackEventBody {
|
|
58
|
+
command?: string;
|
|
59
|
+
text?: string;
|
|
60
|
+
channel_id?: string;
|
|
61
|
+
user_id?: string;
|
|
62
|
+
trigger_id?: string;
|
|
63
|
+
event_id?: string;
|
|
64
|
+
event?: Record<string, unknown>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Map a raw Slack payload (Events API envelope or slash-command body) to the
|
|
68
|
+
* platform-neutral ingress shape, or `undefined` when it isn't a turn/command
|
|
69
|
+
* worth delivering (bot echo, subtype, empty). The caller applies its own
|
|
70
|
+
* policy/entitlement gating and builds the reply route. First slice covers
|
|
71
|
+
* app_mention, direct/thread messages, and slash commands.
|
|
72
|
+
*/
|
|
73
|
+
export declare function normalizeSlackEvent(body: RawSlackEventBody, botUserId?: string): SlackNeutralEvent | undefined;
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=ingress-normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingress-normalize.d.ts","sourceRoot":"","sources":["../src/ingress-normalize.ts"],"names":[],"mappings":"AAeA,6EAA6E;AAC7E,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MACa,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,EACb,KAAK,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAChE,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,SAAS,CAMpB;AAED,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+DAA+D;IAC/D,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,IAAI,gBAAgB,CAe7B;AAED,wFAAwF;AACxF,MAAM,MAAM,iBAAiB,GACzB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,MAAM,EAAE,aAAa,GAAG,gBAAgB,GAAG,cAAc,CAAC;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;CACnB,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEN,UAAU,iBAAiB;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAMD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,iBAAiB,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,iBAAiB,GAAG,SAAS,CAyF/B"}
|