From aaa70cd1850d3859d7aeb644172025c9d3ad260f Mon Sep 17 00:00:00 2001 From: huntharo Date: Fri, 20 Mar 2026 16:28:41 -0400 Subject: [PATCH] Discord: share component registry across module instances --- extensions/discord/src/components-registry.ts | 18 ++++++++++-- extensions/discord/src/components.test.ts | 28 ++++++++++++++++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/extensions/discord/src/components-registry.ts b/extensions/discord/src/components-registry.ts index ce7014aba75..17bbe1408b4 100644 --- a/extensions/discord/src/components-registry.ts +++ b/extensions/discord/src/components-registry.ts @@ -1,9 +1,23 @@ import type { DiscordComponentEntry, DiscordModalEntry } from "./components.js"; const DEFAULT_COMPONENT_TTL_MS = 30 * 60 * 1000; +const DISCORD_COMPONENT_ENTRIES_KEY = Symbol.for("openclaw.discord.componentEntries"); +const DISCORD_MODAL_ENTRIES_KEY = Symbol.for("openclaw.discord.modalEntries"); -const componentEntries = new Map(); -const modalEntries = new Map(); +function resolveGlobalMap(key: symbol): Map { + const globalStore = globalThis as Record; + if (globalStore[key] instanceof Map) { + return globalStore[key] as Map; + } + const created = new Map(); + globalStore[key] = created; + return created; +} + +const componentEntries = resolveGlobalMap( + DISCORD_COMPONENT_ENTRIES_KEY, +); +const modalEntries = resolveGlobalMap(DISCORD_MODAL_ENTRIES_KEY); function isExpired(entry: { expiresAt?: number }, now: number) { return typeof entry.expiresAt === "number" && entry.expiresAt <= now; diff --git a/extensions/discord/src/components.test.ts b/extensions/discord/src/components.test.ts index 44350b4fc4b..74357939629 100644 --- a/extensions/discord/src/components.test.ts +++ b/extensions/discord/src/components.test.ts @@ -1,5 +1,5 @@ import { MessageFlags } from "discord-api-types/v10"; -import { describe, expect, it, beforeEach } from "vitest"; +import { beforeEach, describe, expect, it } from "vitest"; import { clearDiscordComponentEntries, registerDiscordComponentEntries, @@ -78,6 +78,8 @@ describe("discord component registry", () => { clearDiscordComponentEntries(); }); + const componentsRegistryModuleUrl = new URL("./components-registry.ts", import.meta.url).href; + it("registers and consumes component entries", () => { registerDiscordComponentEntries({ entries: [{ id: "btn_1", kind: "button", label: "Confirm" }], @@ -102,4 +104,28 @@ describe("discord component registry", () => { expect(consumed?.id).toBe("btn_1"); expect(resolveDiscordComponentEntry({ id: "btn_1" })).toBeNull(); }); + + it("shares registry state across duplicate module instances", async () => { + const first = (await import( + `${componentsRegistryModuleUrl}?t=first-${Date.now()}` + )) as typeof import("./components-registry.js"); + const second = (await import( + `${componentsRegistryModuleUrl}?t=second-${Date.now()}` + )) as typeof import("./components-registry.js"); + + first.clearDiscordComponentEntries(); + first.registerDiscordComponentEntries({ + entries: [{ id: "btn_shared", kind: "button", label: "Shared" }], + modals: [], + }); + + expect(second.resolveDiscordComponentEntry({ id: "btn_shared", consume: false })).toMatchObject( + { + id: "btn_shared", + label: "Shared", + }, + ); + + second.clearDiscordComponentEntries(); + }); });