@geminixiang/mikan 1.0.0-beta.40 → 1.0.0-beta.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +7 -6
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/discord/components.d.ts +33 -0
- package/dist/adapters/discord/components.d.ts.map +1 -0
- package/dist/adapters/discord/components.js +32 -0
- package/dist/adapters/discord/components.js.map +1 -0
- package/dist/adapters/discord/context.d.ts.map +1 -1
- package/dist/adapters/discord/context.js +16 -6
- package/dist/adapters/discord/context.js.map +1 -1
- package/dist/adapters/discord/format.d.ts +3 -0
- package/dist/adapters/discord/format.d.ts.map +1 -0
- package/dist/adapters/discord/format.js +86 -0
- package/dist/adapters/discord/format.js.map +1 -0
- package/dist/adapters/markdown-tables.d.ts +37 -0
- package/dist/adapters/markdown-tables.d.ts.map +1 -0
- package/dist/adapters/markdown-tables.js +164 -0
- package/dist/adapters/markdown-tables.js.map +1 -0
- package/dist/adapters/progressive-renderer.d.ts.map +1 -1
- package/dist/adapters/progressive-renderer.js +69 -12
- package/dist/adapters/progressive-renderer.js.map +1 -1
- package/dist/adapters/slack/assistant.d.ts +101 -0
- package/dist/adapters/slack/assistant.d.ts.map +1 -0
- package/dist/adapters/slack/assistant.js +204 -0
- package/dist/adapters/slack/assistant.js.map +1 -0
- package/dist/adapters/slack/blocks.d.ts.map +1 -1
- package/dist/adapters/slack/blocks.js +4 -82
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +52 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +161 -5
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/response-lifecycle.d.ts.map +1 -1
- package/dist/adapters/slack/response-lifecycle.js +86 -15
- package/dist/adapters/slack/response-lifecycle.js.map +1 -1
- package/dist/adapters/slack/stream-limits.d.ts +35 -0
- package/dist/adapters/slack/stream-limits.d.ts.map +1 -0
- package/dist/adapters/slack/stream-limits.js +56 -0
- package/dist/adapters/slack/stream-limits.js.map +1 -0
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +26 -47
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/telegram/context.d.ts.map +1 -1
- package/dist/adapters/telegram/context.js +7 -19
- package/dist/adapters/telegram/context.js.map +1 -1
- package/dist/adapters/types.d.ts +24 -1
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +2 -1
- package/dist/agent.js.map +1 -1
- package/dist/commands/extensions.d.ts +18 -6
- package/dist/commands/extensions.d.ts.map +1 -1
- package/dist/commands/extensions.js +55 -8
- package/dist/commands/extensions.js.map +1 -1
- package/dist/env-manifest.d.ts.map +1 -1
- package/dist/env-manifest.js +4 -0
- package/dist/env-manifest.js.map +1 -1
- package/dist/harness/subagent-runner.d.ts +6 -0
- package/dist/harness/subagent-runner.d.ts.map +1 -1
- package/dist/harness/subagent-runner.js +63 -0
- package/dist/harness/subagent-runner.js.map +1 -1
- package/dist/subagent-progress.d.ts +3 -7
- package/dist/subagent-progress.d.ts.map +1 -1
- package/dist/subagent-progress.js +15 -10
- package/dist/subagent-progress.js.map +1 -1
- package/dist/tools/subagent.d.ts +8 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +24 -1
- package/dist/tools/subagent.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/web/admin/portal.js +5 -1
- package/dist/web/admin/portal.js.map +1 -1
- package/package.json +2 -1
- package/dist/adapters/telegram/html.d.ts +0 -3
- package/dist/adapters/telegram/html.d.ts.map +0 -1
- package/dist/adapters/telegram/html.js +0 -101
- package/dist/adapters/telegram/html.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,32 @@ any release.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [1.0.0-beta.41]
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Add Slack App and Agent manifests plus support for `assistant_view` and `agent_view` lifecycle events, including thread-aware assistant context and optional socket-event tracing.
|
|
17
|
+
- Add Discord Components V2 responses with native sections, separators, containers, and markdown-table rendering suited to Discord's width model.
|
|
18
|
+
- Add rich Telegram responses through platform-native formatting.
|
|
19
|
+
- Show the current activity of running subagents in progress dashboards.
|
|
20
|
+
- Add an end-user Agent Project Manager guide in English, Japanese, Simplified Chinese, and Traditional Chinese.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Pace progressive redraws by elapsed time instead of response volume, and edit overflow messages rather than repeatedly posting replacements.
|
|
25
|
+
- Split Slack streamed text at the 12,000-character API limit, decline unsupported native streams before starting them, and hand off a truncated final tail exactly once.
|
|
26
|
+
- Report package-installed extensions in `/extensions` and return a clear compatibility error when an extension requires a newer mikan version.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Preserve final-source preparation during progressive replacement and cut truncated Slack messages at line boundaries.
|
|
31
|
+
- Distinguish Slack length-limit failures from other send errors instead of treating every failure as oversized content.
|
|
32
|
+
- Prevent desktop automation helpers from sending into a Slack thread's main composer.
|
|
33
|
+
|
|
34
|
+
### Tests
|
|
35
|
+
|
|
36
|
+
- Add coverage for Slack assistant lifecycle and stream limits, Discord Components V2 and table formatting, progressive overflow editing, rich Telegram context, subagent activity reporting, and extension compatibility reporting.
|
|
37
|
+
|
|
12
38
|
## [1.0.0-beta.40]
|
|
13
39
|
|
|
14
40
|
### Added
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/discord/bot.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,UAAU,EAEf,KAAK,UAAU,EAKhB,MAAM,YAAY,CAAC;AAIpB,OAAO,KAAK,EAEV,iBAAiB,EACjB,qBAAqB,EAGtB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/discord/bot.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApplicationCommandOptionType, Client, Events, GatewayIntentBits, Partials, } from "discord.js";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { basename } from "node:path";
|
|
4
|
+
import { discordTextPayload } from "./components.js";
|
|
4
5
|
import { createConversationEvent, createConversationMessage, } from "../../adapter.js";
|
|
5
6
|
import * as log from "../../log.js";
|
|
6
7
|
import { createOfficeAddress } from "../../office/index.js";
|
|
@@ -88,7 +89,7 @@ export class DiscordMessagingBot {
|
|
|
88
89
|
async postMessage(channel, text) {
|
|
89
90
|
return discordRetry(async () => {
|
|
90
91
|
const ch = await this.fetchTextChannel(channel);
|
|
91
|
-
const msg = await ch.send(text);
|
|
92
|
+
const msg = await ch.send(discordTextPayload(text));
|
|
92
93
|
return msg.id;
|
|
93
94
|
});
|
|
94
95
|
}
|
|
@@ -122,7 +123,7 @@ export class DiscordMessagingBot {
|
|
|
122
123
|
return {
|
|
123
124
|
name: "discord",
|
|
124
125
|
trustModel: "membership",
|
|
125
|
-
formattingGuide: "## Discord Formatting (Markdown)\nBold: **text**, Italic: *text*, Code: `code`, Block: ```language\ncode```\nLinks: [text](url), Spoiler: ||text
|
|
126
|
+
formattingGuide: "## Discord Formatting (Markdown)\nBold: **text**, Italic: *text*, Code: `code`, Block: ```language\ncode```\nLinks: [text](url), Spoiler: ||text||, Headings: # ## ###\nWrite ordinary Markdown, including tables — the adapter converts anything Discord cannot render. Use code blocks for code.",
|
|
126
127
|
channels: this.getAllChannels(),
|
|
127
128
|
users: this.getAllUsers(),
|
|
128
129
|
diagnostics: {
|
|
@@ -137,14 +138,14 @@ export class DiscordMessagingBot {
|
|
|
137
138
|
return discordRetry(async () => {
|
|
138
139
|
const ch = await this.fetchTextChannel(channelId);
|
|
139
140
|
const msg = await ch.messages.fetch(messageId);
|
|
140
|
-
await msg.edit(text);
|
|
141
|
+
await msg.edit(discordTextPayload(text));
|
|
141
142
|
});
|
|
142
143
|
}
|
|
143
144
|
async postReply(channelId, replyToId, text) {
|
|
144
145
|
return discordRetry(async () => {
|
|
145
146
|
const ch = await this.fetchTextChannel(channelId);
|
|
146
147
|
const replyTarget = await ch.messages.fetch(replyToId);
|
|
147
|
-
const sent = await replyTarget.reply(text);
|
|
148
|
+
const sent = await replyTarget.reply(discordTextPayload(text));
|
|
148
149
|
return sent.id;
|
|
149
150
|
});
|
|
150
151
|
}
|
|
@@ -154,7 +155,7 @@ export class DiscordMessagingBot {
|
|
|
154
155
|
const thread = await this.client.channels.fetch(threadOrMessageId);
|
|
155
156
|
if (thread && (thread.isThread() || thread.isTextBased())) {
|
|
156
157
|
return discordRetry(async () => {
|
|
157
|
-
const msg = await thread.send(text);
|
|
158
|
+
const msg = await thread.send(discordTextPayload(text));
|
|
158
159
|
return msg.id;
|
|
159
160
|
});
|
|
160
161
|
}
|
|
@@ -194,7 +195,7 @@ export class DiscordMessagingBot {
|
|
|
194
195
|
async sendDirectMessage(userId, text) {
|
|
195
196
|
return discordRetry(async () => {
|
|
196
197
|
const user = await this.client.users.fetch(userId);
|
|
197
|
-
const msg = await user.send(text);
|
|
198
|
+
const msg = await user.send(discordTextPayload(text));
|
|
198
199
|
return msg.id;
|
|
199
200
|
});
|
|
200
201
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot.js","sourceRoot":"","sources":["../../../src/adapters/discord/bot.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApplicationCommandOptionType,\n Client,\n Events,\n GatewayIntentBits,\n Partials,\n type ChatInputCommandInteraction,\n type Collection,\n type Message,\n type Attachment,\n type TextChannel,\n type DMChannel,\n type NewsChannel,\n type ThreadChannel,\n} from \"discord.js\";\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\n\nimport type {\n ConversationContext,\n ConversationEvent,\n MessagingEventHandler,\n ConversationResponder,\n ChatToolResult,\n} from \"../../adapter.js\";\nimport {\n createConversationEvent,\n createConversationMessage,\n type MessagingBot,\n type MessagingInfo,\n} from \"../../adapter.js\";\nimport type { DiscordEvent } from \"./types.js\";\nimport * as log from \"../../log.js\";\nimport { createOfficeAddress, type Workspace } from \"../../office/index.js\";\nimport { resolveChatSessionKey } from \"../../sessions/session-key.js\";\nimport { formatNothingRunning } from \"../../platform-messages.js\";\nimport {\n appendBotResponseLog,\n appendChannelLog,\n MessagingEventQueue,\n downloadUrlToFile,\n resolveOnlyScopedStopTarget,\n resolveStopTarget,\n saveIncomingAttachments,\n withRetry,\n type IncomingAttachment,\n} from \"../shared.js\";\nimport { COMMAND_MANIFEST } from \"../../commands/manifest.js\";\nimport { processMessageIntake } from \"../intake.js\";\nimport { createDiscordAdapters } from \"./context.js\";\n\n// discord.js: DiscordAPIError exposes `.status` (HTTP status) and a `.code`.\n// RateLimitError fires when the internal queue gives up. MessagingBoth should retry.\nfunction discordIsRateLimited(err: Error): boolean {\n if ((err as { status?: number }).status === 429) return true;\n if ((err as { httpStatus?: number }).httpStatus === 429) return true;\n if (err.name === \"RateLimitError\") return true;\n return false;\n}\n\nconst discordRetry = <T>(fn: () => Promise<T>): Promise<T> =>\n withRetry(fn, { isRateLimited: discordIsRateLimited });\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type { DiscordEvent } from \"./types.js\";\n\n// ============================================================================\n// DiscordMessagingBot\n// ============================================================================\n\nexport class DiscordMessagingBot implements MessagingBot {\n private client: Client;\n private handler: MessagingEventHandler;\n private token: string;\n private workspace: Workspace;\n private botUserId: string | null = null;\n private queues = new Map<string, MessagingEventQueue>();\n private startupTime: number = 0;\n private channels = new Map<string, { id: string; name: string }>();\n private users = new Map<string, { id: string; userName: string; displayName: string }>();\n\n constructor(handler: MessagingEventHandler, config: { token: string; workspace: Workspace }) {\n this.handler = handler;\n this.token = config.token;\n this.workspace = config.workspace;\n this.client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n ],\n partials: [Partials.Channel, Partials.Message],\n });\n }\n\n // ==========================================================================\n // Public API (implements MessagingBot)\n // ==========================================================================\n\n async start(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.client.once(Events.ClientReady, async (readyClient) => {\n this.botUserId = readyClient.user.id;\n this.startupTime = Date.now();\n log.logConnected(\"Discord\");\n log.logInfo(`Discord bot started as ${readyClient.user.tag}`);\n this.loadCachedGuildData();\n this.setupEventHandlers();\n try {\n // Registration derives from the command manifest; routing below\n // handles new/stop natively and the rest through one generic path.\n await readyClient.application.commands.set(\n COMMAND_MANIFEST.filter((entry) => entry.discord).map((entry) =>\n Object.assign(\n {\n name: entry.name,\n description: entry.description,\n },\n entry.arg\n ? {\n options: [\n {\n name: entry.arg.name,\n description: entry.arg.description,\n type: ApplicationCommandOptionType.String as const,\n required: entry.arg.required,\n },\n ],\n }\n : {},\n ),\n ),\n );\n } catch (err) {\n log.logWarning(\n \"Failed to register Discord slash commands\",\n err instanceof Error ? err.message : String(err),\n );\n }\n resolve();\n });\n this.client.once(Events.Error, reject);\n this.client.login(this.token).catch(reject);\n });\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channel);\n const msg = await ch.send(text);\n return msg.id;\n });\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n await this.updateMessageRaw(channel, ts, text);\n }\n\n async addReaction(channel: string, messageTs: string, emoji: string): Promise<void> {\n // Discord uses Unicode emoji directly (e.g. \"👀\"); accept a bare name too\n // but Slack-style short names won't resolve, so pass through as-is.\n await discordRetry(async () => {\n const ch = await this.fetchTextChannel(channel);\n const msg = await ch.messages.fetch(messageTs);\n await msg.react(emoji.replace(/^:|:$/g, \"\"));\n });\n }\n\n enqueueEvent(event: ConversationEvent): boolean {\n const conversationId = event.conversationId;\n const queue = this.getQueue(conversationId);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const context = createDiscordAdapters(event as DiscordEvent, this);\n return this.handler.handleEvent(event, this, context);\n });\n return true;\n }\n\n getMessagingInfo(): MessagingInfo {\n return {\n name: \"discord\",\n trustModel: \"membership\",\n formattingGuide:\n \"## Discord Formatting (Markdown)\\nBold: **text**, Italic: *text*, Code: `code`, Block: ```language\\ncode```\\nLinks: [text](url), Spoiler: ||text||\\nKeep messages under 2000 characters. Use code blocks for code.\",\n channels: this.getAllChannels(),\n users: this.getAllUsers(),\n diagnostics: {\n showUsageSummary: false,\n },\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async updateMessageRaw(channelId: string, messageId: string, text: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.edit(text);\n });\n }\n\n async postReply(channelId: string, replyToId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const replyTarget = await ch.messages.fetch(replyToId);\n const sent = await replyTarget.reply(text);\n return sent.id;\n });\n }\n\n async postInThread(channelId: string, threadOrMessageId: string, text: string): Promise<string> {\n // Try as a thread channel first, then fall back to posting in the channel\n try {\n const thread = await this.client.channels.fetch(threadOrMessageId);\n if (thread && (thread.isThread() || thread.isTextBased())) {\n return discordRetry(async () => {\n const msg = await (thread as ThreadChannel).send(text);\n return msg.id;\n });\n }\n } catch {\n // Not a thread channel, treat as message ID for reply\n }\n return this.postReply(channelId, threadOrMessageId, text);\n }\n\n async deleteMessageRaw(channelId: string, messageId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.delete();\n } catch {\n // Ignore if already deleted\n }\n }\n\n async sendTyping(channelId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n await ch.sendTyping();\n } catch {\n // Non-fatal\n }\n }\n\n async uploadFile(channelId: string, filePath: string, title?: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await ch.send({ files: [{ attachment: fileContent, name: fileName }] });\n });\n }\n\n async sendDirectMessage(userId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const user = await this.client.users.fetch(userId);\n const msg = await user.send(text);\n return msg.id;\n });\n }\n\n async postPrivate(_conversationId: string, userId: string, text: string): Promise<void> {\n await this.sendDirectMessage(userId, text);\n }\n\n getAllChannels(): { id: string; name: string }[] {\n return Array.from(this.channels.values());\n }\n\n getAllUsers(): { id: string; userName: string; displayName: string }[] {\n return Array.from(this.users.values());\n }\n\n logToFile(channelId: string, entry: object): void {\n appendChannelLog(this.workspace.office(createOfficeAddress(\"discord\", channelId)), entry);\n }\n\n logBotResponse(channelId: string, text: string, ts: string): void {\n appendBotResponseLog(\n this.workspace.office(createOfficeAddress(\"discord\", channelId)),\n text,\n ts,\n );\n }\n\n /**\n * Process attachments from a Discord message.\n * Downloads files before returning so the agent can read them immediately.\n */\n async processAttachments(\n channelId: string,\n attachments: Collection<string, Attachment>,\n _messageId: string,\n ): Promise<{ name: string; localPath: string }[]> {\n const items: IncomingAttachment[] = [];\n for (const attachment of attachments.values()) {\n if (!attachment.name) {\n log.logWarning(\"Discord attachment missing name, skipping\", attachment.url);\n continue;\n }\n items.push({\n name: attachment.name,\n download: (destPath) => downloadUrlToFile(attachment.url, destPath),\n });\n }\n\n const office = this.workspace.office(createOfficeAddress(\"discord\", channelId));\n const { saved, failed } = await saveIncomingAttachments(office, items);\n for (const failure of failed) {\n log.logWarning(`Failed to download Discord attachment`, `${failure.name}: ${failure.error}`);\n }\n return saved.map((item) => ({ name: item.original, localPath: item.localPath }));\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): MessagingEventQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new MessagingEventQueue(\"Discord\");\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private resolveStopTarget(channelId: string, sessionKey: string): string | null {\n const address = createOfficeAddress(\"discord\", channelId);\n const directTarget = resolveStopTarget({ handler: this.handler, address, sessionKey });\n if (directTarget) return directTarget;\n if (sessionKey !== channelId) return null;\n return resolveOnlyScopedStopTarget(this.handler, address);\n }\n\n private loadCachedGuildData(): void {\n for (const guild of this.client.guilds.cache.values()) {\n for (const channel of guild.channels.cache.values()) {\n if (channel.isTextBased() && \"name\" in channel) {\n this.channels.set(channel.id, { id: channel.id, name: channel.name ?? channel.id });\n }\n }\n for (const member of guild.members.cache.values()) {\n this.users.set(member.id, {\n id: member.id,\n userName: member.user.username,\n displayName: member.displayName,\n });\n }\n }\n }\n\n private stripMessagingBotMention(text: string): string {\n if (!this.botUserId) return text;\n return text.replace(new RegExp(`<@!?${this.botUserId}>`, \"g\"), \"\").trim();\n }\n\n private resolveConversationContext(input: {\n channelId: string;\n inGuild: boolean;\n isThread: boolean;\n parentChannelId?: string | null;\n referencedMsgId?: string;\n }): { conversationId: string; threadTs?: string } {\n if (!input.inGuild) {\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n if (input.isThread) {\n return {\n conversationId: input.parentChannelId ?? input.channelId,\n threadTs: input.channelId,\n };\n }\n\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n private createSlashCommandAdapters(\n interaction: ChatInputCommandInteraction,\n commandText: string,\n sessionKey: string,\n conversationId: string,\n ): ConversationContext {\n const isDM = !interaction.inGuild();\n const userId = interaction.user.id;\n const userName = interaction.user.username;\n const platform = this.getMessagingInfo();\n const shouldUseEphemeral = !isDM;\n\n const message = createConversationMessage({\n platform: \"discord\",\n conversationId,\n id: interaction.id,\n sessionKey,\n conversationKind: isDM ? \"direct\" : \"shared\",\n userId,\n userName,\n text: commandText,\n attachments: [],\n });\n\n const respondPrivately = async (text: string, replace = false): Promise<void> => {\n if (interaction.replied || interaction.deferred) {\n if (replace) {\n await interaction.editReply({ content: text });\n } else {\n await interaction.followUp({ content: text, ephemeral: shouldUseEphemeral });\n }\n return;\n }\n\n await interaction.reply({ content: text, ephemeral: shouldUseEphemeral });\n };\n\n const responder: ConversationResponder = {\n respond: async (text: string) => {\n await respondPrivately(text);\n },\n replaceResponse: async (text: string) => {\n await respondPrivately(text, true);\n },\n respondDiagnostic: async (text: string) => {\n await respondPrivately(text);\n },\n respondToolResult: async (result: ChatToolResult) => {\n const duration = (result.durationMs / 1000).toFixed(1);\n const formatted = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName} (${duration}s)\\n${result.result}`;\n await respondPrivately(formatted);\n },\n setTyping: async () => {},\n setWorking: async (working: boolean) => {\n if (working && !interaction.replied && !interaction.deferred) {\n await interaction.deferReply({ ephemeral: shouldUseEphemeral });\n }\n },\n uploadFile: async (filePath: string, title?: string) => {\n await this.uploadFile(conversationId, filePath, title);\n },\n deleteResponse: async () => {},\n };\n\n return { address: message.address, message, responder, platform };\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n const manifestEntry = COMMAND_MANIFEST.find(\n (entry) => entry.discord && entry.name === interaction.commandName,\n );\n if (!manifestEntry) return;\n\n const isDM = !interaction.inGuild();\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: interaction.channelId,\n inGuild: interaction.inGuild(),\n isThread: interaction.channel?.isThread() ?? false,\n parentChannelId:\n interaction.channel && \"parentId\" in interaction.channel\n ? interaction.channel.parentId\n : null,\n });\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n messageId: interaction.id,\n persistentTopLevel: true,\n threadTs,\n });\n const commandArg = manifestEntry.arg\n ? interaction.options.getString(manifestEntry.arg.name)?.trim()\n : undefined;\n const commandText = commandArg\n ? `/${interaction.commandName} ${commandArg}`\n : `/${interaction.commandName}`;\n\n this.logToFile(conversationId, {\n date: new Date(interaction.createdTimestamp).toISOString(),\n ts: interaction.id,\n ...(threadTs ? { threadTs } : {}),\n user: interaction.user.id,\n userName: interaction.user.username,\n text: commandText,\n attachments: [],\n isMessagingBot: false,\n });\n\n const context = this.createSlashCommandAdapters(\n interaction,\n commandText,\n sessionKey,\n conversationId,\n );\n try {\n if (interaction.commandName === \"new\") {\n const event = createConversationEvent({\n platform: \"discord\",\n type: \"dm\",\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n ts: interaction.id,\n thread_ts: threadTs,\n sessionKey,\n user: interaction.user.id,\n text: commandText,\n attachments: [],\n });\n await this.handler.handleEvent(event, this, context);\n return;\n }\n\n if (interaction.commandName === \"stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n await this.handler.handleStop(\n createOfficeAddress(\"discord\", conversationId),\n stopTarget,\n this,\n );\n await context.responder.respond(\"Stopped the current conversation.\");\n } else {\n await context.responder.respond(formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n const event = createConversationEvent({\n platform: \"discord\",\n type: \"dm\",\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n ts: interaction.id,\n thread_ts: threadTs,\n sessionKey,\n user: interaction.user.id,\n text: commandText,\n attachments: [],\n });\n\n await this.handler.handleEvent(event, this, context);\n } catch (err) {\n log.logWarning(\n \"Discord slash command error\",\n err instanceof Error ? err.message : String(err),\n );\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: `${interaction.commandName} command failed. Please try again later.`,\n ephemeral: !isDM,\n });\n }\n }\n });\n\n this.client.on(Events.MessageCreate, async (msg: Message) => {\n // Skip messages from before startup\n if (msg.createdTimestamp < this.startupTime) return;\n // Skip bot messages\n if (msg.author.bot) return;\n const isDM = msg.channel.type === 1; // ChannelType.DM = 1\n const isInThread = msg.channel.isThread();\n const referencedMsgId = msg.reference?.messageId;\n const isThreadReply = isInThread || !!referencedMsgId;\n const isMentioned = msg.mentions.users.has(this.botUserId ?? \"\");\n const isAutoReplyCandidate = !isDM && !isMentioned && !isThreadReply;\n\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: msg.channelId,\n inGuild: !isDM,\n isThread: isInThread,\n parentChannelId: \"parentId\" in msg.channel ? msg.channel.parentId : null,\n referencedMsgId,\n });\n const userId = msg.author.id;\n const userName = msg.author.username;\n const msgId = msg.id;\n\n // Track user\n this.users.set(userId, {\n id: userId,\n userName,\n displayName: msg.member?.displayName ?? userName,\n });\n\n // Track channel\n if (!this.channels.has(conversationId) && \"name\" in msg.channel) {\n const ch = msg.channel as TextChannel | NewsChannel;\n this.channels.set(conversationId, { id: conversationId, name: ch.name });\n }\n\n const conversationKind = isDM ? \"direct\" : \"shared\";\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind,\n messageId: msgId,\n persistentTopLevel: true,\n threadTs,\n });\n\n const cleanedText = this.stripMessagingBotMention(msg.content);\n\n const eventBase = createConversationEvent({\n platform: \"discord\",\n type: isDM ? \"dm\" : \"mention\",\n conversationId,\n conversationKind,\n ts: msgId,\n thread_ts: threadTs,\n sessionKey,\n user: userId,\n userName,\n text: cleanedText,\n }) as DiscordEvent;\n\n await processMessageIntake({\n eventBase,\n office: this.workspace.office(eventBase.address),\n isAutoReplyCandidate,\n magicWord: { addressed: !isAutoReplyCandidate, scopeFallback: \"top-level\" },\n busyPolicy: \"queue\",\n logEntryBase: {\n date: msg.createdAt.toISOString(),\n ts: msgId,\n ...(!isDM && threadTs ? { threadTs } : {}),\n user: userId,\n userName,\n text: cleanedText,\n isMessagingBot: false,\n },\n log: (entry) => this.logToFile(conversationId, entry),\n processAttachments: () => this.processAttachments(conversationId, msg.attachments, msgId),\n queueKey: sessionKey,\n enqueue: (queueKey, work) => this.getQueue(queueKey).enqueue(work),\n handler: this.handler,\n bot: this,\n createContext: (event) => createDiscordAdapters(event, this),\n });\n });\n }\n\n private async fetchTextChannel(\n channelId: string,\n ): Promise<TextChannel | DMChannel | NewsChannel | ThreadChannel> {\n const ch = await this.client.channels.fetch(channelId);\n if (!ch || !ch.isTextBased()) {\n throw new Error(`Channel ${channelId} is not a text channel`);\n }\n return ch as TextChannel | DMChannel | NewsChannel | ThreadChannel;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bot.js","sourceRoot":"","sources":["../../../src/adapters/discord/bot.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,MAAM,EACN,MAAM,EACN,iBAAiB,EACjB,QAAQ,GAST,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AASrC,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EACL,uBAAuB,EACvB,yBAAyB,GAG1B,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAkB,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC3B,iBAAiB,EACjB,uBAAuB,EACvB,SAAS,GAEV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAErD,6EAA6E;AAC7E,qFAAqF;AACrF,SAAS,oBAAoB,CAAC,GAAU;IACtC,IAAK,GAA2B,CAAC,MAAM,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC7D,IAAK,GAA+B,CAAC,UAAU,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACrE,IAAI,GAAG,CAAC,IAAI,KAAK,gBAAgB;QAAE,OAAO,IAAI,CAAC;IAC/C,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,YAAY,GAAG,CAAI,EAAoB,EAAc,EAAE,CAC3D,SAAS,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,oBAAoB,EAAE,CAAC,CAAC;AAQzD,+EAA+E;AAC/E,sBAAsB;AACtB,+EAA+E;AAE/E,MAAM,OAAO,mBAAmB;IAW9B,YAAY,OAA8B,EAAE,MAA+C;QANnF,cAAS,GAAkB,IAAI,CAAC;QAChC,WAAM,GAAG,IAAI,GAAG,EAA+B,CAAC;QAChD,gBAAW,GAAW,CAAC,CAAC;QACxB,aAAQ,GAAG,IAAI,GAAG,EAAwC,CAAC;QAC3D,UAAK,GAAG,IAAI,GAAG,EAAiE,CAAC;QAGvF,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC;YACvB,OAAO,EAAE;gBACP,iBAAiB,CAAC,MAAM;gBACxB,iBAAiB,CAAC,aAAa;gBAC/B,iBAAiB,CAAC,cAAc;gBAChC,iBAAiB,CAAC,cAAc;aACjC;YACD,QAAQ,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC;SAC/C,CAAC,CAAC;IACL,CAAC;IAED,6EAA6E;IAC7E,uCAAuC;IACvC,6EAA6E;IAE7E,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE;gBACzD,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC9B,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;gBAC5B,GAAG,CAAC,OAAO,CAAC,0BAA0B,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC9D,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBAC3B,IAAI,CAAC,kBAAkB,EAAE,CAAC;gBAC1B,IAAI,CAAC;oBACH,gEAAgE;oBAChE,mEAAmE;oBACnE,MAAM,WAAW,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CACxC,gBAAgB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAC9D,MAAM,CAAC,MAAM,CACX;wBACE,IAAI,EAAE,KAAK,CAAC,IAAI;wBAChB,WAAW,EAAE,KAAK,CAAC,WAAW;qBAC/B,EACD,KAAK,CAAC,GAAG;wBACP,CAAC,CAAC;4BACE,OAAO,EAAE;gCACP;oCACE,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI;oCACpB,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,WAAW;oCAClC,IAAI,EAAE,4BAA4B,CAAC,MAAe;oCAClD,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ;iCAC7B;6BACF;yBACF;wBACH,CAAC,CAAC,EAAE,CACP,CACF,CACF,CAAC;gBACJ,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,GAAG,CAAC,UAAU,CACZ,2CAA2C,EAC3C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD,CAAC;gBACJ,CAAC;gBACD,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,IAAY;QAC7C,OAAO,YAAY,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAChD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YACpD,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAe,EAAE,EAAU,EAAE,IAAY;QAC3D,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAe,EAAE,SAAiB,EAAE,KAAa;QACjE,0EAA0E;QAC1E,oEAAoE;QACpE,MAAM,YAAY,CAAC,KAAK,IAAI,EAAE;YAC5B,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAChD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC/C,MAAM,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,YAAY,CAAC,KAAwB;QACnC,MAAM,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QAC5C,IAAI,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YACtB,GAAG,CAAC,UAAU,CACZ,wBAAwB,cAAc,iBAAiB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CACrF,CAAC;YACF,OAAO,KAAK,CAAC;QACf,CAAC;QACD,GAAG,CAAC,OAAO,CAAC,wBAAwB,cAAc,KAAK,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACtF,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;YACjB,MAAM,OAAO,GAAG,qBAAqB,CAAC,KAAqB,EAAE,IAAI,CAAC,CAAC;YACnE,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gBAAgB;QACd,OAAO;YACL,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,YAAY;YACxB,eAAe,EACb,oSAAoS;YACtS,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE;YAC/B,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE;YACzB,WAAW,EAAE;gBACX,gBAAgB,EAAE,KAAK;aACxB;SACF,CAAC;IACJ,CAAC;IAED,6EAA6E;IAC7E,wCAAwC;IACxC,6EAA6E;IAE7E,KAAK,CAAC,gBAAgB,CAAC,SAAiB,EAAE,SAAiB,EAAE,IAAY;QACvE,OAAO,YAAY,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC/C,MAAM,GAAG,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,SAAiB,EAAE,SAAiB,EAAE,IAAY;QAChE,OAAO,YAAY,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YACvD,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC,EAAE,CAAC;QACjB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAiB,EAAE,iBAAyB,EAAE,IAAY;QAC3E,0EAA0E;QAC1E,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACnE,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;gBAC1D,OAAO,YAAY,CAAC,KAAK,IAAI,EAAE;oBAC7B,MAAM,GAAG,GAAG,MAAO,MAAwB,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;oBAC3E,OAAO,GAAG,CAAC,EAAE,CAAC;gBAChB,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,sDAAsD;QACxD,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,iBAAiB,EAAE,IAAI,CAAC,CAAC;IAC5D,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,SAAiB,EAAE,SAAiB;QACzD,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC/C,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,4BAA4B;QAC9B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB;QAChC,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,EAAE,CAAC,UAAU,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,YAAY;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,QAAgB,EAAE,KAAc;QAClE,OAAO,YAAY,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;YAClD,MAAM,QAAQ,GAAG,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC7C,MAAM,WAAW,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YAC3C,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAC,MAAc,EAAE,IAAY;QAClD,OAAO,YAAY,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACnD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;YACtD,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,eAAuB,EAAE,MAAc,EAAE,IAAY;QACrE,MAAM,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,cAAc;QACZ,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,WAAW;QACT,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,SAAS,CAAC,SAAiB,EAAE,KAAa;QACxC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC5F,CAAC;IAED,cAAc,CAAC,SAAiB,EAAE,IAAY,EAAE,EAAU;QACxD,oBAAoB,CAClB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,EAChE,IAAI,EACJ,EAAE,CACH,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,kBAAkB,CACtB,SAAiB,EACjB,WAA2C,EAC3C,UAAkB;QAElB,MAAM,KAAK,GAAyB,EAAE,CAAC;QACvC,KAAK,MAAM,UAAU,IAAI,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC;YAC9C,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;gBACrB,GAAG,CAAC,UAAU,CAAC,2CAA2C,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;gBAC5E,SAAS;YACX,CAAC;YACD,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,UAAU,CAAC,IAAI;gBACrB,QAAQ,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,iBAAiB,CAAC,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;aACpE,CAAC,CAAC;QACL,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC;QAChF,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,uBAAuB,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACvE,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;YAC7B,GAAG,CAAC,UAAU,CAAC,uCAAuC,EAAE,GAAG,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/F,CAAC;QACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC;IACnF,CAAC;IAED,6EAA6E;IAC7E,2BAA2B;IAC3B,6EAA6E;IAErE,QAAQ,CAAC,SAAiB;QAChC,IAAI,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,IAAI,mBAAmB,CAAC,SAAS,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,iBAAiB,CAAC,SAAiB,EAAE,UAAkB;QAC7D,MAAM,OAAO,GAAG,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC1D,MAAM,YAAY,GAAG,iBAAiB,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;QACvF,IAAI,YAAY;YAAE,OAAO,YAAY,CAAC;QACtC,IAAI,UAAU,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAC1C,OAAO,2BAA2B,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IAEO,mBAAmB;QACzB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACtD,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBACpD,IAAI,OAAO,CAAC,WAAW,EAAE,IAAI,MAAM,IAAI,OAAO,EAAE,CAAC;oBAC/C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtF,CAAC;YACH,CAAC;YACD,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAClD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE;oBACxB,EAAE,EAAE,MAAM,CAAC,EAAE;oBACb,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,QAAQ;oBAC9B,WAAW,EAAE,MAAM,CAAC,WAAW;iBAChC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAEO,wBAAwB,CAAC,IAAY;QAC3C,IAAI,CAAC,IAAI,CAAC,SAAS;YAAE,OAAO,IAAI,CAAC;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5E,CAAC;IAEO,0BAA0B,CAAC,KAMlC;QACC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO;gBACL,cAAc,EAAE,KAAK,CAAC,SAAS;gBAC/B,QAAQ,EAAE,KAAK,CAAC,eAAe;aAChC,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACnB,OAAO;gBACL,cAAc,EAAE,KAAK,CAAC,eAAe,IAAI,KAAK,CAAC,SAAS;gBACxD,QAAQ,EAAE,KAAK,CAAC,SAAS;aAC1B,CAAC;QACJ,CAAC;QAED,OAAO;YACL,cAAc,EAAE,KAAK,CAAC,SAAS;YAC/B,QAAQ,EAAE,KAAK,CAAC,eAAe;SAChC,CAAC;IACJ,CAAC;IAEO,0BAA0B,CAChC,WAAwC,EACxC,WAAmB,EACnB,UAAkB,EAClB,cAAsB;QAEtB,MAAM,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QACpC,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC;QAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACzC,MAAM,kBAAkB,GAAG,CAAC,IAAI,CAAC;QAEjC,MAAM,OAAO,GAAG,yBAAyB,CAAC;YACxC,QAAQ,EAAE,SAAS;YACnB,cAAc;YACd,EAAE,EAAE,WAAW,CAAC,EAAE;YAClB,UAAU;YACV,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;YAC5C,MAAM;YACN,QAAQ;YACR,IAAI,EAAE,WAAW;YACjB,WAAW,EAAE,EAAE;SAChB,CAAC,CAAC;QAEH,MAAM,gBAAgB,GAAG,KAAK,EAAE,IAAY,EAAE,OAAO,GAAG,KAAK,EAAiB,EAAE;YAC9E,IAAI,WAAW,CAAC,OAAO,IAAI,WAAW,CAAC,QAAQ,EAAE,CAAC;gBAChD,IAAI,OAAO,EAAE,CAAC;oBACZ,MAAM,WAAW,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBACjD,CAAC;qBAAM,CAAC;oBACN,MAAM,WAAW,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAC/E,CAAC;gBACD,OAAO;YACT,CAAC;YAED,MAAM,WAAW,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC5E,CAAC,CAAC;QAEF,MAAM,SAAS,GAA0B;YACvC,OAAO,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE;gBAC9B,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;YACD,eAAe,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE;gBACtC,MAAM,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACrC,CAAC;YACD,iBAAiB,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE;gBACxC,MAAM,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAC/B,CAAC;YACD,iBAAiB,EAAE,KAAK,EAAE,MAAsB,EAAE,EAAE;gBAClD,MAAM,QAAQ,GAAG,CAAC,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACvD,MAAM,SAAS,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,OAAO,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC7G,MAAM,gBAAgB,CAAC,SAAS,CAAC,CAAC;YACpC,CAAC;YACD,SAAS,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;YACzB,UAAU,EAAE,KAAK,EAAE,OAAgB,EAAE,EAAE;gBACrC,IAAI,OAAO,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;oBAC7D,MAAM,WAAW,CAAC,UAAU,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBAClE,CAAC;YACH,CAAC;YACD,UAAU,EAAE,KAAK,EAAE,QAAgB,EAAE,KAAc,EAAE,EAAE;gBACrD,MAAM,IAAI,CAAC,UAAU,CAAC,cAAc,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;YACzD,CAAC;YACD,cAAc,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;SAC/B,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IACpE,CAAC;IAEO,kBAAkB;QACxB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE;YAC7D,IAAI,CAAC,WAAW,CAAC,kBAAkB,EAAE;gBAAE,OAAO;YAC9C,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,CACzC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC,WAAW,CACnE,CAAC;YACF,IAAI,CAAC,aAAa;gBAAE,OAAO;YAE3B,MAAM,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;YACpC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,0BAA0B,CAAC;gBACnE,SAAS,EAAE,WAAW,CAAC,SAAS;gBAChC,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE;gBAC9B,QAAQ,EAAE,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,KAAK;gBAClD,eAAe,EACb,WAAW,CAAC,OAAO,IAAI,UAAU,IAAI,WAAW,CAAC,OAAO;oBACtD,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ;oBAC9B,CAAC,CAAC,IAAI;aACX,CAAC,CAAC;YACH,MAAM,UAAU,GAAG,qBAAqB,CAAC;gBACvC,cAAc;gBACd,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;gBAC5C,SAAS,EAAE,WAAW,CAAC,EAAE;gBACzB,kBAAkB,EAAE,IAAI;gBACxB,QAAQ;aACT,CAAC,CAAC;YACH,MAAM,UAAU,GAAG,aAAa,CAAC,GAAG;gBAClC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE;gBAC/D,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,WAAW,GAAG,UAAU;gBAC5B,CAAC,CAAC,IAAI,WAAW,CAAC,WAAW,IAAI,UAAU,EAAE;gBAC7C,CAAC,CAAC,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;YAElC,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE;gBAC7B,IAAI,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC,WAAW,EAAE;gBAC1D,EAAE,EAAE,WAAW,CAAC,EAAE;gBAClB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjC,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE;gBACzB,QAAQ,EAAE,WAAW,CAAC,IAAI,CAAC,QAAQ;gBACnC,IAAI,EAAE,WAAW;gBACjB,WAAW,EAAE,EAAE;gBACf,cAAc,EAAE,KAAK;aACtB,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,IAAI,CAAC,0BAA0B,CAC7C,WAAW,EACX,WAAW,EACX,UAAU,EACV,cAAc,CACf,CAAC;YACF,IAAI,CAAC;gBACH,IAAI,WAAW,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;oBACtC,MAAM,KAAK,GAAG,uBAAuB,CAAC;wBACpC,QAAQ,EAAE,SAAS;wBACnB,IAAI,EAAE,IAAI;wBACV,cAAc;wBACd,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;wBAC5C,EAAE,EAAE,WAAW,CAAC,EAAE;wBAClB,SAAS,EAAE,QAAQ;wBACnB,UAAU;wBACV,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE;wBACzB,IAAI,EAAE,WAAW;wBACjB,WAAW,EAAE,EAAE;qBAChB,CAAC,CAAC;oBACH,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;oBACrD,OAAO;gBACT,CAAC;gBAED,IAAI,WAAW,CAAC,WAAW,KAAK,MAAM,EAAE,CAAC;oBACvC,MAAM,UAAU,GAAG,IAAI,CAAC,iBAAiB,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;oBACtE,IAAI,UAAU,EAAE,CAAC;wBACf,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,CAC3B,mBAAmB,CAAC,SAAS,EAAE,cAAc,CAAC,EAC9C,UAAU,EACV,IAAI,CACL,CAAC;wBACF,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC;oBACvE,CAAC;yBAAM,CAAC;wBACN,MAAM,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,CAAC;oBACnE,CAAC;oBACD,OAAO;gBACT,CAAC;gBAED,MAAM,KAAK,GAAG,uBAAuB,CAAC;oBACpC,QAAQ,EAAE,SAAS;oBACnB,IAAI,EAAE,IAAI;oBACV,cAAc;oBACd,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;oBAC5C,EAAE,EAAE,WAAW,CAAC,EAAE;oBAClB,SAAS,EAAE,QAAQ;oBACnB,UAAU;oBACV,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE;oBACzB,IAAI,EAAE,WAAW;oBACjB,WAAW,EAAE,EAAE;iBAChB,CAAC,CAAC;gBAEH,MAAM,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YACvD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,UAAU,CACZ,6BAA6B,EAC7B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CACjD,CAAC;gBACF,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;oBAClD,MAAM,WAAW,CAAC,KAAK,CAAC;wBACtB,OAAO,EAAE,GAAG,WAAW,CAAC,WAAW,0CAA0C;wBAC7E,SAAS,EAAE,CAAC,IAAI;qBACjB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,EAAE,GAAY,EAAE,EAAE;YAC1D,oCAAoC;YACpC,IAAI,GAAG,CAAC,gBAAgB,GAAG,IAAI,CAAC,WAAW;gBAAE,OAAO;YACpD,oBAAoB;YACpB,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG;gBAAE,OAAO;YAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,qBAAqB;YAC1D,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YAC1C,MAAM,eAAe,GAAG,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC;YACjD,MAAM,aAAa,GAAG,UAAU,IAAI,CAAC,CAAC,eAAe,CAAC;YACtD,MAAM,WAAW,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YACjE,MAAM,oBAAoB,GAAG,CAAC,IAAI,IAAI,CAAC,WAAW,IAAI,CAAC,aAAa,CAAC;YAErE,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,0BAA0B,CAAC;gBACnE,SAAS,EAAE,GAAG,CAAC,SAAS;gBACxB,OAAO,EAAE,CAAC,IAAI;gBACd,QAAQ,EAAE,UAAU;gBACpB,eAAe,EAAE,UAAU,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI;gBACxE,eAAe;aAChB,CAAC,CAAC;YACH,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;YACrC,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,CAAC;YAErB,aAAa;YACb,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE;gBACrB,EAAE,EAAE,MAAM;gBACV,QAAQ;gBACR,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,IAAI,QAAQ;aACjD,CAAC,CAAC;YAEH,gBAAgB;YAChB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAChE,MAAM,EAAE,GAAG,GAAG,CAAC,OAAoC,CAAC;gBACpD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;YAC3E,CAAC;YAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;YACpD,MAAM,UAAU,GAAG,qBAAqB,CAAC;gBACvC,cAAc;gBACd,gBAAgB;gBAChB,SAAS,EAAE,KAAK;gBAChB,kBAAkB,EAAE,IAAI;gBACxB,QAAQ;aACT,CAAC,CAAC;YAEH,MAAM,WAAW,GAAG,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAE/D,MAAM,SAAS,GAAG,uBAAuB,CAAC;gBACxC,QAAQ,EAAE,SAAS;gBACnB,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;gBAC7B,cAAc;gBACd,gBAAgB;gBAChB,EAAE,EAAE,KAAK;gBACT,SAAS,EAAE,QAAQ;gBACnB,UAAU;gBACV,IAAI,EAAE,MAAM;gBACZ,QAAQ;gBACR,IAAI,EAAE,WAAW;aAClB,CAAiB,CAAC;YAEnB,MAAM,oBAAoB,CAAC;gBACzB,SAAS;gBACT,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC;gBAChD,oBAAoB;gBACpB,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC,oBAAoB,EAAE,aAAa,EAAE,WAAW,EAAE;gBAC3E,UAAU,EAAE,OAAO;gBACnB,YAAY,EAAE;oBACZ,IAAI,EAAE,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE;oBACjC,EAAE,EAAE,KAAK;oBACT,GAAG,CAAC,CAAC,IAAI,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC1C,IAAI,EAAE,MAAM;oBACZ,QAAQ;oBACR,IAAI,EAAE,WAAW;oBACjB,cAAc,EAAE,KAAK;iBACtB;gBACD,GAAG,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC;gBACrD,kBAAkB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,kBAAkB,CAAC,cAAc,EAAE,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC;gBACzF,QAAQ,EAAE,UAAU;gBACpB,OAAO,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;gBAClE,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,GAAG,EAAE,IAAI;gBACT,aAAa,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,qBAAqB,CAAC,KAAK,EAAE,IAAI,CAAC;aAC7D,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAC5B,SAAiB;QAEjB,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACvD,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,WAAW,SAAS,wBAAwB,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,EAA2D,CAAC;IACrE,CAAC;CACF","sourcesContent":["import {\n ApplicationCommandOptionType,\n Client,\n Events,\n GatewayIntentBits,\n Partials,\n type ChatInputCommandInteraction,\n type Collection,\n type Message,\n type Attachment,\n type TextChannel,\n type DMChannel,\n type NewsChannel,\n type ThreadChannel,\n} from \"discord.js\";\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\n\nimport type {\n ConversationContext,\n ConversationEvent,\n MessagingEventHandler,\n ConversationResponder,\n ChatToolResult,\n} from \"../../adapter.js\";\nimport { discordTextPayload } from \"./components.js\";\nimport {\n createConversationEvent,\n createConversationMessage,\n type MessagingBot,\n type MessagingInfo,\n} from \"../../adapter.js\";\nimport type { DiscordEvent } from \"./types.js\";\nimport * as log from \"../../log.js\";\nimport { createOfficeAddress, type Workspace } from \"../../office/index.js\";\nimport { resolveChatSessionKey } from \"../../sessions/session-key.js\";\nimport { formatNothingRunning } from \"../../platform-messages.js\";\nimport {\n appendBotResponseLog,\n appendChannelLog,\n MessagingEventQueue,\n downloadUrlToFile,\n resolveOnlyScopedStopTarget,\n resolveStopTarget,\n saveIncomingAttachments,\n withRetry,\n type IncomingAttachment,\n} from \"../shared.js\";\nimport { COMMAND_MANIFEST } from \"../../commands/manifest.js\";\nimport { processMessageIntake } from \"../intake.js\";\nimport { createDiscordAdapters } from \"./context.js\";\n\n// discord.js: DiscordAPIError exposes `.status` (HTTP status) and a `.code`.\n// RateLimitError fires when the internal queue gives up. MessagingBoth should retry.\nfunction discordIsRateLimited(err: Error): boolean {\n if ((err as { status?: number }).status === 429) return true;\n if ((err as { httpStatus?: number }).httpStatus === 429) return true;\n if (err.name === \"RateLimitError\") return true;\n return false;\n}\n\nconst discordRetry = <T>(fn: () => Promise<T>): Promise<T> =>\n withRetry(fn, { isRateLimited: discordIsRateLimited });\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type { DiscordEvent } from \"./types.js\";\n\n// ============================================================================\n// DiscordMessagingBot\n// ============================================================================\n\nexport class DiscordMessagingBot implements MessagingBot {\n private client: Client;\n private handler: MessagingEventHandler;\n private token: string;\n private workspace: Workspace;\n private botUserId: string | null = null;\n private queues = new Map<string, MessagingEventQueue>();\n private startupTime: number = 0;\n private channels = new Map<string, { id: string; name: string }>();\n private users = new Map<string, { id: string; userName: string; displayName: string }>();\n\n constructor(handler: MessagingEventHandler, config: { token: string; workspace: Workspace }) {\n this.handler = handler;\n this.token = config.token;\n this.workspace = config.workspace;\n this.client = new Client({\n intents: [\n GatewayIntentBits.Guilds,\n GatewayIntentBits.GuildMessages,\n GatewayIntentBits.MessageContent,\n GatewayIntentBits.DirectMessages,\n ],\n partials: [Partials.Channel, Partials.Message],\n });\n }\n\n // ==========================================================================\n // Public API (implements MessagingBot)\n // ==========================================================================\n\n async start(): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n this.client.once(Events.ClientReady, async (readyClient) => {\n this.botUserId = readyClient.user.id;\n this.startupTime = Date.now();\n log.logConnected(\"Discord\");\n log.logInfo(`Discord bot started as ${readyClient.user.tag}`);\n this.loadCachedGuildData();\n this.setupEventHandlers();\n try {\n // Registration derives from the command manifest; routing below\n // handles new/stop natively and the rest through one generic path.\n await readyClient.application.commands.set(\n COMMAND_MANIFEST.filter((entry) => entry.discord).map((entry) =>\n Object.assign(\n {\n name: entry.name,\n description: entry.description,\n },\n entry.arg\n ? {\n options: [\n {\n name: entry.arg.name,\n description: entry.arg.description,\n type: ApplicationCommandOptionType.String as const,\n required: entry.arg.required,\n },\n ],\n }\n : {},\n ),\n ),\n );\n } catch (err) {\n log.logWarning(\n \"Failed to register Discord slash commands\",\n err instanceof Error ? err.message : String(err),\n );\n }\n resolve();\n });\n this.client.once(Events.Error, reject);\n this.client.login(this.token).catch(reject);\n });\n }\n\n async postMessage(channel: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channel);\n const msg = await ch.send(discordTextPayload(text));\n return msg.id;\n });\n }\n\n async updateMessage(channel: string, ts: string, text: string): Promise<void> {\n await this.updateMessageRaw(channel, ts, text);\n }\n\n async addReaction(channel: string, messageTs: string, emoji: string): Promise<void> {\n // Discord uses Unicode emoji directly (e.g. \"👀\"); accept a bare name too\n // but Slack-style short names won't resolve, so pass through as-is.\n await discordRetry(async () => {\n const ch = await this.fetchTextChannel(channel);\n const msg = await ch.messages.fetch(messageTs);\n await msg.react(emoji.replace(/^:|:$/g, \"\"));\n });\n }\n\n enqueueEvent(event: ConversationEvent): boolean {\n const conversationId = event.conversationId;\n const queue = this.getQueue(conversationId);\n if (queue.size() >= 5) {\n log.logWarning(\n `Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`,\n );\n return false;\n }\n log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);\n queue.enqueue(() => {\n const context = createDiscordAdapters(event as DiscordEvent, this);\n return this.handler.handleEvent(event, this, context);\n });\n return true;\n }\n\n getMessagingInfo(): MessagingInfo {\n return {\n name: \"discord\",\n trustModel: \"membership\",\n formattingGuide:\n \"## Discord Formatting (Markdown)\\nBold: **text**, Italic: *text*, Code: `code`, Block: ```language\\ncode```\\nLinks: [text](url), Spoiler: ||text||, Headings: # ## ###\\nWrite ordinary Markdown, including tables — the adapter converts anything Discord cannot render. Use code blocks for code.\",\n channels: this.getAllChannels(),\n users: this.getAllUsers(),\n diagnostics: {\n showUsageSummary: false,\n },\n };\n }\n\n // ==========================================================================\n // Internal helpers (used by context.ts)\n // ==========================================================================\n\n async updateMessageRaw(channelId: string, messageId: string, text: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.edit(discordTextPayload(text));\n });\n }\n\n async postReply(channelId: string, replyToId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const replyTarget = await ch.messages.fetch(replyToId);\n const sent = await replyTarget.reply(discordTextPayload(text));\n return sent.id;\n });\n }\n\n async postInThread(channelId: string, threadOrMessageId: string, text: string): Promise<string> {\n // Try as a thread channel first, then fall back to posting in the channel\n try {\n const thread = await this.client.channels.fetch(threadOrMessageId);\n if (thread && (thread.isThread() || thread.isTextBased())) {\n return discordRetry(async () => {\n const msg = await (thread as ThreadChannel).send(discordTextPayload(text));\n return msg.id;\n });\n }\n } catch {\n // Not a thread channel, treat as message ID for reply\n }\n return this.postReply(channelId, threadOrMessageId, text);\n }\n\n async deleteMessageRaw(channelId: string, messageId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n const msg = await ch.messages.fetch(messageId);\n await msg.delete();\n } catch {\n // Ignore if already deleted\n }\n }\n\n async sendTyping(channelId: string): Promise<void> {\n try {\n const ch = await this.fetchTextChannel(channelId);\n await ch.sendTyping();\n } catch {\n // Non-fatal\n }\n }\n\n async uploadFile(channelId: string, filePath: string, title?: string): Promise<void> {\n return discordRetry(async () => {\n const ch = await this.fetchTextChannel(channelId);\n const fileName = title ?? basename(filePath);\n const fileContent = readFileSync(filePath);\n await ch.send({ files: [{ attachment: fileContent, name: fileName }] });\n });\n }\n\n async sendDirectMessage(userId: string, text: string): Promise<string> {\n return discordRetry(async () => {\n const user = await this.client.users.fetch(userId);\n const msg = await user.send(discordTextPayload(text));\n return msg.id;\n });\n }\n\n async postPrivate(_conversationId: string, userId: string, text: string): Promise<void> {\n await this.sendDirectMessage(userId, text);\n }\n\n getAllChannels(): { id: string; name: string }[] {\n return Array.from(this.channels.values());\n }\n\n getAllUsers(): { id: string; userName: string; displayName: string }[] {\n return Array.from(this.users.values());\n }\n\n logToFile(channelId: string, entry: object): void {\n appendChannelLog(this.workspace.office(createOfficeAddress(\"discord\", channelId)), entry);\n }\n\n logBotResponse(channelId: string, text: string, ts: string): void {\n appendBotResponseLog(\n this.workspace.office(createOfficeAddress(\"discord\", channelId)),\n text,\n ts,\n );\n }\n\n /**\n * Process attachments from a Discord message.\n * Downloads files before returning so the agent can read them immediately.\n */\n async processAttachments(\n channelId: string,\n attachments: Collection<string, Attachment>,\n _messageId: string,\n ): Promise<{ name: string; localPath: string }[]> {\n const items: IncomingAttachment[] = [];\n for (const attachment of attachments.values()) {\n if (!attachment.name) {\n log.logWarning(\"Discord attachment missing name, skipping\", attachment.url);\n continue;\n }\n items.push({\n name: attachment.name,\n download: (destPath) => downloadUrlToFile(attachment.url, destPath),\n });\n }\n\n const office = this.workspace.office(createOfficeAddress(\"discord\", channelId));\n const { saved, failed } = await saveIncomingAttachments(office, items);\n for (const failure of failed) {\n log.logWarning(`Failed to download Discord attachment`, `${failure.name}: ${failure.error}`);\n }\n return saved.map((item) => ({ name: item.original, localPath: item.localPath }));\n }\n\n // ==========================================================================\n // Private - Event Handlers\n // ==========================================================================\n\n private getQueue(channelId: string): MessagingEventQueue {\n let queue = this.queues.get(channelId);\n if (!queue) {\n queue = new MessagingEventQueue(\"Discord\");\n this.queues.set(channelId, queue);\n }\n return queue;\n }\n\n private resolveStopTarget(channelId: string, sessionKey: string): string | null {\n const address = createOfficeAddress(\"discord\", channelId);\n const directTarget = resolveStopTarget({ handler: this.handler, address, sessionKey });\n if (directTarget) return directTarget;\n if (sessionKey !== channelId) return null;\n return resolveOnlyScopedStopTarget(this.handler, address);\n }\n\n private loadCachedGuildData(): void {\n for (const guild of this.client.guilds.cache.values()) {\n for (const channel of guild.channels.cache.values()) {\n if (channel.isTextBased() && \"name\" in channel) {\n this.channels.set(channel.id, { id: channel.id, name: channel.name ?? channel.id });\n }\n }\n for (const member of guild.members.cache.values()) {\n this.users.set(member.id, {\n id: member.id,\n userName: member.user.username,\n displayName: member.displayName,\n });\n }\n }\n }\n\n private stripMessagingBotMention(text: string): string {\n if (!this.botUserId) return text;\n return text.replace(new RegExp(`<@!?${this.botUserId}>`, \"g\"), \"\").trim();\n }\n\n private resolveConversationContext(input: {\n channelId: string;\n inGuild: boolean;\n isThread: boolean;\n parentChannelId?: string | null;\n referencedMsgId?: string;\n }): { conversationId: string; threadTs?: string } {\n if (!input.inGuild) {\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n if (input.isThread) {\n return {\n conversationId: input.parentChannelId ?? input.channelId,\n threadTs: input.channelId,\n };\n }\n\n return {\n conversationId: input.channelId,\n threadTs: input.referencedMsgId,\n };\n }\n\n private createSlashCommandAdapters(\n interaction: ChatInputCommandInteraction,\n commandText: string,\n sessionKey: string,\n conversationId: string,\n ): ConversationContext {\n const isDM = !interaction.inGuild();\n const userId = interaction.user.id;\n const userName = interaction.user.username;\n const platform = this.getMessagingInfo();\n const shouldUseEphemeral = !isDM;\n\n const message = createConversationMessage({\n platform: \"discord\",\n conversationId,\n id: interaction.id,\n sessionKey,\n conversationKind: isDM ? \"direct\" : \"shared\",\n userId,\n userName,\n text: commandText,\n attachments: [],\n });\n\n const respondPrivately = async (text: string, replace = false): Promise<void> => {\n if (interaction.replied || interaction.deferred) {\n if (replace) {\n await interaction.editReply({ content: text });\n } else {\n await interaction.followUp({ content: text, ephemeral: shouldUseEphemeral });\n }\n return;\n }\n\n await interaction.reply({ content: text, ephemeral: shouldUseEphemeral });\n };\n\n const responder: ConversationResponder = {\n respond: async (text: string) => {\n await respondPrivately(text);\n },\n replaceResponse: async (text: string) => {\n await respondPrivately(text, true);\n },\n respondDiagnostic: async (text: string) => {\n await respondPrivately(text);\n },\n respondToolResult: async (result: ChatToolResult) => {\n const duration = (result.durationMs / 1000).toFixed(1);\n const formatted = `${result.isError ? \"Error\" : \"Done\"} ${result.toolName} (${duration}s)\\n${result.result}`;\n await respondPrivately(formatted);\n },\n setTyping: async () => {},\n setWorking: async (working: boolean) => {\n if (working && !interaction.replied && !interaction.deferred) {\n await interaction.deferReply({ ephemeral: shouldUseEphemeral });\n }\n },\n uploadFile: async (filePath: string, title?: string) => {\n await this.uploadFile(conversationId, filePath, title);\n },\n deleteResponse: async () => {},\n };\n\n return { address: message.address, message, responder, platform };\n }\n\n private setupEventHandlers(): void {\n this.client.on(Events.InteractionCreate, async (interaction) => {\n if (!interaction.isChatInputCommand()) return;\n const manifestEntry = COMMAND_MANIFEST.find(\n (entry) => entry.discord && entry.name === interaction.commandName,\n );\n if (!manifestEntry) return;\n\n const isDM = !interaction.inGuild();\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: interaction.channelId,\n inGuild: interaction.inGuild(),\n isThread: interaction.channel?.isThread() ?? false,\n parentChannelId:\n interaction.channel && \"parentId\" in interaction.channel\n ? interaction.channel.parentId\n : null,\n });\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n messageId: interaction.id,\n persistentTopLevel: true,\n threadTs,\n });\n const commandArg = manifestEntry.arg\n ? interaction.options.getString(manifestEntry.arg.name)?.trim()\n : undefined;\n const commandText = commandArg\n ? `/${interaction.commandName} ${commandArg}`\n : `/${interaction.commandName}`;\n\n this.logToFile(conversationId, {\n date: new Date(interaction.createdTimestamp).toISOString(),\n ts: interaction.id,\n ...(threadTs ? { threadTs } : {}),\n user: interaction.user.id,\n userName: interaction.user.username,\n text: commandText,\n attachments: [],\n isMessagingBot: false,\n });\n\n const context = this.createSlashCommandAdapters(\n interaction,\n commandText,\n sessionKey,\n conversationId,\n );\n try {\n if (interaction.commandName === \"new\") {\n const event = createConversationEvent({\n platform: \"discord\",\n type: \"dm\",\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n ts: interaction.id,\n thread_ts: threadTs,\n sessionKey,\n user: interaction.user.id,\n text: commandText,\n attachments: [],\n });\n await this.handler.handleEvent(event, this, context);\n return;\n }\n\n if (interaction.commandName === \"stop\") {\n const stopTarget = this.resolveStopTarget(conversationId, sessionKey);\n if (stopTarget) {\n await this.handler.handleStop(\n createOfficeAddress(\"discord\", conversationId),\n stopTarget,\n this,\n );\n await context.responder.respond(\"Stopped the current conversation.\");\n } else {\n await context.responder.respond(formatNothingRunning(\"discord\"));\n }\n return;\n }\n\n const event = createConversationEvent({\n platform: \"discord\",\n type: \"dm\",\n conversationId,\n conversationKind: isDM ? \"direct\" : \"shared\",\n ts: interaction.id,\n thread_ts: threadTs,\n sessionKey,\n user: interaction.user.id,\n text: commandText,\n attachments: [],\n });\n\n await this.handler.handleEvent(event, this, context);\n } catch (err) {\n log.logWarning(\n \"Discord slash command error\",\n err instanceof Error ? err.message : String(err),\n );\n if (!interaction.replied && !interaction.deferred) {\n await interaction.reply({\n content: `${interaction.commandName} command failed. Please try again later.`,\n ephemeral: !isDM,\n });\n }\n }\n });\n\n this.client.on(Events.MessageCreate, async (msg: Message) => {\n // Skip messages from before startup\n if (msg.createdTimestamp < this.startupTime) return;\n // Skip bot messages\n if (msg.author.bot) return;\n const isDM = msg.channel.type === 1; // ChannelType.DM = 1\n const isInThread = msg.channel.isThread();\n const referencedMsgId = msg.reference?.messageId;\n const isThreadReply = isInThread || !!referencedMsgId;\n const isMentioned = msg.mentions.users.has(this.botUserId ?? \"\");\n const isAutoReplyCandidate = !isDM && !isMentioned && !isThreadReply;\n\n const { conversationId, threadTs } = this.resolveConversationContext({\n channelId: msg.channelId,\n inGuild: !isDM,\n isThread: isInThread,\n parentChannelId: \"parentId\" in msg.channel ? msg.channel.parentId : null,\n referencedMsgId,\n });\n const userId = msg.author.id;\n const userName = msg.author.username;\n const msgId = msg.id;\n\n // Track user\n this.users.set(userId, {\n id: userId,\n userName,\n displayName: msg.member?.displayName ?? userName,\n });\n\n // Track channel\n if (!this.channels.has(conversationId) && \"name\" in msg.channel) {\n const ch = msg.channel as TextChannel | NewsChannel;\n this.channels.set(conversationId, { id: conversationId, name: ch.name });\n }\n\n const conversationKind = isDM ? \"direct\" : \"shared\";\n const sessionKey = resolveChatSessionKey({\n conversationId,\n conversationKind,\n messageId: msgId,\n persistentTopLevel: true,\n threadTs,\n });\n\n const cleanedText = this.stripMessagingBotMention(msg.content);\n\n const eventBase = createConversationEvent({\n platform: \"discord\",\n type: isDM ? \"dm\" : \"mention\",\n conversationId,\n conversationKind,\n ts: msgId,\n thread_ts: threadTs,\n sessionKey,\n user: userId,\n userName,\n text: cleanedText,\n }) as DiscordEvent;\n\n await processMessageIntake({\n eventBase,\n office: this.workspace.office(eventBase.address),\n isAutoReplyCandidate,\n magicWord: { addressed: !isAutoReplyCandidate, scopeFallback: \"top-level\" },\n busyPolicy: \"queue\",\n logEntryBase: {\n date: msg.createdAt.toISOString(),\n ts: msgId,\n ...(!isDM && threadTs ? { threadTs } : {}),\n user: userId,\n userName,\n text: cleanedText,\n isMessagingBot: false,\n },\n log: (entry) => this.logToFile(conversationId, entry),\n processAttachments: () => this.processAttachments(conversationId, msg.attachments, msgId),\n queueKey: sessionKey,\n enqueue: (queueKey, work) => this.getQueue(queueKey).enqueue(work),\n handler: this.handler,\n bot: this,\n createContext: (event) => createDiscordAdapters(event, this),\n });\n });\n }\n\n private async fetchTextChannel(\n channelId: string,\n ): Promise<TextChannel | DMChannel | NewsChannel | ThreadChannel> {\n const ch = await this.client.channels.fetch(channelId);\n if (!ch || !ch.isTextBased()) {\n throw new Error(`Channel ${channelId} is not a text channel`);\n }\n return ch as TextChannel | DMChannel | NewsChannel | ThreadChannel;\n }\n}\n"]}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord message payloads, built once for every path that sends text.
|
|
3
|
+
*
|
|
4
|
+
* Components V2 is a per-message choice that **cannot be undone**: once a
|
|
5
|
+
* message carries the flag, `content` stops working on it forever, so a
|
|
6
|
+
* response that is posted as V2 must also be edited as V2. Building every
|
|
7
|
+
* outbound payload here is what keeps that true — a single path that forgets
|
|
8
|
+
* would leave a message that can never be updated again.
|
|
9
|
+
*
|
|
10
|
+
* What it buys is the ceiling. Classic `content` caps at 2000 characters, so a
|
|
11
|
+
* long answer arrived as a string of `(continued 1)` posts; V2 allows 4000
|
|
12
|
+
* across a message's text, which halves that. Note the limit is the *sum* over
|
|
13
|
+
* all text displays, not per display — extra components buy layout, not room.
|
|
14
|
+
*/
|
|
15
|
+
/** Sum of all text in one Components V2 message. */
|
|
16
|
+
export declare const DISCORD_V2_TEXT_LIMIT = 4000;
|
|
17
|
+
export interface DiscordTextPayload {
|
|
18
|
+
flags: number;
|
|
19
|
+
components: Array<{
|
|
20
|
+
type: number;
|
|
21
|
+
content: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A text message as Components V2.
|
|
26
|
+
*
|
|
27
|
+
* One Text Display rather than several: the character budget is shared, so
|
|
28
|
+
* splitting prose across components would add structure without adding room,
|
|
29
|
+
* and Discord renders the markdown inside a single display exactly as it
|
|
30
|
+
* renders `content` today.
|
|
31
|
+
*/
|
|
32
|
+
export declare function discordTextPayload(text: string): DiscordTextPayload;
|
|
33
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../src/adapters/discord/components.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,oDAAoD;AACpD,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAE1C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACtD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAKnE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ComponentType, MessageFlags } from "discord.js";
|
|
2
|
+
/**
|
|
3
|
+
* Discord message payloads, built once for every path that sends text.
|
|
4
|
+
*
|
|
5
|
+
* Components V2 is a per-message choice that **cannot be undone**: once a
|
|
6
|
+
* message carries the flag, `content` stops working on it forever, so a
|
|
7
|
+
* response that is posted as V2 must also be edited as V2. Building every
|
|
8
|
+
* outbound payload here is what keeps that true — a single path that forgets
|
|
9
|
+
* would leave a message that can never be updated again.
|
|
10
|
+
*
|
|
11
|
+
* What it buys is the ceiling. Classic `content` caps at 2000 characters, so a
|
|
12
|
+
* long answer arrived as a string of `(continued 1)` posts; V2 allows 4000
|
|
13
|
+
* across a message's text, which halves that. Note the limit is the *sum* over
|
|
14
|
+
* all text displays, not per display — extra components buy layout, not room.
|
|
15
|
+
*/
|
|
16
|
+
/** Sum of all text in one Components V2 message. */
|
|
17
|
+
export const DISCORD_V2_TEXT_LIMIT = 4000;
|
|
18
|
+
/**
|
|
19
|
+
* A text message as Components V2.
|
|
20
|
+
*
|
|
21
|
+
* One Text Display rather than several: the character budget is shared, so
|
|
22
|
+
* splitting prose across components would add structure without adding room,
|
|
23
|
+
* and Discord renders the markdown inside a single display exactly as it
|
|
24
|
+
* renders `content` today.
|
|
25
|
+
*/
|
|
26
|
+
export function discordTextPayload(text) {
|
|
27
|
+
return {
|
|
28
|
+
flags: MessageFlags.IsComponentsV2,
|
|
29
|
+
components: [{ type: ComponentType.TextDisplay, content: text }],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=components.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.js","sourceRoot":"","sources":["../../../src/adapters/discord/components.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzD;;;;;;;;;;;;;GAaG;AAEH,oDAAoD;AACpD,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAO1C;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO;QACL,KAAK,EAAE,YAAY,CAAC,cAAc;QAClC,UAAU,EAAE,CAAC,EAAE,IAAI,EAAE,aAAa,CAAC,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;KACjE,CAAC;AACJ,CAAC","sourcesContent":["import { ComponentType, MessageFlags } from \"discord.js\";\n\n/**\n * Discord message payloads, built once for every path that sends text.\n *\n * Components V2 is a per-message choice that **cannot be undone**: once a\n * message carries the flag, `content` stops working on it forever, so a\n * response that is posted as V2 must also be edited as V2. Building every\n * outbound payload here is what keeps that true — a single path that forgets\n * would leave a message that can never be updated again.\n *\n * What it buys is the ceiling. Classic `content` caps at 2000 characters, so a\n * long answer arrived as a string of `(continued 1)` posts; V2 allows 4000\n * across a message's text, which halves that. Note the limit is the *sum* over\n * all text displays, not per display — extra components buy layout, not room.\n */\n\n/** Sum of all text in one Components V2 message. */\nexport const DISCORD_V2_TEXT_LIMIT = 4000;\n\nexport interface DiscordTextPayload {\n flags: number;\n components: Array<{ type: number; content: string }>;\n}\n\n/**\n * A text message as Components V2.\n *\n * One Text Display rather than several: the character budget is shared, so\n * splitting prose across components would add structure without adding room,\n * and Discord renders the markdown inside a single display exactly as it\n * renders `content` today.\n */\nexport function discordTextPayload(text: string): DiscordTextPayload {\n return {\n flags: MessageFlags.IsComponentsV2,\n components: [{ type: ComponentType.TextDisplay, content: text }],\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/adapters/discord/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/adapters/discord/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAK1B,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAatD,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,mBAAmB,GACvB;IACD,OAAO,EAAE,aAAa,CAAC;IACvB,OAAO,EAAE,mBAAmB,CAAC;IAC7B,SAAS,EAAE,qBAAqB,CAAC;IACjC,QAAQ,EAAE,aAAa,CAAC;CACzB,CAsFA"}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { createConversationMessage, } from "../../adapter.js";
|
|
2
2
|
import { resolveChatSessionKey } from "../../sessions/session-key.js";
|
|
3
3
|
import { createProgressiveRenderer, formatMarkdownToolResult } from "../progressive-renderer.js";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { DISCORD_V2_TEXT_LIMIT } from "./components.js";
|
|
5
|
+
import { formatDiscordMarkdown } from "./format.js";
|
|
6
|
+
// Components V2 allows 4000 characters across a message's text, against 2000
|
|
7
|
+
// for classic content — the reason for using it at all. The margin leaves room
|
|
8
|
+
// for the working indicator and for the continuation marker on a split.
|
|
9
|
+
const MAX_LENGTH = DISCORD_V2_TEXT_LIMIT - 100;
|
|
6
10
|
const formatDiscordContinuation = (partNum) => `*(continued ${partNum})*`;
|
|
7
11
|
function isDiscordMessageReference(id) {
|
|
8
12
|
return typeof id === "string" && id !== "" && !id.startsWith("event:");
|
|
@@ -54,6 +58,10 @@ export function createDiscordAdapters(event, bot) {
|
|
|
54
58
|
intervalMs: 8000,
|
|
55
59
|
stopOnSend: true,
|
|
56
60
|
},
|
|
61
|
+
// The last step before sending: the model writes standard markdown, and
|
|
62
|
+
// the parts Discord cannot render are converted here rather than by
|
|
63
|
+
// constraining what the model may write.
|
|
64
|
+
prepareSource: (text) => formatDiscordMarkdown(text),
|
|
57
65
|
formatToolResult: formatMarkdownToolResult,
|
|
58
66
|
responseErrorContext: (responseId) => ({
|
|
59
67
|
platform: "discord",
|
|
@@ -70,14 +78,16 @@ export function createDiscordAdapters(event, bot) {
|
|
|
70
78
|
return postFirst(text);
|
|
71
79
|
},
|
|
72
80
|
update: (id, text) => bot.updateMessageRaw(channelId, id, text),
|
|
81
|
+
// Returns the id so the renderer can edit this overflow message on the
|
|
82
|
+
// next redraw instead of posting another copy of the tail.
|
|
73
83
|
postExtra: async (text, responseId) => {
|
|
74
84
|
if (threadTargetId)
|
|
75
|
-
return
|
|
85
|
+
return bot.postInThread(channelId, threadTargetId, text);
|
|
76
86
|
if (replyTargetId)
|
|
77
|
-
return
|
|
87
|
+
return bot.postReply(channelId, replyTargetId, text);
|
|
78
88
|
if (responseId !== null)
|
|
79
|
-
return
|
|
80
|
-
return
|
|
89
|
+
return bot.postReply(channelId, responseId, text);
|
|
90
|
+
return bot.postMessage(channelId, text);
|
|
81
91
|
},
|
|
82
92
|
delete: async (id) => {
|
|
83
93
|
await bot.deleteMessageRaw(channelId, id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/discord/context.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,yBAAyB,GAI1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../../src/adapters/discord/context.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createConversationMessage,\n type ConversationMessage,\n type ConversationResponder,\n type MessagingInfo,\n} from \"../../adapter.js\";\nimport { resolveChatSessionKey } from \"../../sessions/session-key.js\";\nimport { createProgressiveRenderer, formatMarkdownToolResult } from \"../progressive-renderer.js\";\nimport { DISCORD_V2_TEXT_LIMIT } from \"./components.js\";\nimport { formatDiscordMarkdown } from \"./format.js\";\nimport type { DiscordMessagingBot, DiscordEvent } from \"./bot.js\";\nimport type { OfficeAddress } from \"../../adapter.js\";\n\n// Components V2 allows 4000 characters across a message's text, against 2000\n// for classic content — the reason for using it at all. The margin leaves room\n// for the working indicator and for the continuation marker on a split.\nconst MAX_LENGTH = DISCORD_V2_TEXT_LIMIT - 100;\n\nconst formatDiscordContinuation = (partNum: number): string => `*(continued ${partNum})*`;\n\nfunction isDiscordMessageReference(id: string | undefined): id is string {\n return typeof id === \"string\" && id !== \"\" && !id.startsWith(\"event:\");\n}\n\nexport function createDiscordAdapters(\n event: DiscordEvent,\n bot: DiscordMessagingBot,\n): {\n address: OfficeAddress;\n message: ConversationMessage;\n responder: ConversationResponder;\n platform: MessagingInfo;\n} {\n const conversationId = event.conversationId;\n const channelId = conversationId;\n const threadTargetId = isDiscordMessageReference(event.thread_ts) ? event.thread_ts : undefined;\n const replyTargetId = isDiscordMessageReference(event.ts) ? event.ts : undefined;\n\n const message = createConversationMessage({\n platform: \"discord\",\n conversationId,\n address: event.address,\n id: event.ts,\n sessionKey:\n event.sessionKey ??\n resolveChatSessionKey({\n conversationId,\n conversationKind: event.conversationKind,\n messageId: event.ts,\n persistentTopLevel: true,\n threadTs: event.thread_ts,\n }),\n conversationKind: event.conversationKind,\n userId: event.user,\n userName: event.userName,\n text: event.text,\n attachments: event.attachments,\n threadTs: event.thread_ts,\n });\n\n // The bot's getMessagingInfo() is the single authority for platform info.\n const platform: MessagingInfo = bot.getMessagingInfo();\n\n function postFirst(text: string): Promise<string> {\n if (threadTargetId) return bot.postInThread(channelId, threadTargetId, text);\n if (replyTargetId) return bot.postReply(channelId, replyTargetId, text);\n return bot.postMessage(channelId, text);\n }\n\n const { responder } = createProgressiveRenderer({\n label: \"Discord\",\n maxLength: MAX_LENGTH,\n formatContinuation: formatDiscordContinuation,\n errorPrefix: \"*Error:* \",\n workingIndicator: \" ...\",\n supportsDeltas: true,\n typing: {\n // Send immediately and repeat every 8s (Discord clears indicator after ~10s)\n send: () => bot.sendTyping(channelId),\n intervalMs: 8000,\n stopOnSend: true,\n },\n // The last step before sending: the model writes standard markdown, and\n // the parts Discord cannot render are converted here rather than by\n // constraining what the model may write.\n prepareSource: (text) => formatDiscordMarkdown(text),\n formatToolResult: formatMarkdownToolResult,\n responseErrorContext: (responseId) => ({\n platform: \"discord\",\n conversationId,\n channelId,\n messageId: message.id,\n sessionKey: message.sessionKey,\n responseMessageId: responseId,\n threadTs: threadTargetId,\n replyTargetId,\n conversationKind: message.conversationKind,\n }),\n post: async (text) => {\n return postFirst(text);\n },\n update: (id, text) => bot.updateMessageRaw(channelId, id, text),\n // Returns the id so the renderer can edit this overflow message on the\n // next redraw instead of posting another copy of the tail.\n postExtra: async (text, responseId) => {\n if (threadTargetId) return bot.postInThread(channelId, threadTargetId, text);\n if (replyTargetId) return bot.postReply(channelId, replyTargetId, text);\n if (responseId !== null) return bot.postReply(channelId, responseId, text);\n return bot.postMessage(channelId, text);\n },\n delete: async (id) => {\n await bot.deleteMessageRaw(channelId, id);\n },\n logBotResponse: (text, id) => bot.logBotResponse(channelId, text, id),\n uploadFile: (filePath, title) => bot.uploadFile(channelId, filePath, title),\n });\n\n return { address: message.address, message, responder, platform };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../src/adapters/discord/format.ts"],"names":[],"mappings":"AA+FA,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5D"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { extractMarkdownTables, normalizeMarkdownTables, renderMonospaceTable, } from "../markdown-tables.js";
|
|
2
|
+
/**
|
|
3
|
+
* Discord-side presentation of a response.
|
|
4
|
+
*
|
|
5
|
+
* The model writes standard markdown for every platform; making it write
|
|
6
|
+
* Discord-flavoured markdown instead would trade one platform's rendering for
|
|
7
|
+
* another's, and Discord is the one that renders the least. So the conversion
|
|
8
|
+
* belongs here, at the last step before sending.
|
|
9
|
+
*
|
|
10
|
+
* Discord renders most of what we emit — headings, lists, quotes, fenced code
|
|
11
|
+
* with highlighting, links, spoilers, and its own `-#` subtext. What it does
|
|
12
|
+
* not render is handled below, each verified against the real client rather
|
|
13
|
+
* than against the docs.
|
|
14
|
+
*/
|
|
15
|
+
/** Fence tables at this width or less; wider ones wrap and look worse aligned. */
|
|
16
|
+
const MAX_FENCED_TABLE_WIDTH = 120;
|
|
17
|
+
/** Stand-in for a horizontal rule, which Discord has no syntax for. */
|
|
18
|
+
const RULE = "─".repeat(30);
|
|
19
|
+
function longestLine(text) {
|
|
20
|
+
return text.split("\n").reduce((max, line) => Math.max(max, line.length), 0);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Replace GFM tables with aligned, fenced equivalents.
|
|
24
|
+
*
|
|
25
|
+
* Prose is sliced verbatim around each table rather than re-serialized, so
|
|
26
|
+
* everything Discord already renders correctly passes through untouched.
|
|
27
|
+
*/
|
|
28
|
+
function convertTables(source) {
|
|
29
|
+
const normalized = normalizeMarkdownTables(source);
|
|
30
|
+
const tables = extractMarkdownTables(normalized);
|
|
31
|
+
if (tables.length === 0)
|
|
32
|
+
return source;
|
|
33
|
+
const lines = normalized.split("\n");
|
|
34
|
+
const out = [];
|
|
35
|
+
let cursor = 0;
|
|
36
|
+
for (const table of tables) {
|
|
37
|
+
out.push(...lines.slice(cursor, table.startLine));
|
|
38
|
+
const rendered = renderMonospaceTable(table);
|
|
39
|
+
// Too wide to align: Discord soft-wraps inside a fence and the columns
|
|
40
|
+
// scatter, which is worse than the pipes. Leave those as they came.
|
|
41
|
+
if (longestLine(rendered) > MAX_FENCED_TABLE_WIDTH) {
|
|
42
|
+
out.push(...lines.slice(table.startLine, table.endLine));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
out.push("```", rendered, "```");
|
|
46
|
+
}
|
|
47
|
+
cursor = table.endLine;
|
|
48
|
+
}
|
|
49
|
+
out.push(...lines.slice(cursor));
|
|
50
|
+
return out.join("\n");
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Line-level rewrites for constructs Discord ignores, applied outside fenced
|
|
54
|
+
* code so a code sample keeps whatever it contains.
|
|
55
|
+
*
|
|
56
|
+
* Runs after table conversion because that step maps source line numbers, and
|
|
57
|
+
* a rewrite that changed the line count first would misplace every table.
|
|
58
|
+
*/
|
|
59
|
+
function convertUnsupportedSyntax(source) {
|
|
60
|
+
let inFence = false;
|
|
61
|
+
return source
|
|
62
|
+
.split("\n")
|
|
63
|
+
.map((line) => {
|
|
64
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
65
|
+
inFence = !inFence;
|
|
66
|
+
return line;
|
|
67
|
+
}
|
|
68
|
+
if (inFence)
|
|
69
|
+
return line;
|
|
70
|
+
// A horizontal rule renders as its own literal dashes. Discord has no
|
|
71
|
+
// rule syntax, so draw one — dropping the line would lose the section
|
|
72
|
+
// break the author meant.
|
|
73
|
+
if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line))
|
|
74
|
+
return RULE;
|
|
75
|
+
// Discord does not render markdown images: `` arrives as a
|
|
76
|
+
// stray "!" followed by a link, and the image never appears. A bare URL
|
|
77
|
+
// *does* auto-embed, so emit that and keep the alt text above it.
|
|
78
|
+
return line.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (_match, alt, url) => alt.trim() ? `${alt}\n${url}` : url);
|
|
79
|
+
})
|
|
80
|
+
.join("\n");
|
|
81
|
+
}
|
|
82
|
+
/** Everything Discord cannot render, converted to something it can. */
|
|
83
|
+
export function formatDiscordMarkdown(source) {
|
|
84
|
+
return convertUnsupportedSyntax(convertTables(source));
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/adapters/discord/format.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n extractMarkdownTables,\n normalizeMarkdownTables,\n renderMonospaceTable,\n} from \"../markdown-tables.js\";\n\n/**\n * Discord-side presentation of a response.\n *\n * The model writes standard markdown for every platform; making it write\n * Discord-flavoured markdown instead would trade one platform's rendering for\n * another's, and Discord is the one that renders the least. So the conversion\n * belongs here, at the last step before sending.\n *\n * Discord renders most of what we emit — headings, lists, quotes, fenced code\n * with highlighting, links, spoilers, and its own `-#` subtext. What it does\n * not render is handled below, each verified against the real client rather\n * than against the docs.\n */\n\n/** Fence tables at this width or less; wider ones wrap and look worse aligned. */\nconst MAX_FENCED_TABLE_WIDTH = 120;\n\n/** Stand-in for a horizontal rule, which Discord has no syntax for. */\nconst RULE = \"─\".repeat(30);\n\nfunction longestLine(text: string): number {\n return text.split(\"\\n\").reduce((max, line) => Math.max(max, line.length), 0);\n}\n\n/**\n * Replace GFM tables with aligned, fenced equivalents.\n *\n * Prose is sliced verbatim around each table rather than re-serialized, so\n * everything Discord already renders correctly passes through untouched.\n */\nfunction convertTables(source: string): string {\n const normalized = normalizeMarkdownTables(source);\n const tables = extractMarkdownTables(normalized);\n if (tables.length === 0) return source;\n\n const lines = normalized.split(\"\\n\");\n const out: string[] = [];\n let cursor = 0;\n\n for (const table of tables) {\n out.push(...lines.slice(cursor, table.startLine));\n const rendered = renderMonospaceTable(table);\n // Too wide to align: Discord soft-wraps inside a fence and the columns\n // scatter, which is worse than the pipes. Leave those as they came.\n if (longestLine(rendered) > MAX_FENCED_TABLE_WIDTH) {\n out.push(...lines.slice(table.startLine, table.endLine));\n } else {\n out.push(\"```\", rendered, \"```\");\n }\n cursor = table.endLine;\n }\n out.push(...lines.slice(cursor));\n\n return out.join(\"\\n\");\n}\n\n/**\n * Line-level rewrites for constructs Discord ignores, applied outside fenced\n * code so a code sample keeps whatever it contains.\n *\n * Runs after table conversion because that step maps source line numbers, and\n * a rewrite that changed the line count first would misplace every table.\n */\nfunction convertUnsupportedSyntax(source: string): string {\n let inFence = false;\n return source\n .split(\"\\n\")\n .map((line) => {\n if (/^\\s*(```|~~~)/.test(line)) {\n inFence = !inFence;\n return line;\n }\n if (inFence) return line;\n\n // A horizontal rule renders as its own literal dashes. Discord has no\n // rule syntax, so draw one — dropping the line would lose the section\n // break the author meant.\n if (/^\\s*(-{3,}|\\*{3,}|_{3,})\\s*$/.test(line)) return RULE;\n\n // Discord does not render markdown images: `` arrives as a\n // stray \"!\" followed by a link, and the image never appears. A bare URL\n // *does* auto-embed, so emit that and keep the alt text above it.\n return line.replace(/!\\[([^\\]]*)\\]\\(([^)\\s]+)\\)/g, (_match, alt: string, url: string) =>\n alt.trim() ? `${alt}\\n${url}` : url,\n );\n })\n .join(\"\\n\");\n}\n\n/** Everything Discord cannot render, converted to something it can. */\nexport function formatDiscordMarkdown(source: string): string {\n return convertUnsupportedSyntax(convertTables(source));\n}\n"]}
|