@spinabot/brigade 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/channels/bundled-channel-metas.d.ts +2 -0
- package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
- package/dist/agents/channels/bundled-channel-metas.js +11 -0
- package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
- package/dist/agents/channels/manager.d.ts.map +1 -1
- package/dist/agents/channels/manager.js +18 -0
- package/dist/agents/channels/manager.js.map +1 -1
- package/dist/agents/channels/sdk.d.ts +2 -0
- package/dist/agents/channels/sdk.d.ts.map +1 -1
- package/dist/agents/channels/sdk.js +2 -0
- package/dist/agents/channels/sdk.js.map +1 -1
- package/dist/agents/channels/slack/account-config.d.ts +172 -0
- package/dist/agents/channels/slack/account-config.d.ts.map +1 -0
- package/dist/agents/channels/slack/account-config.js +353 -0
- package/dist/agents/channels/slack/account-config.js.map +1 -0
- package/dist/agents/channels/slack/account-registry.d.ts +45 -0
- package/dist/agents/channels/slack/account-registry.d.ts.map +1 -0
- package/dist/agents/channels/slack/account-registry.js +58 -0
- package/dist/agents/channels/slack/account-registry.js.map +1 -0
- package/dist/agents/channels/slack/adapter.d.ts +66 -0
- package/dist/agents/channels/slack/adapter.d.ts.map +1 -0
- package/dist/agents/channels/slack/adapter.js +547 -0
- package/dist/agents/channels/slack/adapter.js.map +1 -0
- package/dist/agents/channels/slack/approval-authorize.d.ts +43 -0
- package/dist/agents/channels/slack/approval-authorize.d.ts.map +1 -0
- package/dist/agents/channels/slack/approval-authorize.js +71 -0
- package/dist/agents/channels/slack/approval-authorize.js.map +1 -0
- package/dist/agents/channels/slack/approval-native.d.ts +70 -0
- package/dist/agents/channels/slack/approval-native.d.ts.map +1 -0
- package/dist/agents/channels/slack/approval-native.js +85 -0
- package/dist/agents/channels/slack/approval-native.js.map +1 -0
- package/dist/agents/channels/slack/blocks.d.ts +125 -0
- package/dist/agents/channels/slack/blocks.d.ts.map +1 -0
- package/dist/agents/channels/slack/blocks.js +145 -0
- package/dist/agents/channels/slack/blocks.js.map +1 -0
- package/dist/agents/channels/slack/command-menu.d.ts +44 -0
- package/dist/agents/channels/slack/command-menu.d.ts.map +1 -0
- package/dist/agents/channels/slack/command-menu.js +66 -0
- package/dist/agents/channels/slack/command-menu.js.map +1 -0
- package/dist/agents/channels/slack/connection.d.ts +422 -0
- package/dist/agents/channels/slack/connection.d.ts.map +1 -0
- package/dist/agents/channels/slack/connection.js +1042 -0
- package/dist/agents/channels/slack/connection.js.map +1 -0
- package/dist/agents/channels/slack/directory-live.d.ts +129 -0
- package/dist/agents/channels/slack/directory-live.d.ts.map +1 -0
- package/dist/agents/channels/slack/directory-live.js +148 -0
- package/dist/agents/channels/slack/directory-live.js.map +1 -0
- package/dist/agents/channels/slack/draft-stream.d.ts +93 -0
- package/dist/agents/channels/slack/draft-stream.d.ts.map +1 -0
- package/dist/agents/channels/slack/draft-stream.js +218 -0
- package/dist/agents/channels/slack/draft-stream.js.map +1 -0
- package/dist/agents/channels/slack/format.d.ts +41 -0
- package/dist/agents/channels/slack/format.d.ts.map +1 -0
- package/dist/agents/channels/slack/format.js +271 -0
- package/dist/agents/channels/slack/format.js.map +1 -0
- package/dist/agents/channels/slack/inbound-extras.d.ts +179 -0
- package/dist/agents/channels/slack/inbound-extras.d.ts.map +1 -0
- package/dist/agents/channels/slack/inbound-extras.js +257 -0
- package/dist/agents/channels/slack/inbound-extras.js.map +1 -0
- package/dist/agents/channels/slack/index.d.ts +15 -0
- package/dist/agents/channels/slack/index.d.ts.map +1 -0
- package/dist/agents/channels/slack/index.js +15 -0
- package/dist/agents/channels/slack/index.js.map +1 -0
- package/dist/agents/channels/slack/media.d.ts +90 -0
- package/dist/agents/channels/slack/media.d.ts.map +1 -0
- package/dist/agents/channels/slack/media.js +215 -0
- package/dist/agents/channels/slack/media.js.map +1 -0
- package/dist/agents/channels/slack/module.d.ts +26 -0
- package/dist/agents/channels/slack/module.d.ts.map +1 -0
- package/dist/agents/channels/slack/module.js +67 -0
- package/dist/agents/channels/slack/module.js.map +1 -0
- package/dist/agents/channels/slack/plugin.d.ts +69 -0
- package/dist/agents/channels/slack/plugin.d.ts.map +1 -0
- package/dist/agents/channels/slack/plugin.js +318 -0
- package/dist/agents/channels/slack/plugin.js.map +1 -0
- package/dist/agents/channels/slack/probe.d.ts +72 -0
- package/dist/agents/channels/slack/probe.d.ts.map +1 -0
- package/dist/agents/channels/slack/probe.js +103 -0
- package/dist/agents/channels/slack/probe.js.map +1 -0
- package/dist/agents/channels/slack/proxy-agent.d.ts +30 -0
- package/dist/agents/channels/slack/proxy-agent.d.ts.map +1 -0
- package/dist/agents/channels/slack/proxy-agent.js +44 -0
- package/dist/agents/channels/slack/proxy-agent.js.map +1 -0
- package/dist/agents/channels/slack/reasoning-lane.d.ts +42 -0
- package/dist/agents/channels/slack/reasoning-lane.d.ts.map +1 -0
- package/dist/agents/channels/slack/reasoning-lane.js +68 -0
- package/dist/agents/channels/slack/reasoning-lane.js.map +1 -0
- package/dist/agents/channels/slack/user-directory.d.ts +69 -0
- package/dist/agents/channels/slack/user-directory.d.ts.map +1 -0
- package/dist/agents/channels/slack/user-directory.js +94 -0
- package/dist/agents/channels/slack/user-directory.js.map +1 -0
- package/dist/agents/channels/slack/webhook.d.ts +89 -0
- package/dist/agents/channels/slack/webhook.d.ts.map +1 -0
- package/dist/agents/channels/slack/webhook.js +228 -0
- package/dist/agents/channels/slack/webhook.js.map +1 -0
- package/dist/agents/channels/telegram/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 +5 -0
- package/dist/agents/extensions/modules/index.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +24 -5
- package/dist/core/server.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack inline-approval authorization.
|
|
3
|
+
*
|
|
4
|
+
* When an approval prompt is rendered as Block Kit buttons, ANY member who can
|
|
5
|
+
* see the message could press a button. Brigade's central inbound pipeline
|
|
6
|
+
* already runs the access-control gate before a `block_actions` press reaches
|
|
7
|
+
* the approval-callback path, so only an admitted (allow-listed / owner) peer
|
|
8
|
+
* gets here at all — but a SHARED Slack channel is the edge case: in a channel
|
|
9
|
+
* the bot is in, an admitted member's button press should still be allowed only
|
|
10
|
+
* when that presser is an approved approver, not merely present in the room.
|
|
11
|
+
* Slack's multi-member workspaces make this gate more load-bearing than
|
|
12
|
+
* Telegram's.
|
|
13
|
+
*
|
|
14
|
+
* This predicate is the channel's `approvalCapability.authorizeApprover`. It is
|
|
15
|
+
* invoked CENTRALLY by `tryConsumeChannelApprovalCallback` with the presser's
|
|
16
|
+
* `senderId` (the Slack user id `U…`); returning `{ authorized: false, reason }`
|
|
17
|
+
* refuses the press without consuming the operator's pending approval (so the
|
|
18
|
+
* real operator can still answer). Policy:
|
|
19
|
+
*
|
|
20
|
+
* - When the channel has an explicit allow-from list configured (the approved
|
|
21
|
+
* senders), only those ids may approve.
|
|
22
|
+
* - When NO allow-from list is configured, defer to the access gate that
|
|
23
|
+
* already admitted the inbound and authorize the press (matches the text-
|
|
24
|
+
* reply path, which has no extra approver gate).
|
|
25
|
+
*
|
|
26
|
+
* Pure + deterministic over its `cfg` + `senderId` inputs — no I/O.
|
|
27
|
+
*/
|
|
28
|
+
/** Read the channel's configured allow-from sender ids (string-normalized). */
|
|
29
|
+
function configuredAllowFrom(cfg, accountId) {
|
|
30
|
+
const channels = cfg.channels;
|
|
31
|
+
const slot = channels?.slack;
|
|
32
|
+
if (!slot)
|
|
33
|
+
return [];
|
|
34
|
+
const ids = [];
|
|
35
|
+
const push = (list) => {
|
|
36
|
+
for (const v of list ?? []) {
|
|
37
|
+
const s = String(v).trim();
|
|
38
|
+
if (s)
|
|
39
|
+
ids.push(s);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
push(slot.allowFrom);
|
|
43
|
+
// Per-account allow-from (multi-workspace shape) when an accountId is supplied.
|
|
44
|
+
if (accountId && Array.isArray(slot.accounts)) {
|
|
45
|
+
for (const entry of slot.accounts) {
|
|
46
|
+
if (entry && typeof entry.id === "string" && entry.id.trim() === accountId.trim())
|
|
47
|
+
push(entry.allowFrom);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return ids;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve whether `senderId` is allowed to answer a Slack inline approval.
|
|
54
|
+
* Returns `{ authorized: true }` when no explicit allow-from gate applies (the
|
|
55
|
+
* central access gate already admitted the inbound), or when the presser is on
|
|
56
|
+
* the configured allow-from list. Otherwise refuses with a reason.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveSlackApprover(args) {
|
|
59
|
+
const allow = configuredAllowFrom(args.cfg, args.accountId);
|
|
60
|
+
// No explicit allow list → defer to the access gate that already ran.
|
|
61
|
+
if (allow.length === 0)
|
|
62
|
+
return { authorized: true };
|
|
63
|
+
const sender = (args.senderId ?? "").trim();
|
|
64
|
+
if (sender && allow.includes(sender))
|
|
65
|
+
return { authorized: true };
|
|
66
|
+
return {
|
|
67
|
+
authorized: false,
|
|
68
|
+
reason: "Only an approved sender can answer that approval.",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=approval-authorize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-authorize.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/approval-authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,+EAA+E;AAC/E,SAAS,mBAAmB,CAAC,GAAkB,EAAE,SAAkB;IAClE,MAAM,QAAQ,GAAI,GAA8C,CAAC,QAAQ,CAAC;IAC1E,MAAM,IAAI,GAAG,QAAQ,EAAE,KAEX,CAAC;IACb,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,CAAC,IAA6B,EAAE,EAAE;QAC9C,KAAK,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;YAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3B,IAAI,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACF,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrB,gFAAgF;IAChF,IAAI,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC,IAAI,EAAE;gBAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC1G,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAIpC;IACA,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5D,sEAAsE;IACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAClE,OAAO;QACN,UAAU,EAAE,KAAK;QACjB,MAAM,EAAE,mDAAmD;KAC3D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Block Kit rendering for native approval prompts.
|
|
3
|
+
*
|
|
4
|
+
* When a channel-routed turn raises an exec/plugin approval AND the Slack
|
|
5
|
+
* adapter has opted into `approvalCapability.sendApprovalPrompt`, the central
|
|
6
|
+
* approval-router asks this channel to render the question as native Block Kit
|
|
7
|
+
* BUTTONS instead of the default "reply yes/no" text card. The button payloads
|
|
8
|
+
* are produced by the CENTRAL codec (`buildApprovalCallbackButtons`) so the
|
|
9
|
+
* press comes back as an `InboundMessage.callbackQuery` the central
|
|
10
|
+
* `tryConsumeChannelApprovalCallback` decodes + resolves — this file only maps
|
|
11
|
+
* the codec's `{ label, data }` specs onto Slack's `actions`-block shape (via
|
|
12
|
+
* `blocks.ts`) and assembles the prompt text + fallback.
|
|
13
|
+
*
|
|
14
|
+
* SAFETY: every button `value` here is the codec's output — versioned,
|
|
15
|
+
* base64url + printable-ASCII, already proven `<= 64` UTF-8 bytes by
|
|
16
|
+
* `encodeApprovalCallback` (and well under Slack's 255-char `value` cap). This
|
|
17
|
+
* module never mints its own payloads.
|
|
18
|
+
*
|
|
19
|
+
* Slack mirror of `telegram/approval-native.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import { type SlackBlock, type SlackBlockAction } from "./blocks.js";
|
|
22
|
+
/** The Slack message payload an approval prompt sends: a fallback `text` + `blocks`. */
|
|
23
|
+
export interface SlackApprovalMessage {
|
|
24
|
+
/** Plain fallback text (notifications + clients that can't render blocks). */
|
|
25
|
+
text: string;
|
|
26
|
+
/** The Block Kit blocks: a section (prompt) + the actions block(s). */
|
|
27
|
+
blocks: SlackBlock[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the Block Kit approval message for an approval prompt from the central
|
|
31
|
+
* codec. Returns `null` when fewer than two byte-safe buttons could be minted (a
|
|
32
|
+
* pathologically long approval id) — the caller then falls back to the text
|
|
33
|
+
* prompt rather than ship a half-rendered prompt.
|
|
34
|
+
*
|
|
35
|
+
* `allowAlways: false` drops the "Allow always" button (approvals where
|
|
36
|
+
* persisting an allowlist entry doesn't apply).
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildSlackApprovalMessage(args: {
|
|
39
|
+
approvalId: string;
|
|
40
|
+
command: string;
|
|
41
|
+
approvalKind: "exec" | "plugin";
|
|
42
|
+
toolName?: string;
|
|
43
|
+
allowAlways?: boolean;
|
|
44
|
+
}): SlackApprovalMessage | null;
|
|
45
|
+
/**
|
|
46
|
+
* Compose the operator-facing approval question text rendered ABOVE the buttons.
|
|
47
|
+
* Kept short + control-char-scrubbed; the buttons carry the action, so the text
|
|
48
|
+
* only needs the command preview + a one-line ask. The 🦁 mark is the Brigade
|
|
49
|
+
* brand-stamp so the operator recognises this as a Brigade prompt. The command
|
|
50
|
+
* preview is wrapped in a Slack code span (single backticks) so it renders
|
|
51
|
+
* verbatim.
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildSlackApprovalText(args: {
|
|
54
|
+
command: string;
|
|
55
|
+
approvalKind: "exec" | "plugin";
|
|
56
|
+
toolName?: string;
|
|
57
|
+
agentId?: string;
|
|
58
|
+
}): string;
|
|
59
|
+
/**
|
|
60
|
+
* Parse a `block_actions` interaction's button press into the pending-approval
|
|
61
|
+
* id + decision. Pulls the codec payload out of the first Brigade-owned approval
|
|
62
|
+
* action (via {@link extractBlockActionPayload}) and decodes it with the CENTRAL
|
|
63
|
+
* codec. Returns `null` when the press wasn't an approval button (a general
|
|
64
|
+
* button, or someone else's action) so the caller can fall through.
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseSlackApprovalAction(actions: ReadonlyArray<SlackBlockAction> | undefined): {
|
|
67
|
+
approvalId: string;
|
|
68
|
+
decision: "allow-once" | "allow-always" | "deny";
|
|
69
|
+
} | null;
|
|
70
|
+
//# sourceMappingURL=approval-native.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-native.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/approval-native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAIN,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,MAAM,aAAa,CAAC;AAKrB,wFAAwF;AACxF,MAAM,WAAW,oBAAoB;IACpC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,MAAM,EAAE,UAAU,EAAE,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,QAAQ,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,oBAAoB,GAAG,IAAI,CAe9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,QAAQ,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAWT;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,SAAS,GAClD;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;CAAE,GAAG,IAAI,CAIjF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Block Kit rendering for native approval prompts.
|
|
3
|
+
*
|
|
4
|
+
* When a channel-routed turn raises an exec/plugin approval AND the Slack
|
|
5
|
+
* adapter has opted into `approvalCapability.sendApprovalPrompt`, the central
|
|
6
|
+
* approval-router asks this channel to render the question as native Block Kit
|
|
7
|
+
* BUTTONS instead of the default "reply yes/no" text card. The button payloads
|
|
8
|
+
* are produced by the CENTRAL codec (`buildApprovalCallbackButtons`) so the
|
|
9
|
+
* press comes back as an `InboundMessage.callbackQuery` the central
|
|
10
|
+
* `tryConsumeChannelApprovalCallback` decodes + resolves — this file only maps
|
|
11
|
+
* the codec's `{ label, data }` specs onto Slack's `actions`-block shape (via
|
|
12
|
+
* `blocks.ts`) and assembles the prompt text + fallback.
|
|
13
|
+
*
|
|
14
|
+
* SAFETY: every button `value` here is the codec's output — versioned,
|
|
15
|
+
* base64url + printable-ASCII, already proven `<= 64` UTF-8 bytes by
|
|
16
|
+
* `encodeApprovalCallback` (and well under Slack's 255-char `value` cap). This
|
|
17
|
+
* module never mints its own payloads.
|
|
18
|
+
*
|
|
19
|
+
* Slack mirror of `telegram/approval-native.ts`.
|
|
20
|
+
*/
|
|
21
|
+
import { buildApprovalCallbackButtons, decodeApprovalCallback } from "../sdk.js";
|
|
22
|
+
import { buildSlackApprovalBlocks, extractBlockActionPayload, } from "./blocks.js";
|
|
23
|
+
/** C0/C1 control-character class (incl. NUL) — never a raw control byte in source. */
|
|
24
|
+
const CONTROL_CHARS_RE = /[\x00-\x1f\x7f-\x9f]/g;
|
|
25
|
+
/**
|
|
26
|
+
* Build the Block Kit approval message for an approval prompt from the central
|
|
27
|
+
* codec. Returns `null` when fewer than two byte-safe buttons could be minted (a
|
|
28
|
+
* pathologically long approval id) — the caller then falls back to the text
|
|
29
|
+
* prompt rather than ship a half-rendered prompt.
|
|
30
|
+
*
|
|
31
|
+
* `allowAlways: false` drops the "Allow always" button (approvals where
|
|
32
|
+
* persisting an allowlist entry doesn't apply).
|
|
33
|
+
*/
|
|
34
|
+
export function buildSlackApprovalMessage(args) {
|
|
35
|
+
const specs = buildApprovalCallbackButtons({
|
|
36
|
+
approvalId: args.approvalId,
|
|
37
|
+
...(args.allowAlways === false ? { allowAlways: false } : {}),
|
|
38
|
+
});
|
|
39
|
+
if (specs.length < 2)
|
|
40
|
+
return null; // not enough buttons → caller uses text prompt
|
|
41
|
+
const actionBlocks = buildSlackApprovalBlocks(specs);
|
|
42
|
+
if (actionBlocks.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
const promptText = buildSlackApprovalText({
|
|
45
|
+
command: args.command,
|
|
46
|
+
approvalKind: args.approvalKind,
|
|
47
|
+
...(args.toolName !== undefined ? { toolName: args.toolName } : {}),
|
|
48
|
+
});
|
|
49
|
+
const blocks = [{ type: "section", text: { type: "mrkdwn", text: promptText } }, ...actionBlocks];
|
|
50
|
+
return { text: promptText, blocks };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Compose the operator-facing approval question text rendered ABOVE the buttons.
|
|
54
|
+
* Kept short + control-char-scrubbed; the buttons carry the action, so the text
|
|
55
|
+
* only needs the command preview + a one-line ask. The 🦁 mark is the Brigade
|
|
56
|
+
* brand-stamp so the operator recognises this as a Brigade prompt. The command
|
|
57
|
+
* preview is wrapped in a Slack code span (single backticks) so it renders
|
|
58
|
+
* verbatim.
|
|
59
|
+
*/
|
|
60
|
+
export function buildSlackApprovalText(args) {
|
|
61
|
+
const flat = args.command
|
|
62
|
+
.replace(/[\r\n]+/g, " ")
|
|
63
|
+
// oxlint-disable-next-line no-control-regex
|
|
64
|
+
.replace(CONTROL_CHARS_RE, " ")
|
|
65
|
+
.replace(/\s+/g, " ")
|
|
66
|
+
.trim();
|
|
67
|
+
const preview = flat.length <= 180 ? flat : `${flat.slice(0, 177)}…`;
|
|
68
|
+
const what = args.approvalKind === "plugin" ? "run a plugin action" : "run a shell command";
|
|
69
|
+
const lines = [`🦁 Brigade wants to ${what}:`, `\`${preview}\``, "", "Choose below — times out in 5 minutes."];
|
|
70
|
+
return lines.join("\n");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse a `block_actions` interaction's button press into the pending-approval
|
|
74
|
+
* id + decision. Pulls the codec payload out of the first Brigade-owned approval
|
|
75
|
+
* action (via {@link extractBlockActionPayload}) and decodes it with the CENTRAL
|
|
76
|
+
* codec. Returns `null` when the press wasn't an approval button (a general
|
|
77
|
+
* button, or someone else's action) so the caller can fall through.
|
|
78
|
+
*/
|
|
79
|
+
export function parseSlackApprovalAction(actions) {
|
|
80
|
+
const payload = extractBlockActionPayload(actions);
|
|
81
|
+
if (!payload)
|
|
82
|
+
return null;
|
|
83
|
+
return decodeApprovalCallback(payload);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=approval-native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approval-native.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/approval-native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EACN,wBAAwB,EACxB,yBAAyB,GAIzB,MAAM,aAAa,CAAC;AAErB,sFAAsF;AACtF,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAUjD;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAMzC;IACA,MAAM,KAAK,GAAG,4BAA4B,CAAC;QAC1C,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7D,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,+CAA+C;IAClF,MAAM,YAAY,GAAwB,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAC1E,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,UAAU,GAAG,sBAAsB,CAAC;QACzC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnE,CAAC,CAAC;IACH,MAAM,MAAM,GAAiB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,YAAY,CAAC,CAAC;IAChH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAKtC;IACA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO;SACvB,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC;QACzB,4CAA4C;SAC3C,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC;SAC9B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;IACT,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC;IACrE,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAC5F,MAAM,KAAK,GAAG,CAAC,uBAAuB,IAAI,GAAG,EAAE,KAAK,OAAO,IAAI,EAAE,EAAE,EAAE,wCAAwC,CAAC,CAAC;IAC/G,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACvC,OAAoD;IAEpD,MAAM,OAAO,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Block Kit button rendering + the action_id / value callback codec.
|
|
3
|
+
*
|
|
4
|
+
* Slack's analogue of `telegram/inline-keyboard.ts`. Two button lanes share one
|
|
5
|
+
* builder, exactly as Telegram'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 `actions`-block + 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 Slack button carries BOTH an `action_id` and a
|
|
18
|
+
* `value`. The opaque codec payload rides in `value` (Slack caps it at 255
|
|
19
|
+
* chars — far more generous than Telegram's 64-byte `callback_data`, and the
|
|
20
|
+
* central codec already guarantees ≤64 bytes). The `action_id` is a stable,
|
|
21
|
+
* non-colliding constant ({@link SLACK_APPROVAL_ACTION_ID} /
|
|
22
|
+
* {@link SLACK_GENERAL_ACTION_ID}) so the interactive handler can route the
|
|
23
|
+
* payload without parsing it. A button whose `value` exceeds the cap OR whose
|
|
24
|
+
* label is empty is DROPPED (rather than ship a truncated payload that decodes
|
|
25
|
+
* to the wrong action).
|
|
26
|
+
*
|
|
27
|
+
* Pure / deterministic — no I/O, no globals.
|
|
28
|
+
*/
|
|
29
|
+
/** Slack caps a button's `value` at 2000 chars; we hold to a tight 255 budget. */
|
|
30
|
+
export declare const SLACK_ACTION_VALUE_MAX_CHARS = 255;
|
|
31
|
+
/** Slack allows at most 5 elements per `actions` block; group buttons in fives. */
|
|
32
|
+
export declare const SLACK_ACTIONS_BLOCK_MAX = 5;
|
|
33
|
+
/** Stable `action_id` an APPROVAL button carries (routes to the approval path). */
|
|
34
|
+
export declare const SLACK_APPROVAL_ACTION_ID = "brigade_approval";
|
|
35
|
+
/** Stable `action_id` a GENERAL (agent-attached) button carries. */
|
|
36
|
+
export declare const SLACK_GENERAL_ACTION_ID = "brigade_general";
|
|
37
|
+
/** A Slack Block Kit button element (the subset Brigade emits). */
|
|
38
|
+
export interface SlackButtonElement {
|
|
39
|
+
type: "button";
|
|
40
|
+
text: {
|
|
41
|
+
type: "plain_text";
|
|
42
|
+
text: string;
|
|
43
|
+
emoji: boolean;
|
|
44
|
+
};
|
|
45
|
+
/** Routing id — one of the stable `SLACK_*_ACTION_ID` constants. */
|
|
46
|
+
action_id: string;
|
|
47
|
+
/** Opaque codec payload (≤ 255 chars), read back on press. */
|
|
48
|
+
value: string;
|
|
49
|
+
/** Optional visual style (Slack: `primary` green / `danger` red). */
|
|
50
|
+
style?: "primary" | "danger";
|
|
51
|
+
}
|
|
52
|
+
/** A Slack `actions` block — a row of up to 5 interactive elements. */
|
|
53
|
+
export interface SlackActionsBlock {
|
|
54
|
+
type: "actions";
|
|
55
|
+
/** Optional stable id so the block is addressable in the interaction payload. */
|
|
56
|
+
block_id?: string;
|
|
57
|
+
elements: SlackButtonElement[];
|
|
58
|
+
}
|
|
59
|
+
/** A Slack `section` block carrying mrkdwn text (the prompt body above buttons). */
|
|
60
|
+
export interface SlackSectionBlock {
|
|
61
|
+
type: "section";
|
|
62
|
+
text: {
|
|
63
|
+
type: "mrkdwn";
|
|
64
|
+
text: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Any block this module emits (section + actions). */
|
|
68
|
+
export type SlackBlock = SlackSectionBlock | SlackActionsBlock;
|
|
69
|
+
/** One button spec before it's shaped into a Block Kit element. */
|
|
70
|
+
export interface SlackButtonSpec {
|
|
71
|
+
/** Button label shown to the user. */
|
|
72
|
+
text: string;
|
|
73
|
+
/** Opaque codec payload delivered to the handler on press. */
|
|
74
|
+
value: string;
|
|
75
|
+
/** Optional Slack button style. */
|
|
76
|
+
style?: "primary" | "danger";
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Clamp a button value to Slack's budget + strip control bytes. The central
|
|
80
|
+
* codec already guarantees a printable, short payload, so this is purely
|
|
81
|
+
* defensive for a value that didn't come from the codec — and unlike Telegram we
|
|
82
|
+
* never truncate a codec payload (the budget is far larger than the codec's
|
|
83
|
+
* output), we only DROP an over-budget value at the call site.
|
|
84
|
+
*/
|
|
85
|
+
export declare function sanitizeSlackActionValue(value: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* Build the `actions` blocks for an APPROVAL prompt from the central codec's
|
|
88
|
+
* `{ label, data }` specs (the payload rides in `value`). Returns `[]` when
|
|
89
|
+
* fewer than two byte-safe buttons could be shaped — the caller then falls back
|
|
90
|
+
* to a text prompt rather than ship a half-rendered prompt. `Deny` is styled
|
|
91
|
+
* `danger`; the first button `primary`.
|
|
92
|
+
*/
|
|
93
|
+
export declare function buildSlackApprovalBlocks(specs: ReadonlyArray<{
|
|
94
|
+
label: string;
|
|
95
|
+
data: string;
|
|
96
|
+
decision?: string;
|
|
97
|
+
}>): SlackActionsBlock[];
|
|
98
|
+
/**
|
|
99
|
+
* Build the `actions` blocks for a GENERAL inline keyboard from a grid of button
|
|
100
|
+
* specs. Each spec's `data` is prefixed with {@link GENERAL_CALLBACK_PREFIX} +
|
|
101
|
+
* sanitized; a button whose prefixed value exceeds the budget OR whose label is
|
|
102
|
+
* empty is DROPPED. Returns `null` when no usable button remains (the caller
|
|
103
|
+
* then sends a plain message instead). The grid is flattened + re-chunked into
|
|
104
|
+
* Slack's 5-per-block rows.
|
|
105
|
+
*/
|
|
106
|
+
export declare function buildSlackInlineKeyboard(grid: Array<Array<{
|
|
107
|
+
text: string;
|
|
108
|
+
data: string;
|
|
109
|
+
}>>): SlackActionsBlock[] | null;
|
|
110
|
+
/** A parsed `block_actions` button press (the subset Brigade reads). */
|
|
111
|
+
export interface SlackBlockAction {
|
|
112
|
+
action_id?: string;
|
|
113
|
+
value?: string;
|
|
114
|
+
block_id?: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Pull the opaque codec payload out of a `block_actions` interaction's first
|
|
118
|
+
* Brigade-owned button action. Returns the `value` string the button declared
|
|
119
|
+
* (an approval-codec payload OR a general-prefixed token) so the adapter can
|
|
120
|
+
* stamp it onto `InboundMessage.callbackQuery.data` for the central pipeline to
|
|
121
|
+
* decode — exactly as Telegram surfaces `callback_query.data`. Returns null when
|
|
122
|
+
* no action carried one of our `action_id`s.
|
|
123
|
+
*/
|
|
124
|
+
export declare function extractBlockActionPayload(actions: ReadonlyArray<SlackBlockAction> | undefined): string | null;
|
|
125
|
+
//# sourceMappingURL=blocks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,qBAAqB,CAAC;AAE3D,oEAAoE;AACpE,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAEzD,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC;AAED,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAE/D,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC/B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI9D;AAgCD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,aAAa,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACtE,iBAAiB,EAAE,CAUrB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,GAAG,iBAAiB,EAAE,GAAG,IAAI,CAcvH;AAED,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQ7G"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Block Kit button rendering + the action_id / value callback codec.
|
|
3
|
+
*
|
|
4
|
+
* Slack's analogue of `telegram/inline-keyboard.ts`. Two button lanes share one
|
|
5
|
+
* builder, exactly as Telegram'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 `actions`-block + 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 Slack button carries BOTH an `action_id` and a
|
|
18
|
+
* `value`. The opaque codec payload rides in `value` (Slack caps it at 255
|
|
19
|
+
* chars — far more generous than Telegram's 64-byte `callback_data`, and the
|
|
20
|
+
* central codec already guarantees ≤64 bytes). The `action_id` is a stable,
|
|
21
|
+
* non-colliding constant ({@link SLACK_APPROVAL_ACTION_ID} /
|
|
22
|
+
* {@link SLACK_GENERAL_ACTION_ID}) so the interactive handler can route the
|
|
23
|
+
* payload without parsing it. A button whose `value` exceeds the cap OR whose
|
|
24
|
+
* label is empty is DROPPED (rather than ship a truncated payload that decodes
|
|
25
|
+
* to the wrong action).
|
|
26
|
+
*
|
|
27
|
+
* Pure / deterministic — no I/O, no globals.
|
|
28
|
+
*/
|
|
29
|
+
import { GENERAL_CALLBACK_PREFIX } from "../general-callback.js";
|
|
30
|
+
/** Slack caps a button's `value` at 2000 chars; we hold to a tight 255 budget. */
|
|
31
|
+
export const SLACK_ACTION_VALUE_MAX_CHARS = 255;
|
|
32
|
+
/** Slack allows at most 5 elements per `actions` block; group buttons in fives. */
|
|
33
|
+
export const SLACK_ACTIONS_BLOCK_MAX = 5;
|
|
34
|
+
/** Stable `action_id` an APPROVAL button carries (routes to the approval path). */
|
|
35
|
+
export const SLACK_APPROVAL_ACTION_ID = "brigade_approval";
|
|
36
|
+
/** Stable `action_id` a GENERAL (agent-attached) button carries. */
|
|
37
|
+
export const SLACK_GENERAL_ACTION_ID = "brigade_general";
|
|
38
|
+
/**
|
|
39
|
+
* Clamp a button value to Slack's budget + strip control bytes. The central
|
|
40
|
+
* codec already guarantees a printable, short payload, so this is purely
|
|
41
|
+
* defensive for a value that didn't come from the codec — and unlike Telegram we
|
|
42
|
+
* never truncate a codec payload (the budget is far larger than the codec's
|
|
43
|
+
* output), we only DROP an over-budget value at the call site.
|
|
44
|
+
*/
|
|
45
|
+
export function sanitizeSlackActionValue(value) {
|
|
46
|
+
// Drop C0/C1 control chars (incl. NUL) — a button value must be printable.
|
|
47
|
+
// oxlint-disable-next-line no-control-regex
|
|
48
|
+
return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "");
|
|
49
|
+
}
|
|
50
|
+
/** Shape one button spec into a Block Kit element, or null when it can't fit. */
|
|
51
|
+
function toButtonElement(spec, actionId) {
|
|
52
|
+
const label = (spec?.text ?? "").trim();
|
|
53
|
+
const value = sanitizeSlackActionValue(spec?.value ?? "");
|
|
54
|
+
if (!label || !value)
|
|
55
|
+
return null;
|
|
56
|
+
// Reject (don't truncate) a value that won't fit — a truncated payload would
|
|
57
|
+
// decode to the wrong action on press.
|
|
58
|
+
if (value.length > SLACK_ACTION_VALUE_MAX_CHARS)
|
|
59
|
+
return null;
|
|
60
|
+
return {
|
|
61
|
+
type: "button",
|
|
62
|
+
text: { type: "plain_text", text: label.slice(0, 75), emoji: true },
|
|
63
|
+
action_id: actionId,
|
|
64
|
+
value,
|
|
65
|
+
...(spec.style ? { style: spec.style } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Group a flat element list into `actions` blocks of at most 5 (Slack's cap). */
|
|
69
|
+
function chunkIntoActionBlocks(elements, blockIdPrefix) {
|
|
70
|
+
const blocks = [];
|
|
71
|
+
for (let i = 0; i < elements.length; i += SLACK_ACTIONS_BLOCK_MAX) {
|
|
72
|
+
blocks.push({
|
|
73
|
+
type: "actions",
|
|
74
|
+
block_id: `${blockIdPrefix}_${i / SLACK_ACTIONS_BLOCK_MAX}`,
|
|
75
|
+
elements: elements.slice(i, i + SLACK_ACTIONS_BLOCK_MAX),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return blocks;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build the `actions` blocks for an APPROVAL prompt from the central codec's
|
|
82
|
+
* `{ label, data }` specs (the payload rides in `value`). Returns `[]` when
|
|
83
|
+
* fewer than two byte-safe buttons could be shaped — the caller then falls back
|
|
84
|
+
* to a text prompt rather than ship a half-rendered prompt. `Deny` is styled
|
|
85
|
+
* `danger`; the first button `primary`.
|
|
86
|
+
*/
|
|
87
|
+
export function buildSlackApprovalBlocks(specs) {
|
|
88
|
+
const elements = [];
|
|
89
|
+
for (const s of specs) {
|
|
90
|
+
const style = s.decision === "deny" ? "danger" : s.decision === "allow-once" ? "primary" : undefined;
|
|
91
|
+
const el = toButtonElement({ text: s.label, value: s.data, ...(style ? { style } : {}) }, SLACK_APPROVAL_ACTION_ID);
|
|
92
|
+
if (el)
|
|
93
|
+
elements.push(el);
|
|
94
|
+
}
|
|
95
|
+
if (elements.length < 2)
|
|
96
|
+
return []; // not enough buttons → caller uses text prompt
|
|
97
|
+
return chunkIntoActionBlocks(elements, "brigade_approval");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Build the `actions` blocks for a GENERAL inline keyboard from a grid of button
|
|
101
|
+
* specs. Each spec's `data` is prefixed with {@link GENERAL_CALLBACK_PREFIX} +
|
|
102
|
+
* sanitized; a button whose prefixed value exceeds the budget OR whose label is
|
|
103
|
+
* empty is DROPPED. Returns `null` when no usable button remains (the caller
|
|
104
|
+
* then sends a plain message instead). The grid is flattened + re-chunked into
|
|
105
|
+
* Slack's 5-per-block rows.
|
|
106
|
+
*/
|
|
107
|
+
export function buildSlackInlineKeyboard(grid) {
|
|
108
|
+
const elements = [];
|
|
109
|
+
for (const row of grid) {
|
|
110
|
+
for (const spec of row) {
|
|
111
|
+
const label = (spec?.text ?? "").trim();
|
|
112
|
+
const token = spec?.data ?? "";
|
|
113
|
+
if (!label || !token)
|
|
114
|
+
continue;
|
|
115
|
+
const prefixed = `${GENERAL_CALLBACK_PREFIX}${token}`;
|
|
116
|
+
const el = toButtonElement({ text: label, value: prefixed }, SLACK_GENERAL_ACTION_ID);
|
|
117
|
+
if (el)
|
|
118
|
+
elements.push(el);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (elements.length === 0)
|
|
122
|
+
return null;
|
|
123
|
+
return chunkIntoActionBlocks(elements, "brigade_general");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Pull the opaque codec payload out of a `block_actions` interaction's first
|
|
127
|
+
* Brigade-owned button action. Returns the `value` string the button declared
|
|
128
|
+
* (an approval-codec payload OR a general-prefixed token) so the adapter can
|
|
129
|
+
* stamp it onto `InboundMessage.callbackQuery.data` for the central pipeline to
|
|
130
|
+
* decode — exactly as Telegram surfaces `callback_query.data`. Returns null when
|
|
131
|
+
* no action carried one of our `action_id`s.
|
|
132
|
+
*/
|
|
133
|
+
export function extractBlockActionPayload(actions) {
|
|
134
|
+
if (!Array.isArray(actions))
|
|
135
|
+
return null;
|
|
136
|
+
for (const a of actions) {
|
|
137
|
+
if (a?.action_id !== SLACK_APPROVAL_ACTION_ID && a?.action_id !== SLACK_GENERAL_ACTION_ID)
|
|
138
|
+
continue;
|
|
139
|
+
const value = typeof a.value === "string" ? a.value : "";
|
|
140
|
+
if (value)
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=blocks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/blocks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack slash-command helpers — normalize an inbound `/command` name + map
|
|
3
|
+
* Brigade's central channel commands to a documentable list.
|
|
4
|
+
*
|
|
5
|
+
* Brigade owns the command set centrally (`buildBundledCommands` → `/help`,
|
|
6
|
+
* `/status`, `/allowlist`, `/agent`, `/agents`, `/whoami`, `/org`, plus any
|
|
7
|
+
* module-registered channel commands). Telegram mirrors that set into the bot's
|
|
8
|
+
* `setMyCommands` menu on connect. SLACK IS DIFFERENT: slash commands are
|
|
9
|
+
* registered MANUALLY in the Slack app config UI (each `/command` points at the
|
|
10
|
+
* app's request URL / Socket Mode), so there is NO programmatic "set my
|
|
11
|
+
* commands" call. This module is therefore lighter than Telegram's — it provides:
|
|
12
|
+
*
|
|
13
|
+
* 1. {@link normalizeSlackCommandName} — canonicalise an inbound
|
|
14
|
+
* `slash_commands` event's `command` (e.g. `/Help` → `help`) so the
|
|
15
|
+
* adapter can match it against the central command map.
|
|
16
|
+
* 2. {@link buildSlackCommandManifest} — map the central commands to a plain
|
|
17
|
+
* `{ command, description }[]` the operator copies into the Slack app's
|
|
18
|
+
* slash-command config (surfaced in docs / `brigade channels status`).
|
|
19
|
+
*
|
|
20
|
+
* Pure / deterministic — no I/O. Output command names are printable ASCII
|
|
21
|
+
* (`[a-z0-9_-]`), so no NUL / control byte can appear.
|
|
22
|
+
*/
|
|
23
|
+
import type { ChannelCommand } from "../sdk.js";
|
|
24
|
+
/** A Slack slash-command manifest entry (for the app-config UI / docs). */
|
|
25
|
+
export interface SlackSlashCommand {
|
|
26
|
+
/** Command WITHOUT the leading slash, e.g. `status`. */
|
|
27
|
+
command: string;
|
|
28
|
+
/** Short description shown in the Slack command hint. */
|
|
29
|
+
description: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Normalize a command word to Slack's `[a-z0-9_-]{1,32}` shape, or null if
|
|
33
|
+
* unusable. Strips a leading `/`, lowercases, and drops disallowed chars.
|
|
34
|
+
*/
|
|
35
|
+
export declare function normalizeSlackCommandName(raw: string): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Build the Slack slash-command manifest from Brigade's central channel
|
|
38
|
+
* commands. De-dupes by normalized name (first wins), drops unusable names, and
|
|
39
|
+
* caps at 100. Returns `[]` when nothing maps. Unlike Telegram this is NOT
|
|
40
|
+
* pushed to Slack on connect — it's the list the operator registers by hand in
|
|
41
|
+
* the Slack app config (and what docs / status surface).
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildSlackCommandManifest(commands: ReadonlyArray<ChannelCommand>): SlackSlashCommand[];
|
|
44
|
+
//# sourceMappingURL=command-menu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-menu.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/command-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IACjC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;CACpB;AASD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKpE;AAQD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,iBAAiB,EAAE,CAWtG"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack slash-command helpers — normalize an inbound `/command` name + map
|
|
3
|
+
* Brigade's central channel commands to a documentable list.
|
|
4
|
+
*
|
|
5
|
+
* Brigade owns the command set centrally (`buildBundledCommands` → `/help`,
|
|
6
|
+
* `/status`, `/allowlist`, `/agent`, `/agents`, `/whoami`, `/org`, plus any
|
|
7
|
+
* module-registered channel commands). Telegram mirrors that set into the bot's
|
|
8
|
+
* `setMyCommands` menu on connect. SLACK IS DIFFERENT: slash commands are
|
|
9
|
+
* registered MANUALLY in the Slack app config UI (each `/command` points at the
|
|
10
|
+
* app's request URL / Socket Mode), so there is NO programmatic "set my
|
|
11
|
+
* commands" call. This module is therefore lighter than Telegram's — it provides:
|
|
12
|
+
*
|
|
13
|
+
* 1. {@link normalizeSlackCommandName} — canonicalise an inbound
|
|
14
|
+
* `slash_commands` event's `command` (e.g. `/Help` → `help`) so the
|
|
15
|
+
* adapter can match it against the central command map.
|
|
16
|
+
* 2. {@link buildSlackCommandManifest} — map the central commands to a plain
|
|
17
|
+
* `{ command, description }[]` the operator copies into the Slack app's
|
|
18
|
+
* slash-command config (surfaced in docs / `brigade channels status`).
|
|
19
|
+
*
|
|
20
|
+
* Pure / deterministic — no I/O. Output command names are printable ASCII
|
|
21
|
+
* (`[a-z0-9_-]`), so no NUL / control byte can appear.
|
|
22
|
+
*/
|
|
23
|
+
/** Slack slash-command limits (the app-config UI enforces these). */
|
|
24
|
+
const MAX_COMMANDS = 100;
|
|
25
|
+
const MAX_NAME_LEN = 32;
|
|
26
|
+
const MAX_DESC_LEN = 2000;
|
|
27
|
+
/** Slack allows `[a-z0-9_-]` in a slash command name (lowercased). */
|
|
28
|
+
const COMMAND_NAME_RE = /^[a-z0-9_-]{1,32}$/;
|
|
29
|
+
/**
|
|
30
|
+
* Normalize a command word to Slack's `[a-z0-9_-]{1,32}` shape, or null if
|
|
31
|
+
* unusable. Strips a leading `/`, lowercases, and drops disallowed chars.
|
|
32
|
+
*/
|
|
33
|
+
export function normalizeSlackCommandName(raw) {
|
|
34
|
+
const stripped = raw.trim().replace(/^\/+/, "").toLowerCase();
|
|
35
|
+
const cleaned = stripped.replace(/[^a-z0-9_-]/g, "").slice(0, MAX_NAME_LEN);
|
|
36
|
+
if (!cleaned || !COMMAND_NAME_RE.test(cleaned))
|
|
37
|
+
return null;
|
|
38
|
+
return cleaned;
|
|
39
|
+
}
|
|
40
|
+
/** Clamp + flatten a description to a single printable line within Slack's cap. */
|
|
41
|
+
function normalizeDescription(desc, fallback) {
|
|
42
|
+
const raw = (desc ?? "").replace(/\s+/g, " ").trim() || fallback;
|
|
43
|
+
return raw.length > MAX_DESC_LEN ? `${raw.slice(0, MAX_DESC_LEN - 1)}…` : raw;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build the Slack slash-command manifest from Brigade's central channel
|
|
47
|
+
* commands. De-dupes by normalized name (first wins), drops unusable names, and
|
|
48
|
+
* caps at 100. Returns `[]` when nothing maps. Unlike Telegram this is NOT
|
|
49
|
+
* pushed to Slack on connect — it's the list the operator registers by hand in
|
|
50
|
+
* the Slack app config (and what docs / status surface).
|
|
51
|
+
*/
|
|
52
|
+
export function buildSlackCommandManifest(commands) {
|
|
53
|
+
const out = [];
|
|
54
|
+
const seen = new Set();
|
|
55
|
+
for (const cmd of commands) {
|
|
56
|
+
if (out.length >= MAX_COMMANDS)
|
|
57
|
+
break;
|
|
58
|
+
const name = normalizeSlackCommandName(cmd.name);
|
|
59
|
+
if (!name || seen.has(name))
|
|
60
|
+
continue;
|
|
61
|
+
seen.add(name);
|
|
62
|
+
out.push({ command: name, description: normalizeDescription(cmd.description, name) });
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=command-menu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command-menu.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/command-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,qEAAqE;AACrE,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,sEAAsE;AACtE,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAW;IACpD,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IAC5E,IAAI,CAAC,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,mFAAmF;AACnF,SAAS,oBAAoB,CAAC,IAAwB,EAAE,QAAgB;IACvE,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,CAAC;IACjE,OAAO,GAAG,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AAC/E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,QAAuC;IAChF,MAAM,GAAG,GAAwB,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,MAAM,IAAI,YAAY;YAAE,MAAM;QACtC,MAAM,IAAI,GAAG,yBAAyB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACtC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,oBAAoB,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC"}
|