@spinabot/brigade 1.6.0 → 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/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 +177 -0
- package/dist/agents/channels/discord/account-config.d.ts.map +1 -0
- package/dist/agents/channels/discord/account-config.js +349 -0
- package/dist/agents/channels/discord/account-config.js.map +1 -0
- package/dist/agents/channels/discord/adapter.d.ts +79 -0
- package/dist/agents/channels/discord/adapter.d.ts.map +1 -0
- package/dist/agents/channels/discord/adapter.js +693 -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/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 +175 -0
- package/dist/agents/channels/discord/components.d.ts.map +1 -0
- package/dist/agents/channels/discord/components.js +220 -0
- package/dist/agents/channels/discord/components.js.map +1 -0
- package/dist/agents/channels/discord/connection.d.ts +570 -0
- package/dist/agents/channels/discord/connection.d.ts.map +1 -0
- package/dist/agents/channels/discord/connection.js +1600 -0
- package/dist/agents/channels/discord/connection.js.map +1 -0
- 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/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 +70 -0
- package/dist/agents/channels/discord/format.d.ts.map +1 -0
- package/dist/agents/channels/discord/format.js +303 -0
- package/dist/agents/channels/discord/format.js.map +1 -0
- 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 +377 -0
- package/dist/agents/channels/discord/inbound-extras.d.ts.map +1 -0
- package/dist/agents/channels/discord/inbound-extras.js +589 -0
- package/dist/agents/channels/discord/inbound-extras.js.map +1 -0
- package/dist/agents/channels/discord/index.d.ts +21 -0
- package/dist/agents/channels/discord/index.d.ts.map +1 -0
- package/dist/agents/channels/discord/index.js +21 -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/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/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/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 +89 -0
- package/dist/agents/channels/discord/plugin.d.ts.map +1 -0
- package/dist/agents/channels/discord/plugin.js +372 -0
- package/dist/agents/channels/discord/plugin.js.map +1 -0
- package/dist/agents/channels/discord/probe.d.ts +115 -0
- package/dist/agents/channels/discord/probe.d.ts.map +1 -0
- package/dist/agents/channels/discord/probe.js +193 -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/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 +70 -10
- 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/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/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/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/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,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord Components-V2 layout-block specs (Fix 3c).
|
|
3
|
+
*
|
|
4
|
+
* Components V2 lets a message carry rich layout — containers, sections,
|
|
5
|
+
* separators, media galleries, files, and free-standing text — instead of a
|
|
6
|
+
* plain `content` string + button rows. A V2 message MUST set the
|
|
7
|
+
* `IsComponentsV2` flag and CANNOT carry plain `content`; all text moves into
|
|
8
|
+
* `TextDisplay` blocks. Link buttons (a button with a `url` + no custom_id) are
|
|
9
|
+
* the one button kind that fits naturally in a V2 layout.
|
|
10
|
+
*
|
|
11
|
+
* This module mirrors `components.ts`: it emits PLAIN serializable specs (a
|
|
12
|
+
* `DiscordBlockSpec` list) and never imports discord.js, so it stays pure +
|
|
13
|
+
* unit-testable. The connection turns each spec into the matching discord.js V2
|
|
14
|
+
* builder at send time (`ContainerBuilder` / `SectionBuilder` / `SeparatorBuilder`
|
|
15
|
+
* / `TextDisplayBuilder` / `MediaGalleryBuilder` / `FileBuilder`).
|
|
16
|
+
*
|
|
17
|
+
* Pure / deterministic — no I/O, no globals.
|
|
18
|
+
*/
|
|
19
|
+
import { DISCORD_BUTTON_STYLE } from "./components.js";
|
|
20
|
+
/** `MessageFlags.IsComponentsV2` (1 << 15). A V2 message sets this + drops plain content. */
|
|
21
|
+
export const DISCORD_FLAG_IS_COMPONENTS_V2 = 1 << 15;
|
|
22
|
+
/** discord.js `ButtonStyle.Link` value (a URL button, no custom_id). */
|
|
23
|
+
export const DISCORD_BUTTON_STYLE_LINK = 5;
|
|
24
|
+
/** True when a `buildComponentRows` entry is a V2 container marker. */
|
|
25
|
+
export function isDiscordV2MessageSpec(row) {
|
|
26
|
+
return typeof row === "object" && row !== null && row.row === "v2";
|
|
27
|
+
}
|
|
28
|
+
/** True when a button spec is a LINK button (URL, no custom_id). */
|
|
29
|
+
export function isDiscordLinkButton(b) {
|
|
30
|
+
return typeof b.url === "string" && b.url.length > 0;
|
|
31
|
+
}
|
|
32
|
+
/** The attachment-ref prefix a `file` block expects (Discord requires it). */
|
|
33
|
+
export const DISCORD_ATTACHMENT_REF_PREFIX = "attachment://";
|
|
34
|
+
const DISCORD_BUTTON_LABEL_MAX = 80;
|
|
35
|
+
const DISCORD_SECTION_MAX_TEXTS = 3;
|
|
36
|
+
/**
|
|
37
|
+
* Shape a high-level V2 message into a serializable container spec, or `null`
|
|
38
|
+
* when nothing renderable remains. Defensive caps mirror Discord's limits
|
|
39
|
+
* (section ≤ 3 texts, button label ≤ 80). A `file` block whose url isn't an
|
|
40
|
+
* `attachment://` ref is dropped (Discord rejects external file refs). Returns
|
|
41
|
+
* `null` when the resulting container would be empty so the caller can fall back
|
|
42
|
+
* to a plain text send.
|
|
43
|
+
*/
|
|
44
|
+
export function buildDiscordV2Message(spec) {
|
|
45
|
+
const blocks = [];
|
|
46
|
+
for (const block of spec?.blocks ?? []) {
|
|
47
|
+
if (!block || typeof block !== "object")
|
|
48
|
+
continue;
|
|
49
|
+
switch (block.type) {
|
|
50
|
+
case "text": {
|
|
51
|
+
const text = (block.text ?? "").trim();
|
|
52
|
+
if (text)
|
|
53
|
+
blocks.push({ type: "text", text });
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "section": {
|
|
57
|
+
const texts = (block.texts ?? []).map((t) => (t ?? "").trim()).filter((t) => t.length > 0).slice(0, DISCORD_SECTION_MAX_TEXTS);
|
|
58
|
+
if (texts.length === 0)
|
|
59
|
+
break;
|
|
60
|
+
const out = { type: "section", texts };
|
|
61
|
+
if (block.accessory)
|
|
62
|
+
out.accessory = block.accessory;
|
|
63
|
+
blocks.push(out);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "separator": {
|
|
67
|
+
const out = { type: "separator" };
|
|
68
|
+
if (typeof block.divider === "boolean")
|
|
69
|
+
out.divider = block.divider;
|
|
70
|
+
if (block.spacing)
|
|
71
|
+
out.spacing = block.spacing;
|
|
72
|
+
blocks.push(out);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "actions": {
|
|
76
|
+
const buttons = (block.buttons ?? [])
|
|
77
|
+
.filter((b) => b && typeof b.label === "string" && b.label.trim().length > 0)
|
|
78
|
+
.map((b) => {
|
|
79
|
+
const label = b.label.trim().slice(0, DISCORD_BUTTON_LABEL_MAX);
|
|
80
|
+
return isDiscordLinkButton(b) ? { label, url: b.url } : { label, customId: b.customId, style: b.style ?? DISCORD_BUTTON_STYLE.Secondary };
|
|
81
|
+
});
|
|
82
|
+
if (buttons.length > 0)
|
|
83
|
+
blocks.push({ type: "actions", buttons });
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "media-gallery": {
|
|
87
|
+
const items = (block.items ?? []).filter((it) => it && typeof it.url === "string" && it.url.length > 0);
|
|
88
|
+
if (items.length > 0)
|
|
89
|
+
blocks.push({ type: "media-gallery", items });
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case "file": {
|
|
93
|
+
const url = block.url ?? "";
|
|
94
|
+
if (typeof url === "string" && url.startsWith(DISCORD_ATTACHMENT_REF_PREFIX)) {
|
|
95
|
+
const out = { type: "file", url };
|
|
96
|
+
if (typeof block.spoiler === "boolean")
|
|
97
|
+
out.spoiler = block.spoiler;
|
|
98
|
+
blocks.push(out);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
default:
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (blocks.length === 0)
|
|
107
|
+
return null;
|
|
108
|
+
const out = { row: "v2", blocks };
|
|
109
|
+
if (typeof spec.accentColor === "number" && Number.isFinite(spec.accentColor))
|
|
110
|
+
out.accentColor = spec.accentColor;
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=component-blocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"component-blocks.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/component-blocks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord message-component button rendering + the custom_id callback codec.
|
|
3
|
+
*
|
|
4
|
+
* Discord's analogue of `slack/blocks.ts`. Two button lanes share one builder,
|
|
5
|
+
* exactly as Slack's do:
|
|
6
|
+
*
|
|
7
|
+
* - APPROVAL buttons — payloads come from the CENTRAL approval codec
|
|
8
|
+
* (`buildApprovalCallbackButtons`), so a press resolves through the central
|
|
9
|
+
* `tryConsumeChannelApprovalCallback`. Built in `approval-native.ts`; this
|
|
10
|
+
* module only supplies the low-level row + button shapes.
|
|
11
|
+
* - GENERAL buttons — the agent attaches arbitrary buttons via the
|
|
12
|
+
* `message_action` `buttons` kind; a press routes back through the inbound
|
|
13
|
+
* pipeline as a normal turn. Every general payload is namespaced with
|
|
14
|
+
* {@link GENERAL_CALLBACK_PREFIX} so the pipeline can tell it apart from an
|
|
15
|
+
* approval press (which it consumes FIRST).
|
|
16
|
+
*
|
|
17
|
+
* WHERE THE PAYLOAD RIDES. A Discord button carries its opaque codec payload in
|
|
18
|
+
* `custom_id` (Discord caps it at 100 chars). The central approval codec already
|
|
19
|
+
* guarantees ≤64 UTF-8 bytes, so an approval payload always fits; a general
|
|
20
|
+
* payload that exceeds the budget after prefixing is DROPPED (rather than ship a
|
|
21
|
+
* truncated id that decodes to the wrong action on press). On press, discord.js
|
|
22
|
+
* delivers the pressed `interaction.customId` verbatim, which the central
|
|
23
|
+
* pipeline decodes — there is no separate routing id (unlike Slack's action_id).
|
|
24
|
+
*
|
|
25
|
+
* These builders emit PLAIN serializable specs (a `DiscordButtonSpec` grid); the
|
|
26
|
+
* connection turns each into a discord.js `ActionRowBuilder<ButtonBuilder>` at
|
|
27
|
+
* send time, so this module stays pure + dependency-light + unit-testable
|
|
28
|
+
* without importing discord.js.
|
|
29
|
+
*
|
|
30
|
+
* Pure / deterministic — no I/O, no globals.
|
|
31
|
+
*/
|
|
32
|
+
import { type DiscordModalRegistration } from "./modal-registry.js";
|
|
33
|
+
/** Discord caps a component `custom_id` at 100 chars. */
|
|
34
|
+
export declare const DISCORD_CUSTOM_ID_MAX_CHARS = 100;
|
|
35
|
+
/** Discord allows at most 5 buttons per action row. */
|
|
36
|
+
export declare const DISCORD_BUTTONS_PER_ROW = 5;
|
|
37
|
+
/** Discord allows at most 5 action rows per message. */
|
|
38
|
+
export declare const DISCORD_MAX_ROWS = 5;
|
|
39
|
+
/** Discord ButtonStyle enum values (mirrors discord.js `ButtonStyle`). */
|
|
40
|
+
export declare const DISCORD_BUTTON_STYLE: {
|
|
41
|
+
readonly Primary: 1;
|
|
42
|
+
readonly Secondary: 2;
|
|
43
|
+
readonly Success: 3;
|
|
44
|
+
readonly Danger: 4;
|
|
45
|
+
};
|
|
46
|
+
export type DiscordButtonStyleValue = (typeof DISCORD_BUTTON_STYLE)[keyof typeof DISCORD_BUTTON_STYLE];
|
|
47
|
+
/** A single serializable Discord button spec (the connection turns it into a ButtonBuilder). */
|
|
48
|
+
export interface DiscordButtonSpec {
|
|
49
|
+
/** Button label shown to the user (≤ 80 chars). */
|
|
50
|
+
label: string;
|
|
51
|
+
/** Opaque codec payload carried in `custom_id` (≤ 100 chars), read back on press. */
|
|
52
|
+
customId: string;
|
|
53
|
+
/** Discord button style (default Secondary). */
|
|
54
|
+
style: DiscordButtonStyleValue;
|
|
55
|
+
}
|
|
56
|
+
/** One action row — up to 5 buttons. */
|
|
57
|
+
export type DiscordActionRow = DiscordButtonSpec[];
|
|
58
|
+
/** Discord ComponentType values for the five select-menu kinds (mirror discord.js). */
|
|
59
|
+
export declare const DISCORD_SELECT_COMPONENT_TYPE: {
|
|
60
|
+
readonly string: 3;
|
|
61
|
+
readonly user: 5;
|
|
62
|
+
readonly role: 6;
|
|
63
|
+
readonly mentionable: 7;
|
|
64
|
+
readonly channel: 8;
|
|
65
|
+
};
|
|
66
|
+
/** The five select-menu kinds Brigade can emit. */
|
|
67
|
+
export type DiscordSelectKind = "string" | "user" | "role" | "channel" | "mentionable";
|
|
68
|
+
/** One option of a STRING select (entity selects have no static options). */
|
|
69
|
+
export interface DiscordSelectOption {
|
|
70
|
+
/** Option label shown to the user. */
|
|
71
|
+
label: string;
|
|
72
|
+
/** Opaque value delivered back on selection (NOT the customId; the row's id routes). */
|
|
73
|
+
value: string;
|
|
74
|
+
/** Optional sublabel under the option. */
|
|
75
|
+
description?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A serializable select-menu row spec (the connection turns it into a discord.js
|
|
79
|
+
* `*SelectMenuBuilder` wrapped in its OWN `ActionRowBuilder` — a select must be
|
|
80
|
+
* alone in its row). Like a general button, the select's `customId` carries a
|
|
81
|
+
* {@link GENERAL_CALLBACK_PREFIX} token so a selection routes through the SAME
|
|
82
|
+
* central general-callback path a button press does, just carrying `values`.
|
|
83
|
+
*/
|
|
84
|
+
export interface DiscordSelectSpec {
|
|
85
|
+
/** Discriminator marking this row as a select (not a button grid). */
|
|
86
|
+
readonly row: "select";
|
|
87
|
+
/** Which select kind to render. */
|
|
88
|
+
kind: DiscordSelectKind;
|
|
89
|
+
/** Opaque codec payload carried in `custom_id` (already general-prefixed + ≤ 100 chars). */
|
|
90
|
+
customId: string;
|
|
91
|
+
/** Placeholder shown before a choice is made. */
|
|
92
|
+
placeholder?: string;
|
|
93
|
+
/** Min number of selections (Discord default 1). */
|
|
94
|
+
minValues?: number;
|
|
95
|
+
/** Max number of selections (Discord default 1). */
|
|
96
|
+
maxValues?: number;
|
|
97
|
+
/** STRING-select options (required + non-empty for `kind: "string"`; ignored otherwise). */
|
|
98
|
+
options?: DiscordSelectOption[];
|
|
99
|
+
}
|
|
100
|
+
/** True when a `buildComponentRows` entry is a select-row marker (vs a button grid). */
|
|
101
|
+
export declare function isDiscordSelectSpec(row: unknown): row is DiscordSelectSpec;
|
|
102
|
+
/**
|
|
103
|
+
* Build a select-row marker from a high-level spec. The token is namespaced with
|
|
104
|
+
* {@link GENERAL_CALLBACK_PREFIX} (exactly like a general button) + sanitized; a
|
|
105
|
+
* token that overflows the 100-char budget OR a `string` select with no usable
|
|
106
|
+
* option yields `null` (the caller falls back to a plain message rather than ship
|
|
107
|
+
* a select that decodes to the wrong action / renders empty). Placeholder +
|
|
108
|
+
* option text are capped to Discord's limits.
|
|
109
|
+
*/
|
|
110
|
+
export declare function buildDiscordSelectRow(spec: {
|
|
111
|
+
kind: DiscordSelectKind;
|
|
112
|
+
customIdToken: string;
|
|
113
|
+
placeholder?: string;
|
|
114
|
+
minValues?: number;
|
|
115
|
+
maxValues?: number;
|
|
116
|
+
options?: DiscordSelectOption[];
|
|
117
|
+
}): DiscordSelectSpec | null;
|
|
118
|
+
/** One button spec before it's shaped + validated. */
|
|
119
|
+
export interface DiscordButtonInput {
|
|
120
|
+
/** Button label shown to the user. */
|
|
121
|
+
text: string;
|
|
122
|
+
/** Opaque codec payload delivered to the handler on press. */
|
|
123
|
+
value: string;
|
|
124
|
+
/** Optional Discord button style. */
|
|
125
|
+
style?: DiscordButtonStyleValue;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Strip control bytes from a custom_id. The central codec already guarantees a
|
|
129
|
+
* printable, short payload, so this is purely defensive for a value that didn't
|
|
130
|
+
* come from the codec — and we never truncate a payload (we DROP an over-budget
|
|
131
|
+
* one at the call site), since a truncated id would decode to the wrong action.
|
|
132
|
+
*/
|
|
133
|
+
export declare function sanitizeDiscordCustomId(value: string): string;
|
|
134
|
+
/**
|
|
135
|
+
* Build the action rows for an APPROVAL prompt from the central codec's
|
|
136
|
+
* `{ label, data, decision }` specs (the payload rides in `custom_id`). Returns
|
|
137
|
+
* `[]` when fewer than two byte-safe buttons could be shaped — the caller then
|
|
138
|
+
* falls back to a text prompt rather than ship a half-rendered prompt. `Deny` is
|
|
139
|
+
* styled Danger; the first (allow-once) button Success.
|
|
140
|
+
*/
|
|
141
|
+
export declare function buildDiscordApprovalRows(specs: ReadonlyArray<{
|
|
142
|
+
label: string;
|
|
143
|
+
data: string;
|
|
144
|
+
decision?: string;
|
|
145
|
+
}>): DiscordActionRow[];
|
|
146
|
+
/**
|
|
147
|
+
* Build the action rows for a GENERAL button keyboard from a grid of specs. Each
|
|
148
|
+
* spec's `data` is prefixed with {@link GENERAL_CALLBACK_PREFIX} + sanitized; a
|
|
149
|
+
* button whose prefixed id exceeds the budget OR whose label is empty is
|
|
150
|
+
* DROPPED. Returns `null` when no usable button remains (the caller then sends a
|
|
151
|
+
* plain message instead). The grid is flattened + re-chunked into Discord's
|
|
152
|
+
* 5-per-row / 5-row limits.
|
|
153
|
+
*/
|
|
154
|
+
export declare function buildDiscordButtonRows(grid: Array<Array<{
|
|
155
|
+
text: string;
|
|
156
|
+
data: string;
|
|
157
|
+
}>>): DiscordActionRow[] | null;
|
|
158
|
+
/**
|
|
159
|
+
* Register a modal definition + build the BUTTON that opens it. The button is a
|
|
160
|
+
* normal component whose `custom_id` is a `modal:<modalId>` marker (NOT a general
|
|
161
|
+
* token): on press the connection recognizes the marker and calls `showModal`
|
|
162
|
+
* instead of routing a turn. Returns the button spec + the minted modal id (so a
|
|
163
|
+
* caller can correlate). Returns `null` when the label is empty or the marker
|
|
164
|
+
* overflows the custom_id budget (never the case for a short generated id, but
|
|
165
|
+
* guarded for symmetry with the other builders).
|
|
166
|
+
*/
|
|
167
|
+
export declare function buildDiscordModalTriggerButton(params: {
|
|
168
|
+
label: string;
|
|
169
|
+
registration: DiscordModalRegistration;
|
|
170
|
+
style?: DiscordButtonStyleValue;
|
|
171
|
+
}): {
|
|
172
|
+
button: DiscordButtonSpec;
|
|
173
|
+
modalId: string;
|
|
174
|
+
} | null;
|
|
175
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/components.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord message-component button rendering + the custom_id callback codec.
|
|
3
|
+
*
|
|
4
|
+
* Discord's analogue of `slack/blocks.ts`. Two button lanes share one builder,
|
|
5
|
+
* exactly as Slack's do:
|
|
6
|
+
*
|
|
7
|
+
* - APPROVAL buttons — payloads come from the CENTRAL approval codec
|
|
8
|
+
* (`buildApprovalCallbackButtons`), so a press resolves through the central
|
|
9
|
+
* `tryConsumeChannelApprovalCallback`. Built in `approval-native.ts`; this
|
|
10
|
+
* module only supplies the low-level row + button shapes.
|
|
11
|
+
* - GENERAL buttons — the agent attaches arbitrary buttons via the
|
|
12
|
+
* `message_action` `buttons` kind; a press routes back through the inbound
|
|
13
|
+
* pipeline as a normal turn. Every general payload is namespaced with
|
|
14
|
+
* {@link GENERAL_CALLBACK_PREFIX} so the pipeline can tell it apart from an
|
|
15
|
+
* approval press (which it consumes FIRST).
|
|
16
|
+
*
|
|
17
|
+
* WHERE THE PAYLOAD RIDES. A Discord button carries its opaque codec payload in
|
|
18
|
+
* `custom_id` (Discord caps it at 100 chars). The central approval codec already
|
|
19
|
+
* guarantees ≤64 UTF-8 bytes, so an approval payload always fits; a general
|
|
20
|
+
* payload that exceeds the budget after prefixing is DROPPED (rather than ship a
|
|
21
|
+
* truncated id that decodes to the wrong action on press). On press, discord.js
|
|
22
|
+
* delivers the pressed `interaction.customId` verbatim, which the central
|
|
23
|
+
* pipeline decodes — there is no separate routing id (unlike Slack's action_id).
|
|
24
|
+
*
|
|
25
|
+
* These builders emit PLAIN serializable specs (a `DiscordButtonSpec` grid); the
|
|
26
|
+
* connection turns each into a discord.js `ActionRowBuilder<ButtonBuilder>` at
|
|
27
|
+
* send time, so this module stays pure + dependency-light + unit-testable
|
|
28
|
+
* without importing discord.js.
|
|
29
|
+
*
|
|
30
|
+
* Pure / deterministic — no I/O, no globals.
|
|
31
|
+
*/
|
|
32
|
+
import { GENERAL_CALLBACK_PREFIX } from "../general-callback.js";
|
|
33
|
+
import { registerDiscordModal } from "./modal-registry.js";
|
|
34
|
+
import { buildDiscordModalCustomId } from "./modals.js";
|
|
35
|
+
/** Discord caps a component `custom_id` at 100 chars. */
|
|
36
|
+
export const DISCORD_CUSTOM_ID_MAX_CHARS = 100;
|
|
37
|
+
/** Discord allows at most 5 buttons per action row. */
|
|
38
|
+
export const DISCORD_BUTTONS_PER_ROW = 5;
|
|
39
|
+
/** Discord allows at most 5 action rows per message. */
|
|
40
|
+
export const DISCORD_MAX_ROWS = 5;
|
|
41
|
+
/** Discord button label cap (chars). */
|
|
42
|
+
const DISCORD_BUTTON_LABEL_MAX = 80;
|
|
43
|
+
/** Discord ButtonStyle enum values (mirrors discord.js `ButtonStyle`). */
|
|
44
|
+
export const DISCORD_BUTTON_STYLE = {
|
|
45
|
+
Primary: 1,
|
|
46
|
+
Secondary: 2,
|
|
47
|
+
Success: 3,
|
|
48
|
+
Danger: 4,
|
|
49
|
+
};
|
|
50
|
+
/* ───────────────────────── select-menu specs (Fix 3a) ───────────────────────── */
|
|
51
|
+
/** Discord ComponentType values for the five select-menu kinds (mirror discord.js). */
|
|
52
|
+
export const DISCORD_SELECT_COMPONENT_TYPE = {
|
|
53
|
+
string: 3,
|
|
54
|
+
user: 5,
|
|
55
|
+
role: 6,
|
|
56
|
+
mentionable: 7,
|
|
57
|
+
channel: 8,
|
|
58
|
+
};
|
|
59
|
+
/** True when a `buildComponentRows` entry is a select-row marker (vs a button grid). */
|
|
60
|
+
export function isDiscordSelectSpec(row) {
|
|
61
|
+
return typeof row === "object" && row !== null && row.row === "select";
|
|
62
|
+
}
|
|
63
|
+
/** Discord select placeholder cap (chars). */
|
|
64
|
+
const DISCORD_SELECT_PLACEHOLDER_MAX = 150;
|
|
65
|
+
/** Discord string-select option label/description caps (chars). */
|
|
66
|
+
const DISCORD_SELECT_OPTION_LABEL_MAX = 100;
|
|
67
|
+
const DISCORD_SELECT_OPTION_DESC_MAX = 100;
|
|
68
|
+
/**
|
|
69
|
+
* Build a select-row marker from a high-level spec. The token is namespaced with
|
|
70
|
+
* {@link GENERAL_CALLBACK_PREFIX} (exactly like a general button) + sanitized; a
|
|
71
|
+
* token that overflows the 100-char budget OR a `string` select with no usable
|
|
72
|
+
* option yields `null` (the caller falls back to a plain message rather than ship
|
|
73
|
+
* a select that decodes to the wrong action / renders empty). Placeholder +
|
|
74
|
+
* option text are capped to Discord's limits.
|
|
75
|
+
*/
|
|
76
|
+
export function buildDiscordSelectRow(spec) {
|
|
77
|
+
const token = (spec?.customIdToken ?? "").trim();
|
|
78
|
+
if (!token)
|
|
79
|
+
return null;
|
|
80
|
+
const prefixed = sanitizeDiscordCustomId(`${GENERAL_CALLBACK_PREFIX}${token}`);
|
|
81
|
+
if (!prefixed || prefixed.length > DISCORD_CUSTOM_ID_MAX_CHARS)
|
|
82
|
+
return null;
|
|
83
|
+
let options;
|
|
84
|
+
if (spec.kind === "string") {
|
|
85
|
+
const shaped = [];
|
|
86
|
+
for (const opt of spec.options ?? []) {
|
|
87
|
+
const label = (opt?.label ?? "").trim();
|
|
88
|
+
const value = (opt?.value ?? "").trim();
|
|
89
|
+
if (!label || !value)
|
|
90
|
+
continue;
|
|
91
|
+
const out = {
|
|
92
|
+
label: label.slice(0, DISCORD_SELECT_OPTION_LABEL_MAX),
|
|
93
|
+
value,
|
|
94
|
+
};
|
|
95
|
+
const desc = (opt?.description ?? "").trim();
|
|
96
|
+
if (desc)
|
|
97
|
+
out.description = desc.slice(0, DISCORD_SELECT_OPTION_DESC_MAX);
|
|
98
|
+
shaped.push(out);
|
|
99
|
+
}
|
|
100
|
+
if (shaped.length === 0)
|
|
101
|
+
return null; // a string select with no usable option renders empty
|
|
102
|
+
options = shaped;
|
|
103
|
+
}
|
|
104
|
+
const out = { row: "select", kind: spec.kind, customId: prefixed };
|
|
105
|
+
const placeholder = (spec.placeholder ?? "").trim();
|
|
106
|
+
if (placeholder)
|
|
107
|
+
out.placeholder = placeholder.slice(0, DISCORD_SELECT_PLACEHOLDER_MAX);
|
|
108
|
+
if (typeof spec.minValues === "number" && Number.isFinite(spec.minValues))
|
|
109
|
+
out.minValues = spec.minValues;
|
|
110
|
+
if (typeof spec.maxValues === "number" && Number.isFinite(spec.maxValues))
|
|
111
|
+
out.maxValues = spec.maxValues;
|
|
112
|
+
if (options)
|
|
113
|
+
out.options = options;
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Strip control bytes from a custom_id. The central codec already guarantees a
|
|
118
|
+
* printable, short payload, so this is purely defensive for a value that didn't
|
|
119
|
+
* come from the codec — and we never truncate a payload (we DROP an over-budget
|
|
120
|
+
* one at the call site), since a truncated id would decode to the wrong action.
|
|
121
|
+
*/
|
|
122
|
+
export function sanitizeDiscordCustomId(value) {
|
|
123
|
+
// oxlint-disable-next-line no-control-regex
|
|
124
|
+
return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "");
|
|
125
|
+
}
|
|
126
|
+
/** Shape one button input into a validated spec, or null when it can't fit. */
|
|
127
|
+
function toButton(input) {
|
|
128
|
+
const label = (input?.text ?? "").trim();
|
|
129
|
+
const customId = sanitizeDiscordCustomId(input?.value ?? "");
|
|
130
|
+
if (!label || !customId)
|
|
131
|
+
return null;
|
|
132
|
+
// Reject (don't truncate) an id that won't fit — a truncated payload would
|
|
133
|
+
// decode to the wrong action on press.
|
|
134
|
+
if (customId.length > DISCORD_CUSTOM_ID_MAX_CHARS)
|
|
135
|
+
return null;
|
|
136
|
+
return {
|
|
137
|
+
label: label.slice(0, DISCORD_BUTTON_LABEL_MAX),
|
|
138
|
+
customId,
|
|
139
|
+
style: input.style ?? DISCORD_BUTTON_STYLE.Secondary,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Group a flat button list into rows of at most 5, capped at 5 rows total. */
|
|
143
|
+
function chunkIntoRows(buttons) {
|
|
144
|
+
const rows = [];
|
|
145
|
+
for (let i = 0; i < buttons.length && rows.length < DISCORD_MAX_ROWS; i += DISCORD_BUTTONS_PER_ROW) {
|
|
146
|
+
rows.push(buttons.slice(i, i + DISCORD_BUTTONS_PER_ROW));
|
|
147
|
+
}
|
|
148
|
+
return rows;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Build the action rows for an APPROVAL prompt from the central codec's
|
|
152
|
+
* `{ label, data, decision }` specs (the payload rides in `custom_id`). Returns
|
|
153
|
+
* `[]` when fewer than two byte-safe buttons could be shaped — the caller then
|
|
154
|
+
* falls back to a text prompt rather than ship a half-rendered prompt. `Deny` is
|
|
155
|
+
* styled Danger; the first (allow-once) button Success.
|
|
156
|
+
*/
|
|
157
|
+
export function buildDiscordApprovalRows(specs) {
|
|
158
|
+
const buttons = [];
|
|
159
|
+
for (const s of specs) {
|
|
160
|
+
const style = s.decision === "deny"
|
|
161
|
+
? DISCORD_BUTTON_STYLE.Danger
|
|
162
|
+
: s.decision === "allow-once"
|
|
163
|
+
? DISCORD_BUTTON_STYLE.Success
|
|
164
|
+
: DISCORD_BUTTON_STYLE.Secondary;
|
|
165
|
+
const btn = toButton({ text: s.label, value: s.data, style });
|
|
166
|
+
if (btn)
|
|
167
|
+
buttons.push(btn);
|
|
168
|
+
}
|
|
169
|
+
if (buttons.length < 2)
|
|
170
|
+
return []; // not enough buttons → caller uses text prompt
|
|
171
|
+
return chunkIntoRows(buttons);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Build the action rows for a GENERAL button keyboard from a grid of specs. Each
|
|
175
|
+
* spec's `data` is prefixed with {@link GENERAL_CALLBACK_PREFIX} + sanitized; a
|
|
176
|
+
* button whose prefixed id exceeds the budget OR whose label is empty is
|
|
177
|
+
* DROPPED. Returns `null` when no usable button remains (the caller then sends a
|
|
178
|
+
* plain message instead). The grid is flattened + re-chunked into Discord's
|
|
179
|
+
* 5-per-row / 5-row limits.
|
|
180
|
+
*/
|
|
181
|
+
export function buildDiscordButtonRows(grid) {
|
|
182
|
+
const buttons = [];
|
|
183
|
+
for (const row of grid) {
|
|
184
|
+
for (const spec of row) {
|
|
185
|
+
const label = (spec?.text ?? "").trim();
|
|
186
|
+
const token = spec?.data ?? "";
|
|
187
|
+
if (!label || !token)
|
|
188
|
+
continue;
|
|
189
|
+
const prefixed = `${GENERAL_CALLBACK_PREFIX}${token}`;
|
|
190
|
+
const btn = toButton({ text: label, value: prefixed });
|
|
191
|
+
if (btn)
|
|
192
|
+
buttons.push(btn);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (buttons.length === 0)
|
|
196
|
+
return null;
|
|
197
|
+
return chunkIntoRows(buttons);
|
|
198
|
+
}
|
|
199
|
+
/* ───────────────────────── modal-trigger button (Fix 3b) ───────────────────────── */
|
|
200
|
+
/**
|
|
201
|
+
* Register a modal definition + build the BUTTON that opens it. The button is a
|
|
202
|
+
* normal component whose `custom_id` is a `modal:<modalId>` marker (NOT a general
|
|
203
|
+
* token): on press the connection recognizes the marker and calls `showModal`
|
|
204
|
+
* instead of routing a turn. Returns the button spec + the minted modal id (so a
|
|
205
|
+
* caller can correlate). Returns `null` when the label is empty or the marker
|
|
206
|
+
* overflows the custom_id budget (never the case for a short generated id, but
|
|
207
|
+
* guarded for symmetry with the other builders).
|
|
208
|
+
*/
|
|
209
|
+
export function buildDiscordModalTriggerButton(params) {
|
|
210
|
+
const label = (params?.label ?? "").trim();
|
|
211
|
+
if (!label)
|
|
212
|
+
return null;
|
|
213
|
+
const modalId = registerDiscordModal(params.registration);
|
|
214
|
+
const customId = buildDiscordModalCustomId(modalId);
|
|
215
|
+
const btn = toButton({ text: label, value: customId, style: params.style ?? DISCORD_BUTTON_STYLE.Primary });
|
|
216
|
+
if (!btn)
|
|
217
|
+
return null;
|
|
218
|
+
return { button: btn, modalId };
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/components.ts"],"names":[],"mappings":"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"}
|