@spinabot/brigade 1.6.1 → 1.7.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/dist/agents/channels/discord/account-config.d.ts +60 -0
- package/dist/agents/channels/discord/account-config.d.ts.map +1 -1
- package/dist/agents/channels/discord/account-config.js +89 -0
- package/dist/agents/channels/discord/account-config.js.map +1 -1
- package/dist/agents/channels/discord/adapter.d.ts +24 -1
- package/dist/agents/channels/discord/adapter.d.ts.map +1 -1
- package/dist/agents/channels/discord/adapter.js +208 -41
- package/dist/agents/channels/discord/adapter.js.map +1 -1
- package/dist/agents/channels/discord/component-blocks.d.ts +108 -0
- package/dist/agents/channels/discord/component-blocks.d.ts.map +1 -0
- package/dist/agents/channels/discord/component-blocks.js +113 -0
- package/dist/agents/channels/discord/component-blocks.js.map +1 -0
- package/dist/agents/channels/discord/components.d.ts +78 -0
- package/dist/agents/channels/discord/components.d.ts.map +1 -1
- package/dist/agents/channels/discord/components.js +89 -0
- package/dist/agents/channels/discord/components.js.map +1 -1
- package/dist/agents/channels/discord/connection.d.ts +195 -12
- package/dist/agents/channels/discord/connection.d.ts.map +1 -1
- package/dist/agents/channels/discord/connection.js +852 -38
- package/dist/agents/channels/discord/connection.js.map +1 -1
- package/dist/agents/channels/discord/directory-cache.d.ts +47 -0
- package/dist/agents/channels/discord/directory-cache.d.ts.map +1 -0
- package/dist/agents/channels/discord/directory-cache.js +131 -0
- package/dist/agents/channels/discord/directory-cache.js.map +1 -0
- package/dist/agents/channels/discord/directory-live.d.ts +61 -0
- package/dist/agents/channels/discord/directory-live.d.ts.map +1 -0
- package/dist/agents/channels/discord/directory-live.js +140 -0
- package/dist/agents/channels/discord/directory-live.js.map +1 -0
- package/dist/agents/channels/discord/format.d.ts +15 -0
- package/dist/agents/channels/discord/format.d.ts.map +1 -1
- package/dist/agents/channels/discord/format.js +56 -0
- package/dist/agents/channels/discord/format.js.map +1 -1
- package/dist/agents/channels/discord/guilds.d.ts +25 -0
- package/dist/agents/channels/discord/guilds.d.ts.map +1 -0
- package/dist/agents/channels/discord/guilds.js +46 -0
- package/dist/agents/channels/discord/guilds.js.map +1 -0
- package/dist/agents/channels/discord/inbound-extras.d.ts +166 -9
- package/dist/agents/channels/discord/inbound-extras.d.ts.map +1 -1
- package/dist/agents/channels/discord/inbound-extras.js +246 -8
- package/dist/agents/channels/discord/inbound-extras.js.map +1 -1
- package/dist/agents/channels/discord/index.d.ts +10 -3
- package/dist/agents/channels/discord/index.d.ts.map +1 -1
- package/dist/agents/channels/discord/index.js +10 -3
- package/dist/agents/channels/discord/index.js.map +1 -1
- package/dist/agents/channels/discord/modal-registry.d.ts +89 -0
- package/dist/agents/channels/discord/modal-registry.d.ts.map +1 -0
- package/dist/agents/channels/discord/modal-registry.js +104 -0
- package/dist/agents/channels/discord/modal-registry.js.map +1 -0
- package/dist/agents/channels/discord/modals.d.ts +100 -0
- package/dist/agents/channels/discord/modals.d.ts.map +1 -0
- package/dist/agents/channels/discord/modals.js +124 -0
- package/dist/agents/channels/discord/modals.js.map +1 -0
- package/dist/agents/channels/discord/permission-audit.d.ts +43 -0
- package/dist/agents/channels/discord/permission-audit.d.ts.map +1 -0
- package/dist/agents/channels/discord/permission-audit.js +192 -0
- package/dist/agents/channels/discord/permission-audit.js.map +1 -0
- package/dist/agents/channels/discord/plugin.d.ts +18 -2
- package/dist/agents/channels/discord/plugin.d.ts.map +1 -1
- package/dist/agents/channels/discord/plugin.js +73 -4
- package/dist/agents/channels/discord/plugin.js.map +1 -1
- package/dist/agents/channels/discord/probe.d.ts +23 -1
- package/dist/agents/channels/discord/probe.d.ts.map +1 -1
- package/dist/agents/channels/discord/probe.js +40 -5
- package/dist/agents/channels/discord/probe.js.map +1 -1
- package/dist/agents/channels/discord/rest-actions.d.ts +346 -0
- package/dist/agents/channels/discord/rest-actions.d.ts.map +1 -0
- package/dist/agents/channels/discord/rest-actions.js +559 -0
- package/dist/agents/channels/discord/rest-actions.js.map +1 -0
- package/dist/agents/channels/discord/rest-components.d.ts +122 -0
- package/dist/agents/channels/discord/rest-components.d.ts.map +1 -0
- package/dist/agents/channels/discord/rest-components.js +243 -0
- package/dist/agents/channels/discord/rest-components.js.map +1 -0
- package/dist/agents/channels/discord/security-audit.d.ts +29 -0
- package/dist/agents/channels/discord/security-audit.d.ts.map +1 -0
- package/dist/agents/channels/discord/security-audit.js +94 -0
- package/dist/agents/channels/discord/security-audit.js.map +1 -0
- package/dist/agents/channels/discord/security-doctor.d.ts +43 -0
- package/dist/agents/channels/discord/security-doctor.d.ts.map +1 -0
- package/dist/agents/channels/discord/security-doctor.js +83 -0
- package/dist/agents/channels/discord/security-doctor.js.map +1 -0
- package/dist/agents/channels/discord/status-issues.d.ts +37 -0
- package/dist/agents/channels/discord/status-issues.d.ts.map +1 -0
- package/dist/agents/channels/discord/status-issues.js +66 -0
- package/dist/agents/channels/discord/status-issues.js.map +1 -0
- package/dist/agents/channels/discord/subagent-thread-binding-store.d.ts +57 -0
- package/dist/agents/channels/discord/subagent-thread-binding-store.d.ts.map +1 -0
- package/dist/agents/channels/discord/subagent-thread-binding-store.js +98 -0
- package/dist/agents/channels/discord/subagent-thread-binding-store.js.map +1 -0
- package/dist/agents/channels/discord/subagent-thread-binding.d.ts +95 -0
- package/dist/agents/channels/discord/subagent-thread-binding.d.ts.map +1 -0
- package/dist/agents/channels/discord/subagent-thread-binding.js +208 -0
- package/dist/agents/channels/discord/subagent-thread-binding.js.map +1 -0
- package/dist/agents/channels/discord/system-events.d.ts +31 -0
- package/dist/agents/channels/discord/system-events.d.ts.map +1 -0
- package/dist/agents/channels/discord/system-events.js +74 -0
- package/dist/agents/channels/discord/system-events.js.map +1 -0
- package/dist/agents/channels/general-callback.d.ts +12 -0
- package/dist/agents/channels/general-callback.d.ts.map +1 -1
- package/dist/agents/channels/general-callback.js +18 -0
- package/dist/agents/channels/general-callback.js.map +1 -1
- package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
- package/dist/agents/channels/inbound-pipeline.js +5 -3
- package/dist/agents/channels/inbound-pipeline.js.map +1 -1
- package/dist/agents/extensions/types.d.ts +7 -0
- package/dist/agents/extensions/types.d.ts.map +1 -1
- package/dist/agents/extensions/types.js.map +1 -1
- package/dist/agents/subagent-announce-delivery.d.ts +10 -0
- package/dist/agents/subagent-announce-delivery.d.ts.map +1 -1
- package/dist/agents/subagent-announce-delivery.js +1 -0
- package/dist/agents/subagent-announce-delivery.js.map +1 -1
- package/dist/agents/subagent-completion-bridge.d.ts.map +1 -1
- package/dist/agents/subagent-completion-bridge.js +81 -0
- package/dist/agents/subagent-completion-bridge.js.map +1 -1
- package/dist/agents/subagent-spawn.d.ts.map +1 -1
- package/dist/agents/subagent-spawn.js +57 -4
- package/dist/agents/subagent-spawn.js.map +1 -1
- package/dist/agents/tools/discord-action-tool.d.ts +224 -0
- package/dist/agents/tools/discord-action-tool.d.ts.map +1 -0
- package/dist/agents/tools/discord-action-tool.js +848 -0
- package/dist/agents/tools/discord-action-tool.js.map +1 -0
- package/dist/agents/tools/registry.d.ts.map +1 -1
- package/dist/agents/tools/registry.js +21 -0
- package/dist/agents/tools/registry.js.map +1 -1
- package/dist/agents/tools/sessions/index.d.ts +8 -0
- package/dist/agents/tools/sessions/index.d.ts.map +1 -1
- package/dist/agents/tools/sessions/index.js +15 -3
- package/dist/agents/tools/sessions/index.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/channels.d.ts +2 -0
- package/dist/cli/commands/channels.d.ts.map +1 -1
- package/dist/cli/commands/channels.js +58 -1
- package/dist/cli/commands/channels.js.map +1 -1
- package/package.json +1 -1
|
@@ -38,6 +38,36 @@ declare const CHANNEL_ID = "discord";
|
|
|
38
38
|
declare const DEFAULT_ACCOUNT_ID = "default";
|
|
39
39
|
/** Per-secret env var consulted as a last-resort fallback. */
|
|
40
40
|
declare const BOT_TOKEN_ENV_VAR = "DISCORD_BOT_TOKEN";
|
|
41
|
+
/** Reaction-notification gating modes (see `channels.discord.reactionNotifications`). */
|
|
42
|
+
export type DiscordReactionNotificationMode = "off" | "own" | "all" | "allowlist";
|
|
43
|
+
/** Bot online-dot status (`channels.discord.presence.status`). */
|
|
44
|
+
export type DiscordPresenceStatus = "online" | "idle" | "dnd" | "invisible";
|
|
45
|
+
/** Activity row kind (`channels.discord.presence.activityType`). */
|
|
46
|
+
export type DiscordPresenceActivityType = "playing" | "streaming" | "listening" | "watching" | "custom" | "competing";
|
|
47
|
+
/** How an auto-thread is named (`channels.discord.autoThreadName`). */
|
|
48
|
+
export type DiscordAutoThreadNameMode = "generated" | "first-message";
|
|
49
|
+
/**
|
|
50
|
+
* Resolved presence applied on (re)connect. `activityType` is the discord.js
|
|
51
|
+
* `ActivityType` numeric enum value (playing=0, streaming=1, listening=2,
|
|
52
|
+
* watching=3, custom=4, competing=5); `activityText` is the activity name (the
|
|
53
|
+
* STATE line for a custom activity); `activityUrl` rides only on a streaming
|
|
54
|
+
* activity. `null` when no presence is configured.
|
|
55
|
+
*/
|
|
56
|
+
export interface ResolvedDiscordPresence {
|
|
57
|
+
status: DiscordPresenceStatus;
|
|
58
|
+
activityType?: DiscordPresenceActivityType;
|
|
59
|
+
activityTypeCode?: number;
|
|
60
|
+
activityText?: string;
|
|
61
|
+
activityUrl?: string;
|
|
62
|
+
}
|
|
63
|
+
/** Resolved auto-thread policy (`channels.discord.autoThread*`). */
|
|
64
|
+
export interface ResolvedDiscordAutoThread {
|
|
65
|
+
enabled: boolean;
|
|
66
|
+
/** Thread name source. `"generated"` falls back to first-message until a completion runtime exists. */
|
|
67
|
+
nameMode: DiscordAutoThreadNameMode;
|
|
68
|
+
/** Idle minutes before Discord auto-archives the thread (60 / 1440 / 4320 / 10080). */
|
|
69
|
+
autoArchiveMinutes: number;
|
|
70
|
+
}
|
|
41
71
|
/** Resolved per-account info — what the adapter runtime reads. */
|
|
42
72
|
export interface ResolvedDiscordAccount {
|
|
43
73
|
accountId: string;
|
|
@@ -107,11 +137,41 @@ export declare function discordStreamThrottleMs(cfg: BrigadeConfig): number;
|
|
|
107
137
|
* Default OFF — `<think>` reasoning is stripped from channel replies as today.
|
|
108
138
|
*/
|
|
109
139
|
export declare function discordSurfaceReasoning(cfg: BrigadeConfig): boolean;
|
|
140
|
+
/**
|
|
141
|
+
* Resolve the reaction-notification mode (`channels.discord.reactionNotifications`).
|
|
142
|
+
* Defaults to `"own"` (only reactions on the bot's own messages wake the agent) so
|
|
143
|
+
* a stranger's reaction in an admitted channel no longer spams a turn. An invalid /
|
|
144
|
+
* unset value degrades to the `"own"` default.
|
|
145
|
+
*/
|
|
146
|
+
export declare function discordReactionNotifications(cfg: BrigadeConfig): DiscordReactionNotificationMode;
|
|
110
147
|
/**
|
|
111
148
|
* Resolve the idle-thread-session TTL in ms, or `null` when unset / disabled.
|
|
112
149
|
* Accepts a number (ms) or a duration string (`"6h"`, `"30m"`, …). The cron
|
|
113
150
|
* session-reaper uses this to age out idle Discord thread sessions.
|
|
114
151
|
*/
|
|
115
152
|
export declare function discordThreadIdleTtlMs(cfg: BrigadeConfig): number | null;
|
|
153
|
+
/**
|
|
154
|
+
* Resolve the bot presence to apply on (re)connect, or `null` when none is
|
|
155
|
+
* configured (`channels.discord.presence` absent / empty → keep Discord's
|
|
156
|
+
* default). A `status`-only block (no activity) still resolves so the operator
|
|
157
|
+
* can set just the online dot. An invalid `status` / `activityType` is dropped
|
|
158
|
+
* (degrades to the default), never throwing. The activity type is mapped to its
|
|
159
|
+
* discord.js `ActivityType` numeric code here so the connection stays
|
|
160
|
+
* discord.js-agnostic.
|
|
161
|
+
*
|
|
162
|
+
* Multi-account aware (mirrors the other per-account resolvers): the per-account
|
|
163
|
+
* `accounts[id].presence` wins, falling back to the top-level
|
|
164
|
+
* `channels.discord.presence`. Without this fallback a per-account
|
|
165
|
+
* `set-presence` write was a silent no-op (it read only the top-level slot).
|
|
166
|
+
*/
|
|
167
|
+
export declare function resolveDiscordPresence(cfg: BrigadeConfig, accountId?: string | null): ResolvedDiscordPresence | null;
|
|
168
|
+
/**
|
|
169
|
+
* Resolve the auto-thread policy (`channels.discord.autoThread*`). Returns a
|
|
170
|
+
* resolved view with `enabled` false when the feature is off (the default). The
|
|
171
|
+
* name mode defaults to `"first-message"`; `autoArchiveDuration` is clamped to
|
|
172
|
+
* one of Discord's accepted values (60 / 1440 / 4320 / 10080), defaulting to
|
|
173
|
+
* 1440 (24h).
|
|
174
|
+
*/
|
|
175
|
+
export declare function resolveDiscordAutoThread(cfg: BrigadeConfig): ResolvedDiscordAutoThread;
|
|
116
176
|
export { CHANNEL_ID as DISCORD_CHANNEL_ID, DEFAULT_ACCOUNT_ID as DISCORD_DEFAULT_ACCOUNT_ID, BOT_TOKEN_ENV_VAR as DISCORD_BOT_TOKEN_ENV_VAR, };
|
|
117
177
|
//# sourceMappingURL=account-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-config.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/account-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG3D,QAAA,MAAM,UAAU,YAAY,CAAC;AAC7B,QAAA,MAAM,kBAAkB,YAAY,CAAC;AAErC,8DAA8D;AAC9D,QAAA,MAAM,iBAAiB,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"account-config.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/account-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -53,6 +53,18 @@ const PROXY_ENV_VARS = ["https_proxy", "HTTPS_PROXY", "all_proxy", "ALL_PROXY"];
|
|
|
53
53
|
const SEAL_KEY_BOT = CHANNEL_ID; // `channel:discord`
|
|
54
54
|
/** `${VAR}` secret-ref form — identical to `config/io.ts`'s SECRET_REF_PATTERN. */
|
|
55
55
|
const SECRET_REF_PATTERN = /^\$\{([A-Z_][A-Z0-9_]*)\}$/;
|
|
56
|
+
/** discord.js `ActivityType` numeric codes, keyed by the config string. */
|
|
57
|
+
const ACTIVITY_TYPE_CODE = {
|
|
58
|
+
playing: 0,
|
|
59
|
+
streaming: 1,
|
|
60
|
+
listening: 2,
|
|
61
|
+
watching: 3,
|
|
62
|
+
custom: 4,
|
|
63
|
+
competing: 5,
|
|
64
|
+
};
|
|
65
|
+
/** Valid auto-archive durations Discord accepts (minutes). */
|
|
66
|
+
const AUTO_ARCHIVE_DURATIONS = new Set([60, 1440, 4320, 10080]);
|
|
67
|
+
const DEFAULT_AUTO_ARCHIVE_MINUTES = 1440;
|
|
56
68
|
/** Read `channels.discord` loosely (schema keeps it open). */
|
|
57
69
|
function discordChannelConfig(cfg) {
|
|
58
70
|
return cfg.channels?.[CHANNEL_ID];
|
|
@@ -231,6 +243,18 @@ export function discordStreamThrottleMs(cfg) {
|
|
|
231
243
|
export function discordSurfaceReasoning(cfg) {
|
|
232
244
|
return discordChannelConfig(cfg)?.surfaceReasoning === true;
|
|
233
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* Resolve the reaction-notification mode (`channels.discord.reactionNotifications`).
|
|
248
|
+
* Defaults to `"own"` (only reactions on the bot's own messages wake the agent) so
|
|
249
|
+
* a stranger's reaction in an admitted channel no longer spams a turn. An invalid /
|
|
250
|
+
* unset value degrades to the `"own"` default.
|
|
251
|
+
*/
|
|
252
|
+
export function discordReactionNotifications(cfg) {
|
|
253
|
+
const raw = discordChannelConfig(cfg)?.reactionNotifications;
|
|
254
|
+
if (raw === "off" || raw === "own" || raw === "all" || raw === "allowlist")
|
|
255
|
+
return raw;
|
|
256
|
+
return "own";
|
|
257
|
+
}
|
|
234
258
|
/**
|
|
235
259
|
* Resolve the idle-thread-session TTL in ms, or `null` when unset / disabled.
|
|
236
260
|
* Accepts a number (ms) or a duration string (`"6h"`, `"30m"`, …). The cron
|
|
@@ -256,5 +280,70 @@ export function discordThreadIdleTtlMs(cfg) {
|
|
|
256
280
|
const factor = mult[unit] ?? 1;
|
|
257
281
|
return n * factor;
|
|
258
282
|
}
|
|
283
|
+
/* ───────────────────────── presence / auto-thread config ───────────────────────── */
|
|
284
|
+
/**
|
|
285
|
+
* Resolve the bot presence to apply on (re)connect, or `null` when none is
|
|
286
|
+
* configured (`channels.discord.presence` absent / empty → keep Discord's
|
|
287
|
+
* default). A `status`-only block (no activity) still resolves so the operator
|
|
288
|
+
* can set just the online dot. An invalid `status` / `activityType` is dropped
|
|
289
|
+
* (degrades to the default), never throwing. The activity type is mapped to its
|
|
290
|
+
* discord.js `ActivityType` numeric code here so the connection stays
|
|
291
|
+
* discord.js-agnostic.
|
|
292
|
+
*
|
|
293
|
+
* Multi-account aware (mirrors the other per-account resolvers): the per-account
|
|
294
|
+
* `accounts[id].presence` wins, falling back to the top-level
|
|
295
|
+
* `channels.discord.presence`. Without this fallback a per-account
|
|
296
|
+
* `set-presence` write was a silent no-op (it read only the top-level slot).
|
|
297
|
+
*/
|
|
298
|
+
export function resolveDiscordPresence(cfg, accountId) {
|
|
299
|
+
const id = accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
300
|
+
const entry = findAccountEntry(cfg, id);
|
|
301
|
+
const slot = (entry?.presence && typeof entry.presence === "object" ? entry.presence : undefined) ??
|
|
302
|
+
discordChannelConfig(cfg)?.presence;
|
|
303
|
+
if (!slot || typeof slot !== "object")
|
|
304
|
+
return null;
|
|
305
|
+
const statusRaw = typeof slot.status === "string" ? slot.status.trim().toLowerCase() : "";
|
|
306
|
+
const status = statusRaw === "online" || statusRaw === "idle" || statusRaw === "dnd" || statusRaw === "invisible"
|
|
307
|
+
? statusRaw
|
|
308
|
+
: "online";
|
|
309
|
+
const activityText = typeof slot.activityText === "string" ? slot.activityText.trim() : "";
|
|
310
|
+
const activityTypeRaw = typeof slot.activityType === "string" ? slot.activityType.trim().toLowerCase() : undefined;
|
|
311
|
+
const activityType = activityTypeRaw && activityTypeRaw in ACTIVITY_TYPE_CODE ? activityTypeRaw : undefined;
|
|
312
|
+
const activityUrl = typeof slot.activityUrl === "string" ? slot.activityUrl.trim() : "";
|
|
313
|
+
// Nothing meaningful configured (no status, no activity) → no presence.
|
|
314
|
+
const hasStatus = statusRaw !== "";
|
|
315
|
+
const hasActivity = activityText !== "" || activityType !== undefined;
|
|
316
|
+
if (!hasStatus && !hasActivity)
|
|
317
|
+
return null;
|
|
318
|
+
const resolved = { status };
|
|
319
|
+
if (hasActivity) {
|
|
320
|
+
// An activity row needs at least a type; default to `custom` (the status
|
|
321
|
+
// STATE line) when only text was given.
|
|
322
|
+
const type = activityType ?? "custom";
|
|
323
|
+
resolved.activityType = type;
|
|
324
|
+
resolved.activityTypeCode = ACTIVITY_TYPE_CODE[type];
|
|
325
|
+
if (activityText)
|
|
326
|
+
resolved.activityText = activityText;
|
|
327
|
+
if (type === "streaming" && activityUrl)
|
|
328
|
+
resolved.activityUrl = activityUrl;
|
|
329
|
+
}
|
|
330
|
+
return resolved;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Resolve the auto-thread policy (`channels.discord.autoThread*`). Returns a
|
|
334
|
+
* resolved view with `enabled` false when the feature is off (the default). The
|
|
335
|
+
* name mode defaults to `"first-message"`; `autoArchiveDuration` is clamped to
|
|
336
|
+
* one of Discord's accepted values (60 / 1440 / 4320 / 10080), defaulting to
|
|
337
|
+
* 1440 (24h).
|
|
338
|
+
*/
|
|
339
|
+
export function resolveDiscordAutoThread(cfg) {
|
|
340
|
+
const slot = discordChannelConfig(cfg);
|
|
341
|
+
const enabled = slot?.autoThread === true;
|
|
342
|
+
const nameRaw = typeof slot?.autoThreadName === "string" ? slot.autoThreadName.trim().toLowerCase() : "";
|
|
343
|
+
const nameMode = nameRaw === "generated" ? "generated" : "first-message";
|
|
344
|
+
const rawDuration = typeof slot?.autoArchiveDuration === "number" ? Math.round(slot.autoArchiveDuration) : NaN;
|
|
345
|
+
const autoArchiveMinutes = AUTO_ARCHIVE_DURATIONS.has(rawDuration) ? rawDuration : DEFAULT_AUTO_ARCHIVE_MINUTES;
|
|
346
|
+
return { enabled, nameMode, autoArchiveMinutes };
|
|
347
|
+
}
|
|
259
348
|
export { CHANNEL_ID as DISCORD_CHANNEL_ID, DEFAULT_ACCOUNT_ID as DISCORD_DEFAULT_ACCOUNT_ID, BOT_TOKEN_ENV_VAR as DISCORD_BOT_TOKEN_ENV_VAR, };
|
|
260
349
|
//# sourceMappingURL=account-config.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account-config.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/account-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,UAAU,GAAG,SAAS,CAAC;AAC7B,MAAM,kBAAkB,GAAG,SAAS,CAAC;AAErC,8DAA8D;AAC9D,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,cAAc,GAAG,CAAC,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,CAAU,CAAC;AAEzF;;;;GAIG;AACH,MAAM,YAAY,GAAG,UAAU,CAAC,CAAC,oBAAoB;AAErD,mFAAmF;AACnF,MAAM,kBAAkB,GAAG,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"account-config.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/account-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -19,8 +19,18 @@
|
|
|
19
19
|
* (registered via REST application commands on connect). Unlike Slack, Discord's
|
|
20
20
|
* command menu IS pushed programmatically — `nativeCommands: true`.
|
|
21
21
|
*/
|
|
22
|
+
import type { BrigadeConfig } from "../../../config/io.js";
|
|
22
23
|
import { type ChannelAdapter, type ChannelCapabilities } from "../sdk.js";
|
|
23
|
-
import { type
|
|
24
|
+
import { type ResolvedDiscordPresence } from "./account-config.js";
|
|
25
|
+
import { type ConnectDiscordArgs, type DiscordConnection, type DiscordInboundMessage, type DiscordPresencePayload } from "./connection.js";
|
|
26
|
+
/**
|
|
27
|
+
* Map a resolved presence config into the discord.js `PresenceData` payload the
|
|
28
|
+
* connection applies on (re)connect (Phase 5). A `custom` activity (type 4)
|
|
29
|
+
* carries its text in the `state` field (Discord renders custom status from the
|
|
30
|
+
* state); every other type uses `name`. A `streaming` activity (type 1) adds the
|
|
31
|
+
* `url`. Returns `null` when no presence is configured.
|
|
32
|
+
*/
|
|
33
|
+
export declare function mapDiscordPresencePayload(presence: ResolvedDiscordPresence | null): DiscordPresencePayload | null;
|
|
24
34
|
/** Adapter construction options — all optional for back-compat. */
|
|
25
35
|
export interface CreateDiscordAdapterOptions {
|
|
26
36
|
/** Per-account scope. Defaults to `"default"` (single-account). */
|
|
@@ -32,6 +42,19 @@ export interface CreateDiscordAdapterOptions {
|
|
|
32
42
|
connectImpl?: (args: ConnectDiscordArgs) => Promise<DiscordConnection>;
|
|
33
43
|
}
|
|
34
44
|
export declare function createDiscordAdapter(opts?: CreateDiscordAdapterOptions): ChannelAdapter;
|
|
45
|
+
/**
|
|
46
|
+
* Decide whether an inbound reaction-add should wake the agent, per the
|
|
47
|
+
* `channels.discord.reactionNotifications` mode (default `"own"`):
|
|
48
|
+
* - `"off"` → never;
|
|
49
|
+
* - `"own"` → only when the reacted message was authored by the bot
|
|
50
|
+
* (`reaction.targetAuthorId === selfId`);
|
|
51
|
+
* - `"all"` → always (legacy behavior);
|
|
52
|
+
* - `"allowlist"` → only when the reactor (`msg.from`) is on the channel
|
|
53
|
+
* allow-list — the central store list ∪ config `allowFrom`.
|
|
54
|
+
* A null config defensively falls back to `"own"`. Reaction-REMOVE is unaffected
|
|
55
|
+
* (handled in the connection's dedupe-release path).
|
|
56
|
+
*/
|
|
57
|
+
export declare function shouldNotifyReaction(msg: DiscordInboundMessage, cfg: BrigadeConfig | null, selfId: string | undefined, accountId?: string): boolean;
|
|
35
58
|
/**
|
|
36
59
|
* Synthesise the agent-facing note for an inbound reaction. The reaction itself
|
|
37
60
|
* carries no text, so the note ("<who> reacted :emoji: to message <id>") is what
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAK3D,OAAO,EAGN,KAAK,cAAc,EAEnB,KAAK,mBAAmB,EAQxB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAaN,KAAK,uBAAuB,EAC5B,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EAGN,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,MAAM,iBAAiB,CAAC;AASzB;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,IAAI,GAAG,sBAAsB,GAAG,IAAI,CAiBjH;AAED,mEAAmE;AACnE,MAAM,WAAW,2BAA2B;IAC3C,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;CACvE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,2BAAgC,GAAG,cAAc,CAwiB3F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,qBAAqB,EAC1B,GAAG,EAAE,aAAa,GAAG,IAAI,EACzB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,SAAS,CAAC,EAAE,MAAM,GAChB,OAAO,CAsBT;AASD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAKtG;AAED,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,EAAE,mBASlC,CAAC;AAEF;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,cAAc;IACrD;;;;OAIG;IACH,WAAW,IAAI,MAAM,GAAG,IAAI,CAAC;CAC7B"}
|
|
@@ -24,20 +24,62 @@ import { loadConfig } from "../../../core/config.js";
|
|
|
24
24
|
// contract + shared helpers. Contract types + `chunkText` + `buildBundledCommands`
|
|
25
25
|
// come from one place instead of scattered paths.
|
|
26
26
|
import { buildBundledCommands, chunkText, } from "../sdk.js";
|
|
27
|
-
import {
|
|
27
|
+
import { readAllowFrom } from "../access-control/store.js";
|
|
28
|
+
import { discordChannelEnabled, discordLiveStreamEnabled, discordReactionNotifications, discordStreamThrottleMs, discordSurfaceReasoning, listDiscordAccountIds, resolveDiscordAutoThread, resolveDiscordBotToken, resolveDiscordPresence, resolveDiscordProxyUrl, DISCORD_CHANNEL_ID, DISCORD_DEFAULT_ACCOUNT_ID, } from "./account-config.js";
|
|
28
29
|
import { resolveDiscordApprover } from "./approval-authorize.js";
|
|
29
30
|
import { buildDiscordApprovalMessage } from "./approval-native.js";
|
|
30
31
|
import { buildDiscordButtonRows } from "./components.js";
|
|
31
32
|
import { buildDiscordCommandManifest } from "./command-menu.js";
|
|
32
|
-
import { connectDiscord } from "./connection.js";
|
|
33
|
+
import { connectDiscord, sanitizeThreadName, } from "./connection.js";
|
|
34
|
+
import { resolveDiscordHandle } from "./directory-cache.js";
|
|
33
35
|
import { createDraftStream } from "./draft-stream.js";
|
|
34
|
-
import { discordTextIsEmpty, markdownToDiscord } from "./format.js";
|
|
36
|
+
import { discordTextIsEmpty, markdownToDiscord, rewriteKnownMentions } from "./format.js";
|
|
35
37
|
import { splitDiscordReasoning } from "./reasoning-lane.js";
|
|
36
38
|
/** Discord's per-message text limit (chars) for chunked sends. */
|
|
37
39
|
const DISCORD_TEXT_LIMIT = 2_000;
|
|
40
|
+
/**
|
|
41
|
+
* Map a resolved presence config into the discord.js `PresenceData` payload the
|
|
42
|
+
* connection applies on (re)connect (Phase 5). A `custom` activity (type 4)
|
|
43
|
+
* carries its text in the `state` field (Discord renders custom status from the
|
|
44
|
+
* state); every other type uses `name`. A `streaming` activity (type 1) adds the
|
|
45
|
+
* `url`. Returns `null` when no presence is configured.
|
|
46
|
+
*/
|
|
47
|
+
export function mapDiscordPresencePayload(presence) {
|
|
48
|
+
if (!presence)
|
|
49
|
+
return null;
|
|
50
|
+
const payload = { status: presence.status };
|
|
51
|
+
if (presence.activityTypeCode !== undefined) {
|
|
52
|
+
const isCustom = presence.activityTypeCode === 4;
|
|
53
|
+
const text = presence.activityText ?? "";
|
|
54
|
+
const activity = {
|
|
55
|
+
// A custom activity needs a non-empty `name` per Discord; the visible text
|
|
56
|
+
// rides in `state`. Other types put the text in `name`.
|
|
57
|
+
name: isCustom ? "Custom Status" : text,
|
|
58
|
+
type: presence.activityTypeCode,
|
|
59
|
+
};
|
|
60
|
+
if (isCustom && text)
|
|
61
|
+
activity.state = text;
|
|
62
|
+
if (presence.activityTypeCode === 1 && presence.activityUrl)
|
|
63
|
+
activity.url = presence.activityUrl;
|
|
64
|
+
payload.activities = [activity];
|
|
65
|
+
}
|
|
66
|
+
return payload;
|
|
67
|
+
}
|
|
38
68
|
export function createDiscordAdapter(opts = {}) {
|
|
39
69
|
const accountId = opts.accountId?.trim() || DISCORD_DEFAULT_ACCOUNT_ID;
|
|
40
70
|
const connectImpl = opts.connectImpl ?? connectDiscord;
|
|
71
|
+
// Resolver bound to THIS adapter's account, handed to `rewriteKnownMentions`
|
|
72
|
+
// so a plain `@handle` the agent typed becomes a `<@id>` ping when (and only
|
|
73
|
+
// when) the inbound directory cache has seen that handle for this account.
|
|
74
|
+
const resolveMention = (handle) => resolveDiscordHandle(accountId, handle);
|
|
75
|
+
// Render an outbound chunk: rewrite known `@handle` mentions to `<@id>` FIRST
|
|
76
|
+
// (so the converter sees a real mention token), then markdown→Discord, with the
|
|
77
|
+
// raw chunk as the empty-render fallback (a syntax-only chunk must still send).
|
|
78
|
+
const renderOutbound = (chunk) => {
|
|
79
|
+
const withMentions = rewriteKnownMentions(chunk, resolveMention);
|
|
80
|
+
const rendered = markdownToDiscord(withMentions);
|
|
81
|
+
return discordTextIsEmpty(rendered) ? withMentions : rendered;
|
|
82
|
+
};
|
|
41
83
|
let connection = null;
|
|
42
84
|
// The ChannelStartContext doesn't carry the config, but the manager ALWAYS
|
|
43
85
|
// calls `isConfigured(cfg, env)` immediately before `start(ctx)` — so we
|
|
@@ -52,6 +94,60 @@ export function createDiscordAdapter(opts = {}) {
|
|
|
52
94
|
// only recovery is `brigade channels add --channel discord` with a new token.
|
|
53
95
|
let connected = false;
|
|
54
96
|
let tokenInvalid = false;
|
|
97
|
+
/**
|
|
98
|
+
* Cheap SYNCHRONOUS gate: should this inbound trigger autoThread creation? True
|
|
99
|
+
* only when the feature is on, the message isn't already in a thread, and it's
|
|
100
|
+
* a guild text message carrying text + the ids needed to anchor a thread. Keeps
|
|
101
|
+
* the non-autoThread inbound path fully synchronous.
|
|
102
|
+
*/
|
|
103
|
+
const shouldAutoThread = (msg) => {
|
|
104
|
+
if (msg.threadId)
|
|
105
|
+
return false; // already in a thread
|
|
106
|
+
const cfg = lastConfig;
|
|
107
|
+
if (!cfg || !connection)
|
|
108
|
+
return false;
|
|
109
|
+
if (!resolveDiscordAutoThread(cfg).enabled)
|
|
110
|
+
return false;
|
|
111
|
+
// Guild text message only (a DM has no guildId; a reaction/callback carries no text).
|
|
112
|
+
return Boolean((msg.guildId ?? "").trim() &&
|
|
113
|
+
(msg.conversationId ?? "").trim() &&
|
|
114
|
+
(msg.messageId ?? "").trim() &&
|
|
115
|
+
(msg.text ?? "").trim());
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Phase 5 autoThread: create a thread off an inbound guild text message and
|
|
119
|
+
* return the new thread id (caller guards with {@link shouldAutoThread}).
|
|
120
|
+
* Returns the message's existing `threadId` (undefined) on any failure so the
|
|
121
|
+
* reply stays un-threaded.
|
|
122
|
+
*
|
|
123
|
+
* Thread naming: `"first-message"` uses the inbound's first line; `"generated"`
|
|
124
|
+
* would use an LLM-titled name, but Brigade has no simple-completion helper —
|
|
125
|
+
* so `"generated"` FALLS BACK to the first-message name here (no completion
|
|
126
|
+
* runtime is built).
|
|
127
|
+
*/
|
|
128
|
+
const maybeAutoThread = async (msg) => {
|
|
129
|
+
const existing = msg.threadId;
|
|
130
|
+
const cfg = lastConfig;
|
|
131
|
+
if (!cfg || !connection)
|
|
132
|
+
return existing;
|
|
133
|
+
const auto = resolveDiscordAutoThread(cfg);
|
|
134
|
+
const channelId = (msg.conversationId ?? "").trim();
|
|
135
|
+
const messageId = (msg.messageId ?? "").trim();
|
|
136
|
+
const text = (msg.text ?? "").trim();
|
|
137
|
+
// Name source: first-message (or generated → fallback to first-message until
|
|
138
|
+
// a completion runtime exists).
|
|
139
|
+
const name = sanitizeThreadName(text, messageId);
|
|
140
|
+
try {
|
|
141
|
+
const created = await connection.createThreadFromMessage(channelId, messageId, {
|
|
142
|
+
name,
|
|
143
|
+
autoArchiveMinutes: auto.autoArchiveMinutes,
|
|
144
|
+
});
|
|
145
|
+
return created ?? existing;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return existing;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
55
151
|
const adapter = {
|
|
56
152
|
id: DISCORD_CHANNEL_ID,
|
|
57
153
|
label: "Discord",
|
|
@@ -88,9 +184,12 @@ export function createDiscordAdapter(opts = {}) {
|
|
|
88
184
|
// The native slash-command manifest, derived from Brigade's central
|
|
89
185
|
// channel commands; registered right after the connection is live (below).
|
|
90
186
|
const commandManifest = buildDiscordCommandManifest(buildBundledCommands(adapter));
|
|
187
|
+
// Resolve the optional bot presence to apply on (re)connect (Phase 5).
|
|
188
|
+
const presencePayload = mapDiscordPresencePayload(resolveDiscordPresence(cfg, accountId));
|
|
91
189
|
const conn = await connectImpl({
|
|
92
190
|
botToken,
|
|
93
191
|
...(proxyUrl ? { proxyUrl } : {}),
|
|
192
|
+
...(presencePayload ? { presence: presencePayload } : {}),
|
|
94
193
|
accountId,
|
|
95
194
|
log: ctx.log,
|
|
96
195
|
onConnected: () => {
|
|
@@ -106,40 +205,62 @@ export function createDiscordAdapter(opts = {}) {
|
|
|
106
205
|
ctx.onLoggedOut?.();
|
|
107
206
|
},
|
|
108
207
|
onMessage: (msg) => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
208
|
+
// Build the inbound with a resolved thread id. The dispatch is
|
|
209
|
+
// SYNCHRONOUS when no thread needs creating (the common path, unchanged
|
|
210
|
+
// behavior); only autoThread creation defers to a microtask.
|
|
211
|
+
const dispatch = (threadId) => {
|
|
212
|
+
void ctx.onInbound({
|
|
213
|
+
channel: DISCORD_CHANNEL_ID,
|
|
214
|
+
accountId,
|
|
215
|
+
conversationId: msg.conversationId,
|
|
216
|
+
messageId: msg.messageId,
|
|
217
|
+
messageTimestampMs: msg.messageTimestampMs,
|
|
218
|
+
from: msg.from,
|
|
219
|
+
fromName: msg.fromName,
|
|
220
|
+
text: msg.text,
|
|
221
|
+
chatType: msg.chatType,
|
|
222
|
+
isGroup: msg.chatType === "group",
|
|
223
|
+
threadId,
|
|
224
|
+
// Discord routes on guildId + member role ids (NOT teamId — that
|
|
225
|
+
// is Slack's workspace tier; setting it would risk colliding with
|
|
226
|
+
// a Slack team binding).
|
|
227
|
+
guildId: msg.guildId,
|
|
228
|
+
memberRoleIds: msg.memberRoleIds,
|
|
229
|
+
mentions: msg.mentions,
|
|
230
|
+
replyTo: msg.replyTo,
|
|
231
|
+
// Edit provenance rides through so the central pipeline / agent see
|
|
232
|
+
// "this was an edit".
|
|
233
|
+
...(msg.edited ? { edited: true } : {}),
|
|
234
|
+
// Deferred media thunk rides through untouched — the pipeline
|
|
235
|
+
// resolves it only after the access gate admits the sender.
|
|
236
|
+
resolveMedia: msg.resolveMedia,
|
|
237
|
+
raw: msg.raw,
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
// Phase 5 autoThread: when enabled (and this is a fresh guild text
|
|
241
|
+
// message), spawn a thread off the message and route the reply into it.
|
|
242
|
+
// `shouldAutoThread` is a cheap synchronous gate so the non-autoThread
|
|
243
|
+
// path stays fully synchronous.
|
|
244
|
+
if (shouldAutoThread(msg)) {
|
|
245
|
+
void maybeAutoThread(msg).then(dispatch).catch(() => dispatch(msg.threadId));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
dispatch(msg.threadId);
|
|
249
|
+
}
|
|
136
250
|
},
|
|
137
251
|
// Inbound reaction → synthesise a short note and route it through the
|
|
138
252
|
// SAME inbound pipeline as a normal message so the access gate + routing
|
|
139
253
|
// apply uniformly. The note carries the added emoji(s) + the target id.
|
|
254
|
+
//
|
|
255
|
+
// GATED by `channels.discord.reactionNotifications` (default "own") so a
|
|
256
|
+
// stranger's reaction in an admitted channel no longer spams the agent:
|
|
257
|
+
// off → drop all; own → only reactions on the bot's own messages;
|
|
258
|
+
// all → route every reaction; allowlist → only allow-listed reactors.
|
|
140
259
|
onReaction: (msg) => {
|
|
141
260
|
if (!msg.reaction)
|
|
142
261
|
return;
|
|
262
|
+
if (!shouldNotifyReaction(msg, lastConfig, connection?.selfId() ?? undefined, accountId))
|
|
263
|
+
return;
|
|
143
264
|
const note = buildReactionNote(msg.reaction.emojis, msg.reaction.targetMessageId, msg.fromName);
|
|
144
265
|
void ctx.onInbound({
|
|
145
266
|
channel: DISCORD_CHANNEL_ID,
|
|
@@ -241,14 +362,15 @@ export function createDiscordAdapter(opts = {}) {
|
|
|
241
362
|
// convert each chunk to Discord markup and send. A chunk whose rendered
|
|
242
363
|
// markup is empty (syntax-only) is re-sent as the raw chunk.
|
|
243
364
|
const chunks = chunkText(text, { limit: DISCORD_TEXT_LIMIT });
|
|
365
|
+
// A silent send rides through on every chunk (SuppressNotifications).
|
|
366
|
+
const silentOpt = opts?.silent ? { silent: true } : {};
|
|
244
367
|
let first = true;
|
|
245
368
|
for (const chunk of chunks) {
|
|
246
369
|
const replyOpt = first && replyToMessageId ? { replyToMessageId } : {};
|
|
247
|
-
const
|
|
248
|
-
const body = discordTextIsEmpty(rendered) ? chunk : rendered;
|
|
370
|
+
const body = renderOutbound(chunk);
|
|
249
371
|
if (body.trim().length === 0)
|
|
250
372
|
continue;
|
|
251
|
-
await connection.sendText(conversationId, body, { ...sendExtras, ...replyOpt });
|
|
373
|
+
await connection.sendText(conversationId, body, { ...sendExtras, ...silentOpt, ...replyOpt });
|
|
252
374
|
first = false;
|
|
253
375
|
}
|
|
254
376
|
},
|
|
@@ -290,11 +412,10 @@ export function createDiscordAdapter(opts = {}) {
|
|
|
290
412
|
...(threadId !== undefined ? { threadId } : {}),
|
|
291
413
|
throttleMs: discordStreamThrottleMs(cfg),
|
|
292
414
|
maxChars: DISCORD_TEXT_LIMIT,
|
|
293
|
-
// Render each draft chunk to Discord markup
|
|
294
|
-
// when it renders empty (syntax-only).
|
|
415
|
+
// Render each draft chunk to Discord markup (incl. known-mention rewrite);
|
|
416
|
+
// fall back to the plain chunk when it renders empty (syntax-only).
|
|
295
417
|
renderText: (chunk) => {
|
|
296
|
-
|
|
297
|
-
return { text: discordTextIsEmpty(rendered) ? chunk : rendered };
|
|
418
|
+
return { text: renderOutbound(chunk) };
|
|
298
419
|
},
|
|
299
420
|
});
|
|
300
421
|
return {
|
|
@@ -432,14 +553,19 @@ export function createDiscordAdapter(opts = {}) {
|
|
|
432
553
|
try {
|
|
433
554
|
switch (a.kind) {
|
|
434
555
|
case "edit": {
|
|
435
|
-
const
|
|
436
|
-
const body = discordTextIsEmpty(rendered) ? a.text : rendered;
|
|
556
|
+
const body = renderOutbound(a.text);
|
|
437
557
|
await connection.editMessageText(p.conversationId, a.messageId, body);
|
|
438
558
|
return { ok: true, messageId: a.messageId };
|
|
439
559
|
}
|
|
440
560
|
case "delete":
|
|
441
561
|
await connection.deleteMessage(p.conversationId, a.messageId);
|
|
442
562
|
return { ok: true, messageId: a.messageId };
|
|
563
|
+
case "pin":
|
|
564
|
+
await connection.pinMessage(p.conversationId, a.messageId);
|
|
565
|
+
return { ok: true, messageId: a.messageId };
|
|
566
|
+
case "unpin":
|
|
567
|
+
await connection.unpinMessage(p.conversationId, a.messageId);
|
|
568
|
+
return { ok: true, messageId: a.messageId };
|
|
443
569
|
case "react":
|
|
444
570
|
// An EMPTY emoji means "clear" (parity with WhatsApp/Telegram/Slack):
|
|
445
571
|
// remove the bot's OWN reactions on this message; a non-empty emoji
|
|
@@ -467,8 +593,7 @@ export function createDiscordAdapter(opts = {}) {
|
|
|
467
593
|
if (!rows) {
|
|
468
594
|
return { ok: false, error: "no usable buttons (each needs a label + a data token ≤ 100 chars)" };
|
|
469
595
|
}
|
|
470
|
-
const
|
|
471
|
-
const body = discordTextIsEmpty(rendered) ? a.text : rendered;
|
|
596
|
+
const body = renderOutbound(a.text);
|
|
472
597
|
const sent = await connection.sendInteractive(p.conversationId, body, rows, {
|
|
473
598
|
...(a.threadId !== undefined ? { threadId: a.threadId } : {}),
|
|
474
599
|
});
|
|
@@ -494,6 +619,48 @@ export function createDiscordAdapter(opts = {}) {
|
|
|
494
619
|
};
|
|
495
620
|
return adapter;
|
|
496
621
|
}
|
|
622
|
+
/**
|
|
623
|
+
* Decide whether an inbound reaction-add should wake the agent, per the
|
|
624
|
+
* `channels.discord.reactionNotifications` mode (default `"own"`):
|
|
625
|
+
* - `"off"` → never;
|
|
626
|
+
* - `"own"` → only when the reacted message was authored by the bot
|
|
627
|
+
* (`reaction.targetAuthorId === selfId`);
|
|
628
|
+
* - `"all"` → always (legacy behavior);
|
|
629
|
+
* - `"allowlist"` → only when the reactor (`msg.from`) is on the channel
|
|
630
|
+
* allow-list — the central store list ∪ config `allowFrom`.
|
|
631
|
+
* A null config defensively falls back to `"own"`. Reaction-REMOVE is unaffected
|
|
632
|
+
* (handled in the connection's dedupe-release path).
|
|
633
|
+
*/
|
|
634
|
+
export function shouldNotifyReaction(msg, cfg, selfId, accountId) {
|
|
635
|
+
const mode = cfg ? discordReactionNotifications(cfg) : "own";
|
|
636
|
+
switch (mode) {
|
|
637
|
+
case "off":
|
|
638
|
+
return false;
|
|
639
|
+
case "all":
|
|
640
|
+
return true;
|
|
641
|
+
case "allowlist": {
|
|
642
|
+
const reactor = msg.from?.trim();
|
|
643
|
+
if (!reactor)
|
|
644
|
+
return false;
|
|
645
|
+
const acct = accountId?.trim() || undefined;
|
|
646
|
+
const storeAllow = readAllowFrom(DISCORD_CHANNEL_ID, acct);
|
|
647
|
+
const configAllow = readDiscordAllowFrom(cfg);
|
|
648
|
+
const allow = new Set([...storeAllow, ...configAllow].map((id) => id.trim()).filter(Boolean));
|
|
649
|
+
return allow.has(reactor);
|
|
650
|
+
}
|
|
651
|
+
case "own":
|
|
652
|
+
default: {
|
|
653
|
+
const targetAuthor = msg.reaction?.targetAuthorId?.trim();
|
|
654
|
+
return Boolean(selfId && targetAuthor && targetAuthor === selfId.trim());
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
/** Read `channels.discord.allowFrom` (config-declared allow-list ids) defensively. */
|
|
659
|
+
function readDiscordAllowFrom(cfg) {
|
|
660
|
+
const slot = cfg?.channels?.[DISCORD_CHANNEL_ID];
|
|
661
|
+
const list = slot?.allowFrom;
|
|
662
|
+
return Array.isArray(list) ? list.map((x) => String(x)) : [];
|
|
663
|
+
}
|
|
497
664
|
/**
|
|
498
665
|
* Synthesise the agent-facing note for an inbound reaction. The reaction itself
|
|
499
666
|
* carries no text, so the note ("<who> reacted :emoji: to message <id>") is what
|