@spinabot/brigade 1.4.0 → 1.6.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/README.md +20 -1
- package/dist/agents/channels/bundled-channel-metas.d.ts +2 -0
- package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
- package/dist/agents/channels/bundled-channel-metas.js +11 -0
- package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
- package/dist/agents/channels/manager.d.ts.map +1 -1
- package/dist/agents/channels/manager.js +18 -0
- package/dist/agents/channels/manager.js.map +1 -1
- package/dist/agents/channels/sdk.d.ts +2 -0
- package/dist/agents/channels/sdk.d.ts.map +1 -1
- package/dist/agents/channels/sdk.js +2 -0
- package/dist/agents/channels/sdk.js.map +1 -1
- package/dist/agents/channels/slack/account-config.d.ts +172 -0
- package/dist/agents/channels/slack/account-config.d.ts.map +1 -0
- package/dist/agents/channels/slack/account-config.js +353 -0
- package/dist/agents/channels/slack/account-config.js.map +1 -0
- package/dist/agents/channels/slack/account-registry.d.ts +45 -0
- package/dist/agents/channels/slack/account-registry.d.ts.map +1 -0
- package/dist/agents/channels/slack/account-registry.js +58 -0
- package/dist/agents/channels/slack/account-registry.js.map +1 -0
- package/dist/agents/channels/slack/adapter.d.ts +66 -0
- package/dist/agents/channels/slack/adapter.d.ts.map +1 -0
- package/dist/agents/channels/slack/adapter.js +547 -0
- package/dist/agents/channels/slack/adapter.js.map +1 -0
- package/dist/agents/channels/slack/approval-authorize.d.ts +43 -0
- package/dist/agents/channels/slack/approval-authorize.d.ts.map +1 -0
- package/dist/agents/channels/slack/approval-authorize.js +71 -0
- package/dist/agents/channels/slack/approval-authorize.js.map +1 -0
- package/dist/agents/channels/slack/approval-native.d.ts +70 -0
- package/dist/agents/channels/slack/approval-native.d.ts.map +1 -0
- package/dist/agents/channels/slack/approval-native.js +85 -0
- package/dist/agents/channels/slack/approval-native.js.map +1 -0
- package/dist/agents/channels/slack/blocks.d.ts +125 -0
- package/dist/agents/channels/slack/blocks.d.ts.map +1 -0
- package/dist/agents/channels/slack/blocks.js +145 -0
- package/dist/agents/channels/slack/blocks.js.map +1 -0
- package/dist/agents/channels/slack/command-menu.d.ts +44 -0
- package/dist/agents/channels/slack/command-menu.d.ts.map +1 -0
- package/dist/agents/channels/slack/command-menu.js +66 -0
- package/dist/agents/channels/slack/command-menu.js.map +1 -0
- package/dist/agents/channels/slack/connection.d.ts +422 -0
- package/dist/agents/channels/slack/connection.d.ts.map +1 -0
- package/dist/agents/channels/slack/connection.js +1042 -0
- package/dist/agents/channels/slack/connection.js.map +1 -0
- package/dist/agents/channels/slack/directory-live.d.ts +129 -0
- package/dist/agents/channels/slack/directory-live.d.ts.map +1 -0
- package/dist/agents/channels/slack/directory-live.js +148 -0
- package/dist/agents/channels/slack/directory-live.js.map +1 -0
- package/dist/agents/channels/slack/draft-stream.d.ts +93 -0
- package/dist/agents/channels/slack/draft-stream.d.ts.map +1 -0
- package/dist/agents/channels/slack/draft-stream.js +218 -0
- package/dist/agents/channels/slack/draft-stream.js.map +1 -0
- package/dist/agents/channels/slack/format.d.ts +41 -0
- package/dist/agents/channels/slack/format.d.ts.map +1 -0
- package/dist/agents/channels/slack/format.js +271 -0
- package/dist/agents/channels/slack/format.js.map +1 -0
- package/dist/agents/channels/slack/inbound-extras.d.ts +179 -0
- package/dist/agents/channels/slack/inbound-extras.d.ts.map +1 -0
- package/dist/agents/channels/slack/inbound-extras.js +257 -0
- package/dist/agents/channels/slack/inbound-extras.js.map +1 -0
- package/dist/agents/channels/slack/index.d.ts +15 -0
- package/dist/agents/channels/slack/index.d.ts.map +1 -0
- package/dist/agents/channels/slack/index.js +15 -0
- package/dist/agents/channels/slack/index.js.map +1 -0
- package/dist/agents/channels/slack/media.d.ts +90 -0
- package/dist/agents/channels/slack/media.d.ts.map +1 -0
- package/dist/agents/channels/slack/media.js +215 -0
- package/dist/agents/channels/slack/media.js.map +1 -0
- package/dist/agents/channels/slack/module.d.ts +26 -0
- package/dist/agents/channels/slack/module.d.ts.map +1 -0
- package/dist/agents/channels/slack/module.js +67 -0
- package/dist/agents/channels/slack/module.js.map +1 -0
- package/dist/agents/channels/slack/plugin.d.ts +69 -0
- package/dist/agents/channels/slack/plugin.d.ts.map +1 -0
- package/dist/agents/channels/slack/plugin.js +318 -0
- package/dist/agents/channels/slack/plugin.js.map +1 -0
- package/dist/agents/channels/slack/probe.d.ts +72 -0
- package/dist/agents/channels/slack/probe.d.ts.map +1 -0
- package/dist/agents/channels/slack/probe.js +103 -0
- package/dist/agents/channels/slack/probe.js.map +1 -0
- package/dist/agents/channels/slack/proxy-agent.d.ts +30 -0
- package/dist/agents/channels/slack/proxy-agent.d.ts.map +1 -0
- package/dist/agents/channels/slack/proxy-agent.js +44 -0
- package/dist/agents/channels/slack/proxy-agent.js.map +1 -0
- package/dist/agents/channels/slack/reasoning-lane.d.ts +42 -0
- package/dist/agents/channels/slack/reasoning-lane.d.ts.map +1 -0
- package/dist/agents/channels/slack/reasoning-lane.js +68 -0
- package/dist/agents/channels/slack/reasoning-lane.js.map +1 -0
- package/dist/agents/channels/slack/user-directory.d.ts +69 -0
- package/dist/agents/channels/slack/user-directory.d.ts.map +1 -0
- package/dist/agents/channels/slack/user-directory.js +94 -0
- package/dist/agents/channels/slack/user-directory.js.map +1 -0
- package/dist/agents/channels/slack/webhook.d.ts +89 -0
- package/dist/agents/channels/slack/webhook.d.ts.map +1 -0
- package/dist/agents/channels/slack/webhook.js +228 -0
- package/dist/agents/channels/slack/webhook.js.map +1 -0
- package/dist/agents/channels/telegram/adapter.d.ts.map +1 -1
- package/dist/agents/channels/telegram/adapter.js +10 -3
- package/dist/agents/channels/telegram/adapter.js.map +1 -1
- package/dist/agents/channels/telegram/connection.d.ts +10 -0
- package/dist/agents/channels/telegram/connection.d.ts.map +1 -1
- package/dist/agents/channels/telegram/connection.js +161 -5
- package/dist/agents/channels/telegram/connection.js.map +1 -1
- package/dist/agents/channels/telegram/format.d.ts +17 -0
- package/dist/agents/channels/telegram/format.d.ts.map +1 -1
- package/dist/agents/channels/telegram/format.js +53 -1
- package/dist/agents/channels/telegram/format.js.map +1 -1
- package/dist/agents/channels/telegram/inbound-extras.d.ts +17 -1
- package/dist/agents/channels/telegram/inbound-extras.d.ts.map +1 -1
- package/dist/agents/channels/telegram/inbound-extras.js +68 -7
- package/dist/agents/channels/telegram/inbound-extras.js.map +1 -1
- package/dist/agents/channels/telegram/media.d.ts +8 -0
- package/dist/agents/channels/telegram/media.d.ts.map +1 -1
- package/dist/agents/channels/telegram/media.js +30 -2
- package/dist/agents/channels/telegram/media.js.map +1 -1
- package/dist/agents/channels/telegram/webhook.d.ts.map +1 -1
- package/dist/agents/channels/telegram/webhook.js +7 -1
- package/dist/agents/channels/telegram/webhook.js.map +1 -1
- package/dist/agents/extensions/modules/index.d.ts.map +1 -1
- package/dist/agents/extensions/modules/index.js +5 -0
- package/dist/agents/extensions/modules/index.js.map +1 -1
- package/dist/agents/extensions/types.d.ts +11 -0
- package/dist/agents/extensions/types.d.ts.map +1 -1
- package/dist/agents/extensions/types.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/convex-cmd.d.ts +2 -1
- package/dist/cli/commands/convex-cmd.d.ts.map +1 -1
- package/dist/cli/commands/convex-cmd.js +79 -6
- package/dist/cli/commands/convex-cmd.js.map +1 -1
- package/dist/cli/program/build-program.js +1 -1
- package/dist/cli/program/build-program.js.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +24 -5
- package/dist/core/server.js.map +1 -1
- package/package.json +4 -1
- package/scripts/convex-dev.mjs +28 -2
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack media helpers — inbound download + outbound upload construction.
|
|
3
|
+
*
|
|
4
|
+
* INBOUND: Slack doesn't push file bytes; a message event carries file objects
|
|
5
|
+
* with an authenticated `url_private`. To get the bytes we GET that URL with an
|
|
6
|
+
* `Authorization: Bearer <botToken>` header (a plain fetch returns the login
|
|
7
|
+
* HTML otherwise). Bytes are saved under
|
|
8
|
+
* `~/.brigade/channels/slack/media/<YYYY-MM-DD>/<fileId>.<ext>` so the agent can
|
|
9
|
+
* `read` the attachment by path. In convex mode the cache relocates to the OS
|
|
10
|
+
* cache dir (never under ~/.brigade, to respect the strict-zero guard).
|
|
11
|
+
*
|
|
12
|
+
* OUTBOUND: `uploadSlackFile` posts a local path's bytes via the Web API's
|
|
13
|
+
* `files.uploadV2`, after running the path through Brigade's outbound media-path
|
|
14
|
+
* guard so a prompt-injected "send ~/.ssh/id_rsa" can't exfiltrate a secret. The
|
|
15
|
+
* `@slack/web-api` `WebClient` is injected (not imported here) so this module
|
|
16
|
+
* stays dependency-light + unit-testable.
|
|
17
|
+
*/
|
|
18
|
+
import { createReadStream, mkdirSync, writeFileSync } from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { resolveChannelStateDir, resolveOsCacheDir } from "../../../config/paths.js";
|
|
21
|
+
import { tryGetRuntimeContext } from "../../../storage/runtime-context.js";
|
|
22
|
+
// Channel SDK barrel — the outbound-media exfil guard + the contract types.
|
|
23
|
+
// All contract types come from the channel SDK barrel so the channel is built
|
|
24
|
+
// entirely on `../sdk.js`.
|
|
25
|
+
import { validateOutboundMediaPath, } from "../sdk.js";
|
|
26
|
+
import { resolveSlackFileKind } from "./inbound-extras.js";
|
|
27
|
+
const CHANNEL_ID = "slack";
|
|
28
|
+
/**
|
|
29
|
+
* Defensive ceiling on an inbound file download. Slack's own per-file limit is
|
|
30
|
+
* generous; we cap at 50 MB so a huge upload can't blow out memory. Anything
|
|
31
|
+
* larger is skipped (the message still reaches the agent without the
|
|
32
|
+
* attachment).
|
|
33
|
+
*/
|
|
34
|
+
const MAX_BYTES = 50 * 1024 * 1024;
|
|
35
|
+
/**
|
|
36
|
+
* Slack's file-CDN hosts. An inbound `url_private` only ever points at one of
|
|
37
|
+
* these — we attach the bot token as `Authorization: Bearer …`, so before
|
|
38
|
+
* fetching we REQUIRE https + a Slack host. Without this guard a
|
|
39
|
+
* prompt-injected / spoofed event could carry `http://169.254.169.254/…` (cloud
|
|
40
|
+
* metadata) or any attacker host and Brigade would happily send the bot token to
|
|
41
|
+
* it (SSRF + token exfiltration). Subdomains of these hosts are allowed.
|
|
42
|
+
*/
|
|
43
|
+
const SLACK_FILE_HOSTS = ["slack.com", "slack-edge.com", "slack-files.com"];
|
|
44
|
+
/**
|
|
45
|
+
* True when `rawUrl` is an https URL whose host is a Slack file-CDN host (or a
|
|
46
|
+
* subdomain of one). Anything else (non-https, a non-Slack host, or an
|
|
47
|
+
* unparseable URL) returns false so the caller refuses to fetch with the token.
|
|
48
|
+
*/
|
|
49
|
+
export function isAllowedSlackFileUrl(rawUrl) {
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = new URL(rawUrl);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (parsed.protocol !== "https:")
|
|
58
|
+
return false;
|
|
59
|
+
const host = parsed.hostname.toLowerCase();
|
|
60
|
+
return SLACK_FILE_HOSTS.some((h) => host === h || host.endsWith(`.${h}`));
|
|
61
|
+
}
|
|
62
|
+
/** YYYY-MM-DD (UTC) bucket — stable filename grouping for grep / review. */
|
|
63
|
+
function dayBucket() {
|
|
64
|
+
const d = new Date();
|
|
65
|
+
const pad = (x) => String(x).padStart(2, "0");
|
|
66
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
|
|
67
|
+
}
|
|
68
|
+
/** Derive a file extension from a Slack file object (filetype / name / kind). */
|
|
69
|
+
function extFromFile(file, kind) {
|
|
70
|
+
const type = (file.filetype ?? "").toLowerCase();
|
|
71
|
+
if (type && /^[a-z0-9]+$/.test(type))
|
|
72
|
+
return type;
|
|
73
|
+
const fromName = path.extname(file.name ?? "").replace(/^\./, "").toLowerCase();
|
|
74
|
+
if (fromName && /^[a-z0-9]+$/.test(fromName))
|
|
75
|
+
return fromName;
|
|
76
|
+
// Sensible default by kind when nothing carried an extension.
|
|
77
|
+
switch (kind) {
|
|
78
|
+
case "image":
|
|
79
|
+
return "png";
|
|
80
|
+
case "video":
|
|
81
|
+
return "mp4";
|
|
82
|
+
case "voice":
|
|
83
|
+
return "m4a";
|
|
84
|
+
case "audio":
|
|
85
|
+
return "mp3";
|
|
86
|
+
default:
|
|
87
|
+
return "bin";
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** Where downloaded media lands — OS cache in convex mode, channel-state dir otherwise. */
|
|
91
|
+
function mediaBaseDir() {
|
|
92
|
+
return tryGetRuntimeContext()?.mode === "convex"
|
|
93
|
+
? path.join(resolveOsCacheDir(), "channels", CHANNEL_ID)
|
|
94
|
+
: resolveChannelStateDir(CHANNEL_ID);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Bounded retry for the transient file fetch. Slack's file CDN occasionally
|
|
98
|
+
* blips on a 5xx or a network reset; one or two quick retries turn a dropped
|
|
99
|
+
* attachment into a delivered one. The caller still wraps the whole thing in a
|
|
100
|
+
* try/catch that degrades to `null`, so an exhausted retry never breaks message
|
|
101
|
+
* delivery. Mirrors Telegram's `withMediaRetry`.
|
|
102
|
+
*/
|
|
103
|
+
export async function withSlackRetry(fn, attempts = 3) {
|
|
104
|
+
let lastErr;
|
|
105
|
+
for (let i = 0; i < attempts; i++) {
|
|
106
|
+
try {
|
|
107
|
+
return await fn();
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
lastErr = err;
|
|
111
|
+
if (i < attempts - 1)
|
|
112
|
+
await new Promise((r) => setTimeout(r, 200 * 2 ** i));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
throw lastErr;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Download one inbound Slack file to disk and return its normalized descriptor,
|
|
119
|
+
* or `null` when the file couldn't be fetched (no url / too big / network error
|
|
120
|
+
* / tombstoned). Never throws — a download glitch must not break message
|
|
121
|
+
* delivery. The `Authorization: Bearer` header is REQUIRED; a plain GET of
|
|
122
|
+
* `url_private` returns Slack's HTML login page, not the bytes.
|
|
123
|
+
*/
|
|
124
|
+
export async function downloadSlackFile(args) {
|
|
125
|
+
const { file, token, log } = args;
|
|
126
|
+
const doFetch = args.fetchImpl ?? fetch;
|
|
127
|
+
const url = file.url_private_download || file.url_private;
|
|
128
|
+
if (!url) {
|
|
129
|
+
log?.("slack file skipped — no private url", { fileId: file.id });
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
// SSRF / token-exfil guard: the bot token is attached below, so REFUSE any
|
|
133
|
+
// url that isn't https on a Slack file-CDN host. A spoofed event pointing at
|
|
134
|
+
// `http://169.254.169.254/…` (or any attacker host) must never receive the
|
|
135
|
+
// token. Checked BEFORE the fetch so the token never leaves the process.
|
|
136
|
+
if (!isAllowedSlackFileUrl(url)) {
|
|
137
|
+
log?.("slack file skipped — url is not an allowed Slack host (SSRF guard)", { fileId: file.id });
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
if (typeof file.size === "number" && file.size > MAX_BYTES) {
|
|
141
|
+
log?.("slack file skipped — exceeds size cap", { fileId: file.id, bytes: file.size, cap: MAX_BYTES });
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
const kind = resolveSlackFileKind(file);
|
|
145
|
+
try {
|
|
146
|
+
const res = await withSlackRetry(async () => {
|
|
147
|
+
// `redirect: "manual"` so a cross-origin 30x can't carry the
|
|
148
|
+
// Authorization header off to a non-Slack host (Slack file urls don't
|
|
149
|
+
// legitimately redirect cross-origin). A redirect surfaces as an opaque
|
|
150
|
+
// / non-ok response and falls through to the `!r.ok` handler below.
|
|
151
|
+
const r = await doFetch(url, { headers: { Authorization: `Bearer ${token}` }, redirect: "manual" });
|
|
152
|
+
// Retry 5xx (transient server/CDN blip); a 4xx falls through to the !ok
|
|
153
|
+
// handler below (no point retrying a permanent client error).
|
|
154
|
+
if (!r.ok && r.status >= 500)
|
|
155
|
+
throw new Error(`slack file fetch failed (${r.status})`);
|
|
156
|
+
return r;
|
|
157
|
+
});
|
|
158
|
+
if (!res.ok) {
|
|
159
|
+
log?.("slack file download failed", { fileId: file.id, status: res.status });
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
163
|
+
if (buf.length === 0)
|
|
164
|
+
return null;
|
|
165
|
+
if (buf.length > MAX_BYTES) {
|
|
166
|
+
log?.("slack file skipped — exceeds size cap", { fileId: file.id, bytes: buf.length, cap: MAX_BYTES });
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const dir = path.join(mediaBaseDir(), "media", dayBucket());
|
|
170
|
+
mkdirSync(dir, { recursive: true });
|
|
171
|
+
// file.id is stable across re-deliveries; use it as the filename so the same
|
|
172
|
+
// media resolves idempotently. Fall back to a timestamp.
|
|
173
|
+
const baseName = (file.id || `slack_${Date.now()}`).replace(/[^A-Za-z0-9_-]/g, "_");
|
|
174
|
+
const dest = path.join(dir, `${baseName}.${extFromFile(file, kind)}`);
|
|
175
|
+
writeFileSync(dest, buf, { mode: 0o600 });
|
|
176
|
+
return {
|
|
177
|
+
kind,
|
|
178
|
+
path: dest,
|
|
179
|
+
...(file.mimetype ? { mimeType: file.mimetype } : {}),
|
|
180
|
+
...(file.name ? { fileName: file.name } : {}),
|
|
181
|
+
...(file.title ? { caption: file.title } : {}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
log?.("slack file download failed", {
|
|
186
|
+
fileId: file.id,
|
|
187
|
+
error: err instanceof Error ? err.message : String(err),
|
|
188
|
+
});
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Upload a local file (image / video / audio / doc) to a Slack channel via
|
|
194
|
+
* `files.uploadV2`, after running the path through Brigade's outbound
|
|
195
|
+
* media-path guard. Throws a clear operator-facing error when the guard refuses
|
|
196
|
+
* the path (the `send_media` tool surfaces it). The file is streamed from disk;
|
|
197
|
+
* the caption rides as `initial_comment`.
|
|
198
|
+
*/
|
|
199
|
+
export async function uploadSlackFile(args) {
|
|
200
|
+
const { media } = args;
|
|
201
|
+
const verdict = validateOutboundMediaPath(media.path);
|
|
202
|
+
if (!verdict.ok) {
|
|
203
|
+
throw new Error(`Slack: ${verdict.reason ?? "refusing to attach this file"}`);
|
|
204
|
+
}
|
|
205
|
+
const filename = media.fileName || path.basename(media.path) || "file";
|
|
206
|
+
await args.client.files.uploadV2({
|
|
207
|
+
channel_id: args.channelId,
|
|
208
|
+
file: createReadStream(media.path),
|
|
209
|
+
filename,
|
|
210
|
+
...(media.caption ? { initial_comment: media.caption } : {}),
|
|
211
|
+
...(args.threadId ? { thread_ts: args.threadId } : {}),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
export { MAX_BYTES as SLACK_MEDIA_MAX_BYTES };
|
|
215
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack extension module.
|
|
3
|
+
*
|
|
4
|
+
* Registers the Slack channel adapter through the seam. The loader gates it by
|
|
5
|
+
* the usual extension config (`extensions.disabled` / `entries`), and the
|
|
6
|
+
* adapter itself only starts when `channels.slack.enabled` is true AND a bot
|
|
7
|
+
* token resolves — so bundling this module is inert until the operator opts in.
|
|
8
|
+
*
|
|
9
|
+
* In EVENTS transport mode (`channels.slack.mode: "events"`) the module ALSO
|
|
10
|
+
* registers a gateway HTTP route PER configured workspace that receives Slack's
|
|
11
|
+
* event POSTs and feeds them into the started adapter for THAT workspace (after
|
|
12
|
+
* verifying the request signature with that workspace's signing secret). Socket
|
|
13
|
+
* mode (the default) registers no HTTP surface. Slack mirror of
|
|
14
|
+
* `telegram/module.ts`, extended for multi-workspace inbound:
|
|
15
|
+
*
|
|
16
|
+
* - The default account keeps the SAME inline adapter + base path
|
|
17
|
+
* (`/slack/events`) as the single-workspace path — byte-identical.
|
|
18
|
+
* - Each NAMED account (only present when >1 account is configured) gets its
|
|
19
|
+
* own route on a distinct path (`resolveSlackEventsPath`), whose `resolveSink`
|
|
20
|
+
* looks up THAT account's STARTED adapter at request time via the per-account
|
|
21
|
+
* adapter registry (`account-registry.ts`) the plugin populates on
|
|
22
|
+
* `startAccount`. `b.httpRoute(...)` is only available at `register()` time
|
|
23
|
+
* (before the plugin starts accounts), so the late binding is essential.
|
|
24
|
+
*/
|
|
25
|
+
export declare const slackModule: import("../sdk.js").BrigadeModule;
|
|
26
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAcH,eAAO,MAAM,WAAW,mCAuCtB,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack extension module.
|
|
3
|
+
*
|
|
4
|
+
* Registers the Slack channel adapter through the seam. The loader gates it by
|
|
5
|
+
* the usual extension config (`extensions.disabled` / `entries`), and the
|
|
6
|
+
* adapter itself only starts when `channels.slack.enabled` is true AND a bot
|
|
7
|
+
* token resolves — so bundling this module is inert until the operator opts in.
|
|
8
|
+
*
|
|
9
|
+
* In EVENTS transport mode (`channels.slack.mode: "events"`) the module ALSO
|
|
10
|
+
* registers a gateway HTTP route PER configured workspace that receives Slack's
|
|
11
|
+
* event POSTs and feeds them into the started adapter for THAT workspace (after
|
|
12
|
+
* verifying the request signature with that workspace's signing secret). Socket
|
|
13
|
+
* mode (the default) registers no HTTP surface. Slack mirror of
|
|
14
|
+
* `telegram/module.ts`, extended for multi-workspace inbound:
|
|
15
|
+
*
|
|
16
|
+
* - The default account keeps the SAME inline adapter + base path
|
|
17
|
+
* (`/slack/events`) as the single-workspace path — byte-identical.
|
|
18
|
+
* - Each NAMED account (only present when >1 account is configured) gets its
|
|
19
|
+
* own route on a distinct path (`resolveSlackEventsPath`), whose `resolveSink`
|
|
20
|
+
* looks up THAT account's STARTED adapter at request time via the per-account
|
|
21
|
+
* adapter registry (`account-registry.ts`) the plugin populates on
|
|
22
|
+
* `startAccount`. `b.httpRoute(...)` is only available at `register()` time
|
|
23
|
+
* (before the plugin starts accounts), so the late binding is essential.
|
|
24
|
+
*/
|
|
25
|
+
import { defineModule } from "../sdk.js";
|
|
26
|
+
import { listSlackAccountIds, resolveSlackEventsPath, resolveSlackSigningSecret, slackEventsConfig, SLACK_DEFAULT_ACCOUNT_ID, } from "./account-config.js";
|
|
27
|
+
import { getSlackAccountSink } from "./account-registry.js";
|
|
28
|
+
import { createSlackAdapter } from "./adapter.js";
|
|
29
|
+
import { buildSlackWebhookRoute } from "./webhook.js";
|
|
30
|
+
export const slackModule = defineModule({
|
|
31
|
+
id: "slack",
|
|
32
|
+
register(b) {
|
|
33
|
+
const adapter = createSlackAdapter();
|
|
34
|
+
b.channel(adapter);
|
|
35
|
+
// Events transport: register the inbound gateway route(s). Gated on config so
|
|
36
|
+
// a Socket Mode (default) install exposes no inbound HTTP surface.
|
|
37
|
+
const transport = slackEventsConfig(b.config);
|
|
38
|
+
if (transport.mode !== "events")
|
|
39
|
+
return;
|
|
40
|
+
const accountIds = listSlackAccountIds(b.config);
|
|
41
|
+
// `listSlackAccountIds` returns `["default"]` for a single-workspace install
|
|
42
|
+
// and the named ids for a multi-workspace one. Single → byte-identical to the
|
|
43
|
+
// legacy single-route registration: the default route resolves the inline
|
|
44
|
+
// adapter directly. Multi → one route per workspace, each resolving its
|
|
45
|
+
// account's started adapter via the registry at request time.
|
|
46
|
+
const isMultiWorkspace = accountIds.length > 1;
|
|
47
|
+
for (const accountId of accountIds) {
|
|
48
|
+
const isDefault = accountId === SLACK_DEFAULT_ACCOUNT_ID;
|
|
49
|
+
b.httpRoute(buildSlackWebhookRoute({
|
|
50
|
+
// Default account keeps the base path; named accounts get a distinct,
|
|
51
|
+
// collision-free path so two workspaces never share one route.
|
|
52
|
+
path: resolveSlackEventsPath(b.config, accountId),
|
|
53
|
+
// Each route verifies with ITS OWN account's signing secret.
|
|
54
|
+
signingSecret: resolveSlackSigningSecret(b.config, accountId),
|
|
55
|
+
// The default account in a single-workspace install feeds the inline
|
|
56
|
+
// adapter directly (the legacy adapter owns the default lifecycle).
|
|
57
|
+
// Every other case resolves the per-account started adapter at request
|
|
58
|
+
// time — when the plugin owns the lifecycle the inline adapter steps
|
|
59
|
+
// aside, so we must look up the live one rather than capture it here.
|
|
60
|
+
resolveSink: isDefault && !isMultiWorkspace
|
|
61
|
+
? () => adapter
|
|
62
|
+
: () => getSlackAccountSink(accountId) ?? null,
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EACN,mBAAmB,EACnB,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,EACjB,wBAAwB,GACxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAqB,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAEtD,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;IACvC,EAAE,EAAE,OAAO;IACX,QAAQ,CAAC,CAAC;QACT,MAAM,OAAO,GAAG,kBAAkB,EAAkB,CAAC;QACrD,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACnB,8EAA8E;QAC9E,mEAAmE;QACnE,MAAM,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAC,MAAe,CAAC,CAAC;QACvD,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO;QAExC,MAAM,UAAU,GAAG,mBAAmB,CAAC,CAAC,CAAC,MAAe,CAAC,CAAC;QAC1D,6EAA6E;QAC7E,8EAA8E;QAC9E,0EAA0E;QAC1E,wEAAwE;QACxE,8DAA8D;QAC9D,MAAM,gBAAgB,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;QAC/C,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACpC,MAAM,SAAS,GAAG,SAAS,KAAK,wBAAwB,CAAC;YACzD,CAAC,CAAC,SAAS,CACV,sBAAsB,CAAC;gBACtB,sEAAsE;gBACtE,+DAA+D;gBAC/D,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,MAAe,EAAE,SAAS,CAAC;gBAC1D,6DAA6D;gBAC7D,aAAa,EAAE,yBAAyB,CAAC,CAAC,CAAC,MAAe,EAAE,SAAS,CAAC;gBACtE,qEAAqE;gBACrE,oEAAoE;gBACpE,uEAAuE;gBACvE,qEAAqE;gBACrE,sEAAsE;gBACtE,WAAW,EACV,SAAS,IAAI,CAAC,gBAAgB;oBAC7B,CAAC,CAAC,GAAG,EAAE,CAAC,OAAO;oBACf,CAAC,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,SAAS,CAAC,IAAI,IAAI;aAChD,CAAC,CACF,CAAC;QACH,CAAC;IACF,CAAC;CACD,CAAC,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack `ChannelPlugin` — the multi-WORKSPACE contract surface.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `telegram/plugin.ts`: wraps `createSlackAdapter()` (the per-connection
|
|
5
|
+
* implementation) with the lifecycle adapters the `ChannelPluginManager`
|
|
6
|
+
* consumes, so an operator can run MORE THAN ONE Slack workspace at once via:
|
|
7
|
+
*
|
|
8
|
+
* channels.slack = {
|
|
9
|
+
* enabled: true,
|
|
10
|
+
* accounts: [
|
|
11
|
+
* { id: "acme", botToken: "xoxb-AAA", appToken: "xapp-AAA" },
|
|
12
|
+
* { id: "labs", botToken: "xoxb-BBB", appToken: "xapp-BBB" },
|
|
13
|
+
* ],
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* - `config.listAccountIds` / `resolveAccount` → multi-workspace discovery
|
|
17
|
+
* - `gateway.startAccount` / `stopAccount` → per-workspace app lifecycle
|
|
18
|
+
* - `outbound.sendText` / `sendMedia` → routes by `target.accountId`
|
|
19
|
+
* - per-account approval-dispatcher registration → an exec-gate prompt raised
|
|
20
|
+
* by a turn on (slack, labs) replies on (slack, labs), not the default
|
|
21
|
+
*
|
|
22
|
+
* Per-account state lives in a `Map<accountId, AccountRuntime>` held in this
|
|
23
|
+
* closure — one app connection per account, partitioned token resolution per
|
|
24
|
+
* `channels.slack.accounts[].botToken`. Inbound dispatch reuses the shared
|
|
25
|
+
* `runChannelInboundPipeline` so the multi-workspace path carries the identical
|
|
26
|
+
* ACL + debounce + abort + approval-reply + approval-callback surface as the
|
|
27
|
+
* legacy single-adapter manager.
|
|
28
|
+
*
|
|
29
|
+
* The legacy single-account `createSlackAdapter` (started by the legacy
|
|
30
|
+
* `startChannels` manager) STEPS ASIDE when >1 account is configured — its
|
|
31
|
+
* `isConfigured` returns false for the default account in that case (mirrors
|
|
32
|
+
* Telegram), so the two paths never double-start an app.
|
|
33
|
+
*/
|
|
34
|
+
import type { BrigadeConfig } from "../../../config/types.js";
|
|
35
|
+
import { type ChannelAdapter, type ChannelOutboundTarget, type ChannelPlugin, type StartChannelsArgs } from "../sdk.js";
|
|
36
|
+
import { type ResolvedSlackAccount } from "./account-config.js";
|
|
37
|
+
import { type SlackProbeResult } from "./probe.js";
|
|
38
|
+
/** Dependencies the gateway hands the plugin to drive turns + replies. */
|
|
39
|
+
export interface SlackPluginDeps {
|
|
40
|
+
/** Boot-time default agent for routing fallbacks. */
|
|
41
|
+
defaultAgentId: string;
|
|
42
|
+
/** Active gateway config — re-read fresh per inbound for live policy. */
|
|
43
|
+
loadConfig: () => BrigadeConfig;
|
|
44
|
+
/** Run one agent turn (the gateway's serialised turn executor). */
|
|
45
|
+
runTurn: StartChannelsArgs["runTurn"];
|
|
46
|
+
/**
|
|
47
|
+
* Optional adapter factory — tests inject a fake; production uses
|
|
48
|
+
* `createSlackAdapter`. Receives the per-account scope.
|
|
49
|
+
*/
|
|
50
|
+
adapterFactory?: (args: {
|
|
51
|
+
accountId: string;
|
|
52
|
+
}) => ChannelAdapter;
|
|
53
|
+
}
|
|
54
|
+
/** Operator-grade view of a per-account app — exposed via attached helpers. */
|
|
55
|
+
export interface SlackPluginRuntimeView {
|
|
56
|
+
/** Currently-running account ids. */
|
|
57
|
+
startedAccountIds(): string[];
|
|
58
|
+
/** Look up the per-account adapter (or undefined when the account isn't started). */
|
|
59
|
+
getAdapter(accountId: string): ChannelAdapter | undefined;
|
|
60
|
+
/** Run an `auth.test` probe for an account (for status / doctor). */
|
|
61
|
+
probeAccount(accountId: string, cfg: BrigadeConfig): Promise<SlackProbeResult>;
|
|
62
|
+
}
|
|
63
|
+
/** Plugin handle with the extra per-account introspection surface attached. */
|
|
64
|
+
export type SlackPluginHandle = ChannelPlugin<ResolvedSlackAccount> & SlackPluginRuntimeView;
|
|
65
|
+
/** Construct the plugin instance, capturing per-account runtime state in closure. */
|
|
66
|
+
export declare function createSlackPlugin(deps: SlackPluginDeps): SlackPluginHandle;
|
|
67
|
+
/** Outbound dispatch helper for callers reaching the plugin directly. */
|
|
68
|
+
export type SlackOutboundTarget = ChannelOutboundTarget;
|
|
69
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAK9D,OAAO,EAON,KAAK,cAAc,EAOnB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAKlB,KAAK,iBAAiB,EAEtB,MAAM,WAAW,CAAC;AACnB,OAAO,EAMN,KAAK,oBAAoB,EACzB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAc,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAkB/D,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC/B,qDAAqD;IACrD,cAAc,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,UAAU,EAAE,MAAM,aAAa,CAAC;IAChC,mEAAmE;IACnE,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACtC;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,cAAc,CAAC;CACjE;AAED,+EAA+E;AAC/E,MAAM,WAAW,sBAAsB;IACtC,qCAAqC;IACrC,iBAAiB,IAAI,MAAM,EAAE,CAAC;IAC9B,qFAAqF;IACrF,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;IAC1D,qEAAqE;IACrE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC/E;AAED,+EAA+E;AAC/E,MAAM,MAAM,iBAAiB,GAAG,aAAa,CAAC,oBAAoB,CAAC,GAAG,sBAAsB,CAAC;AAqB7F,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,iBAAiB,CA+O1E;AAOD,yEAAyE;AACzE,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,CAAC"}
|