From 87dddb818dfde6cb802b3eb58905980625e6d4ae Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 27 Mar 2026 14:38:40 +0000 Subject: [PATCH] fix(ci): restore plugin runtime boundaries --- extensions/bluebubbles/src/runtime-api.ts | 57 +++++++++++++++- extensions/discord/src/channel.ts | 14 +++- extensions/discord/src/probe.runtime.ts | 1 + extensions/discord/src/subagent-hooks.test.ts | 7 +- extensions/discord/src/voice-message.test.ts | 4 -- extensions/feishu/src/channel.test.ts | 12 +++- extensions/feishu/src/client.test.ts | 5 ++ extensions/feishu/src/media.test.ts | 24 +++++-- extensions/feishu/src/send-target.test.ts | 7 +- .../feishu/src/send.reply-fallback.test.ts | 13 +++- extensions/feishu/src/send.test.ts | 23 ++++--- .../feishu/src/tool-account-routing.test.ts | 20 ++++-- extensions/msteams/src/setup-surface.test.ts | 5 ++ extensions/signal/api.ts | 2 +- extensions/signal/src/accounts.ts | 2 +- extensions/signal/src/channel.ts | 3 +- .../signal/src/monitor/event-handler.ts | 2 +- extensions/signal/src/normalize.ts | 67 +++++++++++++++++++ extensions/signal/src/shared.ts | 14 ++-- src/plugin-sdk/account-resolution.ts | 14 ++-- src/plugin-sdk/bluebubbles.ts | 2 +- src/plugin-sdk/compat.ts | 2 +- src/plugin-sdk/line.ts | 12 ++-- src/plugin-sdk/signal.ts | 19 +++--- 24 files changed, 266 insertions(+), 65 deletions(-) create mode 100644 extensions/discord/src/probe.runtime.ts create mode 100644 extensions/signal/src/normalize.ts diff --git a/extensions/bluebubbles/src/runtime-api.ts b/extensions/bluebubbles/src/runtime-api.ts index 23c09660d96..fd5793b25c7 100644 --- a/extensions/bluebubbles/src/runtime-api.ts +++ b/extensions/bluebubbles/src/runtime-api.ts @@ -1 +1,56 @@ -export * from "openclaw/plugin-sdk/bluebubbles"; +export { resolveAckReaction } from "../../../src/agents/identity.js"; +export { + createActionGate, + jsonResult, + readNumberParam, + readReactionParams, + readStringParam, +} from "../../../src/agents/tools/common.js"; +export type { HistoryEntry } from "../../../src/auto-reply/reply/history.js"; +export { + evictOldHistoryKeys, + recordPendingHistoryEntryIfEnabled, +} from "../../../src/auto-reply/reply/history.js"; +export { resolveControlCommandGate } from "../../../src/channels/command-gating.js"; +export { logAckFailure, logInboundDrop, logTypingFailure } from "../../../src/channels/logging.js"; +export { + BLUEBUBBLES_ACTION_NAMES, + BLUEBUBBLES_ACTIONS, +} from "../../../src/channels/plugins/bluebubbles-actions.js"; +export { resolveChannelMediaMaxBytes } from "../../../src/channels/plugins/media-limits.js"; +export { PAIRING_APPROVED_MESSAGE } from "../../../src/channels/plugins/pairing-message.js"; +export { collectBlueBubblesStatusIssues } from "../../../src/channels/plugins/status-issues/bluebubbles.js"; +export type { + BaseProbeResult, + ChannelAccountSnapshot, + ChannelMessageActionAdapter, + ChannelMessageActionName, +} from "../../../src/channels/plugins/types.js"; +export type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; +export type { OpenClawConfig } from "../../../src/config/config.js"; +export { parseFiniteNumber } from "../../../src/infra/parse-finite-number.js"; +export type { PluginRuntime } from "../../../src/plugins/runtime/types.js"; +export { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; +export { + DM_GROUP_ACCESS_REASON, + readStoreAllowFromForDmPolicy, + resolveDmGroupAccessWithLists, +} from "../../../src/security/dm-policy-shared.js"; +export { readBooleanParam } from "../../../src/plugin-sdk/boolean-param.js"; +export { mapAllowFromEntries } from "../../../src/plugin-sdk/channel-config-helpers.js"; +export { createChannelPairingController } from "../../../src/plugin-sdk/channel-pairing.js"; +export { createChannelReplyPipeline } from "../../../src/plugin-sdk/channel-reply-pipeline.js"; +export { resolveRequestUrl } from "../../../src/plugin-sdk/request-url.js"; +export { buildProbeChannelStatusSummary } from "../../../src/plugin-sdk/status-helpers.js"; +export { stripMarkdown } from "../../../src/plugin-sdk/text-runtime.js"; +export { extractToolSend } from "../../../src/plugin-sdk/tool-send.js"; +export { + WEBHOOK_RATE_LIMIT_DEFAULTS, + createFixedWindowRateLimiter, + createWebhookInFlightLimiter, + readWebhookBodyOrReject, + registerWebhookTargetWithPluginRoute, + resolveRequestClientIp, + resolveWebhookTargetWithAuthOrRejectSync, + withResolvedWebhookRequestPipeline, +} from "../../../src/plugin-sdk/webhook-ingress.js"; diff --git a/extensions/discord/src/channel.ts b/extensions/discord/src/channel.ts index 861c9cfabb3..8d748e61bc3 100644 --- a/extensions/discord/src/channel.ts +++ b/extensions/discord/src/channel.ts @@ -46,7 +46,7 @@ import { normalizeDiscordOutboundTarget, } from "./normalize.js"; import { resolveDiscordOutboundSessionRoute } from "./outbound-session-route.js"; -import { probeDiscord, type DiscordProbe } from "./probe.js"; +import type { DiscordProbe } from "./probe.js"; import { resolveDiscordUserAllowlist } from "./resolve-users.js"; import { buildTokenChannelStatusSummary, @@ -74,12 +74,18 @@ type DiscordSendFn = ReturnType< let discordProviderRuntimePromise: | Promise | undefined; +let discordProbeRuntimePromise: Promise | undefined; async function loadDiscordProviderRuntime() { discordProviderRuntimePromise ??= import("./monitor/provider.runtime.js"); return await discordProviderRuntimePromise; } +async function loadDiscordProbeRuntime() { + discordProbeRuntimePromise ??= import("./probe.runtime.js"); + return await discordProbeRuntimePromise; +} + const meta = getChatChannelMeta("discord"); const REQUIRED_DISCORD_PERMISSIONS = ["ViewChannel", "SendMessages"] as const; @@ -364,7 +370,7 @@ export const discordPlugin: ChannelPlugin buildChannelSummary: ({ snapshot }) => buildTokenChannelStatusSummary(snapshot, { includeMode: false }), probeAccount: async ({ account, timeoutMs }) => - probeDiscord(account.token, timeoutMs, { + (await loadDiscordProbeRuntime()).probeDiscord(account.token, timeoutMs, { includeApplication: true, }), formatCapabilitiesProbe: ({ probe }) => { @@ -510,7 +516,9 @@ export const discordPlugin: ChannelPlugin const token = account.token.trim(); let discordBotLabel = ""; try { - const probe = await probeDiscord(token, 2500, { + const probe = await ( + await loadDiscordProbeRuntime() + ).probeDiscord(token, 2500, { includeApplication: true, }); const username = probe.ok ? probe.bot?.username?.trim() : null; diff --git a/extensions/discord/src/probe.runtime.ts b/extensions/discord/src/probe.runtime.ts new file mode 100644 index 00000000000..c8d36c0c808 --- /dev/null +++ b/extensions/discord/src/probe.runtime.ts @@ -0,0 +1 @@ +export * from "./probe.js"; diff --git a/extensions/discord/src/subagent-hooks.test.ts b/extensions/discord/src/subagent-hooks.test.ts index 927ae73b0d3..890be595f69 100644 --- a/extensions/discord/src/subagent-hooks.test.ts +++ b/extensions/discord/src/subagent-hooks.test.ts @@ -4,7 +4,6 @@ import { getRequiredHookHandler, registerHookHandlersForTest, } from "../../../test/helpers/extensions/subagent-hooks.js"; -import { registerDiscordSubagentHooks } from "./subagent-hooks.js"; type ThreadBindingRecord = { accountId: string; @@ -39,6 +38,8 @@ const hookMocks = vi.hoisted(() => ({ unbindThreadBindingsBySessionKey: vi.fn(() => []), })); +let registerDiscordSubagentHooks: typeof import("./subagent-hooks.js").registerDiscordSubagentHooks; + vi.mock("./accounts.js", () => ({ resolveDiscordAccount: hookMocks.resolveDiscordAccount, })); @@ -165,7 +166,9 @@ async function expectSubagentSpawningError(params?: { } describe("discord subagent hook handlers", () => { - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); + ({ registerDiscordSubagentHooks } = await import("./subagent-hooks.js")); hookMocks.resolveDiscordAccount.mockClear(); hookMocks.resolveDiscordAccount.mockImplementation((params?: { accountId?: string }) => ({ accountId: params?.accountId?.trim() || "default", diff --git a/extensions/discord/src/voice-message.test.ts b/extensions/discord/src/voice-message.test.ts index 6aad57e6e2a..b07b8a8223b 100644 --- a/extensions/discord/src/voice-message.test.ts +++ b/extensions/discord/src/voice-message.test.ts @@ -4,17 +4,13 @@ const runFfprobeMock = vi.hoisted(() => vi.fn<(...args: unknown[]) => Promise vi.fn<(...args: unknown[]) => Promise>()); vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { - const actual = await importOriginal(); return { - ...actual, resolvePreferredOpenClawTmpDir: () => "/tmp", }; }); vi.mock("openclaw/plugin-sdk/media-runtime", async (importOriginal) => { - const actual = await importOriginal(); return { - ...actual, runFfprobe: runFfprobeMock, runFfmpeg: runFfmpegMock, parseFfprobeCodecAndSampleRate: (stdout: string) => { diff --git a/extensions/feishu/src/channel.test.ts b/extensions/feishu/src/channel.test.ts index 4e4a973a91c..d468dc5ce1c 100644 --- a/extensions/feishu/src/channel.test.ts +++ b/extensions/feishu/src/channel.test.ts @@ -55,7 +55,12 @@ vi.mock("./channel.runtime.js", () => ({ }, })); -import { feishuPlugin } from "./channel.js"; +vi.mock("../../../src/channels/plugins/bundled.js", () => ({ + bundledChannelPlugins: [], + bundledChannelSetupPlugins: [], +})); + +let feishuPlugin: typeof import("./channel.js").feishuPlugin; function getDescribedActions(cfg: OpenClawConfig): string[] { return [...(feishuPlugin.actions?.describeMessageTool?.({ cfg })?.actions ?? [])]; @@ -97,6 +102,11 @@ async function expectLegacyFeishuCardPayloadRejected(cfg: OpenClawConfig, card: } describe("feishuPlugin.status.probeAccount", () => { + beforeEach(async () => { + vi.resetModules(); + ({ feishuPlugin } = await import("./channel.js")); + }); + it("uses current account credentials for multi-account config", async () => { const cfg = { channels: { diff --git a/extensions/feishu/src/client.test.ts b/extensions/feishu/src/client.test.ts index c1bb208e768..d95787bdae9 100644 --- a/extensions/feishu/src/client.test.ts +++ b/extensions/feishu/src/client.test.ts @@ -94,6 +94,11 @@ vi.mock("./subagent-hooks.js", () => ({ registerFeishuSubagentHooks: registerFeishuSubagentHooksMock, })); +vi.mock("../../../src/channels/plugins/bundled.js", () => ({ + bundledChannelPlugins: [], + bundledChannelSetupPlugins: [], +})); + const baseAccount: ResolvedFeishuAccount = { accountId: "main", selectionSource: "explicit", diff --git a/extensions/feishu/src/media.test.ts b/extensions/feishu/src/media.test.ts index fb5b72fe43a..aaf476311ea 100644 --- a/extensions/feishu/src/media.test.ts +++ b/extensions/feishu/src/media.test.ts @@ -42,12 +42,15 @@ vi.mock("./runtime.js", () => ({ }), })); -import { - downloadImageFeishu, - downloadMessageResourceFeishu, - sanitizeFileNameForUpload, - sendMediaFeishu, -} from "./media.js"; +vi.mock("../../../src/channels/plugins/bundled.js", () => ({ + bundledChannelPlugins: [], + bundledChannelSetupPlugins: [], +})); + +let downloadImageFeishu: typeof import("./media.js").downloadImageFeishu; +let downloadMessageResourceFeishu: typeof import("./media.js").downloadMessageResourceFeishu; +let sanitizeFileNameForUpload: typeof import("./media.js").sanitizeFileNameForUpload; +let sendMediaFeishu: typeof import("./media.js").sendMediaFeishu; function expectPathIsolatedToTmpRoot(pathValue: string, key: string): void { expect(pathValue).not.toContain(key); @@ -79,7 +82,14 @@ function mockResolvedFeishuAccount() { } describe("sendMediaFeishu msg_type routing", () => { - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); + ({ + downloadImageFeishu, + downloadMessageResourceFeishu, + sanitizeFileNameForUpload, + sendMediaFeishu, + } = await import("./media.js")); vi.clearAllMocks(); mockResolvedFeishuAccount(); diff --git a/extensions/feishu/src/send-target.test.ts b/extensions/feishu/src/send-target.test.ts index ad2e9326f27..a1a2e17e245 100644 --- a/extensions/feishu/src/send-target.test.ts +++ b/extensions/feishu/src/send-target.test.ts @@ -1,6 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { ClawdbotConfig } from "../runtime-api.js"; -import { resolveFeishuSendTarget } from "./send-target.js"; const resolveFeishuAccountMock = vi.hoisted(() => vi.fn()); const createFeishuClientMock = vi.hoisted(() => vi.fn()); @@ -14,11 +13,15 @@ vi.mock("./client.js", () => ({ createFeishuClient: createFeishuClientMock, })); +let resolveFeishuSendTarget: typeof import("./send-target.js").resolveFeishuSendTarget; + describe("resolveFeishuSendTarget", () => { const cfg = {} as ClawdbotConfig; const client = { id: "client" }; - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); + ({ resolveFeishuSendTarget } = await import("./send-target.js")); resolveFeishuAccountMock.mockReset().mockReturnValue({ accountId: "default", enabled: true, diff --git a/extensions/feishu/src/send.reply-fallback.test.ts b/extensions/feishu/src/send.reply-fallback.test.ts index d4a2f023ac1..a612af2a8e6 100644 --- a/extensions/feishu/src/send.reply-fallback.test.ts +++ b/extensions/feishu/src/send.reply-fallback.test.ts @@ -9,6 +9,7 @@ vi.mock("./send-target.js", () => ({ })); vi.mock("./runtime.js", () => ({ + setFeishuRuntime: vi.fn(), getFeishuRuntime: () => ({ channel: { text: { @@ -19,7 +20,13 @@ vi.mock("./runtime.js", () => ({ }), })); -import { sendCardFeishu, sendMessageFeishu } from "./send.js"; +vi.mock("../../../src/channels/plugins/bundled.js", () => ({ + bundledChannelPlugins: [], + bundledChannelSetupPlugins: [], +})); + +let sendCardFeishu: typeof import("./send.js").sendCardFeishu; +let sendMessageFeishu: typeof import("./send.js").sendMessageFeishu; describe("Feishu reply fallback for withdrawn/deleted targets", () => { const replyMock = vi.fn(); @@ -35,7 +42,9 @@ describe("Feishu reply fallback for withdrawn/deleted targets", () => { expect(result.messageId).toBe(expectedMessageId); } - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); + ({ sendCardFeishu, sendMessageFeishu } = await import("./send.js")); vi.clearAllMocks(); resolveFeishuSendTargetMock.mockReturnValue({ client: { diff --git a/extensions/feishu/src/send.test.ts b/extensions/feishu/src/send.test.ts index 80ff108612e..edd906d984f 100644 --- a/extensions/feishu/src/send.test.ts +++ b/extensions/feishu/src/send.test.ts @@ -1,12 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import type { ClawdbotConfig } from "../runtime-api.js"; -import { - buildStructuredCard, - editMessageFeishu, - getMessageFeishu, - listFeishuThreadMessages, - resolveFeishuCardTemplate, -} from "./send.js"; const { mockClientGet, @@ -42,8 +35,22 @@ vi.mock("./runtime.js", () => ({ }), })); +let buildStructuredCard: typeof import("./send.js").buildStructuredCard; +let editMessageFeishu: typeof import("./send.js").editMessageFeishu; +let getMessageFeishu: typeof import("./send.js").getMessageFeishu; +let listFeishuThreadMessages: typeof import("./send.js").listFeishuThreadMessages; +let resolveFeishuCardTemplate: typeof import("./send.js").resolveFeishuCardTemplate; + describe("getMessageFeishu", () => { - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); + ({ + buildStructuredCard, + editMessageFeishu, + getMessageFeishu, + listFeishuThreadMessages, + resolveFeishuCardTemplate, + } = await import("./send.js")); vi.clearAllMocks(); mockResolveFeishuAccount.mockReturnValue({ accountId: "default", diff --git a/extensions/feishu/src/tool-account-routing.test.ts b/extensions/feishu/src/tool-account-routing.test.ts index bba6b2a0b27..b0f9ba8bf6b 100644 --- a/extensions/feishu/src/tool-account-routing.test.ts +++ b/extensions/feishu/src/tool-account-routing.test.ts @@ -1,10 +1,6 @@ import { beforeEach, describe, expect, test, vi } from "vitest"; import type { OpenClawPluginApi } from "../runtime-api.js"; -import { registerFeishuBitableTools } from "./bitable.js"; -import { registerFeishuDriveTools } from "./drive.js"; -import { registerFeishuPermTools } from "./perm.js"; import { createToolFactoryHarness } from "./tool-factory-test-harness.js"; -import { registerFeishuWikiTools } from "./wiki.js"; const createFeishuClientMock = vi.fn((account: { appId?: string } | undefined) => ({ __appId: account?.appId, @@ -14,6 +10,11 @@ vi.mock("./client.js", () => ({ createFeishuClient: (account: { appId?: string } | undefined) => createFeishuClientMock(account), })); +let registerFeishuBitableTools: typeof import("./bitable.js").registerFeishuBitableTools; +let registerFeishuDriveTools: typeof import("./drive.js").registerFeishuDriveTools; +let registerFeishuPermTools: typeof import("./perm.js").registerFeishuPermTools; +let registerFeishuWikiTools: typeof import("./wiki.js").registerFeishuWikiTools; + function createConfig(params: { toolsA?: { wiki?: boolean; @@ -50,7 +51,16 @@ function createConfig(params: { } describe("feishu tool account routing", () => { - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); + ({ registerFeishuBitableTools, registerFeishuDriveTools, registerFeishuPermTools } = + await import("./bitable.js").then(async ({ registerFeishuBitableTools }) => ({ + registerFeishuBitableTools, + ...(await import("./drive.js")), + ...(await import("./perm.js")), + ...(await import("./wiki.js")), + }))); + ({ registerFeishuWikiTools } = await import("./wiki.js")); vi.clearAllMocks(); }); diff --git a/extensions/msteams/src/setup-surface.test.ts b/extensions/msteams/src/setup-surface.test.ts index a0bc16a42a0..57ba6c388db 100644 --- a/extensions/msteams/src/setup-surface.test.ts +++ b/extensions/msteams/src/setup-surface.test.ts @@ -25,6 +25,11 @@ vi.mock("./token.js", () => ({ resolveMSTeamsCredentials, })); +vi.mock("../../../src/channels/plugins/bundled.js", () => ({ + bundledChannelPlugins: [], + bundledChannelSetupPlugins: [], +})); + describe("msteams setup surface", () => { beforeEach(() => { resolveMSTeamsUserAllowlist.mockReset(); diff --git a/extensions/signal/api.ts b/extensions/signal/api.ts index aa58f388681..aea9ce0ab9c 100644 --- a/extensions/signal/api.ts +++ b/extensions/signal/api.ts @@ -8,4 +8,4 @@ export * from "./src/probe.js"; export * from "./src/reaction-level.js"; export * from "./src/send-reactions.js"; export * from "./src/send.js"; -export { normalizeSignalAccountInput } from "./src/setup-surface.js"; +export { normalizeSignalAccountInput } from "./src/setup-core.js"; diff --git a/extensions/signal/src/accounts.ts b/extensions/signal/src/accounts.ts index 755a3c39a5a..020bf4fac45 100644 --- a/extensions/signal/src/accounts.ts +++ b/extensions/signal/src/accounts.ts @@ -4,7 +4,7 @@ import { resolveMergedAccountConfig, type OpenClawConfig, } from "openclaw/plugin-sdk/account-resolution"; -import type { SignalAccountConfig } from "./runtime-api.js"; +import type { SignalAccountConfig } from "openclaw/plugin-sdk/signal"; export type ResolvedSignalAccount = { accountId: string; diff --git a/extensions/signal/src/channel.ts b/extensions/signal/src/channel.ts index 0e1af819978..048d692aa23 100644 --- a/extensions/signal/src/channel.ts +++ b/extensions/signal/src/channel.ts @@ -13,6 +13,7 @@ import { createComputedAccountStatusAdapter } from "openclaw/plugin-sdk/status-h import { resolveSignalAccount, type ResolvedSignalAccount } from "./accounts.js"; import { markdownToSignalTextChunks } from "./format.js"; import { signalMessageActions } from "./message-actions.js"; +import { looksLikeSignalTargetId, normalizeSignalMessagingTarget } from "./normalize.js"; import { resolveSignalOutboundTarget } from "./outbound-session.js"; import type { SignalProbe } from "./probe.js"; import { @@ -20,9 +21,7 @@ import { collectStatusIssuesFromLastError, createDefaultChannelRuntimeState, DEFAULT_ACCOUNT_ID, - looksLikeSignalTargetId, normalizeE164, - normalizeSignalMessagingTarget, PAIRING_APPROVED_MESSAGE, resolveChannelMediaMaxBytes, type ChannelPlugin, diff --git a/extensions/signal/src/monitor/event-handler.ts b/extensions/signal/src/monitor/event-handler.ts index 58ff8d4f8d7..1e6edb5377c 100644 --- a/extensions/signal/src/monitor/event-handler.ts +++ b/extensions/signal/src/monitor/event-handler.ts @@ -47,7 +47,7 @@ import { resolveSignalSender, type SignalSender, } from "../identity.js"; -import { normalizeSignalMessagingTarget } from "../runtime-api.js"; +import { normalizeSignalMessagingTarget } from "../normalize.js"; import { sendMessageSignal, sendReadReceiptSignal, sendTypingSignal } from "../send.js"; import { handleSignalDirectMessageAccess, resolveSignalAccessState } from "./access-policy.js"; import type { diff --git a/extensions/signal/src/normalize.ts b/extensions/signal/src/normalize.ts new file mode 100644 index 00000000000..0c2cb638aec --- /dev/null +++ b/extensions/signal/src/normalize.ts @@ -0,0 +1,67 @@ +export function normalizeSignalMessagingTarget(raw: string): string | undefined { + const trimmed = raw.trim(); + if (!trimmed) { + return undefined; + } + let normalized = trimmed; + if (normalized.toLowerCase().startsWith("signal:")) { + normalized = normalized.slice("signal:".length).trim(); + } + if (!normalized) { + return undefined; + } + const lower = normalized.toLowerCase(); + if (lower.startsWith("group:")) { + const id = normalized.slice("group:".length).trim(); + return id ? `group:${id}` : undefined; + } + if (lower.startsWith("username:")) { + const id = normalized.slice("username:".length).trim(); + return id ? `username:${id}`.toLowerCase() : undefined; + } + if (lower.startsWith("u:")) { + const id = normalized.slice("u:".length).trim(); + return id ? `username:${id}`.toLowerCase() : undefined; + } + if (lower.startsWith("uuid:")) { + const id = normalized.slice("uuid:".length).trim(); + return id ? id.toLowerCase() : undefined; + } + return normalized.toLowerCase(); +} + +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; +const UUID_COMPACT_PATTERN = /^[0-9a-f]{32}$/i; + +export function looksLikeSignalTargetId(raw: string, normalized?: string): boolean { + const candidates = [raw, normalized ?? ""].map((value) => value.trim()).filter(Boolean); + + for (const candidate of candidates) { + if (/^(signal:)?(group:|username:|u:)/i.test(candidate)) { + return true; + } + if (/^(signal:)?uuid:/i.test(candidate)) { + const stripped = candidate + .replace(/^signal:/i, "") + .replace(/^uuid:/i, "") + .trim(); + if (!stripped) { + continue; + } + if (UUID_PATTERN.test(stripped) || UUID_COMPACT_PATTERN.test(stripped)) { + return true; + } + continue; + } + + const withoutSignalPrefix = candidate.replace(/^signal:/i, "").trim(); + if (UUID_PATTERN.test(withoutSignalPrefix) || UUID_COMPACT_PATTERN.test(withoutSignalPrefix)) { + return true; + } + if (/^\+?\d{3,}$/.test(withoutSignalPrefix)) { + return true; + } + } + + return false; +} diff --git a/extensions/signal/src/shared.ts b/extensions/signal/src/shared.ts index 02921a71f14..982b0517574 100644 --- a/extensions/signal/src/shared.ts +++ b/extensions/signal/src/shared.ts @@ -4,7 +4,12 @@ import { createScopedChannelConfigAdapter, } from "openclaw/plugin-sdk/channel-config-helpers"; import { createRestrictSendersChannelSecurity } from "openclaw/plugin-sdk/channel-policy"; -import { createChannelPluginBase } from "openclaw/plugin-sdk/core"; +import { + createChannelPluginBase, + getChatChannelMeta, + type ChannelPlugin, +} from "openclaw/plugin-sdk/core"; +import { normalizeE164 } from "openclaw/plugin-sdk/setup"; import { listSignalAccountIds, resolveDefaultSignalAccountId, @@ -12,7 +17,6 @@ import { type ResolvedSignalAccount, } from "./accounts.js"; import { SignalChannelConfigSchema } from "./config-schema.js"; -import { getChatChannelMeta, normalizeE164, type ChannelPlugin } from "./runtime-api.js"; import { createSignalSetupWizardProxy } from "./setup-core.js"; export const SIGNAL_CHANNEL = "signal" as const; @@ -27,9 +31,9 @@ export const signalSetupWizard = createSignalSetupWizardProxy( export const signalConfigAdapter = createScopedChannelConfigAdapter({ sectionKey: SIGNAL_CHANNEL, - listAccountIds: listSignalAccountIds, - resolveAccount: adaptScopedAccountAccessor(resolveSignalAccount), - defaultAccountId: resolveDefaultSignalAccountId, + listAccountIds: (cfg) => listSignalAccountIds(cfg), + resolveAccount: adaptScopedAccountAccessor((params) => resolveSignalAccount(params)), + defaultAccountId: (cfg) => resolveDefaultSignalAccountId(cfg), clearBaseFields: ["account", "httpUrl", "httpHost", "httpPort", "cliPath", "name"], resolveAllowFrom: (account: ResolvedSignalAccount) => account.config.allowFrom, formatAllowFrom: (allowFrom) => diff --git a/src/plugin-sdk/account-resolution.ts b/src/plugin-sdk/account-resolution.ts index 490bca54ecd..4f44736dc33 100644 --- a/src/plugin-sdk/account-resolution.ts +++ b/src/plugin-sdk/account-resolution.ts @@ -20,13 +20,19 @@ export { normalizeE164, pathExists, resolveUserPath } from "../utils.js"; export { resolveDiscordAccount, type ResolvedDiscordAccount, -} from "../../extensions/discord/api.js"; -export { resolveSlackAccount, type ResolvedSlackAccount } from "../../extensions/slack/api.js"; +} from "../../extensions/discord/src/accounts.js"; +export { + resolveSlackAccount, + type ResolvedSlackAccount, +} from "../../extensions/slack/src/accounts.js"; export { resolveTelegramAccount, type ResolvedTelegramAccount, -} from "../../extensions/telegram/api.js"; -export { resolveSignalAccount, type ResolvedSignalAccount } from "../../extensions/signal/api.js"; +} from "../../extensions/telegram/src/accounts.js"; +export { + resolveSignalAccount, + type ResolvedSignalAccount, +} from "../../extensions/signal/src/accounts.js"; /** Resolve an account by id, then fall back to the default account when the primary lacks credentials. */ export function resolveAccountWithDefaultFallback(params: { diff --git a/src/plugin-sdk/bluebubbles.ts b/src/plugin-sdk/bluebubbles.ts index 7ae7dd6d22a..d8ca91c53a2 100644 --- a/src/plugin-sdk/bluebubbles.ts +++ b/src/plugin-sdk/bluebubbles.ts @@ -28,7 +28,7 @@ export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; export { resolveBlueBubblesGroupRequireMention, resolveBlueBubblesGroupToolPolicy, -} from "../../extensions/bluebubbles/runtime-api.js"; +} from "../../extensions/bluebubbles/src/group-policy.js"; export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js"; export { diff --git a/src/plugin-sdk/compat.ts b/src/plugin-sdk/compat.ts index eb85c062c71..8013fa6cef6 100644 --- a/src/plugin-sdk/compat.ts +++ b/src/plugin-sdk/compat.ts @@ -48,5 +48,5 @@ export { mapAllowlistResolutionInputs } from "./allow-from.js"; export { resolveBlueBubblesGroupRequireMention, resolveBlueBubblesGroupToolPolicy, -} from "../../extensions/bluebubbles/runtime-api.js"; +} from "../../extensions/bluebubbles/src/group-policy.js"; export { collectBlueBubblesStatusIssues } from "../channels/plugins/status-issues/bluebubbles.js"; diff --git a/src/plugin-sdk/line.ts b/src/plugin-sdk/line.ts index ef3c4d5a815..58d0dd2af70 100644 --- a/src/plugin-sdk/line.ts +++ b/src/plugin-sdk/line.ts @@ -31,14 +31,14 @@ export { normalizeAccountId, resolveDefaultLineAccountId, resolveLineAccount, -} from "../../extensions/line/api.js"; -export { LineConfigSchema } from "../../extensions/line/api.js"; +} from "../../extensions/line/runtime-api.js"; +export { LineConfigSchema } from "../../extensions/line/runtime-api.js"; export type { LineChannelData, LineConfig, ResolvedLineAccount, -} from "../../extensions/line/api.js"; -export type { LineProbeResult } from "../../extensions/line/api.js"; +} from "../../extensions/line/runtime-api.js"; +export type { LineProbeResult } from "../../extensions/line/runtime-api.js"; export { createActionCard, createAgendaCard, @@ -52,5 +52,5 @@ export { createReceiptCard, type CardAction, type ListItem, -} from "../../extensions/line/api.js"; -export { processLineMessage } from "../../extensions/line/api.js"; +} from "../../extensions/line/runtime-api.js"; +export { processLineMessage } from "../../extensions/line/runtime-api.js"; diff --git a/src/plugin-sdk/signal.ts b/src/plugin-sdk/signal.ts index def847ccd33..760998bc710 100644 --- a/src/plugin-sdk/signal.ts +++ b/src/plugin-sdk/signal.ts @@ -4,7 +4,7 @@ export type { ChannelMessageActionAdapter } from "../channels/plugins/types.js"; export type { OpenClawConfig } from "../config/config.js"; export type { SignalAccountConfig } from "../config/types.js"; -export type { ResolvedSignalAccount } from "../../extensions/signal/api.js"; +export type { ResolvedSignalAccount } from "../../extensions/signal/src/accounts.js"; export type { ChannelMessageActionContext, ChannelPlugin, @@ -54,10 +54,13 @@ export { listEnabledSignalAccounts, listSignalAccountIds, resolveDefaultSignalAccountId, -} from "../../extensions/signal/api.js"; -export { monitorSignalProvider } from "../../extensions/signal/api.js"; -export { probeSignal } from "../../extensions/signal/api.js"; -export { resolveSignalReactionLevel } from "../../extensions/signal/api.js"; -export { removeReactionSignal, sendReactionSignal } from "../../extensions/signal/api.js"; -export { sendMessageSignal } from "../../extensions/signal/api.js"; -export { signalMessageActions } from "../../extensions/signal/api.js"; +} from "../../extensions/signal/src/accounts.js"; +export { monitorSignalProvider } from "../../extensions/signal/src/monitor.js"; +export { probeSignal } from "../../extensions/signal/src/probe.js"; +export { resolveSignalReactionLevel } from "../../extensions/signal/src/reaction-level.js"; +export { + removeReactionSignal, + sendReactionSignal, +} from "../../extensions/signal/src/send-reactions.js"; +export { sendMessageSignal } from "../../extensions/signal/src/send.js"; +export { signalMessageActions } from "../../extensions/signal/src/message-actions.js";