From 4e115c5dbb8a393f43b7266cfd84748159ee91a0 Mon Sep 17 00:00:00 2001 From: "openclaw-clawsweeper[bot]" <280122609+openclaw-clawsweeper[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:48:15 +0000 Subject: [PATCH] fix: Found one compatibility regression in the published Discord plugi --- extensions/discord/api.ts | 59 +++++++++++++++++ extensions/discord/src/public-api.test.ts | 80 +++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 extensions/discord/src/public-api.test.ts diff --git a/extensions/discord/api.ts b/extensions/discord/api.ts index 7a69294a541..ca4ec4995b2 100644 --- a/extensions/discord/api.ts +++ b/extensions/discord/api.ts @@ -22,3 +22,62 @@ export { } from "./src/normalize.js"; export { resolveOpenProviderRuntimeGroupPolicy as resolveDiscordRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy"; export { collectDiscordStatusIssues } from "./src/status-issues.js"; + +// Deprecated compatibility surface for existing @openclaw/discord/api.js consumers. +type HandleDiscordMessageAction = + typeof import("./src/actions/handle-action.js").handleDiscordMessageAction; + +export const handleDiscordMessageAction: HandleDiscordMessageAction = (async (...args) => { + const { handleDiscordMessageAction: run } = await import("./src/actions/handle-action.js"); + return run(...args); +}) as HandleDiscordMessageAction; + +export { + buildDiscordInteractiveComponents, + buildDiscordComponentCustomId, + buildDiscordModalCustomId, + parseDiscordComponentCustomId, + parseDiscordComponentCustomIdForInteraction, + parseDiscordModalCustomId, + parseDiscordModalCustomIdForInteraction, + type ComponentData, + type DiscordComponentBuildResult, + type DiscordComponentMessageSpec, +} from "./src/components.js"; +export { + parseDiscordComponentCustomIdForInteraction as parseDiscordComponentCustomIdForCarbon, + parseDiscordModalCustomIdForInteraction as parseDiscordModalCustomIdForCarbon, +} from "./src/component-custom-id.js"; +export { + getDiscordExecApprovalApprovers, + isDiscordExecApprovalApprover, + isDiscordExecApprovalClientEnabled, + shouldSuppressLocalDiscordExecApprovalPrompt, +} from "./src/exec-approvals.js"; +export { + fetchDiscordApplicationId, + fetchDiscordApplicationSummary, + parseApplicationIdFromToken, + probeDiscord, + resolveDiscordPrivilegedIntentsFromFlags, + type DiscordApplicationSummary, + type DiscordPrivilegedIntentsSummary, + type DiscordPrivilegedIntentStatus, + type DiscordProbe, +} from "./src/probe.js"; +export { parseDiscordSendTarget, type SendDiscordTarget } from "./src/send-target-parsing.js"; +export { + parseDiscordTarget, + resolveDiscordChannelId, + resolveDiscordTarget, + type DiscordTarget, + type DiscordTargetKind, + type DiscordTargetParseOptions, +} from "./src/targets.js"; +export { + DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS, + DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, + DISCORD_DEFAULT_INBOUND_WORKER_TIMEOUT_MS, + DISCORD_DEFAULT_LISTENER_TIMEOUT_MS, + mergeAbortSignals, +} from "./src/monitor/timeouts.js"; diff --git a/extensions/discord/src/public-api.test.ts b/extensions/discord/src/public-api.test.ts new file mode 100644 index 00000000000..b5d040ab869 --- /dev/null +++ b/extensions/discord/src/public-api.test.ts @@ -0,0 +1,80 @@ +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import ts from "typescript"; +import { describe, expect, it } from "vitest"; +import { + buildDiscordComponentCustomId, + parseDiscordComponentCustomIdForInteraction, +} from "./component-custom-id.js"; + +const API_SOURCE_PATH = resolve(dirname(fileURLToPath(import.meta.url)), "../api.ts"); + +function collectExportedNames(): Set { + const source = ts.createSourceFile( + API_SOURCE_PATH, + readFileSync(API_SOURCE_PATH, "utf8"), + ts.ScriptTarget.Latest, + true, + ); + const names = new Set(); + for (const statement of source.statements) { + if ( + ts.isVariableStatement(statement) && + statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) + ) { + for (const declaration of statement.declarationList.declarations) { + if (ts.isIdentifier(declaration.name)) { + names.add(declaration.name.text); + } + } + continue; + } + if (!ts.isExportDeclaration(statement) || !statement.exportClause) { + continue; + } + if (ts.isNamedExports(statement.exportClause)) { + for (const element of statement.exportClause.elements) { + names.add(element.name.text); + } + } + } + return names; +} + +describe("discord public API barrel", () => { + it("keeps compatibility exports for existing @openclaw/discord/api.js consumers", () => { + const exportedNames = collectExportedNames(); + + for (const exportName of [ + "DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS", + "buildDiscordInteractiveComponents", + "handleDiscordMessageAction", + "isDiscordExecApprovalApprover", + "isDiscordExecApprovalClientEnabled", + "parseApplicationIdFromToken", + "parseDiscordComponentCustomIdForCarbon", + "parseDiscordSendTarget", + "parseDiscordTarget", + "probeDiscord", + "resolveDiscordChannelId", + "resolveDiscordPrivilegedIntentsFromFlags", + ]) { + expect(exportedNames).toContain(exportName); + } + }); + + it("keeps legacy Carbon component parser aliases aligned with interaction parsers", () => { + const exportedNames = collectExportedNames(); + const customId = buildDiscordComponentCustomId({ + componentId: "approve", + modalId: "details", + }); + + expect(exportedNames).toContain("parseDiscordComponentCustomIdForCarbon"); + expect(parseDiscordComponentCustomIdForInteraction(customId)).toEqual({ + key: "*", + data: { cid: "approve", mid: "details" }, + }); + }); +});