@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.
- package/convex/channels.d.ts +5 -5
- package/convex/schema.d.ts +2 -2
- package/dist/agents/agent-loop.d.ts.map +1 -1
- package/dist/agents/agent-loop.js +27 -4
- package/dist/agents/agent-loop.js.map +1 -1
- package/dist/agents/channels/approval-callback-codec.d.ts +107 -0
- package/dist/agents/channels/approval-callback-codec.d.ts.map +1 -0
- package/dist/agents/channels/approval-callback-codec.js +173 -0
- package/dist/agents/channels/approval-callback-codec.js.map +1 -0
- package/dist/agents/channels/approval-router.d.ts +77 -20
- package/dist/agents/channels/approval-router.d.ts.map +1 -1
- package/dist/agents/channels/approval-router.js +163 -37
- package/dist/agents/channels/approval-router.js.map +1 -1
- package/dist/agents/channels/backoff.d.ts +55 -0
- package/dist/agents/channels/backoff.d.ts.map +1 -0
- package/dist/agents/channels/backoff.js +47 -0
- package/dist/agents/channels/backoff.js.map +1 -0
- package/dist/agents/channels/channel-secrets.d.ts +45 -0
- package/dist/agents/channels/channel-secrets.d.ts.map +1 -0
- package/dist/agents/channels/channel-secrets.js +69 -0
- package/dist/agents/channels/channel-secrets.js.map +1 -0
- package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
- package/dist/agents/channels/inbound-pipeline.js +67 -3
- package/dist/agents/channels/inbound-pipeline.js.map +1 -1
- package/dist/agents/channels/last-sent-message.d.ts +46 -0
- package/dist/agents/channels/last-sent-message.d.ts.map +1 -0
- package/dist/agents/channels/last-sent-message.js +55 -0
- package/dist/agents/channels/last-sent-message.js.map +1 -0
- package/dist/agents/channels/manager.d.ts +52 -0
- package/dist/agents/channels/manager.d.ts.map +1 -1
- package/dist/agents/channels/manager.js +141 -31
- package/dist/agents/channels/manager.js.map +1 -1
- package/dist/agents/channels/plugin-channel-manager-facade.d.ts +13 -2
- package/dist/agents/channels/plugin-channel-manager-facade.d.ts.map +1 -1
- package/dist/agents/channels/plugin-channel-manager-facade.js +21 -0
- package/dist/agents/channels/plugin-channel-manager-facade.js.map +1 -1
- package/dist/agents/channels/sdk.d.ts +426 -0
- package/dist/agents/channels/sdk.d.ts.map +1 -0
- package/dist/agents/channels/sdk.js +274 -0
- package/dist/agents/channels/sdk.js.map +1 -0
- package/dist/agents/channels/telegram/account-config.d.ts +92 -0
- package/dist/agents/channels/telegram/account-config.d.ts.map +1 -0
- package/dist/agents/channels/telegram/account-config.js +192 -0
- package/dist/agents/channels/telegram/account-config.js.map +1 -0
- package/dist/agents/channels/telegram/adapter.d.ts +79 -0
- package/dist/agents/channels/telegram/adapter.d.ts.map +1 -0
- package/dist/agents/channels/telegram/adapter.js +475 -0
- package/dist/agents/channels/telegram/adapter.js.map +1 -0
- package/dist/agents/channels/telegram/allowed-updates.d.ts +44 -0
- package/dist/agents/channels/telegram/allowed-updates.d.ts.map +1 -0
- package/dist/agents/channels/telegram/allowed-updates.js +52 -0
- package/dist/agents/channels/telegram/allowed-updates.js.map +1 -0
- package/dist/agents/channels/telegram/approval-authorize.d.ts +41 -0
- package/dist/agents/channels/telegram/approval-authorize.d.ts.map +1 -0
- package/dist/agents/channels/telegram/approval-authorize.js +69 -0
- package/dist/agents/channels/telegram/approval-authorize.js.map +1 -0
- package/dist/agents/channels/telegram/approval-native.d.ts +68 -0
- package/dist/agents/channels/telegram/approval-native.d.ts.map +1 -0
- package/dist/agents/channels/telegram/approval-native.js +94 -0
- package/dist/agents/channels/telegram/approval-native.js.map +1 -0
- package/dist/agents/channels/telegram/command-menu.d.ts +35 -0
- package/dist/agents/channels/telegram/command-menu.d.ts.map +1 -0
- package/dist/agents/channels/telegram/command-menu.js +59 -0
- package/dist/agents/channels/telegram/command-menu.js.map +1 -0
- package/dist/agents/channels/telegram/connection.d.ts +359 -0
- package/dist/agents/channels/telegram/connection.d.ts.map +1 -0
- package/dist/agents/channels/telegram/connection.js +865 -0
- package/dist/agents/channels/telegram/connection.js.map +1 -0
- package/dist/agents/channels/telegram/format.d.ts +48 -0
- package/dist/agents/channels/telegram/format.d.ts.map +1 -0
- package/dist/agents/channels/telegram/format.js +256 -0
- package/dist/agents/channels/telegram/format.js.map +1 -0
- package/dist/agents/channels/telegram/inbound-extras.d.ts +73 -0
- package/dist/agents/channels/telegram/inbound-extras.d.ts.map +1 -0
- package/dist/agents/channels/telegram/inbound-extras.js +231 -0
- package/dist/agents/channels/telegram/inbound-extras.js.map +1 -0
- package/dist/agents/channels/telegram/index.d.ts +14 -0
- package/dist/agents/channels/telegram/index.d.ts.map +1 -0
- package/dist/agents/channels/telegram/index.js +14 -0
- package/dist/agents/channels/telegram/index.js.map +1 -0
- package/dist/agents/channels/telegram/media.d.ts +68 -0
- package/dist/agents/channels/telegram/media.d.ts.map +1 -0
- package/dist/agents/channels/telegram/media.js +143 -0
- package/dist/agents/channels/telegram/media.js.map +1 -0
- package/dist/agents/channels/telegram/module.d.ts +15 -0
- package/dist/agents/channels/telegram/module.d.ts.map +1 -0
- package/dist/agents/channels/telegram/module.js +36 -0
- package/dist/agents/channels/telegram/module.js.map +1 -0
- package/dist/agents/channels/telegram/plugin.d.ts +76 -0
- package/dist/agents/channels/telegram/plugin.d.ts.map +1 -0
- package/dist/agents/channels/telegram/plugin.js +314 -0
- package/dist/agents/channels/telegram/plugin.js.map +1 -0
- package/dist/agents/channels/telegram/probe.d.ts +54 -0
- package/dist/agents/channels/telegram/probe.d.ts.map +1 -0
- package/dist/agents/channels/telegram/probe.js +95 -0
- package/dist/agents/channels/telegram/probe.js.map +1 -0
- package/dist/agents/channels/telegram/webhook.d.ts +55 -0
- package/dist/agents/channels/telegram/webhook.d.ts.map +1 -0
- package/dist/agents/channels/telegram/webhook.js +141 -0
- package/dist/agents/channels/telegram/webhook.js.map +1 -0
- package/dist/agents/extensions/modules/index.d.ts.map +1 -1
- package/dist/agents/extensions/modules/index.js +4 -0
- package/dist/agents/extensions/modules/index.js.map +1 -1
- package/dist/agents/extensions/types.d.ts +72 -2
- package/dist/agents/extensions/types.d.ts.map +1 -1
- package/dist/agents/extensions/types.js.map +1 -1
- package/dist/agents/tools/connect-channel-tool.d.ts +86 -0
- package/dist/agents/tools/connect-channel-tool.d.ts.map +1 -0
- package/dist/agents/tools/connect-channel-tool.js +398 -0
- package/dist/agents/tools/connect-channel-tool.js.map +1 -0
- package/dist/agents/tools/message-action-tool.d.ts +67 -0
- package/dist/agents/tools/message-action-tool.d.ts.map +1 -0
- package/dist/agents/tools/message-action-tool.js +216 -0
- package/dist/agents/tools/message-action-tool.js.map +1 -0
- package/dist/agents/tools/registry.d.ts.map +1 -1
- package/dist/agents/tools/registry.js +19 -0
- package/dist/agents/tools/registry.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/channels.d.ts.map +1 -1
- package/dist/cli/commands/channels.js +27 -2
- package/dist/cli/commands/channels.js.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +77 -27
- package/dist/core/server.js.map +1 -1
- package/dist/cron/service/state.d.ts +10 -0
- package/dist/cron/service/state.d.ts.map +1 -1
- package/dist/cron/service/state.js.map +1 -1
- package/dist/cron/service/timer.d.ts.map +1 -1
- package/dist/cron/service/timer.js +43 -14
- package/dist/cron/service/timer.js.map +1 -1
- package/dist/cron/session-reaper.d.ts +27 -0
- package/dist/cron/session-reaper.d.ts.map +1 -1
- package/dist/cron/session-reaper.js +81 -0
- package/dist/cron/session-reaper.js.map +1 -1
- package/dist/system-prompt/assembler.d.ts +14 -0
- package/dist/system-prompt/assembler.d.ts.map +1 -1
- package/dist/system-prompt/assembler.js +36 -14
- package/dist/system-prompt/assembler.js.map +1 -1
- 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"}
|