@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,120 @@
1
+ /** Discord's hard per-message character cap — the ceiling {@link splitForDiscord} splits to and the
2
+ * one {@link discordEditMessage} refuses past, stated once so the two can never disagree. */
3
+ export declare const DISCORD_MESSAGE_MAX = 2000;
4
+ export declare function splitForDiscord(text: string, max?: number): string[];
5
+ /**
6
+ * Post a message to a Discord channel — `POST /channels/{id}/messages`. Returns the FIRST
7
+ * message's id (the audit/output anchor). Throws (never a null id) on a missing token, a non-ok
8
+ * status, or a response missing an id — the tool relays that to the model.
9
+ *
10
+ * OVER-CAP SPLIT (OPE-41): `content` longer than Discord's 2000-char limit is split via
11
+ * {@link splitForDiscord} and posted as SEVERAL messages in order, so the reply-tool path
12
+ * (reply-post.ts `dispatchReply`, which hands us the raw text) no longer hard-fails on a long
13
+ * reply the way a single over-cap POST would (400). A reply ANCHOR (`opts.replyToMessageId`)
14
+ * attaches only to the FIRST part — the continuation parts thread naturally after it. A
15
+ * within-cap `content` is one POST with the byte-identical body it always sent.
16
+ *
17
+ * `opts.replyToMessageId` anchors the (first) post as a Discord REPLY to that message
18
+ * (message_reference; fail_if_not_exists=false so a deleted anchor degrades to a plain post
19
+ * rather than a 400) — used by the single-window reply arm so the answer visibly attaches to
20
+ * the triggering mention.
21
+ */
22
+ export declare function discordPostMessage(channelId: string, content: string, opts?: {
23
+ replyToMessageId?: string;
24
+ }): Promise<string>;
25
+ /**
26
+ * Open (or reuse) the 1:1 DM channel with `userId` — `POST /users/@me/channels` with a
27
+ * `recipient_id` (OPE-623). Returns the DM channel id, which every ordinary channel call above then
28
+ * addresses; Discord itself is idempotent here, so a second call for the same recipient returns the
29
+ * SAME channel rather than a second one.
30
+ *
31
+ * WHO CAN BE REACHED, AND WHAT A FAILURE MEANS. Discord delivers a bot DM only when the recipient
32
+ * shares a server with the bot AND has not turned off direct messages from server members. Neither
33
+ * is knowable in advance, so the refusal arrives as a 403 on the POST — an honest "this person
34
+ * cannot be DM'd", not a bug. It THROWS rather than resolving null, because every caller
35
+ * (`deliverDirectMessage`) has to be able to tell a delivered consent ask from an undelivered one:
36
+ * treating "could not open the channel" as a successful send would tell an initiator that somebody
37
+ * was asked to confirm when nobody was.
38
+ *
39
+ * Token handling matches every call in this module: the bot token rides only the header, and no
40
+ * response body ever reaches an error string.
41
+ */
42
+ export declare function discordOpenDmChannel(userId: string): Promise<string>;
43
+ /** One button on a DM, in Discord's own shape. `custom_id` is the CALLER's opaque routing key. */
44
+ export interface DiscordDmButton {
45
+ readonly customId: string;
46
+ readonly label: string;
47
+ readonly intent: "affirm" | "reject";
48
+ }
49
+ /**
50
+ * Post `content` to a DM channel with a row of BUTTONS under it (OPE-622) — one
51
+ * `POST /channels/{id}/messages` carrying a components ACTION_ROW.
52
+ *
53
+ * 🔴 NO SPLIT, AND THAT IS DELIBERATE. `discordPostMessage` splits an over-cap reply into several
54
+ * messages; buttons cannot be split, and a row attached to the wrong part would sit under a
55
+ * fragment. So this refuses past Discord's 2000-character cap rather than truncating or fanning out,
56
+ * and both callers post core-owned copy: the identity consent ask, and the bridged approval card
57
+ * (channels/discord-post-arm.ts), whose own caller falls back to the splitting path past the cap.
58
+ *
59
+ * `opts.replyToMessageId` hangs the message under the one it answers, through the same
60
+ * `message_reference` the plain post path uses and with the same `fail_if_not_exists: false` degrade
61
+ * when the trigger is gone (OPE-1720 — a card posted onto a unified origin must land where the
62
+ * conversation is looking, exactly as its prose sibling does). ABSENT ⇒ a plain channel post.
63
+ *
64
+ * The bot token rides only the header, like every call in this module.
65
+ */
66
+ export declare function discordPostMessageWithButtons(channelId: string, content: string, buttons: readonly DiscordDmButton[], opts?: {
67
+ readonly replyToMessageId?: string;
68
+ }): Promise<string>;
69
+ /**
70
+ * Rewrite one already-posted message — `PATCH /channels/{id}/messages/{mid}` (OPE-793). Discord
71
+ * renders an "(edited)" marker, so the change is visible in the room.
72
+ *
73
+ * 🔴 NO SPLIT, AND THAT IS A REAL LIMIT RATHER THAN AN OMISSION. A message is ONE message: an
74
+ * over-cap edit cannot become several the way an over-cap POST can, so text past Discord's 2000-char
75
+ * cap is REFUSED here with an honest error the model can act on. Refusing beats truncating (silent
76
+ * data loss) and beats posting continuation messages (an edit must add nothing to the room).
77
+ *
78
+ * Ownership is decided upstream against the own-post ledger (core's channels/reply-edit.ts); Discord
79
+ * refusing to edit another author's message is the backstop under it. Token handling mirrors
80
+ * {@link discordPostMessage}: the bot token rides only the header and no response body ever reaches
81
+ * an error string.
82
+ */
83
+ export declare function discordEditMessage(channelId: string, messageId: string, content: string): Promise<void>;
84
+ /**
85
+ * REACT (OPE-987): `PUT /channels/{id}/messages/{mid}/reactions/{emoji}/@me` under the bot token —
86
+ * the SAME primitive `discordMessageOps.setReaction` calls for the host-placed ✋ glyph, but this one
87
+ * THROWS on failure (unlike that primitive, which is deliberately fail-soft — see its own header):
88
+ * a model-invoked `react` is a verb the model gets an honest answer from, not decoration beside work
89
+ * that is already running.
90
+ *
91
+ * An unmapped emoji name refuses DEFINITIVE (`markNotApplied`, OPE-825) before any byte leaves this
92
+ * host — the same shape GitHub's fixed content enum refuses with. A rejected PUT carries the HTTP
93
+ * status on the thrown error (token-free), so `platformFailureClass` can classify it without any
94
+ * string matching.
95
+ */
96
+ export declare function discordAddReaction(channelId: string, messageId: string, emojiName: string): Promise<void>;
97
+ /**
98
+ * READ-ONLY: the id of the thread that already hangs off `messageId`, or null when there is none.
99
+ *
100
+ * 🔴 IT NEVER CREATES A THREAD, and that is the whole reason it exists next to
101
+ * {@link discordThreadForMessage}. The EDIT path needs the same auto-thread the POST path opened
102
+ * (OPE-798), but at edit time a create would open a NEW empty thread that cannot hold the message
103
+ * being edited — a visible side effect in the room, and still a 404. So the edit path asks only
104
+ * "where did the reply land", and degrades to the base channel when the answer is "nowhere".
105
+ *
106
+ * Fail-soft to null on ANY failure (deleted trigger, missing permission, rate limit), matching the
107
+ * create-or-find sibling. Token handling is identical: the bot token rides only the header.
108
+ */
109
+ export declare function discordExistingThreadForMessage(channelId: string, messageId: string): Promise<string | null>;
110
+ /**
111
+ * Open (or find) the thread hanging off a message — the unified reply arm's auto-thread leg.
112
+ * `POST /channels/{id}/messages/{mid}/threads`; when Discord answers THREAD_ALREADY_CREATED
113
+ * (a second reply to the same wake, or a crash-retry), falls back to reading the message's
114
+ * existing `thread.id`. Returns null on ANY failure (missing permission, deleted trigger,
115
+ * rate limit) — the caller degrades to the plain in-channel reply, never blocks the post.
116
+ * Token handling mirrors discordPostMessage: the bot token rides only the header, and no
117
+ * response body ever reaches an error string or return value.
118
+ */
119
+ export declare function discordThreadForMessage(channelId: string, messageId: string, name: string): Promise<string | null>;
120
+ //# sourceMappingURL=discord-post.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-post.d.ts","sourceRoot":"","sources":["../../../agent/channels/discord-post.ts"],"names":[],"mappings":"AAkBA;8FAC8F;AAC9F,eAAO,MAAM,mBAAmB,OAAO,CAAC;AA6BxC,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAsB,GAAG,MAAM,EAAE,CAUjF;AA+DD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAelI;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkB1E;AAUD,kGAAkG;AAClG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,6BAA6B,CACjD,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,eAAe,EAAE,EACnC,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,MAAM,CAAC,CAmCjB;AA4BD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB7G;AA4CD;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB/G;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,+BAA+B,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWlH;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAoBxH"}
@@ -0,0 +1,426 @@
1
+ // Discord REST post for the `reply` tool (tools/reply.ts). This is the reply feature's own
2
+ // Discord write seam — the parallel of slack-read.ts for the read tools, and of
3
+ // slack-events.ts's `slackForm` for the Slack write path. It is kept separate from
4
+ // channels/discord-gateway.ts (which has its OWN private create-message used by the completion
5
+ // post-back) so the tools don't pull the whole gateway channel module into their import graph;
6
+ // the auth pattern here mirrors that module's exactly. {@link discordEditMessage} is the same seam's
7
+ // edit primitive, for the `edit_reply` tool (OPE-793).
8
+ //
9
+ // `Bot <token>` is Discord's scheme; the bot token is a SECRET — it rides only in the header,
10
+ // NEVER in a log line, error string, or return value. Calls throw a clean, token-free error on
11
+ // failure so the tool surfaces an honest result to the model (unlike the gateway's fail-silent
12
+ // post-back, a tool must report whether the post actually landed).
13
+ import { markNotApplied } from "@open-everywhere/core/agent/adapter-api.js";
14
+ import { discordFetch } from "./discord-rest.js";
15
+ const API = "https://discord.com/api/v10";
16
+ /** Discord's hard per-message character cap — the ceiling {@link splitForDiscord} splits to and the
17
+ * one {@link discordEditMessage} refuses past, stated once so the two can never disagree. */
18
+ export const DISCORD_MESSAGE_MAX = 2000;
19
+ function botHeaders(token) {
20
+ return { authorization: `Bot ${token}`, "content-type": "application/json" };
21
+ }
22
+ function botToken() {
23
+ const token = process.env.DISCORD_BOT_TOKEN;
24
+ if (!token) {
25
+ throw new Error("DISCORD_BOT_TOKEN is not set — the reply tool needs the bot token to post to Discord.");
26
+ }
27
+ return token;
28
+ }
29
+ // Discord caps a message at 2000 chars. `splitForDiscord` breaks an over-cap reply into parts
30
+ // that each fit, honouring three rules the reply-tool family needs (OPE-41):
31
+ // 1. BOUNDARY-PREFERRING — cut at a paragraph/line break when one is in reach, else a sentence
32
+ // end, else a word boundary, and only hard-cut mid-word as a last resort. So a split lands
33
+ // between thoughts, not inside one.
34
+ // 2. FENCE-SAFE — never leave a ```code fence dangling across the seam. When a cut falls inside
35
+ // an open fence, the part is CLOSED with ``` and the next part RE-OPENS the same fence (with
36
+ // its language), so every part renders as valid markdown on its own.
37
+ // 3. NUMBERED ONLY WHEN >2 — a 1- or 2-part reply reads fine without ceremony; 3+ parts get a
38
+ // compact `[i/N]` header so the reader knows the order and that more is coming.
39
+ // Guarantees forward progress and loses no CONTENT (a single boundary whitespace char is dropped
40
+ // at each seam, exactly as the old splitter did; the fence markers + `[i/N]` headers are the only
41
+ // added text). Lives HERE (the leaf write seam) so the unified reply arm (unified-reply.ts) can
42
+ // split without importing the whole gateway channel module; discord-gateway.ts re-exports it, so
43
+ // every existing importer keeps compiling untouched. Pure — exported for the test oracle.
44
+ export function splitForDiscord(text, max = DISCORD_MESSAGE_MAX) {
45
+ if (text.length <= max)
46
+ return [text];
47
+ // Pass 1: split with the full budget. If it fits in ≤2 parts, no numbering ceremony.
48
+ const bare = splitRespectingFences(text, max);
49
+ if (bare.length <= 2)
50
+ return bare;
51
+ // Pass 2: 3+ parts → number them. Re-split with a reduced budget so the `[i/N]\n` header still
52
+ // fits under the cap, then prefix the real header (N is known only after the re-split).
53
+ const numbered = splitRespectingFences(text, max - NUMBER_HEADER_RESERVE);
54
+ const n = numbered.length;
55
+ return numbered.map((part, i) => `[${i + 1}/${n}]\n${part}`);
56
+ }
57
+ // Reserve for the `[i/N]\n` header — `[9999/9999]\n` is 12 chars, more than any realistic reply.
58
+ const NUMBER_HEADER_RESERVE = 12;
59
+ const FENCE_CLOSE = "\n```";
60
+ const FENCE_LINE = /^\s*```/;
61
+ /** The fence opener (e.g. "```ts") still open after walking `body` from `start`, or null. Markdown
62
+ * fences toggle (they don't nest), so a ``` line either opens — capturing its language — or closes. */
63
+ function fenceStateAfter(start, body) {
64
+ let opener = start;
65
+ for (const line of body.split("\n")) {
66
+ if (FENCE_LINE.test(line))
67
+ opener = opener === null ? line.trim() : null;
68
+ }
69
+ return opener;
70
+ }
71
+ /** Choose the cut index in (from, hardEnd], preferring a line break, then a sentence end, then a
72
+ * word boundary, then a hard cut. `drop` is how many boundary whitespace chars to skip after the
73
+ * cut so the seam doesn't duplicate them (a newline/space that separated the two parts). */
74
+ function chooseCut(text, from, hardEnd) {
75
+ if (hardEnd >= text.length)
76
+ return { end: text.length, drop: 0 };
77
+ const nl = text.lastIndexOf("\n", hardEnd);
78
+ if (nl > from)
79
+ return { end: nl, drop: 1 }; // line/paragraph boundary (blank lines are newlines)
80
+ // Sentence end: the last ". "/"! "/"? " whose terminator sits within (from, hardEnd].
81
+ let sentence = -1;
82
+ for (const sep of [". ", "! ", "? "]) {
83
+ const i = text.lastIndexOf(sep, hardEnd - 1);
84
+ if (i > from && i + 1 < hardEnd)
85
+ sentence = Math.max(sentence, i + 1); // keep the punctuation
86
+ }
87
+ if (sentence > from)
88
+ return { end: sentence, drop: 1 };
89
+ const sp = text.lastIndexOf(" ", hardEnd);
90
+ if (sp > from)
91
+ return { end: sp, drop: 1 };
92
+ return { end: hardEnd, drop: 0 }; // no boundary in reach → hard cut mid-word
93
+ }
94
+ /** Split `text` into parts whose RENDERED length (fence reopen prefix + body + fence close suffix)
95
+ * never exceeds `renderBudget`, re-opening any fence left open across a seam. */
96
+ function splitRespectingFences(text, renderBudget) {
97
+ const parts = [];
98
+ let carried = null; // fence opener carried INTO the current part
99
+ let pos = 0;
100
+ while (pos < text.length) {
101
+ const reopen = carried !== null ? `${carried}\n` : "";
102
+ const avail = Math.max(1, renderBudget - reopen.length);
103
+ let cut = chooseCut(text, pos, Math.min(pos + avail, text.length));
104
+ let body = text.slice(pos, cut.end);
105
+ let endOpener = fenceStateAfter(carried, body);
106
+ let suffix = endOpener !== null ? FENCE_CLOSE : "";
107
+ // A still-open fence adds a closing ``` — if that tips the part over budget, recut shorter.
108
+ if (reopen.length + body.length + suffix.length > renderBudget && body.length > 1) {
109
+ cut = chooseCut(text, pos, Math.min(pos + avail - suffix.length, text.length));
110
+ body = text.slice(pos, cut.end);
111
+ endOpener = fenceStateAfter(carried, body);
112
+ suffix = endOpener !== null ? FENCE_CLOSE : "";
113
+ }
114
+ parts.push(`${reopen}${body}${suffix}`);
115
+ carried = endOpener;
116
+ pos = cut.end + cut.drop;
117
+ }
118
+ return parts;
119
+ }
120
+ /**
121
+ * Post a message to a Discord channel — `POST /channels/{id}/messages`. Returns the FIRST
122
+ * message's id (the audit/output anchor). Throws (never a null id) on a missing token, a non-ok
123
+ * status, or a response missing an id — the tool relays that to the model.
124
+ *
125
+ * OVER-CAP SPLIT (OPE-41): `content` longer than Discord's 2000-char limit is split via
126
+ * {@link splitForDiscord} and posted as SEVERAL messages in order, so the reply-tool path
127
+ * (reply-post.ts `dispatchReply`, which hands us the raw text) no longer hard-fails on a long
128
+ * reply the way a single over-cap POST would (400). A reply ANCHOR (`opts.replyToMessageId`)
129
+ * attaches only to the FIRST part — the continuation parts thread naturally after it. A
130
+ * within-cap `content` is one POST with the byte-identical body it always sent.
131
+ *
132
+ * `opts.replyToMessageId` anchors the (first) post as a Discord REPLY to that message
133
+ * (message_reference; fail_if_not_exists=false so a deleted anchor degrades to a plain post
134
+ * rather than a 400) — used by the single-window reply arm so the answer visibly attaches to
135
+ * the triggering mention.
136
+ */
137
+ export async function discordPostMessage(channelId, content, opts) {
138
+ const parts = splitForDiscord(content);
139
+ let firstId;
140
+ for (const part of parts) {
141
+ if (!part.trim())
142
+ continue; // never POST an invisible empty part (Discord 400s on empty content)
143
+ // Anchor the reply only on the FIRST posted part; continuations follow it in the channel.
144
+ const id = await discordPostOne(channelId, part, firstId === undefined ? opts?.replyToMessageId : undefined);
145
+ firstId ??= id;
146
+ }
147
+ if (firstId === undefined) {
148
+ // DEFINITIVE (OPE-821): every part was blank, so no POST was ever issued and the room is
149
+ // unchanged. The post spine may release its dedupe claim on this.
150
+ throw markNotApplied(new Error(`Discord create-message had no content to post for channel ${channelId}`));
151
+ }
152
+ return firstId;
153
+ }
154
+ /**
155
+ * Open (or reuse) the 1:1 DM channel with `userId` — `POST /users/@me/channels` with a
156
+ * `recipient_id` (OPE-623). Returns the DM channel id, which every ordinary channel call above then
157
+ * addresses; Discord itself is idempotent here, so a second call for the same recipient returns the
158
+ * SAME channel rather than a second one.
159
+ *
160
+ * WHO CAN BE REACHED, AND WHAT A FAILURE MEANS. Discord delivers a bot DM only when the recipient
161
+ * shares a server with the bot AND has not turned off direct messages from server members. Neither
162
+ * is knowable in advance, so the refusal arrives as a 403 on the POST — an honest "this person
163
+ * cannot be DM'd", not a bug. It THROWS rather than resolving null, because every caller
164
+ * (`deliverDirectMessage`) has to be able to tell a delivered consent ask from an undelivered one:
165
+ * treating "could not open the channel" as a successful send would tell an initiator that somebody
166
+ * was asked to confirm when nobody was.
167
+ *
168
+ * Token handling matches every call in this module: the bot token rides only the header, and no
169
+ * response body ever reaches an error string.
170
+ */
171
+ export async function discordOpenDmChannel(userId) {
172
+ const res = await discordFetch("POST /users/@me/channels", `${API}/users/@me/channels`, {
173
+ method: "POST",
174
+ headers: botHeaders(botToken()),
175
+ body: JSON.stringify({ recipient_id: userId }),
176
+ });
177
+ if (!res.ok) {
178
+ // DEFINITIVE (OPE-821): no message was posted — the channel was never opened — so a caller may
179
+ // retry or fall back without any risk of a double delivery. The status rides as a NUMBER, which
180
+ // carries no token and no response body.
181
+ throw markNotApplied(Object.assign(new Error(`Discord could not open a DM channel (${res.status}) with user ${userId}`), { status: res.status }));
182
+ }
183
+ const body = (await res.json());
184
+ const id = typeof body?.id === "string" ? body.id.trim() : "";
185
+ if (!id)
186
+ throw markNotApplied(new Error(`Discord open-DM returned no channel id for user ${userId}`));
187
+ return id;
188
+ }
189
+ // Discord component wire constants (stable public API integers). Defined locally for the same
190
+ // reason approvals/discord-card.ts defines its own: importing eve's channel module for two integers
191
+ // would widen this leaf write seam's graph.
192
+ const COMPONENT_ACTION_ROW = 1;
193
+ const COMPONENT_BUTTON = 2;
194
+ const BUTTON_STYLE_SUCCESS = 3; // green — the affirmative answer
195
+ const BUTTON_STYLE_DANGER = 4; // red — the rejecting answer
196
+ /**
197
+ * Post `content` to a DM channel with a row of BUTTONS under it (OPE-622) — one
198
+ * `POST /channels/{id}/messages` carrying a components ACTION_ROW.
199
+ *
200
+ * 🔴 NO SPLIT, AND THAT IS DELIBERATE. `discordPostMessage` splits an over-cap reply into several
201
+ * messages; buttons cannot be split, and a row attached to the wrong part would sit under a
202
+ * fragment. So this refuses past Discord's 2000-character cap rather than truncating or fanning out,
203
+ * and both callers post core-owned copy: the identity consent ask, and the bridged approval card
204
+ * (channels/discord-post-arm.ts), whose own caller falls back to the splitting path past the cap.
205
+ *
206
+ * `opts.replyToMessageId` hangs the message under the one it answers, through the same
207
+ * `message_reference` the plain post path uses and with the same `fail_if_not_exists: false` degrade
208
+ * when the trigger is gone (OPE-1720 — a card posted onto a unified origin must land where the
209
+ * conversation is looking, exactly as its prose sibling does). ABSENT ⇒ a plain channel post.
210
+ *
211
+ * The bot token rides only the header, like every call in this module.
212
+ */
213
+ export async function discordPostMessageWithButtons(channelId, content, buttons, opts) {
214
+ if (content.length > DISCORD_MESSAGE_MAX) {
215
+ throw markNotApplied(new Error(`Discord create-message with buttons cannot split ${content.length} characters (cap ${DISCORD_MESSAGE_MAX}) for channel ${channelId}`));
216
+ }
217
+ const res = await discordFetch("POST /channels/{id}/messages", `${API}/channels/${channelId}/messages`, {
218
+ method: "POST",
219
+ headers: botHeaders(botToken()),
220
+ body: JSON.stringify({
221
+ content,
222
+ ...(opts?.replyToMessageId ? { message_reference: { message_id: opts.replyToMessageId, fail_if_not_exists: false } } : {}),
223
+ components: [
224
+ {
225
+ type: COMPONENT_ACTION_ROW,
226
+ components: buttons.map((b) => ({
227
+ type: COMPONENT_BUTTON,
228
+ style: b.intent === "affirm" ? BUTTON_STYLE_SUCCESS : BUTTON_STYLE_DANGER,
229
+ label: b.label,
230
+ custom_id: b.customId,
231
+ })),
232
+ },
233
+ ],
234
+ }),
235
+ });
236
+ if (!res.ok) {
237
+ throw Object.assign(new Error(`Discord create-message with buttons failed (${res.status}) for channel ${channelId}`), { status: res.status });
238
+ }
239
+ const body = (await res.json());
240
+ const id = typeof body?.id === "string" ? body.id : undefined;
241
+ // 🔴 DELIBERATELY UNMARKED, like discordPostOne's own missing-id branch: Discord answered 2xx, so
242
+ // the message IS in the channel — this host just cannot name it. Calling that definitive would
243
+ // license a retry that posts the ask twice.
244
+ if (!id)
245
+ throw new Error(`Discord create-message with buttons returned no message id for channel ${channelId}`);
246
+ return id;
247
+ }
248
+ /** One raw `POST /channels/{id}/messages`. Kept private — every caller goes through
249
+ * {@link discordPostMessage} so the 2000-char split is never bypassed. */
250
+ async function discordPostOne(channelId, content, replyToMessageId) {
251
+ const res = await discordFetch("POST /channels/{id}/messages", `${API}/channels/${channelId}/messages`, {
252
+ method: "POST",
253
+ headers: botHeaders(botToken()),
254
+ body: JSON.stringify({
255
+ content,
256
+ ...(replyToMessageId ? { message_reference: { message_id: replyToMessageId, fail_if_not_exists: false } } : {}),
257
+ }),
258
+ });
259
+ if (!res.ok) {
260
+ // The HTTP status rides as a NUMBER on the error (OPE-821), exactly as the edit arm below does
261
+ // it. The post spine reads it to tell a Discord REFUSAL (404 gone, 403 forbidden — nothing
262
+ // posted) from a 5xx or a timeout, which may have posted. A number carries no token and no
263
+ // response body, so this stays inside the token-free rule the header states.
264
+ throw Object.assign(new Error(`Discord create-message failed (${res.status}) for channel ${channelId}`), { status: res.status });
265
+ }
266
+ const body = (await res.json());
267
+ const id = typeof body?.id === "string" ? body.id : undefined;
268
+ // 🔴 DELIBERATELY UNMARKED. Discord answered 2xx, so the message IS in the channel — this host just
269
+ // cannot name it. Calling that definitive would license a retry that posts the message twice.
270
+ if (!id)
271
+ throw new Error(`Discord create-message returned no message id for channel ${channelId}`);
272
+ return id;
273
+ }
274
+ /**
275
+ * Rewrite one already-posted message — `PATCH /channels/{id}/messages/{mid}` (OPE-793). Discord
276
+ * renders an "(edited)" marker, so the change is visible in the room.
277
+ *
278
+ * 🔴 NO SPLIT, AND THAT IS A REAL LIMIT RATHER THAN AN OMISSION. A message is ONE message: an
279
+ * over-cap edit cannot become several the way an over-cap POST can, so text past Discord's 2000-char
280
+ * cap is REFUSED here with an honest error the model can act on. Refusing beats truncating (silent
281
+ * data loss) and beats posting continuation messages (an edit must add nothing to the room).
282
+ *
283
+ * Ownership is decided upstream against the own-post ledger (core's channels/reply-edit.ts); Discord
284
+ * refusing to edit another author's message is the backstop under it. Token handling mirrors
285
+ * {@link discordPostMessage}: the bot token rides only the header and no response body ever reaches
286
+ * an error string.
287
+ */
288
+ export async function discordEditMessage(channelId, messageId, content) {
289
+ if (content.length > DISCORD_MESSAGE_MAX) {
290
+ // DEFINITIVE (OPE-802): the call never leaves this host, so the message did not change and the
291
+ // model may post a new reply. The HTTP rejection below carries a status instead, which classes
292
+ // itself.
293
+ throw markNotApplied(new Error(`edit_reply couldn't update the Discord message — the new text is ${content.length} characters and Discord caps one message at ${DISCORD_MESSAGE_MAX}. A message cannot be split by an edit; shorten it, or post a new reply.`));
294
+ }
295
+ const res = await discordFetch("PATCH /channels/{id}/messages/{id}", `${API}/channels/${channelId}/messages/${messageId}`, {
296
+ method: "PATCH",
297
+ headers: botHeaders(botToken()),
298
+ body: JSON.stringify({ content }),
299
+ });
300
+ if (!res.ok) {
301
+ // The HTTP status rides as a NUMBER on the error, not only inside the message. The edit spine
302
+ // (core's channels/reply-edit.ts) reads it for the failure audit row, so a platform rejection is
303
+ // visible in the trail with the status that caused it (OPE-798). A number carries no token and
304
+ // no response body, so this stays inside the token-free rule the header states.
305
+ throw Object.assign(new Error(`edit_reply couldn't update the Discord message (${res.status}) in channel ${channelId}`), {
306
+ status: res.status,
307
+ });
308
+ }
309
+ }
310
+ /**
311
+ * REACT (OPE-987): bare Slack-style shortcode name → the Discord-native unicode character this arm
312
+ * reacts with. The `react` tool's own `EMOJI_NAME_PATTERN` (core tools/react.ts) already restricts
313
+ * every model-given name to lowercase letters/digits/`_`/`+`/`-` — no colon, no raw unicode — so this
314
+ * table is the ONE place a bare name becomes something Discord's reaction endpoint accepts (a real
315
+ * unicode emoji, URL-encoded). Scoped to the vocabulary the noise doctrine and Tag's own acknowledge/
316
+ * status vocabulary actually reach for, mirroring GitHub's own answer to the same problem
317
+ * (github-post-arm.ts `GITHUB_REACTION_MAP`) rather than pulling in a general shortcode library —
318
+ * Discord's own reaction endpoint takes ANY unicode emoji, so this table is a vocabulary CHOICE, not
319
+ * a platform limit; growing it costs one line, never a migration.
320
+ */
321
+ const DISCORD_EMOJI_MAP = {
322
+ thumbsup: "👍",
323
+ "+1": "👍",
324
+ thumbsdown: "👎",
325
+ "-1": "👎",
326
+ eyes: "👀",
327
+ white_check_mark: "✅",
328
+ heavy_check_mark: "✔️",
329
+ x: "❌",
330
+ tada: "🎉",
331
+ rocket: "🚀",
332
+ heart: "❤️",
333
+ fire: "🔥",
334
+ clap: "👏",
335
+ wave: "👋",
336
+ raised_hand: "✋",
337
+ raised_hands: "🙌",
338
+ pray: "🙏",
339
+ laughing: "😆",
340
+ joy: "😂",
341
+ confused: "😕",
342
+ thinking_face: "🤔",
343
+ hourglass: "⏳",
344
+ hourglass_flowing_sand: "⏳",
345
+ question: "❓",
346
+ exclamation: "❗",
347
+ warning: "⚠️",
348
+ octagonal_sign: "🛑",
349
+ "100": "💯",
350
+ };
351
+ /**
352
+ * REACT (OPE-987): `PUT /channels/{id}/messages/{mid}/reactions/{emoji}/@me` under the bot token —
353
+ * the SAME primitive `discordMessageOps.setReaction` calls for the host-placed ✋ glyph, but this one
354
+ * THROWS on failure (unlike that primitive, which is deliberately fail-soft — see its own header):
355
+ * a model-invoked `react` is a verb the model gets an honest answer from, not decoration beside work
356
+ * that is already running.
357
+ *
358
+ * An unmapped emoji name refuses DEFINITIVE (`markNotApplied`, OPE-825) before any byte leaves this
359
+ * host — the same shape GitHub's fixed content enum refuses with. A rejected PUT carries the HTTP
360
+ * status on the thrown error (token-free), so `platformFailureClass` can classify it without any
361
+ * string matching.
362
+ */
363
+ export async function discordAddReaction(channelId, messageId, emojiName) {
364
+ const unicode = DISCORD_EMOJI_MAP[emojiName.toLowerCase()];
365
+ if (!unicode) {
366
+ throw markNotApplied(new Error(`react couldn't post to Discord — "${emojiName}" has no Discord emoji equivalent here; this deployment supports: ${Object.keys(DISCORD_EMOJI_MAP).join(", ")}.`));
367
+ }
368
+ const res = await discordFetch("PUT /channels/{id}/messages/{id}/reactions/{emoji}/@me", `${API}/channels/${channelId}/messages/${messageId}/reactions/${encodeURIComponent(unicode)}/@me`, { method: "PUT", headers: botHeaders(botToken()) });
369
+ if (!res.ok) {
370
+ throw Object.assign(new Error(`react couldn't post to Discord — ${res.status} on channel ${channelId}`), { status: res.status });
371
+ }
372
+ }
373
+ /**
374
+ * READ-ONLY: the id of the thread that already hangs off `messageId`, or null when there is none.
375
+ *
376
+ * 🔴 IT NEVER CREATES A THREAD, and that is the whole reason it exists next to
377
+ * {@link discordThreadForMessage}. The EDIT path needs the same auto-thread the POST path opened
378
+ * (OPE-798), but at edit time a create would open a NEW empty thread that cannot hold the message
379
+ * being edited — a visible side effect in the room, and still a 404. So the edit path asks only
380
+ * "where did the reply land", and degrades to the base channel when the answer is "nowhere".
381
+ *
382
+ * Fail-soft to null on ANY failure (deleted trigger, missing permission, rate limit), matching the
383
+ * create-or-find sibling. Token handling is identical: the bot token rides only the header.
384
+ */
385
+ export async function discordExistingThreadForMessage(channelId, messageId) {
386
+ try {
387
+ const msgRes = await discordFetch("GET /channels/{id}/messages/{id}", `${API}/channels/${channelId}/messages/${messageId}`, {
388
+ headers: botHeaders(botToken()),
389
+ });
390
+ if (!msgRes.ok)
391
+ return null;
392
+ const msg = (await msgRes.json());
393
+ return typeof msg?.thread?.id === "string" ? msg.thread.id : null;
394
+ }
395
+ catch {
396
+ return null;
397
+ }
398
+ }
399
+ /**
400
+ * Open (or find) the thread hanging off a message — the unified reply arm's auto-thread leg.
401
+ * `POST /channels/{id}/messages/{mid}/threads`; when Discord answers THREAD_ALREADY_CREATED
402
+ * (a second reply to the same wake, or a crash-retry), falls back to reading the message's
403
+ * existing `thread.id`. Returns null on ANY failure (missing permission, deleted trigger,
404
+ * rate limit) — the caller degrades to the plain in-channel reply, never blocks the post.
405
+ * Token handling mirrors discordPostMessage: the bot token rides only the header, and no
406
+ * response body ever reaches an error string or return value.
407
+ */
408
+ export async function discordThreadForMessage(channelId, messageId, name) {
409
+ try {
410
+ const res = await discordFetch("POST /channels/{id}/messages/{id}/threads", `${API}/channels/${channelId}/messages/${messageId}/threads`, {
411
+ method: "POST",
412
+ headers: botHeaders(botToken()),
413
+ body: JSON.stringify({ name: name.slice(0, 100) || "thread", auto_archive_duration: 1440 }),
414
+ });
415
+ if (res.ok) {
416
+ const body = (await res.json());
417
+ return typeof body?.id === "string" ? body.id : null;
418
+ }
419
+ // Already threaded (idempotency): read the existing thread id off the message itself.
420
+ return await discordExistingThreadForMessage(channelId, messageId);
421
+ }
422
+ catch {
423
+ return null;
424
+ }
425
+ }
426
+ //# sourceMappingURL=discord-post.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-post.js","sourceRoot":"","sources":["../../../agent/channels/discord-post.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ import { type BindableContext, type RenderedLine, type WakeReadAddress } from "@open-everywhere/core/agent/adapter-api.js";
2
+ import { type DiscordRoleProbe } from "./discord-backscroll.js";
3
+ /** A Discord messages GET — mirrors discord-backscroll.ts's live requester but fail-LOUD. Injectable. */
4
+ export type DiscordReadRequester = (channelId: string, limit: number) => Promise<unknown>;
5
+ /**
6
+ * Map a raw Discord messages array (newest-first) to oldest-first RenderedLine[] — reusing
7
+ * linesFromDiscordMessages for the exact system/empty drop + sender classification. Pure; exported
8
+ * for the render test.
9
+ *
10
+ * `author` IS UNCHANGED (the sender id, else `(unknown)`). OPE-474 adds the two GRADING fields
11
+ * beside it, taken from the sender classification linesFromDiscordMessages already made: a `user`
12
+ * line carries its `authorId`, and a `bot`/`agent` line is marked a machine and drops out of the
13
+ * probe. An `unknown` line carries neither, which is the fail-open answer for an author nobody can
14
+ * name.
15
+ */
16
+ export declare function discordLinesFromRaw(raw: unknown): RenderedLine[];
17
+ export declare const discordReadAdapter: {
18
+ platform: "discord";
19
+ /**
20
+ * OPE-929 — Discord's own spelling of "which room and which thread is this turn in".
21
+ *
22
+ * 🔴 A DISCORD THREAD IS ITS OWN CHANNEL. There is no thread id inside a channel to carry, so the
23
+ * CONTAINER and the THREAD ROOT are the same snowflake on a thread turn, and `readThread` is the
24
+ * verb that reads it (it fetches `threadTs` directly — see below). The turn is a thread turn when
25
+ * the ingress stamped a PARENT channel id, which is exactly what `domainSurface` carries: the
26
+ * mapper sets it only from `parent_channel_id`, so its presence IS "this is a thread".
27
+ *
28
+ * A base-channel turn has no parent stamp, so it reports no thread root and `read_thread` refuses
29
+ * it honestly.
30
+ */
31
+ wakeAddress(ctx: BindableContext): WakeReadAddress | undefined;
32
+ readChannel(ctx: BindableContext, channel: string, limit?: number, req?: DiscordReadRequester): Promise<readonly RenderedLine[]>;
33
+ readThread(ctx: BindableContext, channel: string, threadTs: string, limit?: number, req?: DiscordReadRequester): Promise<readonly RenderedLine[]>;
34
+ /** OPE-474 / OPE-534 — the register leg only (see the note above). It answers with the refuted
35
+ * AUTHOR IDS, because core reads the one answer twice: the turn mark and the `withhold` body
36
+ * replacement. `roleProbe` is injected ONLY so a test can drive the grading offline; every live
37
+ * call defaults to the TTL-cached live probe. */
38
+ refutedAuthorIds(ctx: BindableContext, lines: readonly RenderedLine[], roleProbe?: DiscordRoleProbe): Promise<readonly string[]>;
39
+ };
40
+ //# sourceMappingURL=discord-read.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-read.d.ts","sourceRoot":"","sources":["../../../agent/channels/discord-read.ts"],"names":[],"mappings":"AAWA,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAO1F,yGAAyG;AACzG,MAAM,MAAM,oBAAoB,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAa1F;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,EAAE,CAahE;AA0BD,eAAO,MAAM,kBAAkB;IAC7B,QAAQ,EAAE,SAAS;IACnB;;;;;;;;;;;OAWG;IACH,WAAW,MAAM,eAAe,GAAG,eAAe,GAAG,SAAS;IAMxD,WAAW,MAAM,eAAe,WAAW,MAAM,UAAU,MAAM,QAAO,oBAAoB,GAAqB,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAIjJ,UAAU,MAAM,eAAe,WAAW,MAAM,YAAY,MAAM,UAAU,MAAM,QAAO,oBAAoB,GAAqB,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC;IAKxK;;;sDAGkD;IAC5C,gBAAgB,MAAM,eAAe,SAAS,SAAS,YAAY,EAAE,cAAa,gBAAgB,GAAwB,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;CAe3J,CAAC"}