@spinabot/brigade 1.5.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 +4 -0
- package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
- package/dist/agents/channels/bundled-channel-metas.js +22 -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/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 +4 -0
- package/dist/agents/channels/sdk.d.ts.map +1 -1
- package/dist/agents/channels/sdk.js +4 -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/format.d.ts.map +1 -1
- package/dist/agents/channels/telegram/format.js +17 -1
- package/dist/agents/channels/telegram/format.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 +10 -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 +40 -5
- 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 +5 -1
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord channel adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Brigade `ChannelAdapter` contract on top of the discord.js
|
|
5
|
+
* Gateway + REST connection. Like Slack, Discord is TOKEN-based: the operator
|
|
6
|
+
* pastes a bot token from the Discord Developer Portal, so this adapter declares
|
|
7
|
+
* a `setup` wizard (one credential) and has NO QR/link flow. Enablement is
|
|
8
|
+
* explicit — `channels.discord.enabled: true` plus a resolvable bot token.
|
|
9
|
+
*
|
|
10
|
+
* Modeled directly on `slack/adapter.ts`: same health-flag mirroring, same
|
|
11
|
+
* deferred-media passthrough on inbound, same chunk-then-send outbound shape
|
|
12
|
+
* (chunk markdown ≤2000, convert each chunk to Discord markup, send). Discord
|
|
13
|
+
* markup never "fails to parse" the way Telegram HTML can, so the outbound path
|
|
14
|
+
* is simple — an empty rendered chunk falls back to the raw chunk.
|
|
15
|
+
*
|
|
16
|
+
* Capabilities: edit (message.edit), unsend (message.delete), reactions
|
|
17
|
+
* (message.react), reply (reply reference + threads), threads, media
|
|
18
|
+
* (AttachmentBuilder), buttons (ActionRow + Button), and NATIVE slash commands
|
|
19
|
+
* (registered via REST application commands on connect). Unlike Slack, Discord's
|
|
20
|
+
* command menu IS pushed programmatically — `nativeCommands: true`.
|
|
21
|
+
*/
|
|
22
|
+
import { loadConfig } from "../../../core/config.js";
|
|
23
|
+
// Channel SDK barrel — the single import surface for the channel-authoring
|
|
24
|
+
// contract + shared helpers. Contract types + `chunkText` + `buildBundledCommands`
|
|
25
|
+
// come from one place instead of scattered paths.
|
|
26
|
+
import { buildBundledCommands, chunkText, } from "../sdk.js";
|
|
27
|
+
import { discordChannelEnabled, discordLiveStreamEnabled, discordStreamThrottleMs, discordSurfaceReasoning, listDiscordAccountIds, resolveDiscordBotToken, resolveDiscordProxyUrl, DISCORD_CHANNEL_ID, DISCORD_DEFAULT_ACCOUNT_ID, } from "./account-config.js";
|
|
28
|
+
import { resolveDiscordApprover } from "./approval-authorize.js";
|
|
29
|
+
import { buildDiscordApprovalMessage } from "./approval-native.js";
|
|
30
|
+
import { buildDiscordButtonRows } from "./components.js";
|
|
31
|
+
import { buildDiscordCommandManifest } from "./command-menu.js";
|
|
32
|
+
import { connectDiscord } from "./connection.js";
|
|
33
|
+
import { createDraftStream } from "./draft-stream.js";
|
|
34
|
+
import { discordTextIsEmpty, markdownToDiscord } from "./format.js";
|
|
35
|
+
import { splitDiscordReasoning } from "./reasoning-lane.js";
|
|
36
|
+
/** Discord's per-message text limit (chars) for chunked sends. */
|
|
37
|
+
const DISCORD_TEXT_LIMIT = 2_000;
|
|
38
|
+
export function createDiscordAdapter(opts = {}) {
|
|
39
|
+
const accountId = opts.accountId?.trim() || DISCORD_DEFAULT_ACCOUNT_ID;
|
|
40
|
+
const connectImpl = opts.connectImpl ?? connectDiscord;
|
|
41
|
+
let connection = null;
|
|
42
|
+
// The ChannelStartContext doesn't carry the config, but the manager ALWAYS
|
|
43
|
+
// calls `isConfigured(cfg, env)` immediately before `start(ctx)` — so we
|
|
44
|
+
// capture the config + env it passed there and read the token from them in
|
|
45
|
+
// start(). This avoids a second config load and keeps the adapter pure.
|
|
46
|
+
let lastConfig = null;
|
|
47
|
+
let lastEnv = process.env;
|
|
48
|
+
// Health flags mirrored from the connection lifecycle so health() never has to
|
|
49
|
+
// round-trip Discord on the hot path (cron timer / send pre-flight).
|
|
50
|
+
// - `connected` flips true on a successful login + ready.
|
|
51
|
+
// - `tokenInvalid` is STICKY: an auth error means the token is dead and the
|
|
52
|
+
// only recovery is `brigade channels add --channel discord` with a new token.
|
|
53
|
+
let connected = false;
|
|
54
|
+
let tokenInvalid = false;
|
|
55
|
+
const adapter = {
|
|
56
|
+
id: DISCORD_CHANNEL_ID,
|
|
57
|
+
label: "Discord",
|
|
58
|
+
isConfigured(cfg, env) {
|
|
59
|
+
// Capture for start() — the manager calls this right before start(ctx).
|
|
60
|
+
lastConfig = cfg;
|
|
61
|
+
lastEnv = env ?? process.env;
|
|
62
|
+
if (!discordChannelEnabled(cfg))
|
|
63
|
+
return false;
|
|
64
|
+
// Need a resolvable bot token (config `${VAR}` ref, sealed token, or
|
|
65
|
+
// DISCORD_BOT_TOKEN env).
|
|
66
|
+
if (!resolveDiscordBotToken(cfg, accountId, env ?? process.env))
|
|
67
|
+
return false;
|
|
68
|
+
// Multi-account follow-up: when the operator declares >1 account, the
|
|
69
|
+
// plugin path owns lifecycle and the legacy single adapter steps aside.
|
|
70
|
+
const isLegacyAdapter = accountId === DISCORD_DEFAULT_ACCOUNT_ID;
|
|
71
|
+
if (isLegacyAdapter && listDiscordAccountIds(cfg).length > 1)
|
|
72
|
+
return false;
|
|
73
|
+
return true;
|
|
74
|
+
},
|
|
75
|
+
async start(ctx) {
|
|
76
|
+
// Resolve the token from the config the manager handed isConfigured().
|
|
77
|
+
// Fall back to a fresh load defensively (e.g. a direct start() in a test
|
|
78
|
+
// that skipped isConfigured).
|
|
79
|
+
const cfg = lastConfig ?? (await loadStartConfig());
|
|
80
|
+
const botToken = resolveDiscordBotToken(cfg, accountId, lastEnv);
|
|
81
|
+
if (!botToken) {
|
|
82
|
+
ctx.log("Discord not started — no bot token resolved (set channels.discord.botToken or DISCORD_BOT_TOKEN).");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Optional proxy — routes the REST + Gateway websocket through it on
|
|
86
|
+
// networks where discord.com is blocked. Empty → direct (unchanged).
|
|
87
|
+
const proxyUrl = resolveDiscordProxyUrl(cfg, accountId, lastEnv);
|
|
88
|
+
// The native slash-command manifest, derived from Brigade's central
|
|
89
|
+
// channel commands; registered right after the connection is live (below).
|
|
90
|
+
const commandManifest = buildDiscordCommandManifest(buildBundledCommands(adapter));
|
|
91
|
+
const conn = await connectImpl({
|
|
92
|
+
botToken,
|
|
93
|
+
...(proxyUrl ? { proxyUrl } : {}),
|
|
94
|
+
accountId,
|
|
95
|
+
log: ctx.log,
|
|
96
|
+
onConnected: () => {
|
|
97
|
+
connected = true;
|
|
98
|
+
tokenInvalid = false;
|
|
99
|
+
ctx.log("Discord ready");
|
|
100
|
+
ctx.onConnected?.();
|
|
101
|
+
},
|
|
102
|
+
onTokenInvalid: () => {
|
|
103
|
+
connected = false;
|
|
104
|
+
tokenInvalid = true;
|
|
105
|
+
ctx.log("Discord token was rejected. Run `brigade channels add --channel discord` with a fresh bot token.");
|
|
106
|
+
ctx.onLoggedOut?.();
|
|
107
|
+
},
|
|
108
|
+
onMessage: (msg) => {
|
|
109
|
+
void ctx.onInbound({
|
|
110
|
+
channel: DISCORD_CHANNEL_ID,
|
|
111
|
+
accountId,
|
|
112
|
+
conversationId: msg.conversationId,
|
|
113
|
+
messageId: msg.messageId,
|
|
114
|
+
messageTimestampMs: msg.messageTimestampMs,
|
|
115
|
+
from: msg.from,
|
|
116
|
+
fromName: msg.fromName,
|
|
117
|
+
text: msg.text,
|
|
118
|
+
chatType: msg.chatType,
|
|
119
|
+
isGroup: msg.chatType === "group",
|
|
120
|
+
threadId: msg.threadId,
|
|
121
|
+
// Discord routes on guildId + member role ids (NOT teamId — that
|
|
122
|
+
// is Slack's workspace tier; setting it would risk colliding with
|
|
123
|
+
// a Slack team binding).
|
|
124
|
+
guildId: msg.guildId,
|
|
125
|
+
memberRoleIds: msg.memberRoleIds,
|
|
126
|
+
mentions: msg.mentions,
|
|
127
|
+
replyTo: msg.replyTo,
|
|
128
|
+
// Edit provenance rides through so the central pipeline / agent see
|
|
129
|
+
// "this was an edit".
|
|
130
|
+
...(msg.edited ? { edited: true } : {}),
|
|
131
|
+
// Deferred media thunk rides through untouched — the pipeline
|
|
132
|
+
// resolves it only after the access gate admits the sender.
|
|
133
|
+
resolveMedia: msg.resolveMedia,
|
|
134
|
+
raw: msg.raw,
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
// Inbound reaction → synthesise a short note and route it through the
|
|
138
|
+
// SAME inbound pipeline as a normal message so the access gate + routing
|
|
139
|
+
// apply uniformly. The note carries the added emoji(s) + the target id.
|
|
140
|
+
onReaction: (msg) => {
|
|
141
|
+
if (!msg.reaction)
|
|
142
|
+
return;
|
|
143
|
+
const note = buildReactionNote(msg.reaction.emojis, msg.reaction.targetMessageId, msg.fromName);
|
|
144
|
+
void ctx.onInbound({
|
|
145
|
+
channel: DISCORD_CHANNEL_ID,
|
|
146
|
+
accountId,
|
|
147
|
+
conversationId: msg.conversationId,
|
|
148
|
+
from: msg.from,
|
|
149
|
+
...(msg.fromName !== undefined ? { fromName: msg.fromName } : {}),
|
|
150
|
+
text: note,
|
|
151
|
+
chatType: msg.chatType,
|
|
152
|
+
isGroup: msg.chatType === "group",
|
|
153
|
+
...(msg.threadId !== undefined ? { threadId: msg.threadId } : {}),
|
|
154
|
+
...(msg.guildId !== undefined ? { guildId: msg.guildId } : {}),
|
|
155
|
+
...(msg.memberRoleIds !== undefined ? { memberRoleIds: msg.memberRoleIds } : {}),
|
|
156
|
+
reaction: msg.reaction,
|
|
157
|
+
raw: msg.raw,
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
// Button press → emit an InboundMessage carrying `callbackQuery` so the
|
|
161
|
+
// central pipeline's approval-callback path resolves it. The connection
|
|
162
|
+
// has already acked the press.
|
|
163
|
+
onCallbackQuery: (msg) => {
|
|
164
|
+
if (!msg.callbackQuery)
|
|
165
|
+
return;
|
|
166
|
+
void ctx.onInbound({
|
|
167
|
+
channel: DISCORD_CHANNEL_ID,
|
|
168
|
+
accountId,
|
|
169
|
+
conversationId: msg.conversationId,
|
|
170
|
+
from: msg.from,
|
|
171
|
+
...(msg.fromName !== undefined ? { fromName: msg.fromName } : {}),
|
|
172
|
+
text: "",
|
|
173
|
+
chatType: msg.chatType,
|
|
174
|
+
isGroup: msg.chatType === "group",
|
|
175
|
+
...(msg.threadId !== undefined ? { threadId: msg.threadId } : {}),
|
|
176
|
+
...(msg.guildId !== undefined ? { guildId: msg.guildId } : {}),
|
|
177
|
+
...(msg.memberRoleIds !== undefined ? { memberRoleIds: msg.memberRoleIds } : {}),
|
|
178
|
+
callbackQuery: msg.callbackQuery,
|
|
179
|
+
raw: msg.raw,
|
|
180
|
+
});
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
connection = conn;
|
|
184
|
+
// Register the native slash commands now that the connection exists.
|
|
185
|
+
// `connectDiscord` resolves once the first connect (or terminal failure)
|
|
186
|
+
// settles, so a successful boot is already live here. Best-effort: a
|
|
187
|
+
// registration failure is logged inside `registerCommands` and never
|
|
188
|
+
// blocks startup. When the token was rejected we skip (nothing to push).
|
|
189
|
+
if (connected && !tokenInvalid) {
|
|
190
|
+
void conn.registerCommands(commandManifest).catch(() => { });
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
async stop() {
|
|
194
|
+
await connection?.close();
|
|
195
|
+
connection = null;
|
|
196
|
+
connected = false;
|
|
197
|
+
},
|
|
198
|
+
/**
|
|
199
|
+
* Synchronous read of the cached connection state:
|
|
200
|
+
* - `{ ok: true }` once the Gateway is live.
|
|
201
|
+
* - `{ ok: false, kind: "logged-out" }` after an auth error (sticky; re-token).
|
|
202
|
+
* - `{ ok: false, kind: "starting" }` between start() and first connect.
|
|
203
|
+
* - `{ ok: false, kind: "disconnected" }` for a transient drop mid-reconnect.
|
|
204
|
+
*/
|
|
205
|
+
health() {
|
|
206
|
+
if (tokenInvalid || connection?.isTokenInvalid()) {
|
|
207
|
+
return {
|
|
208
|
+
ok: false,
|
|
209
|
+
kind: "logged-out",
|
|
210
|
+
reason: "Discord token was rejected — Brigade can't send until a new token is set.",
|
|
211
|
+
remediation: "Run `brigade channels add --channel discord` and paste a fresh bot token.",
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (!connection) {
|
|
215
|
+
return { ok: false, kind: "starting", reason: "Discord adapter is not started yet." };
|
|
216
|
+
}
|
|
217
|
+
if (!connected || !connection.isConnected()) {
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
kind: "disconnected",
|
|
221
|
+
reason: "Discord is reconnecting — sends will fail until the Gateway resumes.",
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
return { ok: true };
|
|
225
|
+
},
|
|
226
|
+
async sendText(conversationId, text, opts) {
|
|
227
|
+
if (!connection)
|
|
228
|
+
throw new Error("Discord channel is not started");
|
|
229
|
+
if (tokenInvalid || connection.isTokenInvalid()) {
|
|
230
|
+
throw new Error("Discord token is invalid — run `brigade channels add --channel discord` with a new token, then retry.");
|
|
231
|
+
}
|
|
232
|
+
const threadId = opts?.threadId;
|
|
233
|
+
// Native reply target — applied to the FIRST chunk only (threading every
|
|
234
|
+
// chunk of a long reply is redundant once the first lands). Omitted →
|
|
235
|
+
// unthreaded send (unchanged).
|
|
236
|
+
const replyToMessageId = opts?.replyToId;
|
|
237
|
+
const sendExtras = {};
|
|
238
|
+
if (threadId)
|
|
239
|
+
sendExtras.threadId = threadId;
|
|
240
|
+
// Chunk on the RAW markdown so fences/paragraphs aren't shredded, then
|
|
241
|
+
// convert each chunk to Discord markup and send. A chunk whose rendered
|
|
242
|
+
// markup is empty (syntax-only) is re-sent as the raw chunk.
|
|
243
|
+
const chunks = chunkText(text, { limit: DISCORD_TEXT_LIMIT });
|
|
244
|
+
let first = true;
|
|
245
|
+
for (const chunk of chunks) {
|
|
246
|
+
const replyOpt = first && replyToMessageId ? { replyToMessageId } : {};
|
|
247
|
+
const rendered = markdownToDiscord(chunk);
|
|
248
|
+
const body = discordTextIsEmpty(rendered) ? chunk : rendered;
|
|
249
|
+
if (body.trim().length === 0)
|
|
250
|
+
continue;
|
|
251
|
+
await connection.sendText(conversationId, body, { ...sendExtras, ...replyOpt });
|
|
252
|
+
first = false;
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
/**
|
|
256
|
+
* Open a LIVE reply stream — the gateway feeds the accumulating answer text
|
|
257
|
+
* via `update()`, this edits one Discord message in place (throttled
|
|
258
|
+
* ~1×/sec), and `finalize()` settles it on turn end. Returns `null` when
|
|
259
|
+
* streaming is disabled in config (`channels.discord.liveStream` is not true)
|
|
260
|
+
* OR the connection isn't live, so the pipeline falls back to the single
|
|
261
|
+
* final `sendText` — byte-unchanged from before streaming existed.
|
|
262
|
+
*
|
|
263
|
+
* Each draft chunk is rendered through the SAME markdown→Discord converter
|
|
264
|
+
* the final path uses. When the running answer exceeds the limit the stream
|
|
265
|
+
* finalizes the current message at a boundary and rolls overflow into a new
|
|
266
|
+
* message.
|
|
267
|
+
*/
|
|
268
|
+
beginReplyStream(conversationId, sendOpts) {
|
|
269
|
+
if (!connection)
|
|
270
|
+
return null;
|
|
271
|
+
if (tokenInvalid || connection.isTokenInvalid())
|
|
272
|
+
return null;
|
|
273
|
+
const cfg = lastConfig;
|
|
274
|
+
if (!cfg || !discordLiveStreamEnabled(cfg))
|
|
275
|
+
return null;
|
|
276
|
+
const conn = connection;
|
|
277
|
+
const threadId = sendOpts?.threadId;
|
|
278
|
+
const stream = createDraftStream({
|
|
279
|
+
transport: {
|
|
280
|
+
async postMessage(text, o) {
|
|
281
|
+
const sent = await conn.sendText(conversationId, text, {
|
|
282
|
+
...(o.threadId !== undefined ? { threadId: o.threadId } : {}),
|
|
283
|
+
});
|
|
284
|
+
return { id: sent.messageId };
|
|
285
|
+
},
|
|
286
|
+
async editMessage(id, text) {
|
|
287
|
+
await conn.editMessageText(conversationId, id, text);
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
...(threadId !== undefined ? { threadId } : {}),
|
|
291
|
+
throttleMs: discordStreamThrottleMs(cfg),
|
|
292
|
+
maxChars: DISCORD_TEXT_LIMIT,
|
|
293
|
+
// Render each draft chunk to Discord markup; fall back to the plain chunk
|
|
294
|
+
// when it renders empty (syntax-only).
|
|
295
|
+
renderText: (chunk) => {
|
|
296
|
+
const rendered = markdownToDiscord(chunk);
|
|
297
|
+
return { text: discordTextIsEmpty(rendered) ? chunk : rendered };
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
update: (text) => stream.update(text),
|
|
302
|
+
async finalize(finalText) {
|
|
303
|
+
await stream.finalize(finalText);
|
|
304
|
+
const ids = stream.messageIds();
|
|
305
|
+
const last = ids[ids.length - 1];
|
|
306
|
+
return last !== undefined ? { messageId: last } : undefined;
|
|
307
|
+
},
|
|
308
|
+
stop: () => stream.stop(),
|
|
309
|
+
};
|
|
310
|
+
},
|
|
311
|
+
/**
|
|
312
|
+
* OPTIONAL reasoning lane (default OFF). When `channels.discord.
|
|
313
|
+
* surfaceReasoning` is true, split the raw reply's `<think>` trace out and
|
|
314
|
+
* send it as a separate `🧠 Reasoning:` message BEFORE the answer. When the
|
|
315
|
+
* config gate is off (the default) OR the reply carried no reasoning, this
|
|
316
|
+
* sends NOTHING — the answer message the pipeline sends afterward is
|
|
317
|
+
* byte-identical either way.
|
|
318
|
+
*/
|
|
319
|
+
async deliverReasoning(conversationId, rawReply, sendOpts) {
|
|
320
|
+
if (!connection)
|
|
321
|
+
return;
|
|
322
|
+
if (tokenInvalid || connection.isTokenInvalid())
|
|
323
|
+
return;
|
|
324
|
+
const cfg = lastConfig;
|
|
325
|
+
if (!cfg || !discordSurfaceReasoning(cfg))
|
|
326
|
+
return;
|
|
327
|
+
const { reasoningText } = splitDiscordReasoning(rawReply ?? "");
|
|
328
|
+
if (!reasoningText)
|
|
329
|
+
return;
|
|
330
|
+
// Reuse the adapter's own chunk+render send path so a long reasoning trace
|
|
331
|
+
// is chunked at 2000 and formatted consistently with replies.
|
|
332
|
+
await adapter.sendText(conversationId, reasoningText, sendOpts);
|
|
333
|
+
},
|
|
334
|
+
// Discord ids are user snowflakes; the pairing challenge card uses the
|
|
335
|
+
// "account" label. The bot is a SEPARATE account from the operator (its own
|
|
336
|
+
// app), so ownership is bootstrapped from the first CLI `pairing approve` —
|
|
337
|
+
// see `botIsSeparateFromOperator`.
|
|
338
|
+
pairing: { idLabel: "account", botIsSeparateFromOperator: true },
|
|
339
|
+
// Token-based setup wizard — `brigade channels add --channel discord` prompts
|
|
340
|
+
// for the bot token and writes `channels.discord.botToken`. The OAuth invite
|
|
341
|
+
// URL + the privileged "Message Content" gateway intent toggle CAN'T be
|
|
342
|
+
// granted programmatically, so the two setup steps the operator must do by
|
|
343
|
+
// hand are baked into the bot-token prompt copy:
|
|
344
|
+
// 1. Enable the MESSAGE CONTENT intent (Bot → Privileged Gateway Intents),
|
|
345
|
+
// or Brigade can't read message text.
|
|
346
|
+
// 2. Invite the bot with the `bot` + `applications.commands` scopes (OAuth2
|
|
347
|
+
// → URL Generator) + Send Messages / Read Message History / Add Reactions.
|
|
348
|
+
setup: {
|
|
349
|
+
credentialKeys: [
|
|
350
|
+
{
|
|
351
|
+
key: "botToken",
|
|
352
|
+
prompt: "Discord bot token (Developer Portal → Bot → Reset Token). Also: enable the MESSAGE CONTENT intent (Bot → Privileged Gateway Intents) and invite the bot with the bot + applications.commands scopes.",
|
|
353
|
+
secret: true,
|
|
354
|
+
envVar: "DISCORD_BOT_TOKEN",
|
|
355
|
+
docsUrl: "https://discord.com/developers/docs/topics/oauth2#bots",
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
validateInput(key, value) {
|
|
359
|
+
const v = value.trim();
|
|
360
|
+
// Allow a `${VAR}` ref through (resolved at runtime).
|
|
361
|
+
if (/^\$\{[A-Z_][A-Z0-9_]*\}$/.test(v))
|
|
362
|
+
return null;
|
|
363
|
+
if (key === "botToken") {
|
|
364
|
+
// Discord bot tokens are `<id>.<ts>.<secret>` (optionally `Bot `-prefixed).
|
|
365
|
+
if (/^(Bot\s+)?[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{20,}$/.test(v))
|
|
366
|
+
return null;
|
|
367
|
+
return "That doesn't look like a Discord bot token — expected the `…. …. …` token from the Developer Portal.";
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
async sendMedia(conversationId, media) {
|
|
373
|
+
if (!connection)
|
|
374
|
+
throw new Error("Discord channel is not started");
|
|
375
|
+
await connection.sendMedia(conversationId, media);
|
|
376
|
+
},
|
|
377
|
+
async react(conversationId, messageId, emoji) {
|
|
378
|
+
if (!connection)
|
|
379
|
+
return; // cosmetic — refuse silently when not started
|
|
380
|
+
await connection.react(conversationId, messageId, emoji);
|
|
381
|
+
},
|
|
382
|
+
async setComposing(conversationId, state) {
|
|
383
|
+
if (!connection)
|
|
384
|
+
return;
|
|
385
|
+
await connection.setComposing(conversationId, state);
|
|
386
|
+
},
|
|
387
|
+
// Static capability flags. The central `message_action` tool PRE-CHECKS the
|
|
388
|
+
// relevant flag here before calling `handleAction`, so an unsupported action
|
|
389
|
+
// fails cleanly without touching the adapter.
|
|
390
|
+
capabilities: DISCORD_CAPABILITIES,
|
|
391
|
+
// Native component-button approvals. When a channel-routed turn raises an
|
|
392
|
+
// approval, the central router calls `sendApprovalPrompt` to render the
|
|
393
|
+
// question as buttons (payloads from the central codec); the press comes back
|
|
394
|
+
// as `InboundMessage.callbackQuery` and is resolved centrally. A pathological
|
|
395
|
+
// approval id that can't be encoded falls back to the text prompt.
|
|
396
|
+
approvalCapability: {
|
|
397
|
+
async sendApprovalPrompt(params) {
|
|
398
|
+
if (!connection)
|
|
399
|
+
throw new Error("Discord channel is not started");
|
|
400
|
+
const message = buildDiscordApprovalMessage({
|
|
401
|
+
approvalId: params.approvalId,
|
|
402
|
+
command: params.command,
|
|
403
|
+
approvalKind: params.approvalKind,
|
|
404
|
+
...(params.toolName !== undefined ? { toolName: params.toolName } : {}),
|
|
405
|
+
});
|
|
406
|
+
if (!message) {
|
|
407
|
+
// Couldn't build byte-safe buttons — let the router fall back to text.
|
|
408
|
+
throw new Error("discord approval prompt: approval id too long for buttons");
|
|
409
|
+
}
|
|
410
|
+
await connection.sendInteractive(params.conversationId, message.text, message.rows, {
|
|
411
|
+
...(params.threadId !== undefined ? { threadId: params.threadId } : {}),
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
authorizeApprover(p) {
|
|
415
|
+
return resolveDiscordApprover({
|
|
416
|
+
cfg: p.cfg,
|
|
417
|
+
...(p.senderId !== undefined ? { senderId: p.senderId } : {}),
|
|
418
|
+
...(p.accountId !== undefined ? { accountId: p.accountId } : {}),
|
|
419
|
+
});
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
// Edit / delete / react / reply a message + attach buttons. The manager
|
|
423
|
+
// pre-checks the capability flag (above) before calling, so an action only
|
|
424
|
+
// reaches here when Discord advertised support for it.
|
|
425
|
+
async handleAction(p) {
|
|
426
|
+
if (!connection)
|
|
427
|
+
return { ok: false, error: "Discord channel is not started" };
|
|
428
|
+
if (tokenInvalid || connection.isTokenInvalid()) {
|
|
429
|
+
return { ok: false, error: "Discord token is invalid — re-token before acting on messages." };
|
|
430
|
+
}
|
|
431
|
+
const a = p.action;
|
|
432
|
+
try {
|
|
433
|
+
switch (a.kind) {
|
|
434
|
+
case "edit": {
|
|
435
|
+
const rendered = markdownToDiscord(a.text);
|
|
436
|
+
const body = discordTextIsEmpty(rendered) ? a.text : rendered;
|
|
437
|
+
await connection.editMessageText(p.conversationId, a.messageId, body);
|
|
438
|
+
return { ok: true, messageId: a.messageId };
|
|
439
|
+
}
|
|
440
|
+
case "delete":
|
|
441
|
+
await connection.deleteMessage(p.conversationId, a.messageId);
|
|
442
|
+
return { ok: true, messageId: a.messageId };
|
|
443
|
+
case "react":
|
|
444
|
+
// An EMPTY emoji means "clear" (parity with WhatsApp/Telegram/Slack):
|
|
445
|
+
// remove the bot's OWN reactions on this message; a non-empty emoji
|
|
446
|
+
// adds as before.
|
|
447
|
+
if (a.emoji.trim() === "") {
|
|
448
|
+
await connection.removeOwnReactions(p.conversationId, a.messageId);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
await connection.react(p.conversationId, a.messageId, a.emoji);
|
|
452
|
+
}
|
|
453
|
+
return { ok: true, messageId: a.messageId };
|
|
454
|
+
case "reply": {
|
|
455
|
+
// A reply is a send with a native reply reference; surface the new id.
|
|
456
|
+
const sent = await connection.sendText(p.conversationId, a.text, {
|
|
457
|
+
...(a.threadId !== undefined ? { threadId: a.threadId } : {}),
|
|
458
|
+
});
|
|
459
|
+
return { ok: true, messageId: sent.messageId };
|
|
460
|
+
}
|
|
461
|
+
case "buttons": {
|
|
462
|
+
// Send a NEW message with a general button keyboard. The button ids
|
|
463
|
+
// are prefixed/sanitized by the builder; a press arrives as
|
|
464
|
+
// `callbackQuery` and routes through the pipeline as a turn (the
|
|
465
|
+
// central approval path declines a general payload).
|
|
466
|
+
const rows = buildDiscordButtonRows(a.buttons.map((row) => row.map((b) => ({ text: b.text, data: b.data }))));
|
|
467
|
+
if (!rows) {
|
|
468
|
+
return { ok: false, error: "no usable buttons (each needs a label + a data token ≤ 100 chars)" };
|
|
469
|
+
}
|
|
470
|
+
const rendered = markdownToDiscord(a.text);
|
|
471
|
+
const body = discordTextIsEmpty(rendered) ? a.text : rendered;
|
|
472
|
+
const sent = await connection.sendInteractive(p.conversationId, body, rows, {
|
|
473
|
+
...(a.threadId !== undefined ? { threadId: a.threadId } : {}),
|
|
474
|
+
});
|
|
475
|
+
return { ok: true, messageId: sent.messageId };
|
|
476
|
+
}
|
|
477
|
+
default:
|
|
478
|
+
return { ok: false, error: `unsupported action kind` };
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
selfId() {
|
|
486
|
+
return connection?.selfId() ?? undefined;
|
|
487
|
+
},
|
|
488
|
+
connectedAt() {
|
|
489
|
+
return connection?.connectedAt() ?? null;
|
|
490
|
+
},
|
|
491
|
+
lastEventAt() {
|
|
492
|
+
return connection?.lastEventAt() ?? null;
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
return adapter;
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Synthesise the agent-facing note for an inbound reaction. The reaction itself
|
|
499
|
+
* carries no text, so the note ("<who> reacted :emoji: to message <id>") is what
|
|
500
|
+
* the central pipeline routes through dispatchTurn so the agent has context.
|
|
501
|
+
*/
|
|
502
|
+
export function buildReactionNote(emojis, targetMessageId, fromName) {
|
|
503
|
+
const who = fromName?.trim() || "Someone";
|
|
504
|
+
// A custom emoji surfaces as `name:id` — show just the name for the note.
|
|
505
|
+
const emoji = emojis.map((e) => `:${e.includes(":") ? e.split(":")[0] : e}:`).join(" ");
|
|
506
|
+
return `${who} reacted ${emoji} to message ${targetMessageId}.`;
|
|
507
|
+
}
|
|
508
|
+
/** Static Discord capability flags (shared by the legacy adapter + plugin meta). */
|
|
509
|
+
export const DISCORD_CAPABILITIES = {
|
|
510
|
+
chatTypes: ["direct", "group", "thread"],
|
|
511
|
+
reactions: true,
|
|
512
|
+
edit: true,
|
|
513
|
+
unsend: true,
|
|
514
|
+
reply: true,
|
|
515
|
+
threads: true,
|
|
516
|
+
media: true,
|
|
517
|
+
nativeCommands: true,
|
|
518
|
+
};
|
|
519
|
+
/**
|
|
520
|
+
* Defensive config fallback for a direct `start()` that skipped `isConfigured`
|
|
521
|
+
* (the manager always calls isConfigured first, so this is the rare path).
|
|
522
|
+
*/
|
|
523
|
+
async function loadStartConfig() {
|
|
524
|
+
return loadConfig();
|
|
525
|
+
}
|
|
526
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord inline-approval authorization.
|
|
3
|
+
*
|
|
4
|
+
* When an approval prompt is rendered as buttons, ANY member who can see the
|
|
5
|
+
* message could press a button. Brigade's central inbound pipeline already runs
|
|
6
|
+
* the access-control gate before a button press reaches the approval-callback
|
|
7
|
+
* path, so only an admitted (allow-listed / owner) peer gets here at all — but a
|
|
8
|
+
* SHARED Discord guild channel is the edge case: in a channel the bot is in, an
|
|
9
|
+
* admitted member's button press should still be allowed only when that presser
|
|
10
|
+
* is an approved approver, not merely present in the room. Discord's
|
|
11
|
+
* multi-member guilds make this gate more load-bearing than a 1:1 DM.
|
|
12
|
+
*
|
|
13
|
+
* This predicate is the channel's `approvalCapability.authorizeApprover`. It is
|
|
14
|
+
* invoked CENTRALLY by `tryConsumeChannelApprovalCallback` with the presser's
|
|
15
|
+
* `senderId` (the Discord user id, a snowflake); returning `{ authorized: false,
|
|
16
|
+
* reason }` refuses the press without consuming the operator's pending approval
|
|
17
|
+
* (so the real operator can still answer). Policy:
|
|
18
|
+
*
|
|
19
|
+
* - When the channel has an explicit allow-from list configured (the approved
|
|
20
|
+
* senders), only those ids may approve.
|
|
21
|
+
* - When NO allow-from list is configured, defer to the access gate that
|
|
22
|
+
* already admitted the inbound and authorize the press (matches the text-
|
|
23
|
+
* reply path, which has no extra approver gate).
|
|
24
|
+
*
|
|
25
|
+
* Pure + deterministic over its `cfg` + `senderId` inputs — no I/O. Discord
|
|
26
|
+
* mirror of `slack/approval-authorize.ts`.
|
|
27
|
+
*/
|
|
28
|
+
import type { BrigadeConfig } from "../../../config/io.js";
|
|
29
|
+
/**
|
|
30
|
+
* Resolve whether `senderId` is allowed to answer a Discord inline approval.
|
|
31
|
+
* Returns `{ authorized: true }` when no explicit allow-from gate applies (the
|
|
32
|
+
* central access gate already admitted the inbound), or when the presser is on
|
|
33
|
+
* the configured allow-from list. Otherwise refuses with a reason.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveDiscordApprover(args: {
|
|
36
|
+
cfg: BrigadeConfig;
|
|
37
|
+
senderId?: string;
|
|
38
|
+
accountId?: string;
|
|
39
|
+
}): {
|
|
40
|
+
authorized: boolean;
|
|
41
|
+
reason?: string;
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=approval-authorize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-authorize.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/approval-authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA0B3D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC5C,GAAG,EAAE,aAAa,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAU3C"}
|