@open-everywhere/provider-discord 0.1.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.
Files changed (166) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +47 -0
  4. package/agent/access/discord-grant-click.ts +237 -0
  5. package/agent/approvals/discord-admin.ts +227 -0
  6. package/agent/approvals/discord-card.ts +248 -0
  7. package/agent/approvals/discord-flow.ts +469 -0
  8. package/agent/approvals/discord-scope-label.ts +27 -0
  9. package/agent/channels/discord-approval-card.ts +373 -0
  10. package/agent/channels/discord-backscroll.ts +593 -0
  11. package/agent/channels/discord-bot-name.ts +113 -0
  12. package/agent/channels/discord-channel-info.ts +83 -0
  13. package/agent/channels/discord-directory.ts +179 -0
  14. package/agent/channels/discord-dispatch-target.ts +103 -0
  15. package/agent/channels/discord-events.ts +175 -0
  16. package/agent/channels/discord-front.ts +355 -0
  17. package/agent/channels/discord-gateway.ts +3256 -0
  18. package/agent/channels/discord-identity.ts +269 -0
  19. package/agent/channels/discord-interaction-defer.ts +123 -0
  20. package/agent/channels/discord-message-ops.ts +127 -0
  21. package/agent/channels/discord-origin-codec.ts +105 -0
  22. package/agent/channels/discord-post-arm.ts +230 -0
  23. package/agent/channels/discord-post.ts +463 -0
  24. package/agent/channels/discord-read.ts +140 -0
  25. package/agent/channels/discord-rest.ts +85 -0
  26. package/agent/channels/discord.ts +244 -0
  27. package/agent/commands/discord-command-sync.ts +285 -0
  28. package/agent/commands/discord-commands.ts +291 -0
  29. package/agent/contributions.ts +31 -0
  30. package/agent/docs-generated.ts +15 -0
  31. package/agent/identity/discord-link-consent.ts +124 -0
  32. package/agent/ops.ts +14 -0
  33. package/agent/provider-events.ts +86 -0
  34. package/agent/register.ts +147 -0
  35. package/agent/surface-parity.ts +95 -0
  36. package/dist/agent/access/discord-grant-click.d.ts +25 -0
  37. package/dist/agent/access/discord-grant-click.d.ts.map +1 -0
  38. package/dist/agent/access/discord-grant-click.js +210 -0
  39. package/dist/agent/access/discord-grant-click.js.map +1 -0
  40. package/dist/agent/approvals/discord-admin.d.ts +63 -0
  41. package/dist/agent/approvals/discord-admin.d.ts.map +1 -0
  42. package/dist/agent/approvals/discord-admin.js +219 -0
  43. package/dist/agent/approvals/discord-admin.js.map +1 -0
  44. package/dist/agent/approvals/discord-card.d.ts +102 -0
  45. package/dist/agent/approvals/discord-card.d.ts.map +1 -0
  46. package/dist/agent/approvals/discord-card.js +217 -0
  47. package/dist/agent/approvals/discord-card.js.map +1 -0
  48. package/dist/agent/approvals/discord-flow.d.ts +109 -0
  49. package/dist/agent/approvals/discord-flow.d.ts.map +1 -0
  50. package/dist/agent/approvals/discord-flow.js +375 -0
  51. package/dist/agent/approvals/discord-flow.js.map +1 -0
  52. package/dist/agent/approvals/discord-scope-label.d.ts +3 -0
  53. package/dist/agent/approvals/discord-scope-label.d.ts.map +1 -0
  54. package/dist/agent/approvals/discord-scope-label.js +27 -0
  55. package/dist/agent/approvals/discord-scope-label.js.map +1 -0
  56. package/dist/agent/channels/discord-approval-card.d.ts +81 -0
  57. package/dist/agent/channels/discord-approval-card.d.ts.map +1 -0
  58. package/dist/agent/channels/discord-approval-card.js +303 -0
  59. package/dist/agent/channels/discord-approval-card.js.map +1 -0
  60. package/dist/agent/channels/discord-backscroll.d.ts +209 -0
  61. package/dist/agent/channels/discord-backscroll.d.ts.map +1 -0
  62. package/dist/agent/channels/discord-backscroll.js +505 -0
  63. package/dist/agent/channels/discord-backscroll.js.map +1 -0
  64. package/dist/agent/channels/discord-bot-name.d.ts +19 -0
  65. package/dist/agent/channels/discord-bot-name.d.ts.map +1 -0
  66. package/dist/agent/channels/discord-bot-name.js +98 -0
  67. package/dist/agent/channels/discord-bot-name.js.map +1 -0
  68. package/dist/agent/channels/discord-channel-info.d.ts +22 -0
  69. package/dist/agent/channels/discord-channel-info.d.ts.map +1 -0
  70. package/dist/agent/channels/discord-channel-info.js +65 -0
  71. package/dist/agent/channels/discord-channel-info.js.map +1 -0
  72. package/dist/agent/channels/discord-directory.d.ts +11 -0
  73. package/dist/agent/channels/discord-directory.d.ts.map +1 -0
  74. package/dist/agent/channels/discord-directory.js +170 -0
  75. package/dist/agent/channels/discord-directory.js.map +1 -0
  76. package/dist/agent/channels/discord-dispatch-target.d.ts +15 -0
  77. package/dist/agent/channels/discord-dispatch-target.d.ts.map +1 -0
  78. package/dist/agent/channels/discord-dispatch-target.js +99 -0
  79. package/dist/agent/channels/discord-dispatch-target.js.map +1 -0
  80. package/dist/agent/channels/discord-events.d.ts +58 -0
  81. package/dist/agent/channels/discord-events.d.ts.map +1 -0
  82. package/dist/agent/channels/discord-events.js +150 -0
  83. package/dist/agent/channels/discord-events.js.map +1 -0
  84. package/dist/agent/channels/discord-front.d.ts +115 -0
  85. package/dist/agent/channels/discord-front.d.ts.map +1 -0
  86. package/dist/agent/channels/discord-front.js +279 -0
  87. package/dist/agent/channels/discord-front.js.map +1 -0
  88. package/dist/agent/channels/discord-gateway.d.ts +783 -0
  89. package/dist/agent/channels/discord-gateway.d.ts.map +1 -0
  90. package/dist/agent/channels/discord-gateway.js +2591 -0
  91. package/dist/agent/channels/discord-gateway.js.map +1 -0
  92. package/dist/agent/channels/discord-identity.d.ts +110 -0
  93. package/dist/agent/channels/discord-identity.d.ts.map +1 -0
  94. package/dist/agent/channels/discord-identity.js +201 -0
  95. package/dist/agent/channels/discord-identity.js.map +1 -0
  96. package/dist/agent/channels/discord-interaction-defer.d.ts +33 -0
  97. package/dist/agent/channels/discord-interaction-defer.d.ts.map +1 -0
  98. package/dist/agent/channels/discord-interaction-defer.js +102 -0
  99. package/dist/agent/channels/discord-interaction-defer.js.map +1 -0
  100. package/dist/agent/channels/discord-message-ops.d.ts +3 -0
  101. package/dist/agent/channels/discord-message-ops.d.ts.map +1 -0
  102. package/dist/agent/channels/discord-message-ops.js +123 -0
  103. package/dist/agent/channels/discord-message-ops.js.map +1 -0
  104. package/dist/agent/channels/discord-origin-codec.d.ts +7 -0
  105. package/dist/agent/channels/discord-origin-codec.d.ts.map +1 -0
  106. package/dist/agent/channels/discord-origin-codec.js +98 -0
  107. package/dist/agent/channels/discord-origin-codec.js.map +1 -0
  108. package/dist/agent/channels/discord-post-arm.d.ts +3 -0
  109. package/dist/agent/channels/discord-post-arm.d.ts.map +1 -0
  110. package/dist/agent/channels/discord-post-arm.js +195 -0
  111. package/dist/agent/channels/discord-post-arm.js.map +1 -0
  112. package/dist/agent/channels/discord-post.d.ts +120 -0
  113. package/dist/agent/channels/discord-post.d.ts.map +1 -0
  114. package/dist/agent/channels/discord-post.js +426 -0
  115. package/dist/agent/channels/discord-post.js.map +1 -0
  116. package/dist/agent/channels/discord-read.d.ts +40 -0
  117. package/dist/agent/channels/discord-read.d.ts.map +1 -0
  118. package/dist/agent/channels/discord-read.js +128 -0
  119. package/dist/agent/channels/discord-read.js.map +1 -0
  120. package/dist/agent/channels/discord-rest.d.ts +33 -0
  121. package/dist/agent/channels/discord-rest.d.ts.map +1 -0
  122. package/dist/agent/channels/discord-rest.js +75 -0
  123. package/dist/agent/channels/discord-rest.js.map +1 -0
  124. package/dist/agent/channels/discord.d.ts +3 -0
  125. package/dist/agent/channels/discord.d.ts.map +1 -0
  126. package/dist/agent/channels/discord.js +230 -0
  127. package/dist/agent/channels/discord.js.map +1 -0
  128. package/dist/agent/commands/discord-command-sync.d.ts +66 -0
  129. package/dist/agent/commands/discord-command-sync.d.ts.map +1 -0
  130. package/dist/agent/commands/discord-command-sync.js +224 -0
  131. package/dist/agent/commands/discord-command-sync.js.map +1 -0
  132. package/dist/agent/commands/discord-commands.d.ts +45 -0
  133. package/dist/agent/commands/discord-commands.d.ts.map +1 -0
  134. package/dist/agent/commands/discord-commands.js +266 -0
  135. package/dist/agent/commands/discord-commands.js.map +1 -0
  136. package/dist/agent/contributions.d.ts +17 -0
  137. package/dist/agent/contributions.d.ts.map +1 -0
  138. package/dist/agent/contributions.js +31 -0
  139. package/dist/agent/contributions.js.map +1 -0
  140. package/dist/agent/docs-generated.d.ts +3 -0
  141. package/dist/agent/docs-generated.d.ts.map +1 -0
  142. package/dist/agent/docs-generated.js +13 -0
  143. package/dist/agent/docs-generated.js.map +1 -0
  144. package/dist/agent/identity/discord-link-consent.d.ts +13 -0
  145. package/dist/agent/identity/discord-link-consent.d.ts.map +1 -0
  146. package/dist/agent/identity/discord-link-consent.js +111 -0
  147. package/dist/agent/identity/discord-link-consent.js.map +1 -0
  148. package/dist/agent/ops.d.ts +2 -0
  149. package/dist/agent/ops.d.ts.map +1 -0
  150. package/dist/agent/ops.js +13 -0
  151. package/dist/agent/ops.js.map +1 -0
  152. package/dist/agent/provider-events.d.ts +3 -0
  153. package/dist/agent/provider-events.d.ts.map +1 -0
  154. package/dist/agent/provider-events.js +78 -0
  155. package/dist/agent/provider-events.js.map +1 -0
  156. package/dist/agent/register.d.ts +5 -0
  157. package/dist/agent/register.d.ts.map +1 -0
  158. package/dist/agent/register.js +125 -0
  159. package/dist/agent/register.js.map +1 -0
  160. package/dist/agent/surface-parity.d.ts +3 -0
  161. package/dist/agent/surface-parity.d.ts.map +1 -0
  162. package/dist/agent/surface-parity.js +90 -0
  163. package/dist/agent/surface-parity.js.map +1 -0
  164. package/package.json +185 -0
  165. package/tsconfig.build.json +22 -0
  166. package/tsconfig.json +15 -0
@@ -0,0 +1,109 @@
1
+ import { type ApprovalClickResolution, type DecisionWakeDeps, type PendingActionRow } from "@open-everywhere/core/agent/adapter-api.js";
2
+ /** One Discord interaction response body (`{ type, data? }`), ready to JSON out. */
3
+ export type DiscordInteractionResponse = Record<string, unknown>;
4
+ /** Injectable seams so the poster is unit-tested without a live token or network. */
5
+ export interface DiscordPostDeps {
6
+ readonly fetchFn?: typeof fetch;
7
+ readonly token?: string;
8
+ }
9
+ /**
10
+ * Render + post the approval card into `row.channelId` via Discord REST create-message
11
+ * (`POST /channels/{id}/messages` with `content` + `components`). Resolves to the posted
12
+ * message's id, or undefined on ANY failure โ€” no token, an over-long custom_id (render
13
+ * throws), a non-ok status, a network error, or a response missing an id. NEVER throws,
14
+ * and the bot token is a SECRET โ€” it rides the header, never a log line (both conventions
15
+ * lifted verbatim from channels/discord-gateway.ts's discordCreateMessage).
16
+ */
17
+ export declare function postDiscordApprovalCard(row: PendingActionRow, summaryLines: readonly string[], deps?: DiscordPostDeps): Promise<string | undefined>;
18
+ /**
19
+ * THE JUDGE-LIFT NOTICE POSTER (OPE-1103) โ€” the `JudgeLiftNoticePoster` this bundle registers, and
20
+ * the Discord half of what was a Slack-only surface.
21
+ *
22
+ * It is NOT the CardPoster above with a different render, and the two must stay apart: that one
23
+ * posts the PENDING card for a LIVE row, and `requestApproval` treats its failure as fatal (it
24
+ * expires the pending and throws, because a proposal nobody can see must never linger). Here the
25
+ * frozen args have ALREADY RUN. A thrown error could not undo them and would only turn a completed
26
+ * action into a failed tool call, so this resolves `void` on every path โ€” the core's own contract
27
+ * for the hook.
28
+ *
29
+ * WHAT A FAILURE COSTS, stated plainly: the room loses its only trace of the lift and the one button
30
+ * that answers it. The audit rows (`approval.autoapproved`, `approval.judge.verdict`) still land, so
31
+ * the event stays queryable โ€” but nobody in the room is told. That is why the failure is LOGGED
32
+ * rather than swallowed silently.
33
+ */
34
+ export declare function postDiscordJudgeLiftNotice(row: PendingActionRow, summaryLines: readonly string[], outcomeLine: string, deps?: DiscordPostDeps): Promise<void>;
35
+ /** The subset of a MESSAGE_COMPONENT interaction the approval click path reads. This is
36
+ * untrusted wire JSON reaching a public URL (post-signature-check, but Discord relays
37
+ * whatever a client sends) โ€” every field is probed, nothing is assumed (same defensive
38
+ * posture as discord-gateway.ts's parseRelayPayload). */
39
+ export interface DiscordApprovalClick {
40
+ readonly customId: string;
41
+ /** The clicking user's id โ€” `member.user.id` in a guild, top-level `user.id` in a DM. */
42
+ readonly clickerId?: string;
43
+ readonly guildId?: string;
44
+ /** `member.permissions` โ€” Discord's stringified permission bitfield for the clicker in
45
+ * the channel the click happened in. Absent outside guilds. */
46
+ readonly memberPermissions?: string;
47
+ }
48
+ /**
49
+ * Probe a raw interaction body for an `oe-approval:` MESSAGE_COMPONENT click. Returns null
50
+ * for anything else โ€” wrong type, no custom_id, or a foreign prefix โ€” which the caller
51
+ * treats as "not ours โ†’ forward to Eve". A click that IS ours but is missing its clicker
52
+ * still returns (with `clickerId` undefined) so the handler can fail soft ON the click
53
+ * rather than forward a half-understood payload to Eve (mirrors handleApprovalPayload's
54
+ * malformed-payload branch). NEVER throws.
55
+ */
56
+ export declare function parseDiscordComponentClick(raw: unknown): DiscordApprovalClick | null;
57
+ /** Test seams for the decide FEEDBACK (task #106) โ€” absent โ‡’ the live poster/dispatch. */
58
+ export interface DiscordApprovalDeps {
59
+ /** Notice poster โ€” defaults to {@link postDiscordDecisionNotice} (bot-token create-message). */
60
+ readonly postNotice?: (row: PendingActionRow, text: string) => Promise<unknown>;
61
+ readonly decisionWake?: DecisionWakeDeps;
62
+ /**
63
+ * The route's own `waitUntil` (OPE-1407). PRESENT โ‡’ the decision FEEDBACK runs off the response
64
+ * path and the interaction is answered as soon as the decision itself is made. ABSENT โ‡’ the
65
+ * feedback is awaited first, the pre-OPE-1407 shape โ€” correct, and slower by a Discord REST post
66
+ * plus a wake dispatch.
67
+ *
68
+ * ๐Ÿ”ด TWO LEGS MOVE, AND THE SECOND ONE EXECUTES. The decision FEEDBACK above, and the core's
69
+ * OPE-251 session-grant RELEASE (OPE-1709), which this file forwards into `resolveApprovalClick`
70
+ * below โ€” an "Approve for session" click decides and runs up to `MAX_SESSION_GRANT_RELEASES`
71
+ * sibling cards, so it is the heavier of the two. Do not read this hook as feedback plumbing.
72
+ *
73
+ * ๐Ÿ”ด WHAT DOES NOT MOVE IS THE RESPONSE BODY. `resolveApprovalClick` decides it, so that CALL
74
+ * cannot be deferred without answering before the answer is known โ€” see the header. Handing it the
75
+ * hook defers work it starts, never the resolution it returns.
76
+ */
77
+ readonly waitUntil?: (p: Promise<unknown>) => void;
78
+ }
79
+ /**
80
+ * Map a decide resolution to the interaction response โ€” the Discord rendering of exactly
81
+ * what handleApprovalPayload renders as Slack calls. Pure โ†’ unit-tested as a matrix.
82
+ * decided / expired โ†’ type 7: REPLACE the card with the decided render (components: [],
83
+ * so the buttons are gone and the card can't be re-clicked โ€” the CAS
84
+ * already protects correctness; this is the UX half).
85
+ * denied โ†’ type 4 ephemeral: the row stays pending (a real admin can still act
86
+ * on the same card), the clicker alone is told why.
87
+ * unknown โ†’ type 4 ephemeral: same wording as the Slack card-text fallback.
88
+ * everything else โ†’ type 6: silent ack, leave the message alone (a stale re-click /
89
+ * lost race / unregistered kind โ€” same silent-by-design branches as
90
+ * the Slack path; re-rendering could clobber the winner's card).
91
+ */
92
+ export declare function discordResponseFor(res: ApprovalClickResolution): DiscordInteractionResponse;
93
+ /**
94
+ * Handle one verified Discord interaction body. Returns null for anything that isn't an
95
+ * `oe-approval:` component click (caller forwards to Eve); otherwise owns the click and
96
+ * returns the interaction response to send. The approver policy on this surface is
97
+ * two-legged, fail-closed:
98
+ * - the config-as-code roster (clickerMayDecide inside the core โ€” org admins, or the
99
+ * FROZEN row domain's admins; a Discord clicker is matched as the PLATFORM-QUALIFIED key
100
+ * `discord/<user-id>`, which is why this call states its platform rather than letting the
101
+ * core infer one โ€” see `clickerPlatform` below), OR
102
+ * - Discord-native standing: the clicker's member.permissions carries ADMINISTRATOR
103
+ * (missing/unparseable โ†’ not an admin) AND the frozen row's scope is inside THIS guild
104
+ * (scopeWithinGuild โ€” a guild admin is authority over their own server's scopes only).
105
+ * NEVER throws โ€” an unexpected failure resolves to an ephemeral note, because the
106
+ * interactions endpoint must always answer.
107
+ */
108
+ export declare function handleDiscordApprovalInteraction(raw: unknown, deps?: DiscordApprovalDeps): Promise<DiscordInteractionResponse | null>;
109
+ //# sourceMappingURL=discord-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-flow.d.ts","sourceRoot":"","sources":["../../../agent/approvals/discord-flow.ts"],"names":[],"mappings":"AAyDA,OAAO,EAOL,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EAErB,KAAK,gBAAgB,EACtB,MAAM,4CAA4C,CAAC;AAyBpD,oFAAoF;AACpF,MAAM,MAAM,0BAA0B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjE,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,gBAAgB,EACrB,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,gBAAgB,EACrB,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,IAAI,CAAC,CAEf;AAkDD;;;0DAG0D;AAC1D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;oEACgE;IAChE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CACrC;AASD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,GAAG,IAAI,CAcpF;AAMD,0FAA0F;AAC1F,MAAM,WAAW,mBAAmB;IAClC,gGAAgG;IAChG,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IACzC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;CACpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,uBAAuB,GAAG,0BAA0B,CAuD3F;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gCAAgC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAwH/I"}
@@ -0,0 +1,375 @@
1
+ // The Discord I/O half of the admin approvals flow โ€” the sibling of flow.ts's Slack
2
+ // wiring, funnelling through the SAME platform-neutral decide core (resolveApprovalClick).
3
+ // Two entry points:
4
+ //
5
+ // postDiscordApprovalCard() โ€” the CardPoster the propose tools inject into
6
+ // requestApproval on a Discord surface: renders the
7
+ // pure card (discord-card.ts) and posts it via the
8
+ // Discord REST create-message API with the bot token.
9
+ // NEVER throws (CardPoster contract) โ€” any failure
10
+ // resolves undefined and requestApproval expires the
11
+ // pending + throws its own explanatory error.
12
+ // postDiscordJudgeLiftNotice() โ€” the JudgeLiftNoticePoster this bundle registers
13
+ // (OPE-1103): the notice for an action the armed judge
14
+ // LIFTED, carrying the one "This should not have run"
15
+ // button. Same REST transport, opposite failure
16
+ // posture โ€” the act has already run, so a failed post
17
+ // is logged and swallowed, never fatal.
18
+ // handleDiscordApprovalInteraction() โ€” called by the Discord front route
19
+ // (channels/discord-front.ts) for every verified
20
+ // interaction body. Returns null for anything that
21
+ // isn't an `oe-approval:` MESSAGE_COMPONENT click
22
+ // (the caller forwards those to Eve โ€” PING, slash
23
+ // commands, Eve's own `eve_input:` HITL buttons);
24
+ // otherwise it OWNS the click end-to-end and returns
25
+ // the interaction RESPONSE body to send. NEVER throws
26
+ // โ€” Discord's interactions endpoint must answer
27
+ // within 3s, so every failure mode is fail-soft.
28
+ //
29
+ // WHY the click path is response-shaped (vs. Slack's fire-and-forget chat.update): a Discord
30
+ // component click is answered by REPLYING to the interaction itself โ€” type 7 UPDATE_MESSAGE
31
+ // rewrites the very message the button lives on (no bot token, no extra REST call), and
32
+ // type 4 + flags 64 is an EPHEMERAL note only the clicker sees (the denial UX Slack fakes
33
+ // with a thread note).
34
+ //
35
+ // ๐Ÿ”ด THE THREE-SECOND BUDGET, AND WHAT THIS DOOR PUTS INSIDE IT (OPE-1407). Discord gives an
36
+ // interactions endpoint THREE SECONDS and does not redeliver an expired token, so an admin whose
37
+ // decision really landed can still see a red "This interaction failed" over it. This door used to
38
+ // spend the whole click inside that budget: `resolveApprovalClick` AND `deliverDecisionFeedback`,
39
+ // which posts a notice through Discord REST and dispatches a decision wake. The FEEDBACK now runs
40
+ // off the response path under the route's `waitUntil` when there is one (`DiscordApprovalDeps`).
41
+ // Nothing in the interaction response depends on it, so it is the leg that should never have been in
42
+ // the budget.
43
+ //
44
+ // ๐Ÿ”ด `resolveApprovalClick` STAYS ON THE RESPONSE PATH, and this door therefore CANNOT copy the
45
+ // grant door's ack-then-decide shape (access/discord-grant-click.ts). That door answers every case
46
+ // with one `type 4` ephemeral, so a `type 5` deferred ephemeral plus a follow-up edit is the same
47
+ // answer arriving later. This door answers with THREE different shapes chosen by the resolution:
48
+ // `type 7` REPLACES the card with the decided render and strips its buttons, `type 6` acks silently
49
+ // and leaves the card alone, `type 4` sends an ephemeral refusal and leaves the row pending. The
50
+ // shape is not known until the decision is made, and Discord fixes a response's KIND at the first
51
+ // response. Deferring as `type 5` would drop the card replacement โ€” the decided card would keep its
52
+ // buttons โ€” and deferring as `type 6` would have no way to send the refusal branches' ephemeral
53
+ // without overwriting the pending card in the channel. Closing that half needs a follow-up MESSAGE
54
+ // (`POST /webhooks/{app}/{token}` with `flags 64`) rather than the `@original` edit this package
55
+ // has, and that behaviour cannot be measured offline. It is a separate change, and OPE-1407's
56
+ // definition-of-done comment records it as open.
57
+ import { resolveApprovalClick, deliverDecisionFeedback, discordDecisionWakeTarget, postDiscordDecisionNotice, clickerFloorRefusalNotice, judgeContradictionNotice, } from "@open-everywhere/core/agent/adapter-api.js";
58
+ import { OE_DISCORD_APPROVAL_PREFIX, parseDiscordApprovalCustomId, pendingDiscordCard, decidedDiscordCard, judgeLiftNoticeDiscordCard, isGuildAdministrator, scopeWithinGuild, } from "./discord-card.js";
59
+ import { guildIdFromScopeKey, isGuildAdminViaRest } from "./discord-admin.js";
60
+ function log(msg) {
61
+ console.log(`[open-everywhere:discord-approvals] ${msg}`);
62
+ }
63
+ // Discord interaction-response wire constants. Defined locally: eve's exported
64
+ // DISCORD_INTERACTION_RESPONSE_TYPE enum has no UPDATE_MESSAGE (7) โ€” its HITL path only
65
+ // ever defers โ€” so importing it would still leave the one we need most as a bare literal.
66
+ const RESPONSE_CHANNEL_MESSAGE = 4; // CHANNEL_MESSAGE_WITH_SOURCE โ€” a fresh reply to the click
67
+ const RESPONSE_DEFERRED_UPDATE = 6; // DEFERRED_UPDATE_MESSAGE โ€” ack, leave the message as-is
68
+ const RESPONSE_UPDATE_MESSAGE = 7; // UPDATE_MESSAGE โ€” replace the message the button lives on
69
+ const EPHEMERAL_FLAG = 64; // only the clicking user sees the reply
70
+ /**
71
+ * Render + post the approval card into `row.channelId` via Discord REST create-message
72
+ * (`POST /channels/{id}/messages` with `content` + `components`). Resolves to the posted
73
+ * message's id, or undefined on ANY failure โ€” no token, an over-long custom_id (render
74
+ * throws), a non-ok status, a network error, or a response missing an id. NEVER throws,
75
+ * and the bot token is a SECRET โ€” it rides the header, never a log line (both conventions
76
+ * lifted verbatim from channels/discord-gateway.ts's discordCreateMessage).
77
+ */
78
+ export async function postDiscordApprovalCard(row, summaryLines, deps = {}) {
79
+ return postDiscordCardMessage(row, deps, "approval card", () => pendingDiscordCard(row, summaryLines));
80
+ }
81
+ /**
82
+ * THE JUDGE-LIFT NOTICE POSTER (OPE-1103) โ€” the `JudgeLiftNoticePoster` this bundle registers, and
83
+ * the Discord half of what was a Slack-only surface.
84
+ *
85
+ * It is NOT the CardPoster above with a different render, and the two must stay apart: that one
86
+ * posts the PENDING card for a LIVE row, and `requestApproval` treats its failure as fatal (it
87
+ * expires the pending and throws, because a proposal nobody can see must never linger). Here the
88
+ * frozen args have ALREADY RUN. A thrown error could not undo them and would only turn a completed
89
+ * action into a failed tool call, so this resolves `void` on every path โ€” the core's own contract
90
+ * for the hook.
91
+ *
92
+ * WHAT A FAILURE COSTS, stated plainly: the room loses its only trace of the lift and the one button
93
+ * that answers it. The audit rows (`approval.autoapproved`, `approval.judge.verdict`) still land, so
94
+ * the event stays queryable โ€” but nobody in the room is told. That is why the failure is LOGGED
95
+ * rather than swallowed silently.
96
+ */
97
+ export async function postDiscordJudgeLiftNotice(row, summaryLines, outcomeLine, deps = {}) {
98
+ await postDiscordCardMessage(row, deps, "judge-lift notice", () => judgeLiftNoticeDiscordCard(row, summaryLines, outcomeLine));
99
+ }
100
+ /**
101
+ * Render + POST one code-rendered message into `row.channelId` via Discord REST create-message
102
+ * (`POST /channels/{id}/messages` with `content` + `components`), and hand back the posted message's
103
+ * id โ€” or undefined on ANY failure: no token, no channel, an over-cap custom_id (the render throws),
104
+ * a non-ok status, a network error, or a response missing an id. NEVER throws.
105
+ *
106
+ * `render` is a THUNK, not a rendered message, so an over-cap custom_id is caught by this function's
107
+ * own try rather than at each call site. `what` names the message in the log line only.
108
+ *
109
+ * The bot token is a SECRET โ€” it rides the header, never a log line (both conventions lifted
110
+ * verbatim from channels/discord-gateway.ts's discordCreateMessage).
111
+ */
112
+ async function postDiscordCardMessage(row, deps, what, render) {
113
+ const token = deps.token ?? process.env.DISCORD_BOT_TOKEN;
114
+ if (!token) {
115
+ log(`DISCORD_BOT_TOKEN is not set โ€” cannot post the ${what} to channel ${row.channelId}`);
116
+ return undefined;
117
+ }
118
+ if (!row.channelId) {
119
+ log(`no channel id on the pending row โ€” nothing to post the ${what} to`);
120
+ return undefined;
121
+ }
122
+ const doFetch = deps.fetchFn ?? fetch;
123
+ try {
124
+ const card = render(); // throws on an over-cap custom_id โ€” caught below
125
+ const res = await doFetch(`https://discord.com/api/v10/channels/${row.channelId}/messages`, {
126
+ method: "POST",
127
+ headers: { authorization: `Bot ${token}`, "content-type": "application/json" },
128
+ body: JSON.stringify({ content: card.content, components: card.components }),
129
+ });
130
+ if (!res.ok) {
131
+ log(`Discord create-message failed (${res.status}) posting the ${what} to channel ${row.channelId}`);
132
+ return undefined;
133
+ }
134
+ const body = (await res.json());
135
+ const id = body && typeof body === "object" ? body["id"] : undefined;
136
+ return typeof id === "string" && id.length > 0 ? id : undefined;
137
+ }
138
+ catch (err) {
139
+ log(`Discord ${what} post error for channel ${row.channelId}: ${err?.message ?? String(err)}`);
140
+ return undefined;
141
+ }
142
+ }
143
+ function isObject(v) {
144
+ return typeof v === "object" && v !== null;
145
+ }
146
+ function str(v) {
147
+ return typeof v === "string" && v.length > 0 ? v : undefined;
148
+ }
149
+ /**
150
+ * Probe a raw interaction body for an `oe-approval:` MESSAGE_COMPONENT click. Returns null
151
+ * for anything else โ€” wrong type, no custom_id, or a foreign prefix โ€” which the caller
152
+ * treats as "not ours โ†’ forward to Eve". A click that IS ours but is missing its clicker
153
+ * still returns (with `clickerId` undefined) so the handler can fail soft ON the click
154
+ * rather than forward a half-understood payload to Eve (mirrors handleApprovalPayload's
155
+ * malformed-payload branch). NEVER throws.
156
+ */
157
+ export function parseDiscordComponentClick(raw) {
158
+ if (!isObject(raw) || raw["type"] !== 3)
159
+ return null; // not a MESSAGE_COMPONENT interaction
160
+ const data = isObject(raw["data"]) ? raw["data"] : undefined;
161
+ const customId = data ? str(data["custom_id"]) : undefined;
162
+ if (!customId || !customId.startsWith(OE_DISCORD_APPROVAL_PREFIX))
163
+ return null; // not ours โ€” forward
164
+ const member = isObject(raw["member"]) ? raw["member"] : undefined;
165
+ const memberUser = member && isObject(member["user"]) ? member["user"] : undefined;
166
+ const user = isObject(raw["user"]) ? raw["user"] : undefined;
167
+ return {
168
+ customId,
169
+ clickerId: (memberUser ? str(memberUser["id"]) : undefined) ?? (user ? str(user["id"]) : undefined),
170
+ guildId: str(raw["guild_id"]),
171
+ memberPermissions: member ? str(member["permissions"]) : undefined,
172
+ };
173
+ }
174
+ function ephemeral(content) {
175
+ return { type: RESPONSE_CHANNEL_MESSAGE, data: { content, flags: EPHEMERAL_FLAG } };
176
+ }
177
+ /**
178
+ * Map a decide resolution to the interaction response โ€” the Discord rendering of exactly
179
+ * what handleApprovalPayload renders as Slack calls. Pure โ†’ unit-tested as a matrix.
180
+ * decided / expired โ†’ type 7: REPLACE the card with the decided render (components: [],
181
+ * so the buttons are gone and the card can't be re-clicked โ€” the CAS
182
+ * already protects correctness; this is the UX half).
183
+ * denied โ†’ type 4 ephemeral: the row stays pending (a real admin can still act
184
+ * on the same card), the clicker alone is told why.
185
+ * unknown โ†’ type 4 ephemeral: same wording as the Slack card-text fallback.
186
+ * everything else โ†’ type 6: silent ack, leave the message alone (a stale re-click /
187
+ * lost race / unregistered kind โ€” same silent-by-design branches as
188
+ * the Slack path; re-rendering could clobber the winner's card).
189
+ */
190
+ export function discordResponseFor(res) {
191
+ switch (res.outcome) {
192
+ case "unknown":
193
+ return ephemeral("This approval request is no longer known (it may have already been decided or has aged out).");
194
+ case "not-pending":
195
+ case "expired-race":
196
+ case "unregistered-kind":
197
+ case "lost-race":
198
+ return { type: RESPONSE_DEFERRED_UPDATE };
199
+ case "expired": {
200
+ const card = decidedDiscordCard(res.row, res.summaryLines, "expired");
201
+ return { type: RESPONSE_UPDATE_MESSAGE, data: { content: card.content, components: card.components } };
202
+ }
203
+ case "denied":
204
+ return ephemeral("Only a domain/org admin โ€” or a server administrator, for this server's own scopes โ€” can decide this request.");
205
+ case "clicker-denied":
206
+ // The clicker gate (OPE-185, core approvals/clicker-floor.ts) โ€” same UX as `denied`: an
207
+ // ephemeral to the clicker alone, card untouched, row still pending.
208
+ //
209
+ // โš ๏ธ NOT REACHABLE FROM THIS SURFACE TODAY, and it is honest to say so rather than let the
210
+ // branch imply Discord enforces something it doesn't. The gate runs only for a caller that
211
+ // supplies a `clickContext`, and `handleDiscordApprovalInteraction` below deliberately
212
+ // supplies none: Discord has no member supplier (the privileged GUILD_MEMBERS intent, and no
213
+ // guest concept to map onto `hasGuest` even then), so every leg would be inert anyway and
214
+ // Discord clicks stay byte-identical to pre-OPE-185. The case exists because the resolution
215
+ // union is SHARED across surfaces, and this switch must stay exhaustive over it.
216
+ return ephemeral(clickerFloorRefusalNotice(res.reason));
217
+ case "halted":
218
+ // The break-glass halt arrow at the click (OPE-1604, core approvals/flow.ts) โ€” same UX as
219
+ // `denied`: an ephemeral to the clicker alone, message untouched, row still pending, so the
220
+ // same card works again once an admin runs `resume` โ€” until the TTL sweep reaps it, which a
221
+ // halt does not stop (see the `halted` resolution's own doc). REACHABLE from this surface: unlike the
222
+ // clicker gate above, this arrow needs no `clickContext` and runs for every click path.
223
+ // `res.notice` is core's ONE halt renderer (access/halt.ts `haltRefusalMessage`), so a
224
+ // Discord room and a Slack room read the same sentence โ€” including the fail-closed clause
225
+ // when the halt state could not be read at all.
226
+ return ephemeral(res.notice);
227
+ // NB: this renderer stays PURE โ€” the decide/expire decision FEEDBACK (fresh notice beside the
228
+ // card + window wake, task #106 โ€” an UPDATE_MESSAGE edit is invisible to backscroll, exactly
229
+ // like Slack's chat.update) is handleDiscordApprovalInteraction's side-effect leg, not a
230
+ // response concern.
231
+ case "contradicted":
232
+ // The judge-contradiction overrule (OPE-965; reachable on this surface since OPE-1103, when
233
+ // `postDiscordJudgeLiftNotice` above started rendering the button). An ephemeral to the
234
+ // clicker, message UNTOUCHED โ€” deliberately unlike `decided`, which strips its buttons: the
235
+ // notice reports an act that really did happen and stays in the room as its trace, and a
236
+ // second click is a defined no-op the copy answers ("already flagged, it still counts once").
237
+ // `judgeContradictionNotice` is core's ONE renderer for all four outcomes, so this room and a
238
+ // Slack room are told the same thing about the same overrule.
239
+ return ephemeral(judgeContradictionNotice(res.contradiction));
240
+ case "decided": {
241
+ const card = decidedDiscordCard(res.row, res.summaryLines, res.outcomeLine);
242
+ return { type: RESPONSE_UPDATE_MESSAGE, data: { content: card.content, components: card.components } };
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * Handle one verified Discord interaction body. Returns null for anything that isn't an
248
+ * `oe-approval:` component click (caller forwards to Eve); otherwise owns the click and
249
+ * returns the interaction response to send. The approver policy on this surface is
250
+ * two-legged, fail-closed:
251
+ * - the config-as-code roster (clickerMayDecide inside the core โ€” org admins, or the
252
+ * FROZEN row domain's admins; a Discord clicker is matched as the PLATFORM-QUALIFIED key
253
+ * `discord/<user-id>`, which is why this call states its platform rather than letting the
254
+ * core infer one โ€” see `clickerPlatform` below), OR
255
+ * - Discord-native standing: the clicker's member.permissions carries ADMINISTRATOR
256
+ * (missing/unparseable โ†’ not an admin) AND the frozen row's scope is inside THIS guild
257
+ * (scopeWithinGuild โ€” a guild admin is authority over their own server's scopes only).
258
+ * NEVER throws โ€” an unexpected failure resolves to an ephemeral note, because the
259
+ * interactions endpoint must always answer.
260
+ */
261
+ export async function handleDiscordApprovalInteraction(raw, deps = {}) {
262
+ const click = parseDiscordComponentClick(raw);
263
+ if (!click)
264
+ return null; // not ours โ€” the front route forwards to Eve
265
+ try {
266
+ const parsed = parseDiscordApprovalCustomId(click.customId);
267
+ if (!parsed)
268
+ return null; // ours-prefixed but unparseable โ†’ forward (mirrors parseApprovalAction's null)
269
+ if (!click.clickerId) {
270
+ // It WAS ours; fail soft on the click rather than forward a half-understood payload.
271
+ console.error(`[open-everywhere:discord-approvals] component click "${click.customId}" carries no user id โ€” ignoring the click`);
272
+ return ephemeral("Couldn't read who clicked โ€” this decision was not applied.");
273
+ }
274
+ const clickerId = click.clickerId;
275
+ const res = await resolveApprovalClick({
276
+ org: parsed.org,
277
+ id: parsed.id,
278
+ decision: parsed.decision,
279
+ clickerId,
280
+ // The clicker id is a Discord snowflake, so the roster arm matches it as
281
+ // `discord/<id>` (identity/principal-key.ts). Stated here rather than inferred: this
282
+ // surface passes no `clickContext`, and a DM-context click has no room to read a platform
283
+ // off at all โ€” the leg that most needs the roster is exactly the one with no other source.
284
+ clickerPlatform: "discord",
285
+ // ๐Ÿ”ด THE ROUTE'S `waitUntil` REACHES THE CORE TOO (OPE-1709). The decision FEEDBACK below
286
+ // already rides it; this hands the core the same hook for the OPE-251 session-grant RELEASE,
287
+ // which decides and EXECUTES the sibling cards the grant covers. Both legs are work nothing in
288
+ // the interaction response depends on, so both belong off the three-second budget. Absent โ‡’
289
+ // the core awaits the release, the same "no waitUntil โ‡’ no defer" rule this file's feedback
290
+ // leg states below.
291
+ ...(deps.waitUntil ? { waitUntil: deps.waitUntil } : {}),
292
+ // Discord-native admin leg โ€” "administrator of the guild the FROZEN row's scope lives
293
+ // in", two evidence paths by where the click happened:
294
+ // in a guild โ†’ the payload's member.permissions is authoritative for THAT guild, so
295
+ // it must also BE the row's guild (scopeWithinGuild) โ€” a guild-A admin clicking a
296
+ // card in guild A for a row aimed at guild B proves nothing about B.
297
+ // in a DM โ†’ no member payload exists at all, so ask Discord directly: REST-check
298
+ // the clicker's standing in the row's own guild (isGuildAdminViaRest, fail-closed;
299
+ // a non-guild scope โ€” Slack, a DM bucket โ€” resolves no guild and denies). This is
300
+ // what makes the over-budget escape hatch work: the โ›”'d channel drops every turn,
301
+ // the proposal happens in a DM, and the card lands there โ€” the admin must be able
302
+ // to decide it where it is.
303
+ platformAdmin: async (row) => {
304
+ if (click.guildId)
305
+ return isGuildAdministrator(click.memberPermissions) && scopeWithinGuild(row.scopeKey, click.guildId);
306
+ const rowGuild = guildIdFromScopeKey(row.scopeKey);
307
+ return rowGuild ? isGuildAdminViaRest(rowGuild, clickerId) : false;
308
+ },
309
+ });
310
+ // Decision FEEDBACK (task #106 โ€” Slack click-path parity): a fresh notice beside the card (the
311
+ // UPDATE_MESSAGE response is an EDIT, invisible to backscroll) + the cross-thread window wake.
312
+ // The wake's guild id comes from the SIGNED interaction (Slack's team.id analogue โ€” the frozen
313
+ // scopeKey can name a different workspace under an explicit-scope override); a DM-context
314
+ // interaction has none โ†’ no wake, fail-soft, the notice still posts beside the card. No transport
315
+ // failure throws out of it; the egress spoof's Vercel refusal can (OPE-1132). On the AWAITED path
316
+ // below that refusal reaches the caller's own catch, which answers the interaction anyway rather
317
+ // than leaving Discord to retry. On the DEFERRED path the response has already gone, so the catch
318
+ // around the deferred promise logs it instead and the door's answer is unaffected.
319
+ //
320
+ // ๐Ÿ”ด IT RUNS OFF THE RESPONSE PATH WHEN THE ROUTE GAVE US A `waitUntil` (OPE-1407). It is a
321
+ // Discord REST post plus a wake dispatch, and nothing in the interaction response depends on it,
322
+ // so it is exactly the leg that should not sit inside a three-second budget. See the header.
323
+ const feedback = () => {
324
+ if (res.outcome === "expired") {
325
+ return deliverDecisionFeedback({
326
+ row: res.row,
327
+ summaryLines: res.summaryLines,
328
+ outcome: "expired",
329
+ outcomeLine: "expired",
330
+ executed: "none",
331
+ target: discordDecisionWakeTarget(res.row, click.guildId),
332
+ postNotice: (text) => (deps.postNotice ?? postDiscordDecisionNotice)(res.row, text),
333
+ }, deps.decisionWake);
334
+ }
335
+ if (res.outcome === "decided") {
336
+ return deliverDecisionFeedback({
337
+ row: res.row,
338
+ summaryLines: res.summaryLines,
339
+ outcome: res.row.status,
340
+ outcomeLine: res.outcomeLine,
341
+ executed: res.executed,
342
+ clickerId,
343
+ target: discordDecisionWakeTarget(res.row, click.guildId),
344
+ postNotice: (text) => (deps.postNotice ?? postDiscordDecisionNotice)(res.row, text),
345
+ }, deps.decisionWake);
346
+ }
347
+ return undefined;
348
+ };
349
+ const pending = feedback();
350
+ if (pending !== undefined) {
351
+ if (deps.waitUntil === undefined) {
352
+ // No place to run it past the reply: await, the pre-OPE-1407 shape. On a serverless host a
353
+ // promise left running after the response is frozen with the invocation, so a defer with
354
+ // nowhere to live would DROP the notice โ€” strictly worse than answering slowly.
355
+ await pending;
356
+ }
357
+ else {
358
+ // TOTAL. This runs after the response has gone; a throw here reaches the host's unhandled
359
+ // rejection handler and tells nobody anything. The decision itself has already landed and is
360
+ // audited, so what a failure costs is the notice and the wake โ€” logged, never fatal.
361
+ deps.waitUntil(pending.catch((err) => {
362
+ log(`deferred decision feedback failed for "${click.customId}": ${err?.message ?? String(err)}`);
363
+ }));
364
+ }
365
+ }
366
+ return discordResponseFor(res);
367
+ }
368
+ catch (err) {
369
+ // Genuinely unexpected (store outage, โ€ฆ) after we'd established this WAS our click โ€”
370
+ // fail soft with an honest ephemeral note; never throw out of the interactions path.
371
+ console.error(`[open-everywhere:discord-approvals] click handling failed softly for "${click.customId}": ${String(err)}`);
372
+ return ephemeral("Something went wrong handling this click โ€” the decision was not applied.");
373
+ }
374
+ }
375
+ //# sourceMappingURL=discord-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-flow.js","sourceRoot":"","sources":["../../../agent/approvals/discord-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ScopeLabelResolver } from "@open-everywhere/core/agent/adapter-api.js";
2
+ export declare const discordScopeLabelResolver: ScopeLabelResolver;
3
+ //# sourceMappingURL=discord-scope-label.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-scope-label.d.ts","sourceRoot":"","sources":["../../../agent/approvals/discord-scope-label.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4CAA4C,CAAC;AAGrF,eAAO,MAAM,yBAAyB,EAAE,kBAYvC,CAAC"}
@@ -0,0 +1,27 @@
1
+ // The Discord scope-label resolver โ€” EXTRACTED from core approvals/scope-label.ts (OPE-114 stage 4).
2
+ // Friendly DISPLAY labels for an approval-card scope key, registered via registerScopeLabelResolver
3
+ // from registerDiscordProvider(). DISPLAY ONLY, never an access/execution input; fail-open (any
4
+ // lookup failure โ†’ undefined โ‡’ the card renders the raw key). Uses the shared discord-channel-info
5
+ // `GET /channels|guilds/{id}` fetch (moved into this bundle).
6
+ //
7
+ // discord/<guild> โ†’ `the <guild name> server`
8
+ // discord/<guild>/<channel> โ†’ `#<channel name>`
9
+ // discord/<guild>/<thread> โ†’ `#<parent name> ยท thread "<thread name>"` (anchor on the parent โ€”
10
+ // an auto-thread's name is derived from the triggering message, gibberish as a scope label).
11
+ import { fetchDiscordInfo } from "../channels/discord-channel-info.js";
12
+ export const discordScopeLabelResolver = async ({ workspace, surface }) => {
13
+ if (surface) {
14
+ const info = await fetchDiscordInfo("channels", surface);
15
+ if (!info)
16
+ return undefined;
17
+ if (info.parentId) {
18
+ const parent = await fetchDiscordInfo("channels", info.parentId);
19
+ if (parent?.name)
20
+ return `#${parent.name} ยท thread "${info.name}"`;
21
+ }
22
+ return info.isThread ? `thread "${info.name}"` : `#${info.name}`;
23
+ }
24
+ const info = await fetchDiscordInfo("guilds", workspace);
25
+ return info?.name ? `the ${info.name} server` : undefined;
26
+ };
27
+ //# sourceMappingURL=discord-scope-label.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-scope-label.js","sourceRoot":"","sources":["../../../agent/approvals/discord-scope-label.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,oGAAoG;AACpG,gGAAgG;AAChG,mGAAmG;AACnG,8DAA8D;AAC9D,EAAE;AACF,2DAA2D;AAC3D,mDAAmD;AACnD,mGAAmG;AACnG,iGAAiG;AAGjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAEvE,MAAM,CAAC,MAAM,yBAAyB,GAAuB,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE;IAC5F,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QAC5B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YACjE,IAAI,MAAM,EAAE,IAAI;gBAAE,OAAO,IAAI,MAAM,CAAC,IAAI,cAAc,IAAI,CAAC,IAAI,GAAG,CAAC;QACrE,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;IACnE,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACzD,OAAO,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC,CAAC"}