@spinabot/brigade 1.6.0 → 1.7.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 (194) hide show
  1. package/dist/agents/agent-loop.d.ts.map +1 -1
  2. package/dist/agents/agent-loop.js +51 -1
  3. package/dist/agents/agent-loop.js.map +1 -1
  4. package/dist/agents/channels/bundled-channel-metas.d.ts +2 -0
  5. package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
  6. package/dist/agents/channels/bundled-channel-metas.js +11 -0
  7. package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
  8. package/dist/agents/channels/discord/account-config.d.ts +177 -0
  9. package/dist/agents/channels/discord/account-config.d.ts.map +1 -0
  10. package/dist/agents/channels/discord/account-config.js +349 -0
  11. package/dist/agents/channels/discord/account-config.js.map +1 -0
  12. package/dist/agents/channels/discord/adapter.d.ts +79 -0
  13. package/dist/agents/channels/discord/adapter.d.ts.map +1 -0
  14. package/dist/agents/channels/discord/adapter.js +693 -0
  15. package/dist/agents/channels/discord/adapter.js.map +1 -0
  16. package/dist/agents/channels/discord/approval-authorize.d.ts +43 -0
  17. package/dist/agents/channels/discord/approval-authorize.d.ts.map +1 -0
  18. package/dist/agents/channels/discord/approval-authorize.js +71 -0
  19. package/dist/agents/channels/discord/approval-authorize.js.map +1 -0
  20. package/dist/agents/channels/discord/approval-native.d.ts +68 -0
  21. package/dist/agents/channels/discord/approval-native.d.ts.map +1 -0
  22. package/dist/agents/channels/discord/approval-native.js +81 -0
  23. package/dist/agents/channels/discord/approval-native.js.map +1 -0
  24. package/dist/agents/channels/discord/command-menu.d.ts +49 -0
  25. package/dist/agents/channels/discord/command-menu.d.ts.map +1 -0
  26. package/dist/agents/channels/discord/command-menu.js +73 -0
  27. package/dist/agents/channels/discord/command-menu.js.map +1 -0
  28. package/dist/agents/channels/discord/component-blocks.d.ts +108 -0
  29. package/dist/agents/channels/discord/component-blocks.d.ts.map +1 -0
  30. package/dist/agents/channels/discord/component-blocks.js +113 -0
  31. package/dist/agents/channels/discord/component-blocks.js.map +1 -0
  32. package/dist/agents/channels/discord/components.d.ts +175 -0
  33. package/dist/agents/channels/discord/components.d.ts.map +1 -0
  34. package/dist/agents/channels/discord/components.js +220 -0
  35. package/dist/agents/channels/discord/components.js.map +1 -0
  36. package/dist/agents/channels/discord/connection.d.ts +570 -0
  37. package/dist/agents/channels/discord/connection.d.ts.map +1 -0
  38. package/dist/agents/channels/discord/connection.js +1600 -0
  39. package/dist/agents/channels/discord/connection.js.map +1 -0
  40. package/dist/agents/channels/discord/directory-cache.d.ts +47 -0
  41. package/dist/agents/channels/discord/directory-cache.d.ts.map +1 -0
  42. package/dist/agents/channels/discord/directory-cache.js +131 -0
  43. package/dist/agents/channels/discord/directory-cache.js.map +1 -0
  44. package/dist/agents/channels/discord/directory-live.d.ts +61 -0
  45. package/dist/agents/channels/discord/directory-live.d.ts.map +1 -0
  46. package/dist/agents/channels/discord/directory-live.js +140 -0
  47. package/dist/agents/channels/discord/directory-live.js.map +1 -0
  48. package/dist/agents/channels/discord/draft-stream.d.ts +92 -0
  49. package/dist/agents/channels/discord/draft-stream.d.ts.map +1 -0
  50. package/dist/agents/channels/discord/draft-stream.js +213 -0
  51. package/dist/agents/channels/discord/draft-stream.js.map +1 -0
  52. package/dist/agents/channels/discord/format.d.ts +70 -0
  53. package/dist/agents/channels/discord/format.d.ts.map +1 -0
  54. package/dist/agents/channels/discord/format.js +303 -0
  55. package/dist/agents/channels/discord/format.js.map +1 -0
  56. package/dist/agents/channels/discord/guilds.d.ts +25 -0
  57. package/dist/agents/channels/discord/guilds.d.ts.map +1 -0
  58. package/dist/agents/channels/discord/guilds.js +46 -0
  59. package/dist/agents/channels/discord/guilds.js.map +1 -0
  60. package/dist/agents/channels/discord/inbound-extras.d.ts +377 -0
  61. package/dist/agents/channels/discord/inbound-extras.d.ts.map +1 -0
  62. package/dist/agents/channels/discord/inbound-extras.js +589 -0
  63. package/dist/agents/channels/discord/inbound-extras.js.map +1 -0
  64. package/dist/agents/channels/discord/index.d.ts +21 -0
  65. package/dist/agents/channels/discord/index.d.ts.map +1 -0
  66. package/dist/agents/channels/discord/index.js +21 -0
  67. package/dist/agents/channels/discord/index.js.map +1 -0
  68. package/dist/agents/channels/discord/media.d.ts +85 -0
  69. package/dist/agents/channels/discord/media.d.ts.map +1 -0
  70. package/dist/agents/channels/discord/media.js +242 -0
  71. package/dist/agents/channels/discord/media.js.map +1 -0
  72. package/dist/agents/channels/discord/modal-registry.d.ts +89 -0
  73. package/dist/agents/channels/discord/modal-registry.d.ts.map +1 -0
  74. package/dist/agents/channels/discord/modal-registry.js +104 -0
  75. package/dist/agents/channels/discord/modal-registry.js.map +1 -0
  76. package/dist/agents/channels/discord/modals.d.ts +100 -0
  77. package/dist/agents/channels/discord/modals.d.ts.map +1 -0
  78. package/dist/agents/channels/discord/modals.js +124 -0
  79. package/dist/agents/channels/discord/modals.js.map +1 -0
  80. package/dist/agents/channels/discord/module.d.ts +15 -0
  81. package/dist/agents/channels/discord/module.d.ts.map +1 -0
  82. package/dist/agents/channels/discord/module.js +22 -0
  83. package/dist/agents/channels/discord/module.js.map +1 -0
  84. package/dist/agents/channels/discord/permission-audit.d.ts +43 -0
  85. package/dist/agents/channels/discord/permission-audit.d.ts.map +1 -0
  86. package/dist/agents/channels/discord/permission-audit.js +192 -0
  87. package/dist/agents/channels/discord/permission-audit.js.map +1 -0
  88. package/dist/agents/channels/discord/plugin.d.ts +89 -0
  89. package/dist/agents/channels/discord/plugin.d.ts.map +1 -0
  90. package/dist/agents/channels/discord/plugin.js +372 -0
  91. package/dist/agents/channels/discord/plugin.js.map +1 -0
  92. package/dist/agents/channels/discord/probe.d.ts +115 -0
  93. package/dist/agents/channels/discord/probe.d.ts.map +1 -0
  94. package/dist/agents/channels/discord/probe.js +193 -0
  95. package/dist/agents/channels/discord/probe.js.map +1 -0
  96. package/dist/agents/channels/discord/reasoning-lane.d.ts +42 -0
  97. package/dist/agents/channels/discord/reasoning-lane.d.ts.map +1 -0
  98. package/dist/agents/channels/discord/reasoning-lane.js +68 -0
  99. package/dist/agents/channels/discord/reasoning-lane.js.map +1 -0
  100. package/dist/agents/channels/discord/rest-actions.d.ts +346 -0
  101. package/dist/agents/channels/discord/rest-actions.d.ts.map +1 -0
  102. package/dist/agents/channels/discord/rest-actions.js +559 -0
  103. package/dist/agents/channels/discord/rest-actions.js.map +1 -0
  104. package/dist/agents/channels/discord/rest-components.d.ts +122 -0
  105. package/dist/agents/channels/discord/rest-components.d.ts.map +1 -0
  106. package/dist/agents/channels/discord/rest-components.js +243 -0
  107. package/dist/agents/channels/discord/rest-components.js.map +1 -0
  108. package/dist/agents/channels/discord/security-audit.d.ts +29 -0
  109. package/dist/agents/channels/discord/security-audit.d.ts.map +1 -0
  110. package/dist/agents/channels/discord/security-audit.js +94 -0
  111. package/dist/agents/channels/discord/security-audit.js.map +1 -0
  112. package/dist/agents/channels/discord/security-doctor.d.ts +43 -0
  113. package/dist/agents/channels/discord/security-doctor.d.ts.map +1 -0
  114. package/dist/agents/channels/discord/security-doctor.js +83 -0
  115. package/dist/agents/channels/discord/security-doctor.js.map +1 -0
  116. package/dist/agents/channels/discord/status-issues.d.ts +37 -0
  117. package/dist/agents/channels/discord/status-issues.d.ts.map +1 -0
  118. package/dist/agents/channels/discord/status-issues.js +66 -0
  119. package/dist/agents/channels/discord/status-issues.js.map +1 -0
  120. package/dist/agents/channels/discord/subagent-thread-binding-store.d.ts +57 -0
  121. package/dist/agents/channels/discord/subagent-thread-binding-store.d.ts.map +1 -0
  122. package/dist/agents/channels/discord/subagent-thread-binding-store.js +98 -0
  123. package/dist/agents/channels/discord/subagent-thread-binding-store.js.map +1 -0
  124. package/dist/agents/channels/discord/subagent-thread-binding.d.ts +95 -0
  125. package/dist/agents/channels/discord/subagent-thread-binding.d.ts.map +1 -0
  126. package/dist/agents/channels/discord/subagent-thread-binding.js +208 -0
  127. package/dist/agents/channels/discord/subagent-thread-binding.js.map +1 -0
  128. package/dist/agents/channels/discord/system-events.d.ts +31 -0
  129. package/dist/agents/channels/discord/system-events.d.ts.map +1 -0
  130. package/dist/agents/channels/discord/system-events.js +74 -0
  131. package/dist/agents/channels/discord/system-events.js.map +1 -0
  132. package/dist/agents/channels/general-callback.d.ts +12 -0
  133. package/dist/agents/channels/general-callback.d.ts.map +1 -1
  134. package/dist/agents/channels/general-callback.js +18 -0
  135. package/dist/agents/channels/general-callback.js.map +1 -1
  136. package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
  137. package/dist/agents/channels/inbound-pipeline.js +70 -10
  138. package/dist/agents/channels/inbound-pipeline.js.map +1 -1
  139. package/dist/agents/channels/sdk.d.ts +2 -0
  140. package/dist/agents/channels/sdk.d.ts.map +1 -1
  141. package/dist/agents/channels/sdk.js +2 -0
  142. package/dist/agents/channels/sdk.js.map +1 -1
  143. package/dist/agents/extensions/modules/index.d.ts.map +1 -1
  144. package/dist/agents/extensions/modules/index.js +5 -0
  145. package/dist/agents/extensions/modules/index.js.map +1 -1
  146. package/dist/agents/extensions/types.d.ts +7 -0
  147. package/dist/agents/extensions/types.d.ts.map +1 -1
  148. package/dist/agents/extensions/types.js.map +1 -1
  149. package/dist/agents/subagent-announce-delivery.d.ts +10 -0
  150. package/dist/agents/subagent-announce-delivery.d.ts.map +1 -1
  151. package/dist/agents/subagent-announce-delivery.js +1 -0
  152. package/dist/agents/subagent-announce-delivery.js.map +1 -1
  153. package/dist/agents/subagent-completion-bridge.d.ts.map +1 -1
  154. package/dist/agents/subagent-completion-bridge.js +81 -0
  155. package/dist/agents/subagent-completion-bridge.js.map +1 -1
  156. package/dist/agents/subagent-spawn.d.ts.map +1 -1
  157. package/dist/agents/subagent-spawn.js +57 -4
  158. package/dist/agents/subagent-spawn.js.map +1 -1
  159. package/dist/agents/tools/cron-tool.d.ts.map +1 -1
  160. package/dist/agents/tools/cron-tool.js +4 -1
  161. package/dist/agents/tools/cron-tool.js.map +1 -1
  162. package/dist/agents/tools/discord-action-tool.d.ts +224 -0
  163. package/dist/agents/tools/discord-action-tool.d.ts.map +1 -0
  164. package/dist/agents/tools/discord-action-tool.js +848 -0
  165. package/dist/agents/tools/discord-action-tool.js.map +1 -0
  166. package/dist/agents/tools/registry.d.ts.map +1 -1
  167. package/dist/agents/tools/registry.js +21 -0
  168. package/dist/agents/tools/registry.js.map +1 -1
  169. package/dist/agents/tools/sessions/index.d.ts +8 -0
  170. package/dist/agents/tools/sessions/index.d.ts.map +1 -1
  171. package/dist/agents/tools/sessions/index.js +15 -3
  172. package/dist/agents/tools/sessions/index.js.map +1 -1
  173. package/dist/buildstamp.json +1 -1
  174. package/dist/cli/commands/channels.d.ts +2 -0
  175. package/dist/cli/commands/channels.d.ts.map +1 -1
  176. package/dist/cli/commands/channels.js +58 -1
  177. package/dist/cli/commands/channels.js.map +1 -1
  178. package/dist/core/auth-bridge.d.ts +1 -0
  179. package/dist/core/auth-bridge.d.ts.map +1 -1
  180. package/dist/core/auth-bridge.js +46 -1
  181. package/dist/core/auth-bridge.js.map +1 -1
  182. package/dist/core/server.d.ts.map +1 -1
  183. package/dist/core/server.js +18 -2
  184. package/dist/core/server.js.map +1 -1
  185. package/dist/cron/isolated-agent/run-executor.d.ts +11 -0
  186. package/dist/cron/isolated-agent/run-executor.d.ts.map +1 -1
  187. package/dist/cron/isolated-agent/run-executor.js +20 -4
  188. package/dist/cron/isolated-agent/run-executor.js.map +1 -1
  189. package/dist/cron/types.d.ts +8 -0
  190. package/dist/cron/types.d.ts.map +1 -1
  191. package/dist/system-prompt/assembler.d.ts.map +1 -1
  192. package/dist/system-prompt/assembler.js +4 -2
  193. package/dist/system-prompt/assembler.js.map +1 -1
  194. package/package.json +2 -1
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Discord live-streaming draft message.
3
+ *
4
+ * Brigade agents normally deliver one FINAL chunked reply (see
5
+ * `inbound-pipeline.dispatchTurn` → `adapter.sendText`). This module adds the
6
+ * OPTIONAL progressive-edit behavior: post a placeholder message on the first
7
+ * content, then `message.edit` it with the accumulating answer as tokens arrive —
8
+ * THROTTLED to roughly one edit per second so a long reply doesn't hammer the
9
+ * REST API. When the running text outgrows the per-message limit the stream
10
+ * FINALIZES the current message at its last safe chunk boundary and ROLLS to a
11
+ * fresh message for the overflow, so a multi-message reply streams naturally.
12
+ *
13
+ * Design notes (vs the simpler one-shot send):
14
+ * - Transport-agnostic + pure-ish: the two Discord calls (`postMessage`,
15
+ * `editMessage`) are INJECTED, so the throttle / roll / finalize state
16
+ * machine is unit-tested with fakes and never imports `discord.js`.
17
+ * - Throttle window: edits coalesce — `update(text)` only records the latest
18
+ * text; the loop flushes at most once per `throttleMs`. `flush()` forces an
19
+ * immediate delivery.
20
+ * - Char-limit roll: when an edit would exceed the limit, the current message
21
+ * is finalized to the largest chunk that fits (split on a paragraph /
22
+ * newline / space boundary) and the remainder starts a NEW message.
23
+ * - Render hook: `renderText` converts a markdown chunk to Discord markup
24
+ * (same `markdownToDiscord` the final path uses). A render that throws or
25
+ * yields empty text falls back to the plain chunk so a half-streamed code
26
+ * fence never wedges the stream.
27
+ * - Idempotent finalize: `finalize(text)` is safe to call once at turn end —
28
+ * it flushes the final text and stops the loop. After finalize the stream is
29
+ * inert.
30
+ *
31
+ * Discord-native analogue of `slack/draft-stream.ts` — the same state machine,
32
+ * with Discord's string message ids and its 2000-char content limit.
33
+ */
34
+ /** Discord's hard per-message content limit (chars). */
35
+ export const DISCORD_STREAM_MAX_CHARS = 2000;
36
+ /** Default minimum gap between edits (≈ 1 edit/sec, REST friendly). */
37
+ export const DEFAULT_THROTTLE_MS = 1000;
38
+ /** Floor on the throttle so a misconfig can't spam the API. */
39
+ const MIN_THROTTLE_MS = 250;
40
+ /**
41
+ * Split `text` so the head fits within `limit` chars, preferring (in order) a
42
+ * paragraph break, a newline, then a space, falling back to a hard cut. Returns
43
+ * `[head, rest]`; `rest` is "" when the whole text fits.
44
+ */
45
+ export function splitAtBoundary(text, limit) {
46
+ if (text.length <= limit)
47
+ return [text, ""];
48
+ const window = text.slice(0, limit);
49
+ const candidates = [window.lastIndexOf("\n\n"), window.lastIndexOf("\n"), window.lastIndexOf(" ")];
50
+ for (const idx of candidates) {
51
+ // Require the boundary to land past the half-way mark so we don't emit a
52
+ // tiny head and shove almost everything into the overflow message.
53
+ if (idx > limit * 0.5) {
54
+ return [text.slice(0, idx).trimEnd(), text.slice(idx).trimStart()];
55
+ }
56
+ }
57
+ return [text.slice(0, limit), text.slice(limit)];
58
+ }
59
+ export function createDraftStream(params) {
60
+ const throttleMs = Math.max(MIN_THROTTLE_MS, params.throttleMs ?? DEFAULT_THROTTLE_MS);
61
+ const maxChars = Math.min(DISCORD_STREAM_MAX_CHARS, Math.max(1, params.maxChars ?? DISCORD_STREAM_MAX_CHARS));
62
+ const render = params.renderText ?? ((t) => ({ text: t }));
63
+ const warn = params.warn ?? (() => { });
64
+ // Full accumulated answer text the agent has produced so far.
65
+ let pending = "";
66
+ // Text of the CURRENT (live) message that's already been delivered — used to
67
+ // skip no-op edits and to know how much of `pending` belongs to this message.
68
+ let liveDelivered = "";
69
+ // Char offset into `pending` where the current live message STARTS. Each roll
70
+ // advances this past the finalized head.
71
+ let baseOffset = 0;
72
+ const sentIds = [];
73
+ let liveId;
74
+ let done = false;
75
+ let inFlight = false;
76
+ let lastSentAt = 0;
77
+ let timer;
78
+ const clearTimer = () => {
79
+ if (timer) {
80
+ clearTimeout(timer);
81
+ timer = undefined;
82
+ }
83
+ };
84
+ /** The slice of `pending` that belongs to the current live message. */
85
+ const liveSlice = () => pending.slice(baseOffset);
86
+ /**
87
+ * Deliver the current live slice: post a placeholder on first content, edit
88
+ * thereafter, and roll to a new message when the slice exceeds `maxChars`.
89
+ * Re-entrancy guarded by `inFlight` so overlapping flushes serialize.
90
+ */
91
+ const deliver = async (isFinal) => {
92
+ if (done && !isFinal)
93
+ return;
94
+ if (inFlight)
95
+ return;
96
+ if (!liveSlice().trim())
97
+ return;
98
+ inFlight = true;
99
+ try {
100
+ // Roll: finalize the current message at a boundary, start a new one for
101
+ // the overflow. Loop because a very long burst can overflow twice. We
102
+ // work against the UNTRIMMED live slice so `baseOffset` advances by the
103
+ // exact number of `pending` chars consumed (including the boundary
104
+ // whitespace `splitAtBoundary` drops), keeping the next message aligned.
105
+ let rawSlice = liveSlice();
106
+ while (rawSlice.trimEnd().length > maxChars) {
107
+ const [head] = splitAtBoundary(rawSlice.trimEnd(), maxChars);
108
+ await deliverOne(head);
109
+ const headEnd = rawSlice.indexOf(head) + head.length;
110
+ let advance = headEnd;
111
+ while (advance < rawSlice.length && /\s/.test(rawSlice[advance] ?? ""))
112
+ advance++;
113
+ baseOffset += advance;
114
+ liveDelivered = "";
115
+ liveId = undefined;
116
+ rawSlice = liveSlice();
117
+ }
118
+ const slice = rawSlice.trimEnd();
119
+ if (slice && (slice !== liveDelivered || isFinal)) {
120
+ await deliverOne(slice);
121
+ }
122
+ lastSentAt = Date.now();
123
+ }
124
+ finally {
125
+ inFlight = false;
126
+ }
127
+ };
128
+ /** Post-or-edit a single message body (≤ maxChars). */
129
+ const deliverOne = async (body) => {
130
+ let rendered;
131
+ try {
132
+ rendered = render(body);
133
+ if (!rendered.text.trim())
134
+ rendered = { text: body };
135
+ }
136
+ catch {
137
+ rendered = { text: body };
138
+ }
139
+ // A render can inflate length; guard the hard cap.
140
+ if (rendered.text.length > DISCORD_STREAM_MAX_CHARS) {
141
+ rendered = { text: body.slice(0, maxChars) };
142
+ }
143
+ try {
144
+ if (typeof liveId === "string") {
145
+ if (rendered.text === liveDelivered)
146
+ return;
147
+ await params.transport.editMessage(liveId, rendered.text);
148
+ }
149
+ else {
150
+ const sent = await params.transport.postMessage(rendered.text, {
151
+ ...(params.threadId !== undefined ? { threadId: params.threadId } : {}),
152
+ });
153
+ liveId = sent.id;
154
+ sentIds.push(sent.id);
155
+ }
156
+ liveDelivered = rendered.text;
157
+ }
158
+ catch (err) {
159
+ // A post / edit failure must never wedge the turn — the final-only
160
+ // fallback in the pipeline still delivers the complete reply.
161
+ warn(`discord stream deliver failed: ${err instanceof Error ? err.message : String(err)}`);
162
+ }
163
+ };
164
+ /** Schedule a throttled flush if one isn't already pending. */
165
+ const scheduleFlush = () => {
166
+ if (done || timer || inFlight)
167
+ return;
168
+ const elapsed = Date.now() - lastSentAt;
169
+ const wait = elapsed >= throttleMs ? 0 : throttleMs - elapsed;
170
+ timer = setTimeout(() => {
171
+ timer = undefined;
172
+ void deliver(false);
173
+ }, wait);
174
+ };
175
+ return {
176
+ update(fullText) {
177
+ if (done)
178
+ return;
179
+ if (typeof fullText !== "string")
180
+ return;
181
+ pending = fullText;
182
+ scheduleFlush();
183
+ },
184
+ async flush() {
185
+ if (done)
186
+ return;
187
+ clearTimer();
188
+ await deliver(false);
189
+ },
190
+ async finalize(fullText) {
191
+ if (done)
192
+ return;
193
+ clearTimer();
194
+ if (typeof fullText === "string")
195
+ pending = fullText;
196
+ // Drain: deliver until the live slice is fully sent (handles a final burst
197
+ // that rolls into one or more new messages).
198
+ await deliver(true);
199
+ done = true;
200
+ },
201
+ stop() {
202
+ clearTimer();
203
+ done = true;
204
+ },
205
+ messageIds() {
206
+ return [...sentIds];
207
+ },
208
+ isDone() {
209
+ return done;
210
+ },
211
+ };
212
+ }
213
+ //# sourceMappingURL=draft-stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-stream.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/draft-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAC7C,uEAAuE;AACvE,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC;AACxC,+DAA+D;AAC/D,MAAM,eAAe,GAAG,GAAG,CAAC;AAiD5B;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,KAAa;IAC1D,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACpC,MAAM,UAAU,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IACnG,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC9B,yEAAyE;QACzE,mEAAmE;QACnE,IAAI,GAAG,GAAG,KAAK,GAAG,GAAG,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QACpE,CAAC;IACF,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAA+B;IAChE,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,UAAU,IAAI,mBAAmB,CAAC,CAAC;IACvF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,wBAAwB,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,IAAI,wBAAwB,CAAC,CAAC,CAAC;IAC9G,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC,CAAS,EAAe,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAChF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAEvC,8DAA8D;IAC9D,IAAI,OAAO,GAAG,EAAE,CAAC;IACjB,6EAA6E;IAC7E,8EAA8E;IAC9E,IAAI,aAAa,GAAG,EAAE,CAAC;IACvB,8EAA8E;IAC9E,yCAAyC;IACzC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,MAA0B,CAAC;IAC/B,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,KAAgD,CAAC;IAErD,MAAM,UAAU,GAAG,GAAS,EAAE;QAC7B,IAAI,KAAK,EAAE,CAAC;YACX,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,GAAG,SAAS,CAAC;QACnB,CAAC;IACF,CAAC,CAAC;IAEF,uEAAuE;IACvE,MAAM,SAAS,GAAG,GAAW,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAE1D;;;;OAIG;IACH,MAAM,OAAO,GAAG,KAAK,EAAE,OAAgB,EAAiB,EAAE;QACzD,IAAI,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO;QAC7B,IAAI,QAAQ;YAAE,OAAO;QACrB,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE;YAAE,OAAO;QAChC,QAAQ,GAAG,IAAI,CAAC;QAChB,IAAI,CAAC;YACJ,wEAAwE;YACxE,sEAAsE;YACtE,wEAAwE;YACxE,mEAAmE;YACnE,yEAAyE;YACzE,IAAI,QAAQ,GAAG,SAAS,EAAE,CAAC;YAC3B,OAAO,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;gBAC7C,MAAM,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;gBAC7D,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;gBACvB,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC;gBACrD,IAAI,OAAO,GAAG,OAAO,CAAC;gBACtB,OAAO,OAAO,GAAG,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;oBAAE,OAAO,EAAE,CAAC;gBAClF,UAAU,IAAI,OAAO,CAAC;gBACtB,aAAa,GAAG,EAAE,CAAC;gBACnB,MAAM,GAAG,SAAS,CAAC;gBACnB,QAAQ,GAAG,SAAS,EAAE,CAAC;YACxB,CAAC;YACD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,KAAK,IAAI,CAAC,KAAK,KAAK,aAAa,IAAI,OAAO,CAAC,EAAE,CAAC;gBACnD,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;YACD,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,CAAC;gBAAS,CAAC;YACV,QAAQ,GAAG,KAAK,CAAC;QAClB,CAAC;IACF,CAAC,CAAC;IAEF,uDAAuD;IACvD,MAAM,UAAU,GAAG,KAAK,EAAE,IAAY,EAAiB,EAAE;QACxD,IAAI,QAAqB,CAAC;QAC1B,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YACxB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;gBAAE,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACR,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAC3B,CAAC;QACD,mDAAmD;QACnD,IAAI,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,wBAAwB,EAAE,CAAC;YACrD,QAAQ,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC;YACJ,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;gBAChC,IAAI,QAAQ,CAAC,IAAI,KAAK,aAAa;oBAAE,OAAO;gBAC5C,MAAM,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC3D,CAAC;iBAAM,CAAC;gBACP,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,EAAE;oBAC9D,GAAG,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACvE,CAAC,CAAC;gBACH,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;gBACjB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC;YACD,aAAa,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,mEAAmE;YACnE,8DAA8D;YAC9D,IAAI,CAAC,kCAAkC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5F,CAAC;IACF,CAAC,CAAC;IAEF,+DAA+D;IAC/D,MAAM,aAAa,GAAG,GAAS,EAAE;QAChC,IAAI,IAAI,IAAI,KAAK,IAAI,QAAQ;YAAE,OAAO;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,OAAO,CAAC;QAC9D,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YACvB,KAAK,GAAG,SAAS,CAAC;YAClB,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC,EAAE,IAAI,CAAC,CAAC;IACV,CAAC,CAAC;IAEF,OAAO;QACN,MAAM,CAAC,QAAgB;YACtB,IAAI,IAAI;gBAAE,OAAO;YACjB,IAAI,OAAO,QAAQ,KAAK,QAAQ;gBAAE,OAAO;YACzC,OAAO,GAAG,QAAQ,CAAC;YACnB,aAAa,EAAE,CAAC;QACjB,CAAC;QACD,KAAK,CAAC,KAAK;YACV,IAAI,IAAI;gBAAE,OAAO;YACjB,UAAU,EAAE,CAAC;YACb,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,QAAgB;YAC9B,IAAI,IAAI;gBAAE,OAAO;YACjB,UAAU,EAAE,CAAC;YACb,IAAI,OAAO,QAAQ,KAAK,QAAQ;gBAAE,OAAO,GAAG,QAAQ,CAAC;YACrD,2EAA2E;YAC3E,6CAA6C;YAC7C,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;YACpB,IAAI,GAAG,IAAI,CAAC;QACb,CAAC;QACD,IAAI;YACH,UAAU,EAAE,CAAC;YACb,IAAI,GAAG,IAAI,CAAC;QACb,CAAC;QACD,UAAU;YACT,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;QACrB,CAAC;QACD,MAAM;YACL,OAAO,IAAI,CAAC;QACb,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Convert agent-style markdown (Brigade agents output this by default) into
3
+ * Discord's message markup.
4
+ *
5
+ * Discord renders a near-CommonMark dialect natively, so — unlike Slack's
6
+ * `mrkdwn` (single-asterisk bold) or Telegram's strict HTML — most agent
7
+ * markdown passes through UNCHANGED:
8
+ * - **bold** / __underline__ → kept (Discord: `**bold**`, `__underline__`)
9
+ * - *italic* / _italic_ → kept
10
+ * - ~~strike~~ → kept
11
+ * - `code` → kept (interior verbatim)
12
+ * - ```fenced``` → kept (interior verbatim, language tag kept)
13
+ * - > quote → kept
14
+ * - `-`/`*`/`+` bullets → kept
15
+ * - 1. numbered lists → kept
16
+ * - # headings → kept (Discord renders `#`/`##`/`###` headers)
17
+ *
18
+ * The ONE load-bearing transform: a markdown link `[label](url)` renders
19
+ * LITERALLY in a normal Discord message (Discord only auto-links bare URLs and
20
+ * honours `[label](url)` in embeds, not plain content). So a plain-message link
21
+ * is rewritten to `label (url)` — the same readable fallback Slack/Telegram use
22
+ * when a link can't be a native token. A bare URL is left as-is (Discord
23
+ * auto-links it).
24
+ *
25
+ * MENTION PASSTHROUGH (critical): an agent that authored a Discord mention token
26
+ * means it to PING. Discord's tokens are `<@123>` (user), `<@!123>` (member
27
+ * nickname), `<@&123>` (role), `<#123>` (channel), `<:name:123>` / `<a:name:123>`
28
+ * (custom / animated emoji), and the literal `@everyone` / `@here`. Those are
29
+ * passed through VERBATIM so the mention actually resolves; everything else is
30
+ * left as Discord-native markdown. We do NOT entity-escape (Discord has no HTML
31
+ * entities) — we only neutralize a STRAY markdown link.
32
+ *
33
+ * Pure / deterministic — no I/O, no globals. Models the SHAPE of
34
+ * `slack/format.ts` (markdown in → channel-native formatting out) but emits
35
+ * Discord markup.
36
+ */
37
+ /** Discord's hard per-message content limit (chars). Sends chunk under this. */
38
+ export declare const DISCORD_MESSAGE_LIMIT = 2000;
39
+ /**
40
+ * Convert agent-style markdown into Discord markup. Block structure (fenced code,
41
+ * tables) is handled line-by-line so a link inside a code fence is never
42
+ * rewritten; inline markup (links, mentions, code) is handled per-line by
43
+ * {@link renderInlineSpans}. Headings / bullets / numbered lists / blockquotes
44
+ * render natively on Discord, so they pass through with only their inline spans
45
+ * rewritten.
46
+ */
47
+ export declare function markdownToDiscord(markdown: string): string;
48
+ /**
49
+ * Rewrite a plain `@handle` to its `<@id>` mention token — but ONLY when
50
+ * `resolve(handle)` returns a known user id. An unknown handle stays literal
51
+ * (we never invent a ping). Skips:
52
+ * - code spans / fenced blocks (an `@alex` there is verbatim, not a mention);
53
+ * - `@everyone` / `@here` (Discord-reserved, and `<@…>`-safe-mentions never
54
+ * parse "everyone" anyway);
55
+ * - a handle already sitting inside a `<@…>` token (the boundary group means
56
+ * the `@` after `<` is never a candidate start).
57
+ *
58
+ * Called by the adapter BEFORE `markdownToDiscord` so a freshly-minted `<@id>`
59
+ * passes through the converter as a verbatim mention token. Pure over its
60
+ * `resolve` argument — the resolver carries the account-scoped directory cache.
61
+ */
62
+ export declare function rewriteKnownMentions(text: string, resolve: (handle: string) => string | undefined): string;
63
+ /**
64
+ * True when the rendered Discord text carries no visible content — only
65
+ * whitespace and/or markdown markers. Discord rejects an empty message body, so
66
+ * the send path falls back / skips when this returns true. Mirrors
67
+ * `slackMrkdwnIsEmpty` intent.
68
+ */
69
+ export declare function discordTextIsEmpty(text: string): boolean;
70
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAmI1C;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA4C1D;AAqBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,MAAM,CAuB1G;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAWxD"}
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Convert agent-style markdown (Brigade agents output this by default) into
3
+ * Discord's message markup.
4
+ *
5
+ * Discord renders a near-CommonMark dialect natively, so — unlike Slack's
6
+ * `mrkdwn` (single-asterisk bold) or Telegram's strict HTML — most agent
7
+ * markdown passes through UNCHANGED:
8
+ * - **bold** / __underline__ → kept (Discord: `**bold**`, `__underline__`)
9
+ * - *italic* / _italic_ → kept
10
+ * - ~~strike~~ → kept
11
+ * - `code` → kept (interior verbatim)
12
+ * - ```fenced``` → kept (interior verbatim, language tag kept)
13
+ * - > quote → kept
14
+ * - `-`/`*`/`+` bullets → kept
15
+ * - 1. numbered lists → kept
16
+ * - # headings → kept (Discord renders `#`/`##`/`###` headers)
17
+ *
18
+ * The ONE load-bearing transform: a markdown link `[label](url)` renders
19
+ * LITERALLY in a normal Discord message (Discord only auto-links bare URLs and
20
+ * honours `[label](url)` in embeds, not plain content). So a plain-message link
21
+ * is rewritten to `label (url)` — the same readable fallback Slack/Telegram use
22
+ * when a link can't be a native token. A bare URL is left as-is (Discord
23
+ * auto-links it).
24
+ *
25
+ * MENTION PASSTHROUGH (critical): an agent that authored a Discord mention token
26
+ * means it to PING. Discord's tokens are `<@123>` (user), `<@!123>` (member
27
+ * nickname), `<@&123>` (role), `<#123>` (channel), `<:name:123>` / `<a:name:123>`
28
+ * (custom / animated emoji), and the literal `@everyone` / `@here`. Those are
29
+ * passed through VERBATIM so the mention actually resolves; everything else is
30
+ * left as Discord-native markdown. We do NOT entity-escape (Discord has no HTML
31
+ * entities) — we only neutralize a STRAY markdown link.
32
+ *
33
+ * Pure / deterministic — no I/O, no globals. Models the SHAPE of
34
+ * `slack/format.ts` (markdown in → channel-native formatting out) but emits
35
+ * Discord markup.
36
+ */
37
+ /** Discord's hard per-message content limit (chars). Sends chunk under this. */
38
+ export const DISCORD_MESSAGE_LIMIT = 2000;
39
+ /**
40
+ * A pre-formed Discord token that must pass through verbatim so it resolves:
41
+ * - `<@123>` / `<@!123>` user / member mention
42
+ * - `<@&123>` role mention
43
+ * - `<#123>` channel mention
44
+ * - `<:name:123>` / `<a:name:123>` custom / animated emoji
45
+ * - `<t:123>` / `<t:123:R>` timestamp
46
+ * Anchored at the `<` the scanner is sitting on. Returns the matched token
47
+ * length, or 0 when the `<…>` isn't a recognised Discord token.
48
+ */
49
+ function matchDiscordToken(text, start) {
50
+ // Find the closing `>` for this `<`.
51
+ const close = text.indexOf(">", start + 1);
52
+ if (close === -1)
53
+ return 0;
54
+ const inner = text.slice(start + 1, close);
55
+ // user / member: @123 or @!123 ; role: @&123 ; channel: #123
56
+ if (/^@!?\d+$/.test(inner))
57
+ return close - start + 1;
58
+ if (/^@&\d+$/.test(inner))
59
+ return close - start + 1;
60
+ if (/^#\d+$/.test(inner))
61
+ return close - start + 1;
62
+ // custom / animated emoji: :name:123 or a:name:123
63
+ if (/^a?:[A-Za-z0-9_]{2,32}:\d+$/.test(inner))
64
+ return close - start + 1;
65
+ // timestamp: t:123 or t:123:R
66
+ if (/^t:\d+(?::[tTdDfFR])?$/.test(inner))
67
+ return close - start + 1;
68
+ return 0;
69
+ }
70
+ /** Try to match a `[label](url)` link starting at `start` (the `[`). */
71
+ function matchMarkdownLink(text, start) {
72
+ const labelEnd = text.indexOf("]", start + 1);
73
+ if (labelEnd === -1)
74
+ return null;
75
+ if (text[labelEnd + 1] !== "(")
76
+ return null;
77
+ // Balanced-paren scan from just after the opening `(` so a URL that itself
78
+ // contains parentheses (e.g. `…/Mercury_(planet)`) keeps its closing `)`. A
79
+ // plain `indexOf(")")` would truncate at the FIRST `)`, dropping the rest.
80
+ let depth = 1;
81
+ let urlEnd = -1;
82
+ for (let j = labelEnd + 2; j < text.length; j++) {
83
+ const c = text[j];
84
+ if (c === "(")
85
+ depth += 1;
86
+ else if (c === ")") {
87
+ depth -= 1;
88
+ if (depth === 0) {
89
+ urlEnd = j;
90
+ break;
91
+ }
92
+ }
93
+ }
94
+ if (urlEnd === -1)
95
+ return null;
96
+ const label = text.slice(start + 1, labelEnd);
97
+ const url = text.slice(labelEnd + 2, urlEnd).trim();
98
+ // Only honour http/https/mailto/tel links — anything else stays literal so we
99
+ // never linkify a path / fragment that wasn't a real URL.
100
+ if (!/^(https?:\/\/|mailto:|tel:)/i.test(url))
101
+ return null;
102
+ if (!label)
103
+ return null;
104
+ return { label, url, end: urlEnd + 1 };
105
+ }
106
+ /**
107
+ * Render the INLINE span markup of one already-newline-free run into Discord
108
+ * markup. Inline code is preserved verbatim (its interior is never rewritten),
109
+ * Discord mention/emoji tokens pass through verbatim, and a markdown link is
110
+ * rewritten to the readable `label (url)` fallback. Everything else is passed
111
+ * through unchanged (Discord renders **bold** / *italic* / ~~strike~~ natively).
112
+ */
113
+ function renderInlineSpans(text) {
114
+ const out = [];
115
+ let i = 0;
116
+ const n = text.length;
117
+ while (i < n) {
118
+ const ch = text[i];
119
+ // Inline code: `…` — interior is verbatim (no link rewrite inside code).
120
+ if (ch === "`") {
121
+ // Support a run of backticks (`` `x` `` / ``` ``y`` ``` ) — match the
122
+ // same-length closing fence so an interior backtick doesn't close early.
123
+ const fenceMatch = /^`+/.exec(text.slice(i));
124
+ const fence = fenceMatch ? fenceMatch[0] : "`";
125
+ const close = text.indexOf(fence, i + fence.length);
126
+ if (close !== -1) {
127
+ out.push(text.slice(i, close + fence.length));
128
+ i = close + fence.length;
129
+ continue;
130
+ }
131
+ }
132
+ // Pre-formed Discord token (mention / channel / role / emoji / timestamp) —
133
+ // pass through verbatim so it actually resolves on Discord.
134
+ if (ch === "<") {
135
+ const len = matchDiscordToken(text, i);
136
+ if (len > 0) {
137
+ out.push(text.slice(i, i + len));
138
+ i += len;
139
+ continue;
140
+ }
141
+ }
142
+ // Markdown link: [label](url) → "label (url)" (a plain Discord message
143
+ // renders the markdown-link form literally, so we degrade to readable text).
144
+ if (ch === "[") {
145
+ const link = matchMarkdownLink(text, i);
146
+ if (link) {
147
+ // When the label already equals the url, just emit the bare url
148
+ // (Discord auto-links it) to avoid the noisy "url (url)".
149
+ out.push(link.label === link.url ? link.url : `${link.label} (${link.url})`);
150
+ i = link.end;
151
+ continue;
152
+ }
153
+ }
154
+ out.push(ch ?? "");
155
+ i += 1;
156
+ }
157
+ return out.join("");
158
+ }
159
+ /** Render a markdown pipe-table block as flat "cell | cell" lines. */
160
+ function renderTableBlock(block) {
161
+ const rows = [];
162
+ for (const row of block) {
163
+ // Drop the separator row (`| --- | :--: |`).
164
+ if (/^\s*\|?[\s|:-]+\|?\s*$/.test(row))
165
+ continue;
166
+ const cells = row
167
+ .trim()
168
+ .replace(/^\||\|$/g, "")
169
+ .split("|")
170
+ .map((c) => c.trim())
171
+ .filter(Boolean);
172
+ if (cells.length)
173
+ rows.push(cells.map((c) => renderInlineSpans(c)).join(" | "));
174
+ }
175
+ return rows.join("\n");
176
+ }
177
+ /**
178
+ * Convert agent-style markdown into Discord markup. Block structure (fenced code,
179
+ * tables) is handled line-by-line so a link inside a code fence is never
180
+ * rewritten; inline markup (links, mentions, code) is handled per-line by
181
+ * {@link renderInlineSpans}. Headings / bullets / numbered lists / blockquotes
182
+ * render natively on Discord, so they pass through with only their inline spans
183
+ * rewritten.
184
+ */
185
+ export function markdownToDiscord(markdown) {
186
+ if (!markdown)
187
+ return "";
188
+ const lines = markdown.split("\n");
189
+ const out = [];
190
+ let i = 0;
191
+ const n = lines.length;
192
+ while (i < n) {
193
+ const line = lines[i] ?? "";
194
+ // Fenced code block: ```lang … ``` — kept VERBATIM (language tag + interior),
195
+ // Discord renders it natively and a link inside must not be rewritten.
196
+ const fenceOpen = /^\s*```/.exec(line);
197
+ if (fenceOpen) {
198
+ out.push(line);
199
+ i += 1;
200
+ while (i < n) {
201
+ const inner = lines[i] ?? "";
202
+ out.push(inner);
203
+ i += 1;
204
+ if (/^\s*```\s*$/.test(inner))
205
+ break;
206
+ }
207
+ continue;
208
+ }
209
+ // Pipe-table block: ≥2 contiguous lines that start+end with `|`. Discord has
210
+ // no table rendering, so flatten to "cell | cell" lines (parity w/ Slack).
211
+ if (/^\s*\|.*\|\s*$/.test(line)) {
212
+ const start = i;
213
+ while (i < n && /^\s*\|.*\|\s*$/.test(lines[i] ?? ""))
214
+ i += 1;
215
+ if (i - start >= 2) {
216
+ out.push(renderTableBlock(lines.slice(start, i)));
217
+ continue;
218
+ }
219
+ i = start;
220
+ }
221
+ // Plain / heading / bullet / quote line — render inline spans; the block
222
+ // markers (`#`, `-`, `>`, `1.`) are left in place for Discord to render.
223
+ out.push(renderInlineSpans(line));
224
+ i += 1;
225
+ }
226
+ return out.join("\n");
227
+ }
228
+ /**
229
+ * Code-span matcher: a fenced ```…``` block OR an inline `…` span. Used by
230
+ * {@link rewriteKnownMentions} to SKIP rewriting handles that live inside code
231
+ * (where an `@alex` is verbatim text, not a mention the author wants pinged).
232
+ */
233
+ const CODE_SEGMENT_PATTERN = /```[\s\S]*?```|`[^`\n]*`/g;
234
+ /**
235
+ * A plain `@handle` candidate OUTSIDE a code span. Group 1 is the preceding
236
+ * boundary char (start-of-string or a separator) so we never match an `@` glued
237
+ * to a word (an email's `@`, or a `<@id>` token's `@`); group 2 is the handle
238
+ * body (Discord usernames: letters/digits/underscore/dot/hyphen, 2–32 chars,
239
+ * with an optional legacy `#1234` discriminator).
240
+ */
241
+ const MENTION_CANDIDATE_PATTERN = /(^|[\s([{"'.,;:!?])@([a-z0-9_.-]{2,32}(?:#[0-9]{4})?)/gi;
242
+ /** Discord resolves these itself — never rewrite them to a user token. */
243
+ const RESERVED_MENTIONS = new Set(["everyone", "here"]);
244
+ /**
245
+ * Rewrite a plain `@handle` to its `<@id>` mention token — but ONLY when
246
+ * `resolve(handle)` returns a known user id. An unknown handle stays literal
247
+ * (we never invent a ping). Skips:
248
+ * - code spans / fenced blocks (an `@alex` there is verbatim, not a mention);
249
+ * - `@everyone` / `@here` (Discord-reserved, and `<@…>`-safe-mentions never
250
+ * parse "everyone" anyway);
251
+ * - a handle already sitting inside a `<@…>` token (the boundary group means
252
+ * the `@` after `<` is never a candidate start).
253
+ *
254
+ * Called by the adapter BEFORE `markdownToDiscord` so a freshly-minted `<@id>`
255
+ * passes through the converter as a verbatim mention token. Pure over its
256
+ * `resolve` argument — the resolver carries the account-scoped directory cache.
257
+ */
258
+ export function rewriteKnownMentions(text, resolve) {
259
+ if (!text || !text.includes("@"))
260
+ return text;
261
+ const rewriteOutsideCode = (segment) => segment.replace(MENTION_CANDIDATE_PATTERN, (match, boundary, handle) => {
262
+ const lookup = handle.toLowerCase();
263
+ if (RESERVED_MENTIONS.has(lookup.replace(/#[0-9]{4}$/, "")))
264
+ return match;
265
+ const id = resolve(handle);
266
+ if (!id || !/^\d+$/.test(id))
267
+ return match;
268
+ return `${boundary}<@${id}>`;
269
+ });
270
+ // Walk the string, leaving every code segment untouched and rewriting the
271
+ // gaps between them.
272
+ let out = "";
273
+ let offset = 0;
274
+ CODE_SEGMENT_PATTERN.lastIndex = 0;
275
+ for (const m of text.matchAll(CODE_SEGMENT_PATTERN)) {
276
+ const idx = m.index ?? 0;
277
+ out += rewriteOutsideCode(text.slice(offset, idx));
278
+ out += m[0];
279
+ offset = idx + m[0].length;
280
+ }
281
+ out += rewriteOutsideCode(text.slice(offset));
282
+ return out;
283
+ }
284
+ /**
285
+ * True when the rendered Discord text carries no visible content — only
286
+ * whitespace and/or markdown markers. Discord rejects an empty message body, so
287
+ * the send path falls back / skips when this returns true. Mirrors
288
+ * `slackMrkdwnIsEmpty` intent.
289
+ */
290
+ export function discordTextIsEmpty(text) {
291
+ if (!text)
292
+ return true;
293
+ const stripped = text
294
+ // Mentions / channels / roles / emoji ARE content.
295
+ .replace(/<a?:[A-Za-z0-9_]+:\d+>/g, "x")
296
+ .replace(/<@[!&]?\d+>/g, "x")
297
+ .replace(/<#\d+>/g, "x")
298
+ .replace(/<t:\d+(?::[tTdDfFR])?>/g, "x")
299
+ .replace(/[*_~`>#|.\-\s]/g, "")
300
+ .trim();
301
+ return stripped.length === 0;
302
+ }
303
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Discord guild listing — the cheap `GET /users/@me/guilds` call the directory
3
+ * + diagnostics build on.
4
+ *
5
+ * Like `probe.ts` this is a SELF-CONTAINED REST call (no `discord.js`, no
6
+ * Gateway socket): a single authenticated GET that returns the bot's joined
7
+ * guilds. The directory walks these to enumerate members + channels; the
8
+ * permission audit doesn't need them but other diagnostics might. Injectable
9
+ * fetch (tests stub it); never throws — a failed call returns `[]`.
10
+ */
11
+ /** One guild the bot belongs to (the subset callers read). */
12
+ export interface DiscordGuildSummary {
13
+ /** Guild (server) id (snowflake). */
14
+ id: string;
15
+ /** Guild name. */
16
+ name: string;
17
+ }
18
+ /**
19
+ * List the guilds the bot is a member of via `GET /users/@me/guilds`. Returns
20
+ * `{ id, name }` rows, skipping malformed entries. Best-effort: a network error
21
+ * / non-ok / unparseable body returns `[]` (the caller degrades gracefully).
22
+ * The token rides in the `Authorization: Bot <token>` header; never logged.
23
+ */
24
+ export declare function listDiscordGuilds(token: string, fetchImpl?: typeof fetch): Promise<DiscordGuildSummary[]>;
25
+ //# sourceMappingURL=guilds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guilds.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/guilds.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IACnC,qCAAqC;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,kBAAkB;IAClB,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACtC,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,OAAO,KAAa,GAC7B,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAsBhC"}