@spinabot/brigade 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/agent-loop.d.ts.map +1 -1
- package/dist/agents/agent-loop.js +51 -1
- package/dist/agents/agent-loop.js.map +1 -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/discord/account-config.d.ts +117 -0
- package/dist/agents/channels/discord/account-config.d.ts.map +1 -0
- package/dist/agents/channels/discord/account-config.js +260 -0
- package/dist/agents/channels/discord/account-config.js.map +1 -0
- package/dist/agents/channels/discord/adapter.d.ts +56 -0
- package/dist/agents/channels/discord/adapter.d.ts.map +1 -0
- package/dist/agents/channels/discord/adapter.js +526 -0
- package/dist/agents/channels/discord/adapter.js.map +1 -0
- package/dist/agents/channels/discord/approval-authorize.d.ts +43 -0
- package/dist/agents/channels/discord/approval-authorize.d.ts.map +1 -0
- package/dist/agents/channels/discord/approval-authorize.js +71 -0
- package/dist/agents/channels/discord/approval-authorize.js.map +1 -0
- package/dist/agents/channels/discord/approval-native.d.ts +68 -0
- package/dist/agents/channels/discord/approval-native.d.ts.map +1 -0
- package/dist/agents/channels/discord/approval-native.js +81 -0
- package/dist/agents/channels/discord/approval-native.js.map +1 -0
- package/dist/agents/channels/discord/command-menu.d.ts +49 -0
- package/dist/agents/channels/discord/command-menu.d.ts.map +1 -0
- package/dist/agents/channels/discord/command-menu.js +73 -0
- package/dist/agents/channels/discord/command-menu.js.map +1 -0
- package/dist/agents/channels/discord/components.d.ts +97 -0
- package/dist/agents/channels/discord/components.d.ts.map +1 -0
- package/dist/agents/channels/discord/components.js +131 -0
- package/dist/agents/channels/discord/components.js.map +1 -0
- package/dist/agents/channels/discord/connection.d.ts +387 -0
- package/dist/agents/channels/discord/connection.d.ts.map +1 -0
- package/dist/agents/channels/discord/connection.js +786 -0
- package/dist/agents/channels/discord/connection.js.map +1 -0
- package/dist/agents/channels/discord/draft-stream.d.ts +92 -0
- package/dist/agents/channels/discord/draft-stream.d.ts.map +1 -0
- package/dist/agents/channels/discord/draft-stream.js +213 -0
- package/dist/agents/channels/discord/draft-stream.js.map +1 -0
- package/dist/agents/channels/discord/format.d.ts +55 -0
- package/dist/agents/channels/discord/format.d.ts.map +1 -0
- package/dist/agents/channels/discord/format.js +247 -0
- package/dist/agents/channels/discord/format.js.map +1 -0
- package/dist/agents/channels/discord/inbound-extras.d.ts +220 -0
- package/dist/agents/channels/discord/inbound-extras.d.ts.map +1 -0
- package/dist/agents/channels/discord/inbound-extras.js +351 -0
- package/dist/agents/channels/discord/inbound-extras.js.map +1 -0
- package/dist/agents/channels/discord/index.d.ts +14 -0
- package/dist/agents/channels/discord/index.d.ts.map +1 -0
- package/dist/agents/channels/discord/index.js +14 -0
- package/dist/agents/channels/discord/index.js.map +1 -0
- package/dist/agents/channels/discord/media.d.ts +85 -0
- package/dist/agents/channels/discord/media.d.ts.map +1 -0
- package/dist/agents/channels/discord/media.js +242 -0
- package/dist/agents/channels/discord/media.js.map +1 -0
- package/dist/agents/channels/discord/module.d.ts +15 -0
- package/dist/agents/channels/discord/module.d.ts.map +1 -0
- package/dist/agents/channels/discord/module.js +22 -0
- package/dist/agents/channels/discord/module.js.map +1 -0
- package/dist/agents/channels/discord/plugin.d.ts +73 -0
- package/dist/agents/channels/discord/plugin.d.ts.map +1 -0
- package/dist/agents/channels/discord/plugin.js +303 -0
- package/dist/agents/channels/discord/plugin.js.map +1 -0
- package/dist/agents/channels/discord/probe.d.ts +93 -0
- package/dist/agents/channels/discord/probe.d.ts.map +1 -0
- package/dist/agents/channels/discord/probe.js +158 -0
- package/dist/agents/channels/discord/probe.js.map +1 -0
- package/dist/agents/channels/discord/reasoning-lane.d.ts +42 -0
- package/dist/agents/channels/discord/reasoning-lane.d.ts.map +1 -0
- package/dist/agents/channels/discord/reasoning-lane.js +68 -0
- package/dist/agents/channels/discord/reasoning-lane.js.map +1 -0
- package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
- package/dist/agents/channels/inbound-pipeline.js +65 -7
- package/dist/agents/channels/inbound-pipeline.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/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/tools/cron-tool.d.ts.map +1 -1
- package/dist/agents/tools/cron-tool.js +4 -1
- package/dist/agents/tools/cron-tool.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/core/auth-bridge.d.ts +1 -0
- package/dist/core/auth-bridge.d.ts.map +1 -1
- package/dist/core/auth-bridge.js +46 -1
- package/dist/core/auth-bridge.js.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +18 -2
- package/dist/core/server.js.map +1 -1
- package/dist/cron/isolated-agent/run-executor.d.ts +11 -0
- package/dist/cron/isolated-agent/run-executor.d.ts.map +1 -1
- package/dist/cron/isolated-agent/run-executor.js +20 -4
- package/dist/cron/isolated-agent/run-executor.js.map +1 -1
- package/dist/cron/types.d.ts +8 -0
- package/dist/cron/types.d.ts.map +1 -1
- package/dist/system-prompt/assembler.d.ts.map +1 -1
- package/dist/system-prompt/assembler.js +4 -2
- package/dist/system-prompt/assembler.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord media helpers — inbound download + outbound upload construction.
|
|
3
|
+
*
|
|
4
|
+
* INBOUND: a Discord message attachment carries a public CDN `url`
|
|
5
|
+
* (cdn.discordapp.com / media.discordapp.net). We GET that URL (NO auth header —
|
|
6
|
+
* Discord attachment URLs are public, unlike Slack's `url_private`) and save the
|
|
7
|
+
* bytes under `~/.brigade/channels/discord/media/<YYYY-MM-DD>/<id>.<ext>` so the
|
|
8
|
+
* agent can `read` the attachment by path. In convex mode the cache relocates to
|
|
9
|
+
* the OS cache dir (never under ~/.brigade, to respect the strict-zero guard).
|
|
10
|
+
*
|
|
11
|
+
* OUTBOUND: `buildDiscordAttachment` validates a local path through Brigade's
|
|
12
|
+
* outbound media-path guard (so a prompt-injected "send ~/.ssh/id_rsa" can't
|
|
13
|
+
* exfiltrate a secret), then returns the `{ path, name }` the connection wraps
|
|
14
|
+
* in a discord.js `AttachmentBuilder`. The builder itself lives in the
|
|
15
|
+
* connection (which imports discord.js); this module stays dependency-light +
|
|
16
|
+
* unit-testable.
|
|
17
|
+
*
|
|
18
|
+
* SSRF GUARD: even though Discord URLs are public, we REQUIRE https + a Discord
|
|
19
|
+
* CDN host before fetching. Without this a prompt-injected / spoofed message
|
|
20
|
+
* could carry `http://169.254.169.254/…` (cloud metadata) or any attacker host
|
|
21
|
+
* and Brigade would fetch it — a classic SSRF. Mirrors `slack/media.ts`.
|
|
22
|
+
*/
|
|
23
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { resolveChannelStateDir, resolveOsCacheDir } from "../../../config/paths.js";
|
|
26
|
+
import { tryGetRuntimeContext } from "../../../storage/runtime-context.js";
|
|
27
|
+
// Channel SDK barrel — the outbound-media exfil guard + the contract types. All
|
|
28
|
+
// contract types come from the channel SDK barrel so the channel is built
|
|
29
|
+
// entirely on `../sdk.js`.
|
|
30
|
+
import { validateOutboundMediaPath } from "../sdk.js";
|
|
31
|
+
import { resolveDiscordAttachmentKind } from "./inbound-extras.js";
|
|
32
|
+
const CHANNEL_ID = "discord";
|
|
33
|
+
/**
|
|
34
|
+
* Defensive ceiling on an inbound attachment download. Discord's own per-file
|
|
35
|
+
* limit is generous (and tiered by server boost); we cap at 50 MB so a huge
|
|
36
|
+
* upload can't blow out memory. Anything larger is skipped (the message still
|
|
37
|
+
* reaches the agent without the attachment).
|
|
38
|
+
*/
|
|
39
|
+
const MAX_BYTES = 50 * 1024 * 1024;
|
|
40
|
+
/**
|
|
41
|
+
* Discord's attachment-CDN hosts. An inbound attachment `url` only ever points
|
|
42
|
+
* at one of these — before fetching we REQUIRE https + a Discord host so a
|
|
43
|
+
* spoofed message can't make Brigade fetch an arbitrary internal URL (SSRF).
|
|
44
|
+
* Subdomains of these hosts are allowed.
|
|
45
|
+
*/
|
|
46
|
+
const DISCORD_CDN_HOSTS = ["cdn.discordapp.com", "media.discordapp.net", "cdn.discord.com"];
|
|
47
|
+
/**
|
|
48
|
+
* True when `rawUrl` is an https URL whose host is a Discord CDN host (or a
|
|
49
|
+
* subdomain of one). Anything else (non-https, a non-Discord host, or an
|
|
50
|
+
* unparseable URL) returns false so the caller refuses to fetch.
|
|
51
|
+
*/
|
|
52
|
+
export function isAllowedDiscordAttachmentUrl(rawUrl) {
|
|
53
|
+
let parsed;
|
|
54
|
+
try {
|
|
55
|
+
parsed = new URL(rawUrl);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (parsed.protocol !== "https:")
|
|
61
|
+
return false;
|
|
62
|
+
const host = parsed.hostname.toLowerCase();
|
|
63
|
+
return DISCORD_CDN_HOSTS.some((h) => host === h || host.endsWith(`.${h}`));
|
|
64
|
+
}
|
|
65
|
+
/** YYYY-MM-DD (UTC) bucket — stable filename grouping for grep / review. */
|
|
66
|
+
function dayBucket() {
|
|
67
|
+
const d = new Date();
|
|
68
|
+
const pad = (x) => String(x).padStart(2, "0");
|
|
69
|
+
return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())}`;
|
|
70
|
+
}
|
|
71
|
+
/** Derive a file extension from a Discord attachment (filename / content-type / kind). */
|
|
72
|
+
function extFromAttachment(att, kind) {
|
|
73
|
+
const name = (att.name ?? "").toLowerCase();
|
|
74
|
+
const fromName = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).replace(/[^a-z0-9]/g, "") : "";
|
|
75
|
+
if (fromName && /^[a-z0-9]+$/.test(fromName))
|
|
76
|
+
return fromName;
|
|
77
|
+
const mime = (att.contentType ?? "").toLowerCase();
|
|
78
|
+
const fromMime = mime.includes("/") ? mime.slice(mime.lastIndexOf("/") + 1).split(";")[0]?.replace(/[^a-z0-9]/g, "") : "";
|
|
79
|
+
if (fromMime && /^[a-z0-9]+$/.test(fromMime))
|
|
80
|
+
return fromMime;
|
|
81
|
+
// Sensible default by kind when nothing carried an extension.
|
|
82
|
+
switch (kind) {
|
|
83
|
+
case "image":
|
|
84
|
+
return "png";
|
|
85
|
+
case "video":
|
|
86
|
+
return "mp4";
|
|
87
|
+
case "voice":
|
|
88
|
+
return "ogg";
|
|
89
|
+
case "audio":
|
|
90
|
+
return "mp3";
|
|
91
|
+
default:
|
|
92
|
+
return "bin";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Where downloaded media lands — OS cache in convex mode, channel-state dir otherwise. */
|
|
96
|
+
function mediaBaseDir() {
|
|
97
|
+
return tryGetRuntimeContext()?.mode === "convex"
|
|
98
|
+
? path.join(resolveOsCacheDir(), "channels", CHANNEL_ID)
|
|
99
|
+
: resolveChannelStateDir(CHANNEL_ID);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Bounded retry for the transient attachment fetch. Discord's CDN occasionally
|
|
103
|
+
* blips on a 5xx or a network reset; one or two quick retries turn a dropped
|
|
104
|
+
* attachment into a delivered one. The caller still wraps the whole thing in a
|
|
105
|
+
* try/catch that degrades to `null`, so an exhausted retry never breaks message
|
|
106
|
+
* delivery. Mirrors `slack/media.ts`'s `withSlackRetry`.
|
|
107
|
+
*/
|
|
108
|
+
export async function withDiscordRetry(fn, attempts = 3) {
|
|
109
|
+
let lastErr;
|
|
110
|
+
for (let i = 0; i < attempts; i++) {
|
|
111
|
+
try {
|
|
112
|
+
return await fn();
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
lastErr = err;
|
|
116
|
+
if (i < attempts - 1)
|
|
117
|
+
await new Promise((r) => setTimeout(r, 200 * 2 ** i));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
throw lastErr;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Download one inbound Discord attachment to disk and return its normalized
|
|
124
|
+
* descriptor, or `null` when it couldn't be fetched (no url / too big / network
|
|
125
|
+
* error / non-Discord host). Never throws — a download glitch must not break
|
|
126
|
+
* message delivery.
|
|
127
|
+
*/
|
|
128
|
+
export async function downloadDiscordAttachment(args) {
|
|
129
|
+
const { attachment, log } = args;
|
|
130
|
+
const doFetch = args.fetchImpl ?? fetch;
|
|
131
|
+
const url = attachment.url || attachment.proxyURL;
|
|
132
|
+
if (!url) {
|
|
133
|
+
log?.("discord attachment skipped — no url", { id: attachment.id });
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
// SSRF guard: REFUSE any url that isn't https on a Discord CDN host — a spoofed
|
|
137
|
+
// message pointing at `http://169.254.169.254/…` (or any attacker host) must
|
|
138
|
+
// never be fetched. Checked BEFORE the fetch.
|
|
139
|
+
if (!isAllowedDiscordAttachmentUrl(url)) {
|
|
140
|
+
log?.("discord attachment skipped — url is not an allowed Discord CDN host (SSRF guard)", { id: attachment.id });
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
if (typeof attachment.size === "number" && attachment.size > MAX_BYTES) {
|
|
144
|
+
log?.("discord attachment skipped — exceeds size cap", { id: attachment.id, bytes: attachment.size, cap: MAX_BYTES });
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const kind = resolveDiscordAttachmentKind(attachment);
|
|
148
|
+
try {
|
|
149
|
+
const res = await withDiscordRetry(async () => {
|
|
150
|
+
// `redirect: "manual"` so a cross-origin 30x can't carry the request off to
|
|
151
|
+
// a non-Discord host. A redirect surfaces as a non-ok response and falls
|
|
152
|
+
// through to the `!r.ok` handler below.
|
|
153
|
+
const r = await doFetch(url, { redirect: "manual" });
|
|
154
|
+
// Retry 5xx (transient CDN blip); a 4xx falls through to the !ok handler.
|
|
155
|
+
if (!r.ok && r.status >= 500)
|
|
156
|
+
throw new Error(`discord attachment fetch failed (${r.status})`);
|
|
157
|
+
return r;
|
|
158
|
+
});
|
|
159
|
+
if (!res.ok) {
|
|
160
|
+
log?.("discord attachment download failed", { id: attachment.id, status: res.status });
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
164
|
+
if (buf.length === 0)
|
|
165
|
+
return null;
|
|
166
|
+
if (buf.length > MAX_BYTES) {
|
|
167
|
+
log?.("discord attachment skipped — exceeds size cap", { id: attachment.id, bytes: buf.length, cap: MAX_BYTES });
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const dir = path.join(mediaBaseDir(), "media", dayBucket());
|
|
171
|
+
mkdirSync(dir, { recursive: true });
|
|
172
|
+
// attachment.id is stable; use it as the filename so the same media resolves
|
|
173
|
+
// idempotently. Fall back to a timestamp.
|
|
174
|
+
const baseName = (attachment.id || `discord_${Date.now()}`).replace(/[^A-Za-z0-9_-]/g, "_");
|
|
175
|
+
const dest = path.join(dir, `${baseName}.${extFromAttachment(attachment, kind)}`);
|
|
176
|
+
writeFileSync(dest, buf, { mode: 0o600 });
|
|
177
|
+
return {
|
|
178
|
+
kind,
|
|
179
|
+
path: dest,
|
|
180
|
+
...(attachment.contentType ? { mimeType: attachment.contentType } : {}),
|
|
181
|
+
...(attachment.name ? { fileName: attachment.name } : {}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
log?.("discord attachment download failed", {
|
|
186
|
+
id: attachment.id,
|
|
187
|
+
error: err instanceof Error ? err.message : String(err),
|
|
188
|
+
});
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/** Default outbound file extension by media kind (used when the path has none). */
|
|
193
|
+
function outboundExtForKind(kind) {
|
|
194
|
+
switch (kind) {
|
|
195
|
+
case "image":
|
|
196
|
+
return "png";
|
|
197
|
+
case "video":
|
|
198
|
+
return "mp4";
|
|
199
|
+
case "voice":
|
|
200
|
+
return "ogg";
|
|
201
|
+
case "audio":
|
|
202
|
+
return "mp3";
|
|
203
|
+
default:
|
|
204
|
+
// document / sticker / anything else: leave extensionless (Discord falls
|
|
205
|
+
// back to the content the byte-sniff detects).
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Validate + shape a local file for outbound upload. Runs the path through
|
|
211
|
+
* Brigade's outbound media-path guard and throws a clear operator-facing error
|
|
212
|
+
* when the guard refuses it (the `send_media` tool surfaces it). Returns the
|
|
213
|
+
* `{ path, name, caption? }` the connection turns into a discord.js
|
|
214
|
+
* `AttachmentBuilder`.
|
|
215
|
+
*
|
|
216
|
+
* Filename precedence: an explicit `media.fileName` wins; otherwise the path's
|
|
217
|
+
* basename. When the chosen name has NO extension we append one inferred from
|
|
218
|
+
* `media.kind` (image→png, video→mp4, audio→mp3, voice→ogg) so Discord detects
|
|
219
|
+
* the file type instead of treating it as an opaque blob (an extensionless
|
|
220
|
+
* `image` upload otherwise renders as a generic file, not an inline preview).
|
|
221
|
+
*/
|
|
222
|
+
export function buildDiscordAttachment(media) {
|
|
223
|
+
const verdict = validateOutboundMediaPath(media.path);
|
|
224
|
+
if (!verdict.ok) {
|
|
225
|
+
throw new Error(`Discord: ${verdict.reason ?? "refusing to attach this file"}`);
|
|
226
|
+
}
|
|
227
|
+
let name = media.fileName || path.basename(media.path) || "file";
|
|
228
|
+
// No extension on the resolved name → infer one from the media kind so Discord
|
|
229
|
+
// type-detects the attachment. `path.extname` returns "" when there's no dot.
|
|
230
|
+
if (!path.extname(name)) {
|
|
231
|
+
const ext = outboundExtForKind(media.kind);
|
|
232
|
+
if (ext)
|
|
233
|
+
name = `${name}.${ext}`;
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
path: media.path,
|
|
237
|
+
name,
|
|
238
|
+
...(media.caption ? { caption: media.caption } : {}),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
export { MAX_BYTES as DISCORD_MEDIA_MAX_BYTES };
|
|
242
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord extension module.
|
|
3
|
+
*
|
|
4
|
+
* Registers the Discord 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.discord.enabled` is true AND a bot
|
|
7
|
+
* token resolves — so bundling this module is inert until the operator opts in.
|
|
8
|
+
*
|
|
9
|
+
* Unlike Slack's module, Discord registers NO gateway HTTP route: the Gateway
|
|
10
|
+
* (WebSocket) is the only inbound transport (no public URL needed, analogous to
|
|
11
|
+
* Slack Socket Mode / Telegram long-polling), so there is no events-mode webhook
|
|
12
|
+
* to wire. Discord mirror of `telegram/module.ts` (the polling-only shape).
|
|
13
|
+
*/
|
|
14
|
+
export declare const discordModule: import("../sdk.js").BrigadeModule;
|
|
15
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,eAAO,MAAM,aAAa,mCAKxB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord extension module.
|
|
3
|
+
*
|
|
4
|
+
* Registers the Discord 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.discord.enabled` is true AND a bot
|
|
7
|
+
* token resolves — so bundling this module is inert until the operator opts in.
|
|
8
|
+
*
|
|
9
|
+
* Unlike Slack's module, Discord registers NO gateway HTTP route: the Gateway
|
|
10
|
+
* (WebSocket) is the only inbound transport (no public URL needed, analogous to
|
|
11
|
+
* Slack Socket Mode / Telegram long-polling), so there is no events-mode webhook
|
|
12
|
+
* to wire. Discord mirror of `telegram/module.ts` (the polling-only shape).
|
|
13
|
+
*/
|
|
14
|
+
import { defineModule } from "../sdk.js";
|
|
15
|
+
import { createDiscordAdapter } from "./adapter.js";
|
|
16
|
+
export const discordModule = defineModule({
|
|
17
|
+
id: "discord",
|
|
18
|
+
register(b) {
|
|
19
|
+
b.channel(createDiscordAdapter());
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAC;IACzC,EAAE,EAAE,SAAS;IACb,QAAQ,CAAC,CAAC;QACT,CAAC,CAAC,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACnC,CAAC;CACD,CAAC,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord `ChannelPlugin` — the multi-ACCOUNT contract surface.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `slack/plugin.ts`: wraps `createDiscordAdapter()` (the per-connection
|
|
5
|
+
* implementation) with the lifecycle adapters the `ChannelPluginManager`
|
|
6
|
+
* consumes, so an operator can run MORE THAN ONE Discord bot at once via:
|
|
7
|
+
*
|
|
8
|
+
* channels.discord = {
|
|
9
|
+
* enabled: true,
|
|
10
|
+
* accounts: [
|
|
11
|
+
* { id: "main", botToken: "…AAA" },
|
|
12
|
+
* { id: "labs", botToken: "…BBB" },
|
|
13
|
+
* ],
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* - `config.listAccountIds` / `resolveAccount` → multi-account discovery
|
|
17
|
+
* - `gateway.startAccount` / `stopAccount` → per-account bot 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 (discord, labs) replies on (discord, labs), not the default
|
|
21
|
+
*
|
|
22
|
+
* Per-account state lives in a `Map<accountId, AccountRuntime>` held in this
|
|
23
|
+
* closure — one Gateway connection per account, partitioned token resolution per
|
|
24
|
+
* `channels.discord.accounts[].botToken`. Inbound dispatch reuses the shared
|
|
25
|
+
* `runChannelInboundPipeline` so the multi-account 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 `createDiscordAdapter` (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
|
+
* Slack), so the two paths never double-start a bot.
|
|
33
|
+
*
|
|
34
|
+
* Discord has no events-mode HTTP route (the Gateway is the only inbound
|
|
35
|
+
* transport), so — unlike Slack's plugin — there is NO per-account webhook-sink
|
|
36
|
+
* registry to populate.
|
|
37
|
+
*/
|
|
38
|
+
import type { BrigadeConfig } from "../../../config/types.js";
|
|
39
|
+
import { type ChannelAdapter, type ChannelOutboundTarget, type ChannelPlugin, type StartChannelsArgs } from "../sdk.js";
|
|
40
|
+
import { type ResolvedDiscordAccount } from "./account-config.js";
|
|
41
|
+
import { type DiscordProbeResult } from "./probe.js";
|
|
42
|
+
/** Dependencies the gateway hands the plugin to drive turns + replies. */
|
|
43
|
+
export interface DiscordPluginDeps {
|
|
44
|
+
/** Boot-time default agent for routing fallbacks. */
|
|
45
|
+
defaultAgentId: string;
|
|
46
|
+
/** Active gateway config — re-read fresh per inbound for live policy. */
|
|
47
|
+
loadConfig: () => BrigadeConfig;
|
|
48
|
+
/** Run one agent turn (the gateway's serialised turn executor). */
|
|
49
|
+
runTurn: StartChannelsArgs["runTurn"];
|
|
50
|
+
/**
|
|
51
|
+
* Optional adapter factory — tests inject a fake; production uses
|
|
52
|
+
* `createDiscordAdapter`. Receives the per-account scope.
|
|
53
|
+
*/
|
|
54
|
+
adapterFactory?: (args: {
|
|
55
|
+
accountId: string;
|
|
56
|
+
}) => ChannelAdapter;
|
|
57
|
+
}
|
|
58
|
+
/** Operator-grade view of a per-account bot — exposed via attached helpers. */
|
|
59
|
+
export interface DiscordPluginRuntimeView {
|
|
60
|
+
/** Currently-running account ids. */
|
|
61
|
+
startedAccountIds(): string[];
|
|
62
|
+
/** Look up the per-account adapter (or undefined when the account isn't started). */
|
|
63
|
+
getAdapter(accountId: string): ChannelAdapter | undefined;
|
|
64
|
+
/** Run a `/users/@me` probe for an account (for status / doctor). */
|
|
65
|
+
probeAccount(accountId: string, cfg: BrigadeConfig): Promise<DiscordProbeResult>;
|
|
66
|
+
}
|
|
67
|
+
/** Plugin handle with the extra per-account introspection surface attached. */
|
|
68
|
+
export type DiscordPluginHandle = ChannelPlugin<ResolvedDiscordAccount> & DiscordPluginRuntimeView;
|
|
69
|
+
/** Construct the plugin instance, capturing per-account runtime state in closure. */
|
|
70
|
+
export declare function createDiscordPlugin(deps: DiscordPluginDeps): DiscordPluginHandle;
|
|
71
|
+
/** Outbound dispatch helper for callers reaching the plugin directly. */
|
|
72
|
+
export type DiscordOutboundTarget = ChannelOutboundTarget;
|
|
73
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;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,sBAAsB,EAC3B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAgB,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAkBnE,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IACjC,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,wBAAwB;IACxC,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,kBAAkB,CAAC,CAAC;CACjF;AAED,+EAA+E;AAC/E,MAAM,MAAM,mBAAmB,GAAG,aAAa,CAAC,sBAAsB,CAAC,GAAG,wBAAwB,CAAC;AAqBnG,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,mBAAmB,CA6NhF;AAOD,yEAAyE;AACzE,MAAM,MAAM,qBAAqB,GAAG,qBAAqB,CAAC"}
|