@spinabot/brigade 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/convex/channels.d.ts +5 -5
  2. package/convex/schema.d.ts +2 -2
  3. package/dist/agents/agent-loop.d.ts.map +1 -1
  4. package/dist/agents/agent-loop.js +27 -4
  5. package/dist/agents/agent-loop.js.map +1 -1
  6. package/dist/agents/channels/approval-callback-codec.d.ts +107 -0
  7. package/dist/agents/channels/approval-callback-codec.d.ts.map +1 -0
  8. package/dist/agents/channels/approval-callback-codec.js +173 -0
  9. package/dist/agents/channels/approval-callback-codec.js.map +1 -0
  10. package/dist/agents/channels/approval-router.d.ts +77 -20
  11. package/dist/agents/channels/approval-router.d.ts.map +1 -1
  12. package/dist/agents/channels/approval-router.js +163 -37
  13. package/dist/agents/channels/approval-router.js.map +1 -1
  14. package/dist/agents/channels/backoff.d.ts +55 -0
  15. package/dist/agents/channels/backoff.d.ts.map +1 -0
  16. package/dist/agents/channels/backoff.js +47 -0
  17. package/dist/agents/channels/backoff.js.map +1 -0
  18. package/dist/agents/channels/channel-secrets.d.ts +45 -0
  19. package/dist/agents/channels/channel-secrets.d.ts.map +1 -0
  20. package/dist/agents/channels/channel-secrets.js +69 -0
  21. package/dist/agents/channels/channel-secrets.js.map +1 -0
  22. package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
  23. package/dist/agents/channels/inbound-pipeline.js +67 -3
  24. package/dist/agents/channels/inbound-pipeline.js.map +1 -1
  25. package/dist/agents/channels/last-sent-message.d.ts +46 -0
  26. package/dist/agents/channels/last-sent-message.d.ts.map +1 -0
  27. package/dist/agents/channels/last-sent-message.js +55 -0
  28. package/dist/agents/channels/last-sent-message.js.map +1 -0
  29. package/dist/agents/channels/manager.d.ts +52 -0
  30. package/dist/agents/channels/manager.d.ts.map +1 -1
  31. package/dist/agents/channels/manager.js +141 -31
  32. package/dist/agents/channels/manager.js.map +1 -1
  33. package/dist/agents/channels/plugin-channel-manager-facade.d.ts +13 -2
  34. package/dist/agents/channels/plugin-channel-manager-facade.d.ts.map +1 -1
  35. package/dist/agents/channels/plugin-channel-manager-facade.js +21 -0
  36. package/dist/agents/channels/plugin-channel-manager-facade.js.map +1 -1
  37. package/dist/agents/channels/sdk.d.ts +426 -0
  38. package/dist/agents/channels/sdk.d.ts.map +1 -0
  39. package/dist/agents/channels/sdk.js +274 -0
  40. package/dist/agents/channels/sdk.js.map +1 -0
  41. package/dist/agents/channels/telegram/account-config.d.ts +92 -0
  42. package/dist/agents/channels/telegram/account-config.d.ts.map +1 -0
  43. package/dist/agents/channels/telegram/account-config.js +192 -0
  44. package/dist/agents/channels/telegram/account-config.js.map +1 -0
  45. package/dist/agents/channels/telegram/adapter.d.ts +79 -0
  46. package/dist/agents/channels/telegram/adapter.d.ts.map +1 -0
  47. package/dist/agents/channels/telegram/adapter.js +475 -0
  48. package/dist/agents/channels/telegram/adapter.js.map +1 -0
  49. package/dist/agents/channels/telegram/allowed-updates.d.ts +44 -0
  50. package/dist/agents/channels/telegram/allowed-updates.d.ts.map +1 -0
  51. package/dist/agents/channels/telegram/allowed-updates.js +52 -0
  52. package/dist/agents/channels/telegram/allowed-updates.js.map +1 -0
  53. package/dist/agents/channels/telegram/approval-authorize.d.ts +41 -0
  54. package/dist/agents/channels/telegram/approval-authorize.d.ts.map +1 -0
  55. package/dist/agents/channels/telegram/approval-authorize.js +69 -0
  56. package/dist/agents/channels/telegram/approval-authorize.js.map +1 -0
  57. package/dist/agents/channels/telegram/approval-native.d.ts +68 -0
  58. package/dist/agents/channels/telegram/approval-native.d.ts.map +1 -0
  59. package/dist/agents/channels/telegram/approval-native.js +94 -0
  60. package/dist/agents/channels/telegram/approval-native.js.map +1 -0
  61. package/dist/agents/channels/telegram/command-menu.d.ts +35 -0
  62. package/dist/agents/channels/telegram/command-menu.d.ts.map +1 -0
  63. package/dist/agents/channels/telegram/command-menu.js +59 -0
  64. package/dist/agents/channels/telegram/command-menu.js.map +1 -0
  65. package/dist/agents/channels/telegram/connection.d.ts +359 -0
  66. package/dist/agents/channels/telegram/connection.d.ts.map +1 -0
  67. package/dist/agents/channels/telegram/connection.js +865 -0
  68. package/dist/agents/channels/telegram/connection.js.map +1 -0
  69. package/dist/agents/channels/telegram/format.d.ts +48 -0
  70. package/dist/agents/channels/telegram/format.d.ts.map +1 -0
  71. package/dist/agents/channels/telegram/format.js +256 -0
  72. package/dist/agents/channels/telegram/format.js.map +1 -0
  73. package/dist/agents/channels/telegram/inbound-extras.d.ts +73 -0
  74. package/dist/agents/channels/telegram/inbound-extras.d.ts.map +1 -0
  75. package/dist/agents/channels/telegram/inbound-extras.js +231 -0
  76. package/dist/agents/channels/telegram/inbound-extras.js.map +1 -0
  77. package/dist/agents/channels/telegram/index.d.ts +14 -0
  78. package/dist/agents/channels/telegram/index.d.ts.map +1 -0
  79. package/dist/agents/channels/telegram/index.js +14 -0
  80. package/dist/agents/channels/telegram/index.js.map +1 -0
  81. package/dist/agents/channels/telegram/media.d.ts +68 -0
  82. package/dist/agents/channels/telegram/media.d.ts.map +1 -0
  83. package/dist/agents/channels/telegram/media.js +143 -0
  84. package/dist/agents/channels/telegram/media.js.map +1 -0
  85. package/dist/agents/channels/telegram/module.d.ts +15 -0
  86. package/dist/agents/channels/telegram/module.d.ts.map +1 -0
  87. package/dist/agents/channels/telegram/module.js +36 -0
  88. package/dist/agents/channels/telegram/module.js.map +1 -0
  89. package/dist/agents/channels/telegram/plugin.d.ts +76 -0
  90. package/dist/agents/channels/telegram/plugin.d.ts.map +1 -0
  91. package/dist/agents/channels/telegram/plugin.js +314 -0
  92. package/dist/agents/channels/telegram/plugin.js.map +1 -0
  93. package/dist/agents/channels/telegram/probe.d.ts +54 -0
  94. package/dist/agents/channels/telegram/probe.d.ts.map +1 -0
  95. package/dist/agents/channels/telegram/probe.js +95 -0
  96. package/dist/agents/channels/telegram/probe.js.map +1 -0
  97. package/dist/agents/channels/telegram/webhook.d.ts +55 -0
  98. package/dist/agents/channels/telegram/webhook.d.ts.map +1 -0
  99. package/dist/agents/channels/telegram/webhook.js +141 -0
  100. package/dist/agents/channels/telegram/webhook.js.map +1 -0
  101. package/dist/agents/extensions/modules/index.d.ts.map +1 -1
  102. package/dist/agents/extensions/modules/index.js +4 -0
  103. package/dist/agents/extensions/modules/index.js.map +1 -1
  104. package/dist/agents/extensions/types.d.ts +72 -2
  105. package/dist/agents/extensions/types.d.ts.map +1 -1
  106. package/dist/agents/extensions/types.js.map +1 -1
  107. package/dist/agents/tools/connect-channel-tool.d.ts +86 -0
  108. package/dist/agents/tools/connect-channel-tool.d.ts.map +1 -0
  109. package/dist/agents/tools/connect-channel-tool.js +398 -0
  110. package/dist/agents/tools/connect-channel-tool.js.map +1 -0
  111. package/dist/agents/tools/message-action-tool.d.ts +67 -0
  112. package/dist/agents/tools/message-action-tool.d.ts.map +1 -0
  113. package/dist/agents/tools/message-action-tool.js +216 -0
  114. package/dist/agents/tools/message-action-tool.js.map +1 -0
  115. package/dist/agents/tools/registry.d.ts.map +1 -1
  116. package/dist/agents/tools/registry.js +19 -0
  117. package/dist/agents/tools/registry.js.map +1 -1
  118. package/dist/buildstamp.json +1 -1
  119. package/dist/cli/commands/channels.d.ts.map +1 -1
  120. package/dist/cli/commands/channels.js +27 -2
  121. package/dist/cli/commands/channels.js.map +1 -1
  122. package/dist/core/server.d.ts.map +1 -1
  123. package/dist/core/server.js +77 -27
  124. package/dist/core/server.js.map +1 -1
  125. package/dist/cron/service/state.d.ts +10 -0
  126. package/dist/cron/service/state.d.ts.map +1 -1
  127. package/dist/cron/service/state.js.map +1 -1
  128. package/dist/cron/service/timer.d.ts.map +1 -1
  129. package/dist/cron/service/timer.js +43 -14
  130. package/dist/cron/service/timer.js.map +1 -1
  131. package/dist/cron/session-reaper.d.ts +27 -0
  132. package/dist/cron/session-reaper.d.ts.map +1 -1
  133. package/dist/cron/session-reaper.js +81 -0
  134. package/dist/cron/session-reaper.js.map +1 -1
  135. package/dist/system-prompt/assembler.d.ts +14 -0
  136. package/dist/system-prompt/assembler.d.ts.map +1 -1
  137. package/dist/system-prompt/assembler.js +36 -14
  138. package/dist/system-prompt/assembler.js.map +1 -1
  139. package/package.json +22 -6
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Telegram `allowed_updates` resolver.
3
+ *
4
+ * Telegram's `getUpdates` / `setWebhook` take an `allowed_updates` allow-list:
5
+ * only the named update kinds are delivered. Brigade subscribes the MINIMAL set
6
+ * its central pipeline actually consumes — keeping the firehose narrow means a
7
+ * chatty group never floods the poller with update kinds Brigade ignores
8
+ * (business messages, channel posts, inline queries, shipping/checkout, …, all
9
+ * of which the reference upstream subscribes but Brigade has no consumer for).
10
+ *
11
+ * The base set is always:
12
+ * - `message` — the inbound text/media path (the core surface).
13
+ * - `callback_query` — inline-button presses (interactive approvals). Without
14
+ * this in the list, a button tap is silently never delivered and the
15
+ * approval prompt hangs for its full timeout. Subscribed unconditionally so
16
+ * a button rendered by `sendApprovalPrompt` is always answerable.
17
+ *
18
+ * Conditionally added:
19
+ * - `message_reaction` — only when the channel opts into reaction inbound
20
+ * handling (`opts.reactions`). Brigade does not route inbound reactions to a
21
+ * turn today, so it is OFF by default; the flag exists so a future reaction-
22
+ * trigger feature can switch it on without touching the poller wiring.
23
+ * - `edited_message` — only when `opts.editedMessages` (off by default;
24
+ * Brigade treats an edit as a no-op rather than re-running the turn).
25
+ *
26
+ * Output is always a DEDUPED, STABLE-ORDER array of plain lowercase ASCII update
27
+ * names (no NUL / control bytes — these are fixed string literals).
28
+ */
29
+ /**
30
+ * Resolve the `allowed_updates` list Brigade's Telegram poller/webhook should
31
+ * request. `message` + `callback_query` are always present; reactions / edited
32
+ * messages are added only when explicitly enabled. Deduped + stable order.
33
+ */
34
+ export function resolveTelegramAllowedUpdates(opts = {}) {
35
+ const out = ["message", "callback_query"];
36
+ if (opts.reactions)
37
+ out.push("message_reaction");
38
+ if (opts.editedMessages)
39
+ out.push("edited_message");
40
+ // De-dupe defensively (the base list is already unique, but a future caller
41
+ // could pass overlapping flags) while preserving first-seen order.
42
+ const seen = new Set();
43
+ const deduped = [];
44
+ for (const u of out) {
45
+ if (seen.has(u))
46
+ continue;
47
+ seen.add(u);
48
+ deduped.push(u);
49
+ }
50
+ return deduped;
51
+ }
52
+ //# sourceMappingURL=allowed-updates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowed-updates.js","sourceRoot":"","sources":["../../../../src/agents/channels/telegram/allowed-updates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAiBH;;;;GAIG;AACH,MAAM,UAAU,6BAA6B,CAC5C,OAA6C,EAAE;IAE/C,MAAM,GAAG,GAA4B,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;IACnE,IAAI,IAAI,CAAC,SAAS;QAAE,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACjD,IAAI,IAAI,CAAC,cAAc;QAAE,GAAG,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpD,4EAA4E;IAC5E,mEAAmE;IACnE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC9C,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,GAAG,EAAE,CAAC;QACrB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAAE,SAAS;QAC1B,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACZ,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Telegram inline-approval authorization.
3
+ *
4
+ * When an approval prompt is rendered as inline buttons, ANY member who can see
5
+ * the message could press a button. Brigade's central inbound pipeline already
6
+ * runs the access-control gate before a `callback_query` reaches the
7
+ * approval-callback path, so only an admitted (allow-listed / owner) peer gets
8
+ * here at all — but a SHARED group is the edge case: in a group the bot is in,
9
+ * an admitted member's button press should still be allowed only when that
10
+ * presser is an approved approver, not merely present in the room.
11
+ *
12
+ * This predicate is the channel's `approvalCapability.authorizeApprover`. It is
13
+ * invoked CENTRALLY by `tryConsumeChannelApprovalCallback` with the presser's
14
+ * `senderId`; returning `{ authorized: false, reason }` refuses the press
15
+ * without consuming the operator's pending approval (so the real operator can
16
+ * still answer). Policy:
17
+ *
18
+ * - When the channel has an explicit allow-from list configured (the approved
19
+ * senders), only those ids may approve.
20
+ * - When NO allow-from list is configured, defer to the access gate that
21
+ * already admitted the inbound and authorize the press (matches the text-
22
+ * reply path, which has no extra approver gate).
23
+ *
24
+ * Pure + deterministic over its `cfg` + `senderId` inputs — no I/O.
25
+ */
26
+ import type { BrigadeConfig } from "../../../config/io.js";
27
+ /**
28
+ * Resolve whether `senderId` is allowed to answer a Telegram inline approval.
29
+ * Returns `{ authorized: true }` when no explicit allow-from gate applies (the
30
+ * central access gate already admitted the inbound), or when the presser is on
31
+ * the configured allow-from list. Otherwise refuses with a reason.
32
+ */
33
+ export declare function resolveTelegramApprover(args: {
34
+ cfg: BrigadeConfig;
35
+ senderId?: string;
36
+ accountId?: string;
37
+ }): {
38
+ authorized: boolean;
39
+ reason?: string;
40
+ };
41
+ //# sourceMappingURL=approval-authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-authorize.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/telegram/approval-authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA0B3D;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC7C,GAAG,EAAE,aAAa,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAU3C"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Telegram inline-approval authorization.
3
+ *
4
+ * When an approval prompt is rendered as inline buttons, ANY member who can see
5
+ * the message could press a button. Brigade's central inbound pipeline already
6
+ * runs the access-control gate before a `callback_query` reaches the
7
+ * approval-callback path, so only an admitted (allow-listed / owner) peer gets
8
+ * here at all — but a SHARED group is the edge case: in a group the bot is in,
9
+ * an admitted member's button press should still be allowed only when that
10
+ * presser is an approved approver, not merely present in the room.
11
+ *
12
+ * This predicate is the channel's `approvalCapability.authorizeApprover`. It is
13
+ * invoked CENTRALLY by `tryConsumeChannelApprovalCallback` with the presser's
14
+ * `senderId`; returning `{ authorized: false, reason }` refuses the press
15
+ * without consuming the operator's pending approval (so the real operator can
16
+ * still answer). Policy:
17
+ *
18
+ * - When the channel has an explicit allow-from list configured (the approved
19
+ * senders), only those ids may approve.
20
+ * - When NO allow-from list is configured, defer to the access gate that
21
+ * already admitted the inbound and authorize the press (matches the text-
22
+ * reply path, which has no extra approver gate).
23
+ *
24
+ * Pure + deterministic over its `cfg` + `senderId` inputs — no I/O.
25
+ */
26
+ /** Read the channel's configured allow-from sender ids (string-normalized). */
27
+ function configuredAllowFrom(cfg, accountId) {
28
+ const channels = cfg.channels;
29
+ const slot = channels?.telegram;
30
+ if (!slot)
31
+ return [];
32
+ const ids = [];
33
+ const push = (list) => {
34
+ for (const v of list ?? []) {
35
+ const s = String(v).trim();
36
+ if (s)
37
+ ids.push(s);
38
+ }
39
+ };
40
+ push(slot.allowFrom);
41
+ // Per-account allow-from (multi-account shape) when an accountId is supplied.
42
+ if (accountId && Array.isArray(slot.accounts)) {
43
+ for (const entry of slot.accounts) {
44
+ if (entry && typeof entry.id === "string" && entry.id.trim() === accountId.trim())
45
+ push(entry.allowFrom);
46
+ }
47
+ }
48
+ return ids;
49
+ }
50
+ /**
51
+ * Resolve whether `senderId` is allowed to answer a Telegram inline approval.
52
+ * Returns `{ authorized: true }` when no explicit allow-from gate applies (the
53
+ * central access gate already admitted the inbound), or when the presser is on
54
+ * the configured allow-from list. Otherwise refuses with a reason.
55
+ */
56
+ export function resolveTelegramApprover(args) {
57
+ const allow = configuredAllowFrom(args.cfg, args.accountId);
58
+ // No explicit allow list → defer to the access gate that already ran.
59
+ if (allow.length === 0)
60
+ return { authorized: true };
61
+ const sender = (args.senderId ?? "").trim();
62
+ if (sender && allow.includes(sender))
63
+ return { authorized: true };
64
+ return {
65
+ authorized: false,
66
+ reason: "Only an approved sender can answer that approval.",
67
+ };
68
+ }
69
+ //# sourceMappingURL=approval-authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-authorize.js","sourceRoot":"","sources":["../../../../src/agents/channels/telegram/approval-authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;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,QAEX,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,8EAA8E;IAC9E,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,uBAAuB,CAAC,IAIvC;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,68 @@
1
+ /**
2
+ * Telegram inline-keyboard rendering for native approval prompts.
3
+ *
4
+ * When a channel-routed turn raises an exec/plugin approval AND the Telegram
5
+ * adapter has opted into `approvalCapability.sendApprovalPrompt`, the central
6
+ * approval-router asks this channel to render the question as native inline
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 Telegram's `InlineKeyboardMarkup`
12
+ * shape and assembles the prompt text.
13
+ *
14
+ * Layout mirrors the reference Telegram extension: buttons are laid out in rows
15
+ * of at most {@link TELEGRAM_INTERACTIVE_ROW_SIZE} (3). The standard approval has
16
+ * three buttons (Allow once / Allow always / Deny) so they sit on one row.
17
+ *
18
+ * SAFETY: every `callback_data` value here is the codec's output — versioned,
19
+ * base64url + printable-ASCII, already proven `<= 64` UTF-8 bytes by
20
+ * `encodeApprovalCallback`. This module never mints its own payloads, so no NUL
21
+ * / control byte can appear. `sanitizeTelegramCallbackData` is a defensive
22
+ * belt-and-braces clamp for any externally-supplied value.
23
+ */
24
+ /** Telegram's `callback_data` hard cap (UTF-8 bytes). Matches the codec ceiling. */
25
+ export declare const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
26
+ /** Max inline buttons per keyboard row (reference parity). */
27
+ export declare const TELEGRAM_INTERACTIVE_ROW_SIZE = 3;
28
+ /** One Telegram inline button (the subset Brigade emits). */
29
+ export interface TelegramInlineButton {
30
+ text: string;
31
+ callback_data: string;
32
+ }
33
+ /** Telegram `InlineKeyboardMarkup` — rows of buttons. */
34
+ export interface TelegramInlineKeyboardMarkup {
35
+ inline_keyboard: TelegramInlineButton[][];
36
+ }
37
+ /**
38
+ * Clamp a callback-data string to Telegram's 64-byte budget and strip any
39
+ * control bytes. The codec already guarantees both, so this is purely defensive
40
+ * for a value that didn't come from the codec. Truncates on a UTF-8 boundary.
41
+ */
42
+ export declare function sanitizeTelegramCallbackData(value: string): string;
43
+ /**
44
+ * Build the inline keyboard for an approval prompt from the central codec.
45
+ * Returns `null` when fewer than two byte-safe buttons could be minted (a
46
+ * pathologically long approval id) — the caller then falls back to the text
47
+ * prompt rather than ship a half-rendered keyboard.
48
+ *
49
+ * `allowAlways: false` drops the "Allow always" button (approvals where
50
+ * persisting an allowlist entry doesn't apply).
51
+ */
52
+ export declare function buildTelegramApprovalKeyboard(args: {
53
+ approvalId: string;
54
+ allowAlways?: boolean;
55
+ }): TelegramInlineKeyboardMarkup | null;
56
+ /**
57
+ * Compose the operator-facing approval question text rendered ABOVE the inline
58
+ * keyboard. Kept short + control-char-scrubbed; the buttons carry the action,
59
+ * so the text only needs the command preview + a one-line ask. The 🦁 mark is
60
+ * the Brigade brand-stamp so the operator recognises this as a Brigade prompt.
61
+ */
62
+ export declare function buildTelegramApprovalText(args: {
63
+ command: string;
64
+ approvalKind: "exec" | "plugin";
65
+ toolName?: string;
66
+ agentId?: string;
67
+ }): string;
68
+ //# sourceMappingURL=approval-native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-native.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/telegram/approval-native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,oFAAoF;AACpF,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD,8DAA8D;AAC9D,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAK/C,6DAA6D;AAC7D,MAAM,WAAW,oBAAoB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,yDAAyD;AACzD,MAAM,WAAW,4BAA4B;IAC5C,eAAe,EAAE,oBAAoB,EAAE,EAAE,CAAC;CAC1C;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQlE;AAWD;;;;;;;;GAQG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,4BAA4B,GAAG,IAAI,CAWtC;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC/C,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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Telegram inline-keyboard rendering for native approval prompts.
3
+ *
4
+ * When a channel-routed turn raises an exec/plugin approval AND the Telegram
5
+ * adapter has opted into `approvalCapability.sendApprovalPrompt`, the central
6
+ * approval-router asks this channel to render the question as native inline
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 Telegram's `InlineKeyboardMarkup`
12
+ * shape and assembles the prompt text.
13
+ *
14
+ * Layout mirrors the reference Telegram extension: buttons are laid out in rows
15
+ * of at most {@link TELEGRAM_INTERACTIVE_ROW_SIZE} (3). The standard approval has
16
+ * three buttons (Allow once / Allow always / Deny) so they sit on one row.
17
+ *
18
+ * SAFETY: every `callback_data` value here is the codec's output — versioned,
19
+ * base64url + printable-ASCII, already proven `<= 64` UTF-8 bytes by
20
+ * `encodeApprovalCallback`. This module never mints its own payloads, so no NUL
21
+ * / control byte can appear. `sanitizeTelegramCallbackData` is a defensive
22
+ * belt-and-braces clamp for any externally-supplied value.
23
+ */
24
+ import { buildApprovalCallbackButtons } from "../sdk.js";
25
+ /** Telegram's `callback_data` hard cap (UTF-8 bytes). Matches the codec ceiling. */
26
+ export const TELEGRAM_CALLBACK_DATA_MAX_BYTES = 64;
27
+ /** Max inline buttons per keyboard row (reference parity). */
28
+ export const TELEGRAM_INTERACTIVE_ROW_SIZE = 3;
29
+ /** C0/C1 control-character class (incl. NUL) as printable hex escapes — never a raw control byte in source. */
30
+ const CONTROL_CHARS_RE = /[\x00-\x1f\x7f-\x9f]/g;
31
+ /**
32
+ * Clamp a callback-data string to Telegram's 64-byte budget and strip any
33
+ * control bytes. The codec already guarantees both, so this is purely defensive
34
+ * for a value that didn't come from the codec. Truncates on a UTF-8 boundary.
35
+ */
36
+ export function sanitizeTelegramCallbackData(value) {
37
+ // Drop C0/C1 control chars (incl. NUL) — callback_data must be printable.
38
+ // oxlint-disable-next-line no-control-regex
39
+ const cleaned = value.replace(CONTROL_CHARS_RE, "");
40
+ if (Buffer.byteLength(cleaned, "utf8") <= TELEGRAM_CALLBACK_DATA_MAX_BYTES)
41
+ return cleaned;
42
+ // Truncate to the byte budget without splitting a multi-byte sequence.
43
+ const buf = Buffer.from(cleaned, "utf8").subarray(0, TELEGRAM_CALLBACK_DATA_MAX_BYTES);
44
+ return new TextDecoder("utf-8", { fatal: false }).decode(buf).replace(/�+$/g, "");
45
+ }
46
+ /** Chunk a flat button list into rows of at most `rowSize`. */
47
+ function chunkIntoRows(buttons, rowSize) {
48
+ const rows = [];
49
+ for (let i = 0; i < buttons.length; i += rowSize) {
50
+ rows.push(buttons.slice(i, i + rowSize));
51
+ }
52
+ return rows;
53
+ }
54
+ /**
55
+ * Build the inline keyboard for an approval prompt from the central codec.
56
+ * Returns `null` when fewer than two byte-safe buttons could be minted (a
57
+ * pathologically long approval id) — the caller then falls back to the text
58
+ * prompt rather than ship a half-rendered keyboard.
59
+ *
60
+ * `allowAlways: false` drops the "Allow always" button (approvals where
61
+ * persisting an allowlist entry doesn't apply).
62
+ */
63
+ export function buildTelegramApprovalKeyboard(args) {
64
+ const specs = buildApprovalCallbackButtons({
65
+ approvalId: args.approvalId,
66
+ ...(args.allowAlways === false ? { allowAlways: false } : {}),
67
+ });
68
+ if (specs.length < 2)
69
+ return null; // not enough buttons → caller uses text prompt
70
+ const buttons = specs.map((s) => ({
71
+ text: s.label,
72
+ callback_data: sanitizeTelegramCallbackData(s.data),
73
+ }));
74
+ return { inline_keyboard: chunkIntoRows(buttons, TELEGRAM_INTERACTIVE_ROW_SIZE) };
75
+ }
76
+ /**
77
+ * Compose the operator-facing approval question text rendered ABOVE the inline
78
+ * keyboard. Kept short + control-char-scrubbed; the buttons carry the action,
79
+ * so the text only needs the command preview + a one-line ask. The 🦁 mark is
80
+ * the Brigade brand-stamp so the operator recognises this as a Brigade prompt.
81
+ */
82
+ export function buildTelegramApprovalText(args) {
83
+ const flat = args.command
84
+ .replace(/[\r\n]+/g, " ")
85
+ // oxlint-disable-next-line no-control-regex
86
+ .replace(CONTROL_CHARS_RE, " ")
87
+ .replace(/\s+/g, " ")
88
+ .trim();
89
+ const preview = flat.length <= 180 ? flat : `${flat.slice(0, 177)}…`;
90
+ const what = args.approvalKind === "plugin" ? "run a plugin action" : "run a shell command";
91
+ const lines = [`🦁 Brigade wants to ${what}:`, `\`${preview}\``, "", "Choose below — times out in 5 minutes."];
92
+ return lines.join("\n");
93
+ }
94
+ //# sourceMappingURL=approval-native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-native.js","sourceRoot":"","sources":["../../../../src/agents/channels/telegram/approval-native.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Telegram native command menu — map Brigade's central channel commands onto
3
+ * the `setMyCommands` shape that populates Telegram's `/` quick-menu.
4
+ *
5
+ * Brigade owns the command set centrally (`buildBundledCommands` → `/help`,
6
+ * `/status`, `/allowlist`, `/agent`, `/agents`, `/whoami`, `/org`, plus any
7
+ * module-registered channel commands). On connect the Telegram channel mirrors
8
+ * that set into the bot's command menu so the operator sees the same commands
9
+ * surfaced as native `/` suggestions in the Telegram client.
10
+ *
11
+ * Telegram's constraints (enforced here so a malformed command never makes
12
+ * `setMyCommands` reject the WHOLE list):
13
+ * - command name: 1–32 chars, lowercase `[a-z0-9_]` only (leading `/` stripped).
14
+ * - description: 1–256 chars (clamped).
15
+ * - at most 100 commands.
16
+ *
17
+ * Pure / deterministic — no I/O. Output is always printable ASCII command names
18
+ * (the regex strips everything else), so no NUL / control byte can appear.
19
+ */
20
+ import type { ChannelCommand } from "../sdk.js";
21
+ /** Telegram bot-command entry. */
22
+ export interface TelegramBotCommand {
23
+ command: string;
24
+ description: string;
25
+ }
26
+ /** Normalize a command word to Telegram's `[a-z0-9_]{1,32}` shape, or null if unusable. */
27
+ export declare function normalizeTelegramCommandName(raw: string): string | null;
28
+ /**
29
+ * Build the Telegram command menu from Brigade's central channel commands.
30
+ * De-dupes by normalized name (first wins), drops unusable names, and caps at
31
+ * Telegram's 100-command ceiling. Returns `[]` when nothing maps (caller skips
32
+ * the `setMyCommands` call entirely).
33
+ */
34
+ export declare function buildTelegramCommandMenu(commands: ReadonlyArray<ChannelCommand>): TelegramBotCommand[];
35
+ //# sourceMappingURL=command-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-menu.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/telegram/command-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,kCAAkC;AAClC,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACpB;AAQD,2FAA2F;AAC3F,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMvE;AAQD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,kBAAkB,EAAE,CAWtG"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Telegram native command menu — map Brigade's central channel commands onto
3
+ * the `setMyCommands` shape that populates Telegram's `/` quick-menu.
4
+ *
5
+ * Brigade owns the command set centrally (`buildBundledCommands` → `/help`,
6
+ * `/status`, `/allowlist`, `/agent`, `/agents`, `/whoami`, `/org`, plus any
7
+ * module-registered channel commands). On connect the Telegram channel mirrors
8
+ * that set into the bot's command menu so the operator sees the same commands
9
+ * surfaced as native `/` suggestions in the Telegram client.
10
+ *
11
+ * Telegram's constraints (enforced here so a malformed command never makes
12
+ * `setMyCommands` reject the WHOLE list):
13
+ * - command name: 1–32 chars, lowercase `[a-z0-9_]` only (leading `/` stripped).
14
+ * - description: 1–256 chars (clamped).
15
+ * - at most 100 commands.
16
+ *
17
+ * Pure / deterministic — no I/O. Output is always printable ASCII command names
18
+ * (the regex strips everything else), so no NUL / control byte can appear.
19
+ */
20
+ /** Telegram limits. */
21
+ const MAX_COMMANDS = 100;
22
+ const MAX_NAME_LEN = 32;
23
+ const MAX_DESC_LEN = 256;
24
+ const COMMAND_NAME_RE = /^[a-z0-9_]{1,32}$/;
25
+ /** Normalize a command word to Telegram's `[a-z0-9_]{1,32}` shape, or null if unusable. */
26
+ export function normalizeTelegramCommandName(raw) {
27
+ const stripped = raw.trim().replace(/^\/+/, "").toLowerCase();
28
+ // Replace any disallowed char with nothing, then clamp length.
29
+ const cleaned = stripped.replace(/[^a-z0-9_]/g, "").slice(0, MAX_NAME_LEN);
30
+ if (!cleaned || !COMMAND_NAME_RE.test(cleaned))
31
+ return null;
32
+ return cleaned;
33
+ }
34
+ /** Clamp + flatten a description to a single printable line within Telegram's cap. */
35
+ function normalizeDescription(desc, fallback) {
36
+ const raw = (desc ?? "").replace(/\s+/g, " ").trim() || fallback;
37
+ return raw.length > MAX_DESC_LEN ? `${raw.slice(0, MAX_DESC_LEN - 1)}…` : raw;
38
+ }
39
+ /**
40
+ * Build the Telegram command menu from Brigade's central channel commands.
41
+ * De-dupes by normalized name (first wins), drops unusable names, and caps at
42
+ * Telegram's 100-command ceiling. Returns `[]` when nothing maps (caller skips
43
+ * the `setMyCommands` call entirely).
44
+ */
45
+ export function buildTelegramCommandMenu(commands) {
46
+ const out = [];
47
+ const seen = new Set();
48
+ for (const cmd of commands) {
49
+ if (out.length >= MAX_COMMANDS)
50
+ break;
51
+ const name = normalizeTelegramCommandName(cmd.name);
52
+ if (!name || seen.has(name))
53
+ continue;
54
+ seen.add(name);
55
+ out.push({ command: name, description: normalizeDescription(cmd.description, name) });
56
+ }
57
+ return out;
58
+ }
59
+ //# sourceMappingURL=command-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-menu.js","sourceRoot":"","sources":["../../../../src/agents/channels/telegram/command-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAUH,uBAAuB;AACvB,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAE5C,2FAA2F;AAC3F,MAAM,UAAU,4BAA4B,CAAC,GAAW;IACvD,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,+DAA+D;IAC/D,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IAC3E,IAAI,CAAC,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,sFAAsF;AACtF,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;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAuC;IAC/E,MAAM,GAAG,GAAyB,EAAE,CAAC;IACrC,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,4BAA4B,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpD,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"}