diff --git a/extensions/mattermost/src/channel.test.ts b/extensions/mattermost/src/channel.test.ts index 25445a9a459..41ae267c156 100644 --- a/extensions/mattermost/src/channel.test.ts +++ b/extensions/mattermost/src/channel.test.ts @@ -2,6 +2,12 @@ import { Type } from "@sinclair/typebox"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../runtime-api.js"; import { createChannelReplyPipeline } from "../runtime-api.js"; + +vi.mock("../../../src/config/bundled-channel-config-runtime.js", () => ({ + getBundledChannelRuntimeMap: () => new Map(), + getBundledChannelConfigSchemaMap: () => new Map(), +})); + const { sendMessageMattermostMock, mockFetchGuard } = vi.hoisted(() => ({ sendMessageMattermostMock: vi.fn(), mockFetchGuard: vi.fn(async (p: { url: string; init?: RequestInit }) => { @@ -19,14 +25,15 @@ vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { return { ...original, fetchWithSsrFGuard: mockFetchGuard }; }); -import { mattermostPlugin } from "./channel.js"; -import { resetMattermostReactionBotUserCacheForTests } from "./mattermost/reactions.js"; import { createMattermostReactionFetchMock, createMattermostTestConfig, withMockedGlobalFetch, } from "./mattermost/reactions.test-helpers.js"; +let mattermostPlugin: typeof import("./channel.js").mattermostPlugin; +let resetMattermostReactionBotUserCacheForTests: typeof import("./mattermost/reactions.js").resetMattermostReactionBotUserCacheForTests; + type MattermostHandleAction = NonNullable< NonNullable["handleAction"] >; @@ -93,7 +100,10 @@ function createMattermostActionContext( } describe("mattermostPlugin", () => { - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); + ({ mattermostPlugin } = await import("./channel.js")); + ({ resetMattermostReactionBotUserCacheForTests } = await import("./mattermost/reactions.js")); sendMessageMattermostMock.mockReset(); sendMessageMattermostMock.mockResolvedValue({ messageId: "post-1", diff --git a/extensions/mattermost/src/mattermost/directory.test.ts b/extensions/mattermost/src/mattermost/directory.test.ts index 9d955169367..bb7723e22dd 100644 --- a/extensions/mattermost/src/mattermost/directory.test.ts +++ b/extensions/mattermost/src/mattermost/directory.test.ts @@ -28,11 +28,15 @@ vi.mock("./client.js", () => { }; }); -import { listMattermostDirectoryGroups, listMattermostDirectoryPeers } from "./directory.js"; +let listMattermostDirectoryGroups: typeof import("./directory.js").listMattermostDirectoryGroups; +let listMattermostDirectoryPeers: typeof import("./directory.js").listMattermostDirectoryPeers; describe("mattermost directory", () => { - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); vi.clearAllMocks(); + ({ listMattermostDirectoryGroups, listMattermostDirectoryPeers } = + await import("./directory.js")); }); it("deduplicates channels across enabled accounts and skips failing accounts", async () => { diff --git a/extensions/mattermost/src/setup-core.ts b/extensions/mattermost/src/setup-core.ts index 126b5890044..cfa145cfc99 100644 --- a/extensions/mattermost/src/setup-core.ts +++ b/extensions/mattermost/src/setup-core.ts @@ -1,5 +1,5 @@ import type { ChannelSetupAdapter } from "openclaw/plugin-sdk/channel-setup"; -import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup"; +import { createSetupInputPresenceValidator } from "openclaw/plugin-sdk/setup-runtime"; import { resolveMattermostAccount, type ResolvedMattermostAccount } from "./mattermost/accounts.js"; import { normalizeMattermostBaseUrl } from "./mattermost/client.js"; import { diff --git a/extensions/mattermost/src/setup.test.ts b/extensions/mattermost/src/setup.test.ts index c69560b0138..87f20cb1c37 100644 --- a/extensions/mattermost/src/setup.test.ts +++ b/extensions/mattermost/src/setup.test.ts @@ -1,9 +1,12 @@ import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/setup"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createTestPluginApi } from "../../../test/helpers/extensions/plugin-api.js"; -import plugin from "../index.js"; import type { OpenClawConfig, OpenClawPluginApi } from "../runtime-api.js"; -import { mattermostSetupWizard } from "./setup-surface.js"; + +vi.mock("../../../src/config/bundled-channel-config-runtime.js", () => ({ + getBundledChannelRuntimeMap: () => new Map(), + getBundledChannelConfigSchemaMap: () => new Map(), +})); const resolveMattermostAccount = vi.hoisted(() => vi.fn()); const normalizeMattermostBaseUrl = vi.hoisted(() => vi.fn((value: string | undefined) => value)); @@ -51,7 +54,16 @@ function createApi( }); } +let plugin: typeof import("../index.js").default; +let mattermostSetupWizard: typeof import("./setup-surface.js").mattermostSetupWizard; + describe("mattermost setup", () => { + beforeEach(async () => { + vi.resetModules(); + ({ default: plugin } = await import("../index.js")); + ({ mattermostSetupWizard } = await import("./setup-surface.js")); + }); + afterEach(() => { resolveMattermostAccount.mockReset(); normalizeMattermostBaseUrl.mockReset(); diff --git a/extensions/nextcloud-talk/src/core.test.ts b/extensions/nextcloud-talk/src/core.test.ts index e106aa20cac..54fda379d77 100644 --- a/extensions/nextcloud-talk/src/core.test.ts +++ b/extensions/nextcloud-talk/src/core.test.ts @@ -1,9 +1,7 @@ import { mkdtemp, rm } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { afterEach, describe, expect, it, vi } from "vitest"; -import { nextcloudTalkPlugin } from "./channel.js"; -import { NextcloudTalkConfigSchema } from "./config-schema.js"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { escapeNextcloudTalkMarkdown, formatNextcloudTalkCodeBlock, @@ -28,6 +26,11 @@ import { } from "./signature.js"; import type { CoreConfig } from "./types.js"; +vi.mock("../../../src/config/bundled-channel-config-runtime.js", () => ({ + getBundledChannelRuntimeMap: () => new Map(), + getBundledChannelConfigSchemaMap: () => new Map(), +})); + const fetchWithSsrFGuard = vi.hoisted(() => vi.fn()); const readFileSync = vi.hoisted(() => vi.fn()); @@ -39,6 +42,14 @@ vi.mock("../runtime-api.js", async (importOriginal) => { }; }); +vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetchWithSsrFGuard, + }; +}); + vi.mock("node:fs", async (importOriginal) => { const actual = await importOriginal(); return { @@ -48,6 +59,14 @@ vi.mock("node:fs", async (importOriginal) => { }); const tempDirs: string[] = []; +let nextcloudTalkPlugin: typeof import("./channel.js").nextcloudTalkPlugin; +let NextcloudTalkConfigSchema: typeof import("./config-schema.js").NextcloudTalkConfigSchema; + +beforeEach(async () => { + vi.resetModules(); + ({ nextcloudTalkPlugin } = await import("./channel.js")); + ({ NextcloudTalkConfigSchema } = await import("./config-schema.js")); +}); afterEach(async () => { fetchWithSsrFGuard.mockReset(); @@ -406,6 +425,7 @@ describe("nextcloud talk core", () => { }); it("resolves direct rooms from the room info endpoint", async () => { + vi.resetModules(); const release = vi.fn(async () => {}); fetchWithSsrFGuard.mockResolvedValue({ response: { @@ -445,6 +465,7 @@ describe("nextcloud talk core", () => { }); it("reads the api password from a file and logs non-ok room info responses", async () => { + vi.resetModules(); const release = vi.fn(async () => {}); const log = vi.fn(); const error = vi.fn(); @@ -480,6 +501,7 @@ describe("nextcloud talk core", () => { }); it("returns undefined from room info without credentials or base url", async () => { + vi.resetModules(); const { resolveNextcloudTalkRoomKind } = await import("./room-info.js"); await expect( diff --git a/extensions/nextcloud-talk/src/setup-core.ts b/extensions/nextcloud-talk/src/setup-core.ts index 3732c75c8a7..113bfd6a1cb 100644 --- a/extensions/nextcloud-talk/src/setup-core.ts +++ b/extensions/nextcloud-talk/src/setup-core.ts @@ -2,21 +2,18 @@ import type { ChannelSetupAdapter, ChannelSetupInput } from "openclaw/plugin-sdk import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/routing"; import { - applyAccountNameToChannelSection, createSetupInputPresenceValidator, - patchScopedAccountConfig, -} from "openclaw/plugin-sdk/setup"; -import { mergeAllowFromEntries, createTopLevelChannelDmPolicy, promptParsedAllowFromForAccount, resolveSetupAccountId, setSetupChannelEnabled, -} from "openclaw/plugin-sdk/setup"; -import type { ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup"; -import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "openclaw/plugin-sdk/setup"; -import type { WizardPrompter } from "openclaw/plugin-sdk/setup"; + type ChannelSetupDmPolicy, + type ChannelSetupWizard, + type WizardPrompter, +} from "openclaw/plugin-sdk/setup-runtime"; +import { formatDocsLink } from "openclaw/plugin-sdk/setup-tools"; +import { applyAccountNameToChannelSection, patchScopedAccountConfig } from "../runtime-api.js"; import { listNextcloudTalkAccountIds, resolveDefaultNextcloudTalkAccountId, diff --git a/extensions/nextcloud-talk/src/setup.test.ts b/extensions/nextcloud-talk/src/setup.test.ts index 16596228cda..be710b5691a 100644 --- a/extensions/nextcloud-talk/src/setup.test.ts +++ b/extensions/nextcloud-talk/src/setup.test.ts @@ -14,19 +14,14 @@ import { startAccountAndTrackLifecycle, waitForStartedMocks, } from "../../../test/helpers/extensions/start-account-lifecycle.js"; -import { resolveNextcloudTalkAccount, type ResolvedNextcloudTalkAccount } from "./accounts.js"; -import { nextcloudTalkPlugin } from "./channel.js"; -import { - clearNextcloudTalkAccountFields, - nextcloudTalkDmPolicy, - nextcloudTalkSetupAdapter, - normalizeNextcloudTalkBaseUrl, - setNextcloudTalkAccountConfig, - validateNextcloudTalkBaseUrl, -} from "./setup-core.js"; -import { nextcloudTalkSetupWizard } from "./setup-surface.js"; +import type { ResolvedNextcloudTalkAccount } from "./accounts.js"; import type { CoreConfig } from "./types.js"; +vi.mock("../../../src/config/bundled-channel-config-runtime.js", () => ({ + getBundledChannelRuntimeMap: () => new Map(), + getBundledChannelConfigSchemaMap: () => new Map(), +})); + const hoisted = vi.hoisted(() => ({ monitorNextcloudTalkProvider: vi.fn(), loadConfig: vi.fn(), @@ -49,9 +44,13 @@ vi.mock("./monitor.js", async () => { }; }); -vi.mock("./runtime.js", () => ({ - getNextcloudTalkRuntime: () => createSendCfgThreadingRuntime(hoisted), -})); +vi.mock("./runtime.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getNextcloudTalkRuntime: () => createSendCfgThreadingRuntime(hoisted), + }; +}); vi.mock("./accounts.js", async (importOriginal) => { const actual = await importOriginal(); @@ -80,7 +79,17 @@ vi.mock("openclaw/plugin-sdk/infra-runtime", async (importOriginal) => { const accountsActual = await vi.importActual("./accounts.js"); hoisted.resolveNextcloudTalkAccount.mockImplementation(accountsActual.resolveNextcloudTalkAccount); -import { sendMessageNextcloudTalk, sendReactionNextcloudTalk } from "./send.js"; +let resolveNextcloudTalkAccount: typeof import("./accounts.js").resolveNextcloudTalkAccount; +let nextcloudTalkPlugin: typeof import("./channel.js").nextcloudTalkPlugin; +let clearNextcloudTalkAccountFields: typeof import("./setup-core.js").clearNextcloudTalkAccountFields; +let nextcloudTalkDmPolicy: typeof import("./setup-core.js").nextcloudTalkDmPolicy; +let nextcloudTalkSetupAdapter: typeof import("./setup-core.js").nextcloudTalkSetupAdapter; +let normalizeNextcloudTalkBaseUrl: typeof import("./setup-core.js").normalizeNextcloudTalkBaseUrl; +let setNextcloudTalkAccountConfig: typeof import("./setup-core.js").setNextcloudTalkAccountConfig; +let validateNextcloudTalkBaseUrl: typeof import("./setup-core.js").validateNextcloudTalkBaseUrl; +let nextcloudTalkSetupWizard: typeof import("./setup-surface.js").nextcloudTalkSetupWizard; +let sendMessageNextcloudTalk: typeof import("./send.js").sendMessageNextcloudTalk; +let sendReactionNextcloudTalk: typeof import("./send.js").sendReactionNextcloudTalk; function buildAccount(): ResolvedNextcloudTalkAccount { return { @@ -114,6 +123,25 @@ function startNextcloudAccount(abortSignal?: AbortSignal) { } describe("nextcloud talk setup", () => { + beforeEach(async () => { + vi.resetModules(); + ({ resolveNextcloudTalkAccount } = await import("./accounts.js")); + ({ nextcloudTalkPlugin } = await import("./channel.js")); + ({ + clearNextcloudTalkAccountFields, + nextcloudTalkDmPolicy, + nextcloudTalkSetupAdapter, + normalizeNextcloudTalkBaseUrl, + setNextcloudTalkAccountConfig, + validateNextcloudTalkBaseUrl, + } = await import("./setup-core.js")); + ({ nextcloudTalkSetupWizard } = await import("./setup-surface.js")); + ({ sendMessageNextcloudTalk, sendReactionNextcloudTalk } = await import("./send.js")); + hoisted.resolveNextcloudTalkAccount.mockImplementation( + accountsActual.resolveNextcloudTalkAccount, + ); + }); + afterEach(() => { vi.clearAllMocks(); hoisted.resolveNextcloudTalkAccount.mockImplementation( diff --git a/extensions/signal/src/setup-core.ts b/extensions/signal/src/setup-core.ts index 89a3e80845f..63defa05f5a 100644 --- a/extensions/signal/src/setup-core.ts +++ b/extensions/signal/src/setup-core.ts @@ -5,20 +5,18 @@ import { createPatchedAccountSetupAdapter, createSetupInputPresenceValidator, createTopLevelChannelDmPolicy, - normalizeE164, parseSetupEntriesAllowingWildcard, promptParsedAllowFromForAccount, setAccountAllowFromForChannel, setSetupChannelEnabled, type OpenClawConfig, type WizardPrompter, -} from "openclaw/plugin-sdk/setup"; -import type { - ChannelSetupAdapter, - ChannelSetupWizard, - ChannelSetupWizardTextInput, -} from "openclaw/plugin-sdk/setup"; + type ChannelSetupAdapter, + type ChannelSetupWizard, + type ChannelSetupWizardTextInput, +} from "openclaw/plugin-sdk/setup-runtime"; import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools"; +import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime"; import { listSignalAccountIds, resolveDefaultSignalAccountId, diff --git a/extensions/signal/src/shared.ts b/extensions/signal/src/shared.ts index 982b0517574..7b79b762db6 100644 --- a/extensions/signal/src/shared.ts +++ b/extensions/signal/src/shared.ts @@ -9,7 +9,7 @@ import { getChatChannelMeta, type ChannelPlugin, } from "openclaw/plugin-sdk/core"; -import { normalizeE164 } from "openclaw/plugin-sdk/setup"; +import { normalizeE164 } from "openclaw/plugin-sdk/text-runtime"; import { listSignalAccountIds, resolveDefaultSignalAccountId, diff --git a/extensions/slack/src/outbound-adapter.test.ts b/extensions/slack/src/outbound-adapter.test.ts index fe1ab2dadc9..50fb2059254 100644 --- a/extensions/slack/src/outbound-adapter.test.ts +++ b/extensions/slack/src/outbound-adapter.test.ts @@ -1,8 +1,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -const sendMessageSlackMock = vi.fn(); -const hasHooksMock = vi.fn(); -const runMessageSendingMock = vi.fn(); +const sendMessageSlackMock = vi.hoisted(() => vi.fn()); +const hasHooksMock = vi.hoisted(() => vi.fn()); +const runMessageSendingMock = vi.hoisted(() => vi.fn()); vi.mock("./send.js", () => ({ sendMessageSlack: (...args: unknown[]) => sendMessageSlackMock(...args), @@ -15,7 +15,7 @@ vi.mock("openclaw/plugin-sdk/plugin-runtime", () => ({ }), })); -import { slackOutbound } from "./outbound-adapter.js"; +let slackOutbound: typeof import("./outbound-adapter.js").slackOutbound; describe("slackOutbound", () => { const cfg = { @@ -27,11 +27,13 @@ describe("slackOutbound", () => { }, }; - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); sendMessageSlackMock.mockReset(); hasHooksMock.mockReset(); runMessageSendingMock.mockReset(); hasHooksMock.mockReturnValue(false); + ({ slackOutbound } = await import("./outbound-adapter.js")); }); it("sends payload media first, then finalizes with blocks", async () => { diff --git a/extensions/slack/src/outbound-hooks.test.ts b/extensions/slack/src/outbound-hooks.test.ts index c573b8988ae..8eccc072083 100644 --- a/extensions/slack/src/outbound-hooks.test.ts +++ b/extensions/slack/src/outbound-hooks.test.ts @@ -1,17 +1,18 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../../../src/config/config.js"; +const sendMessageSlackMock = vi.hoisted(() => vi.fn()); +const getGlobalHookRunnerMock = vi.hoisted(() => vi.fn()); + vi.mock("./send.js", () => ({ - sendMessageSlack: vi.fn().mockResolvedValue({ messageId: "1234.5678", channelId: "C123" }), + sendMessageSlack: sendMessageSlackMock, })); vi.mock("openclaw/plugin-sdk/plugin-runtime", () => ({ - getGlobalHookRunner: vi.fn(), + getGlobalHookRunner: getGlobalHookRunnerMock, })); -import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime"; -import { slackOutbound } from "./outbound-adapter.js"; -import { sendMessageSlack } from "./send.js"; +let slackOutbound: typeof import("./outbound-adapter.js").slackOutbound; type SlackSendTextCtx = { to: string; @@ -64,12 +65,19 @@ const expectSlackSendCalledWith = ( cfg: expect.any(Object), ...(options?.identity ? { identity: expect.objectContaining(options.identity) } : {}), }; - expect(sendMessageSlack).toHaveBeenCalledWith("C123", text, expect.objectContaining(expected)); + expect(sendMessageSlackMock).toHaveBeenCalledWith( + "C123", + text, + expect.objectContaining(expected), + ); }; describe("slack outbound hook wiring", () => { - beforeEach(() => { + beforeEach(async () => { + vi.resetModules(); vi.clearAllMocks(); + sendMessageSlackMock.mockResolvedValue({ messageId: "1234.5678", channelId: "C123" }); + ({ slackOutbound } = await import("./outbound-adapter.js")); }); afterEach(() => { @@ -77,14 +85,14 @@ describe("slack outbound hook wiring", () => { }); it("calls send without hooks when no hooks registered", async () => { - vi.mocked(getGlobalHookRunner).mockReturnValue(null); + getGlobalHookRunnerMock.mockReturnValue(null); await sendSlackTextWithDefaults({ text: "hello" }); expectSlackSendCalledWith("hello"); }); it("forwards identity opts when present", async () => { - vi.mocked(getGlobalHookRunner).mockReturnValue(null); + getGlobalHookRunnerMock.mockReturnValue(null); await sendSlackTextWithDefaults({ text: "hello", @@ -101,7 +109,7 @@ describe("slack outbound hook wiring", () => { }); it("forwards icon_emoji only when icon_url is absent", async () => { - vi.mocked(getGlobalHookRunner).mockReturnValue(null); + getGlobalHookRunnerMock.mockReturnValue(null); await sendSlackTextWithDefaults({ text: "hello", @@ -118,7 +126,7 @@ describe("slack outbound hook wiring", () => { hasHooks: vi.fn().mockReturnValue(true), runMessageSending: vi.fn().mockResolvedValue(undefined), }; - vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as never); + getGlobalHookRunnerMock.mockReturnValue(mockRunner); await sendSlackTextWithDefaults({ text: "hello" }); @@ -135,11 +143,11 @@ describe("slack outbound hook wiring", () => { hasHooks: vi.fn().mockReturnValue(true), runMessageSending: vi.fn().mockResolvedValue({ cancel: true }), }; - vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as never); + getGlobalHookRunnerMock.mockReturnValue(mockRunner); const result = await sendSlackTextWithDefaults({ text: "hello" }); - expect(sendMessageSlack).not.toHaveBeenCalled(); + expect(sendMessageSlackMock).not.toHaveBeenCalled(); expect(result.channel).toBe("slack"); }); @@ -148,7 +156,7 @@ describe("slack outbound hook wiring", () => { hasHooks: vi.fn().mockReturnValue(true), runMessageSending: vi.fn().mockResolvedValue({ content: "modified" }), }; - vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as never); + getGlobalHookRunnerMock.mockReturnValue(mockRunner); await sendSlackTextWithDefaults({ text: "original" }); expectSlackSendCalledWith("modified"); @@ -159,11 +167,11 @@ describe("slack outbound hook wiring", () => { hasHooks: vi.fn().mockReturnValue(false), runMessageSending: vi.fn(), }; - vi.mocked(getGlobalHookRunner).mockReturnValue(mockRunner as never); + getGlobalHookRunnerMock.mockReturnValue(mockRunner); await sendSlackTextWithDefaults({ text: "hello" }); expect(mockRunner.runMessageSending).not.toHaveBeenCalled(); - expect(sendMessageSlack).toHaveBeenCalled(); + expect(sendMessageSlackMock).toHaveBeenCalled(); }); }); diff --git a/extensions/telegram/src/dm-access.test.ts b/extensions/telegram/src/dm-access.test.ts index e798450ef07..3954a3c7794 100644 --- a/extensions/telegram/src/dm-access.test.ts +++ b/extensions/telegram/src/dm-access.test.ts @@ -2,15 +2,29 @@ import type { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/ch import { beforeEach, describe, expect, it, vi } from "vitest"; const createChannelPairingChallengeIssuerMock = vi.hoisted(() => vi.fn()); -const upsertChannelPairingRequestMock = vi.hoisted(() => vi.fn(async () => undefined)); +const upsertChannelPairingRequestMock = vi.hoisted(() => + vi.fn(async () => ({ code: "123456", created: true })), +); const withTelegramApiErrorLoggingMock = vi.hoisted(() => vi.fn(async ({ fn }) => await fn())); +const createPairingPrefixStripperMock = vi.hoisted( + () => (prefix: RegExp, normalize: (value: string) => string) => (value: string) => + normalize(value.replace(prefix, "")), +); vi.mock("openclaw/plugin-sdk/channel-pairing", () => ({ createChannelPairingChallengeIssuer: createChannelPairingChallengeIssuerMock, + createPairingPrefixStripper: createPairingPrefixStripperMock, + createLoggedPairingApprovalNotifier: () => undefined, + createTextPairingAdapter: () => undefined, + createChannelPairingController: () => ({}), })); vi.mock("openclaw/plugin-sdk/conversation-runtime", () => ({ upsertChannelPairingRequest: upsertChannelPairingRequestMock, + createStaticReplyToModeResolver: (mode: string) => () => mode, + createTopLevelChannelReplyToModeResolver: () => () => "off", + createScopedAccountReplyToModeResolver: () => () => "off", + resolvePinnedMainDmOwnerFromAllowlist: () => undefined, })); vi.mock("./api-logging.js", () => ({ @@ -56,6 +70,7 @@ describe("enforceTelegramDmAccess", () => { accountId: "main", bot: bot as never, logger: { info: vi.fn() }, + upsertPairingRequest: upsertChannelPairingRequestMock, }); expect(allowed).toBe(true); @@ -72,6 +87,7 @@ describe("enforceTelegramDmAccess", () => { accountId: "main", bot: { api: { sendMessage: vi.fn(async () => undefined) } } as never, logger: { info: vi.fn() }, + upsertPairingRequest: upsertChannelPairingRequestMock, }); expect(allowed).toBe(false); @@ -87,6 +103,7 @@ describe("enforceTelegramDmAccess", () => { accountId: "main", bot: { api: { sendMessage: vi.fn(async () => undefined) } } as never, logger: { info: vi.fn() }, + upsertPairingRequest: upsertChannelPairingRequestMock, }); expect(allowed).toBe(true); @@ -116,6 +133,7 @@ describe("enforceTelegramDmAccess", () => { accountId: "main", bot: { api: { sendMessage } } as never, logger, + upsertPairingRequest: upsertChannelPairingRequestMock, }); expect(allowed).toBe(false); diff --git a/extensions/telegram/src/fetch.test.ts b/extensions/telegram/src/fetch.test.ts index 098868aa9b7..0f932c33136 100644 --- a/extensions/telegram/src/fetch.test.ts +++ b/extensions/telegram/src/fetch.test.ts @@ -56,20 +56,24 @@ vi.mock("undici", () => ({ setGlobalDispatcher, })); -vi.mock("openclaw/plugin-sdk/runtime-env", () => ({ - createSubsystemLogger: () => ({ - info: loggerInfo, - debug: loggerDebug, - warn: vi.fn(), - error: vi.fn(), - child: () => ({ +vi.mock("openclaw/plugin-sdk/runtime-env", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + createSubsystemLogger: () => ({ info: loggerInfo, debug: loggerDebug, warn: vi.fn(), error: vi.fn(), + child: () => ({ + info: loggerInfo, + debug: loggerDebug, + warn: vi.fn(), + error: vi.fn(), + }), }), - }), -})); + }; +}); let resolveFetch: typeof import("../../../src/infra/fetch.js").resolveFetch; let resolveTelegramFetch: typeof import("./fetch.js").resolveTelegramFetch; diff --git a/scripts/load-channel-config-surface.ts b/scripts/load-channel-config-surface.ts index 3852711851b..30396938008 100644 --- a/scripts/load-channel-config-surface.ts +++ b/scripts/load-channel-config-surface.ts @@ -1,7 +1,14 @@ +import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; +import { createJiti } from "jiti"; import { buildChannelConfigSchema } from "../src/channels/plugins/config-schema.js"; +import { + buildPluginLoaderJitiOptions, + resolvePluginSdkAliasFile, + resolvePluginSdkScopedAliasMap, +} from "../src/plugins/sdk-alias.js"; function isBuiltChannelConfigSchema( value: unknown, @@ -182,9 +189,88 @@ export async function loadChannelConfigSurfaceModule( options?: { repoRoot?: string }, ): Promise<{ schema: Record; uiHints?: Record } | null> { const repoRoot = options?.repoRoot ?? resolveRepoRoot(); + const loaderRepoRoot = resolveRepoRoot(); + const bunBuildChannelConfigSchemaUrl = pathToFileURL( + path.join(loaderRepoRoot, "src/channels/plugins/config-schema.ts"), + ).href; + const loadViaBun = (candidatePath: string) => { + const script = ` + import { pathToFileURL } from "node:url"; + const { buildChannelConfigSchema } = await import(${JSON.stringify(bunBuildChannelConfigSchemaUrl)}); + const modulePath = process.env.OPENCLAW_CONFIG_SURFACE_MODULE; + if (!modulePath) { + throw new Error("missing OPENCLAW_CONFIG_SURFACE_MODULE"); + } + const imported = await import(pathToFileURL(modulePath).href); + const isBuilt = (value) => Boolean( + value && + typeof value === "object" && + value.schema && + typeof value.schema === "object" + ); + const resolve = (mod) => { + for (const [name, value] of Object.entries(mod)) { + if (name.endsWith("ChannelConfigSchema") && isBuilt(value)) return value; + } + for (const [name, value] of Object.entries(mod)) { + if (!name.endsWith("ConfigSchema") || name.endsWith("AccountConfigSchema")) continue; + if (isBuilt(value)) return value; + if (value && typeof value === "object") return buildChannelConfigSchema(value); + } + for (const value of Object.values(mod)) { + if (isBuilt(value)) return value; + } + return null; + }; + process.stdout.write(JSON.stringify(resolve(imported))); + `; + const result = spawnSync("bun", ["-e", script], { + cwd: repoRoot, + encoding: "utf8", + env: { + ...process.env, + OPENCLAW_CONFIG_SURFACE_MODULE: path.resolve(candidatePath), + }, + }); + if (result.status !== 0) { + throw new Error(result.stderr || result.stdout || `bun loader failed for ${candidatePath}`); + } + return JSON.parse(result.stdout || "null") as { + schema: Record; + uiHints?: Record; + } | null; + }; + const loadViaJiti = (candidatePath: string) => { + const resolvedPath = path.resolve(candidatePath); + const pluginSdkAlias = resolvePluginSdkAliasFile({ + srcFile: "root-alias.cjs", + distFile: "root-alias.cjs", + modulePath: resolvedPath, + pluginSdkResolution: "src", + }); + const aliasMap = { + ...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}), + ...resolvePluginSdkScopedAliasMap({ + modulePath: resolvedPath, + pluginSdkResolution: "src", + }), + }; + const jiti = createJiti(import.meta.url, { + ...buildPluginLoaderJitiOptions(aliasMap), + interopDefault: true, + tryNative: false, + moduleCache: false, + fsCache: false, + }); + return jiti(resolvedPath) as Record; + }; try { - const imported = (await import(pathToFileURL(modulePath).href)) as Record; + const bunLoaded = loadViaBun(modulePath); + if (bunLoaded && isBuiltChannelConfigSchema(bunLoaded)) { + return bunLoaded; + } + const imported = loadViaJiti(modulePath); return resolveConfigSchemaExport(imported); } catch (error) { if (!shouldRetryViaIsolatedCopy(error)) { @@ -193,9 +279,11 @@ export async function loadChannelConfigSurfaceModule( const isolatedCopy = copyModuleImportGraphWithoutNodeModules({ modulePath, repoRoot }); try { - const imported = (await import( - `${pathToFileURL(isolatedCopy.copiedModulePath).href}?isolated=${Date.now()}` - )) as Record; + const bunLoaded = loadViaBun(isolatedCopy.copiedModulePath); + if (bunLoaded && isBuiltChannelConfigSchema(bunLoaded)) { + return bunLoaded; + } + const imported = loadViaJiti(isolatedCopy.copiedModulePath); return resolveConfigSchemaExport(imported); } finally { isolatedCopy.cleanup(); diff --git a/src/channels/chat-meta.ts b/src/channels/chat-meta.ts new file mode 100644 index 00000000000..f4ddd75143c --- /dev/null +++ b/src/channels/chat-meta.ts @@ -0,0 +1,135 @@ +import { CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js"; +import type { ChannelMeta } from "./plugins/types.js"; + +export type ChatChannelMeta = ChannelMeta; + +const WEBSITE_URL = "https://openclaw.ai"; + +const CHAT_CHANNEL_META: Record = { + telegram: { + id: "telegram", + label: "Telegram", + selectionLabel: "Telegram (Bot API)", + detailLabel: "Telegram Bot", + docsPath: "/channels/telegram", + docsLabel: "telegram", + blurb: "simplest way to get started — register a bot with @BotFather and get going.", + systemImage: "paperplane", + selectionDocsPrefix: "", + selectionDocsOmitLabel: true, + selectionExtras: [WEBSITE_URL], + }, + whatsapp: { + id: "whatsapp", + label: "WhatsApp", + selectionLabel: "WhatsApp (QR link)", + detailLabel: "WhatsApp Web", + docsPath: "/channels/whatsapp", + docsLabel: "whatsapp", + blurb: "works with your own number; recommend a separate phone + eSIM.", + systemImage: "message", + }, + discord: { + id: "discord", + label: "Discord", + selectionLabel: "Discord (Bot API)", + detailLabel: "Discord Bot", + docsPath: "/channels/discord", + docsLabel: "discord", + blurb: "very well supported right now.", + systemImage: "bubble.left.and.bubble.right", + }, + irc: { + id: "irc", + label: "IRC", + selectionLabel: "IRC (Server + Nick)", + detailLabel: "IRC", + docsPath: "/channels/irc", + docsLabel: "irc", + blurb: "classic IRC networks with DM/channel routing and pairing controls.", + systemImage: "network", + }, + googlechat: { + id: "googlechat", + label: "Google Chat", + selectionLabel: "Google Chat (Chat API)", + detailLabel: "Google Chat", + docsPath: "/channels/googlechat", + docsLabel: "googlechat", + blurb: "Google Workspace Chat app with HTTP webhook.", + systemImage: "message.badge", + }, + slack: { + id: "slack", + label: "Slack", + selectionLabel: "Slack (Socket Mode)", + detailLabel: "Slack Bot", + docsPath: "/channels/slack", + docsLabel: "slack", + blurb: "supported (Socket Mode).", + systemImage: "number", + }, + signal: { + id: "signal", + label: "Signal", + selectionLabel: "Signal (signal-cli)", + detailLabel: "Signal REST", + docsPath: "/channels/signal", + docsLabel: "signal", + blurb: 'signal-cli linked device; more setup (David Reagans: "Hop on Discord.").', + systemImage: "antenna.radiowaves.left.and.right", + }, + imessage: { + id: "imessage", + label: "iMessage", + selectionLabel: "iMessage (imsg)", + detailLabel: "iMessage", + docsPath: "/channels/imessage", + docsLabel: "imessage", + blurb: "this is still a work in progress.", + systemImage: "message.fill", + }, + line: { + id: "line", + label: "LINE", + selectionLabel: "LINE (Messaging API)", + detailLabel: "LINE Bot", + docsPath: "/channels/line", + docsLabel: "line", + blurb: "LINE Messaging API webhook bot.", + systemImage: "message", + }, +}; + +export const CHAT_CHANNEL_ALIASES: Record = { + imsg: "imessage", + "internet-relay-chat": "irc", + "google-chat": "googlechat", + gchat: "googlechat", +}; + +function normalizeChannelKey(raw?: string | null): string | undefined { + const normalized = raw?.trim().toLowerCase(); + return normalized || undefined; +} + +export function listChatChannels(): ChatChannelMeta[] { + return CHAT_CHANNEL_ORDER.map((id) => CHAT_CHANNEL_META[id]); +} + +export function listChatChannelAliases(): string[] { + return Object.keys(CHAT_CHANNEL_ALIASES); +} + +export function getChatChannelMeta(id: ChatChannelId): ChatChannelMeta { + return CHAT_CHANNEL_META[id]; +} + +export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null { + const normalized = normalizeChannelKey(raw); + if (!normalized) { + return null; + } + const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized; + return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null; +} diff --git a/src/channels/registry.ts b/src/channels/registry.ts index 3c8050767a8..f2152bb9917 100644 --- a/src/channels/registry.ts +++ b/src/channels/registry.ts @@ -1,14 +1,17 @@ import { getActivePluginRegistry } from "../plugins/runtime.js"; +import { + CHAT_CHANNEL_ALIASES, + getChatChannelMeta, + listChatChannelAliases, + listChatChannels, + normalizeChatChannelId, + type ChatChannelMeta, +} from "./chat-meta.js"; import { CHANNEL_IDS, CHAT_CHANNEL_ORDER, type ChatChannelId } from "./ids.js"; -import type { ChannelMeta } from "./plugins/types.js"; import type { ChannelId } from "./plugins/types.js"; export { CHANNEL_IDS, CHAT_CHANNEL_ORDER } from "./ids.js"; export type { ChatChannelId } from "./ids.js"; -export type ChatChannelMeta = ChannelMeta; - -const WEBSITE_URL = "https://openclaw.ai"; - type RegisteredChannelPluginEntry = { plugin: { id?: string | null; @@ -36,134 +39,17 @@ function findRegisteredChannelPluginEntry( }); } -const CHAT_CHANNEL_META: Record = { - telegram: { - id: "telegram", - label: "Telegram", - selectionLabel: "Telegram (Bot API)", - detailLabel: "Telegram Bot", - docsPath: "/channels/telegram", - docsLabel: "telegram", - blurb: "simplest way to get started — register a bot with @BotFather and get going.", - systemImage: "paperplane", - selectionDocsPrefix: "", - selectionDocsOmitLabel: true, - selectionExtras: [WEBSITE_URL], - }, - whatsapp: { - id: "whatsapp", - label: "WhatsApp", - selectionLabel: "WhatsApp (QR link)", - detailLabel: "WhatsApp Web", - docsPath: "/channels/whatsapp", - docsLabel: "whatsapp", - blurb: "works with your own number; recommend a separate phone + eSIM.", - systemImage: "message", - }, - discord: { - id: "discord", - label: "Discord", - selectionLabel: "Discord (Bot API)", - detailLabel: "Discord Bot", - docsPath: "/channels/discord", - docsLabel: "discord", - blurb: "very well supported right now.", - systemImage: "bubble.left.and.bubble.right", - }, - irc: { - id: "irc", - label: "IRC", - selectionLabel: "IRC (Server + Nick)", - detailLabel: "IRC", - docsPath: "/channels/irc", - docsLabel: "irc", - blurb: "classic IRC networks with DM/channel routing and pairing controls.", - systemImage: "network", - }, - googlechat: { - id: "googlechat", - label: "Google Chat", - selectionLabel: "Google Chat (Chat API)", - detailLabel: "Google Chat", - docsPath: "/channels/googlechat", - docsLabel: "googlechat", - blurb: "Google Workspace Chat app with HTTP webhook.", - systemImage: "message.badge", - }, - slack: { - id: "slack", - label: "Slack", - selectionLabel: "Slack (Socket Mode)", - detailLabel: "Slack Bot", - docsPath: "/channels/slack", - docsLabel: "slack", - blurb: "supported (Socket Mode).", - systemImage: "number", - }, - signal: { - id: "signal", - label: "Signal", - selectionLabel: "Signal (signal-cli)", - detailLabel: "Signal REST", - docsPath: "/channels/signal", - docsLabel: "signal", - blurb: 'signal-cli linked device; more setup (David Reagans: "Hop on Discord.").', - systemImage: "antenna.radiowaves.left.and.right", - }, - imessage: { - id: "imessage", - label: "iMessage", - selectionLabel: "iMessage (imsg)", - detailLabel: "iMessage", - docsPath: "/channels/imessage", - docsLabel: "imessage", - blurb: "this is still a work in progress.", - systemImage: "message.fill", - }, - line: { - id: "line", - label: "LINE", - selectionLabel: "LINE (Messaging API)", - detailLabel: "LINE Bot", - docsPath: "/channels/line", - docsLabel: "line", - blurb: "LINE Messaging API webhook bot.", - systemImage: "message", - }, -}; - -export const CHAT_CHANNEL_ALIASES: Record = { - imsg: "imessage", - "internet-relay-chat": "irc", - "google-chat": "googlechat", - gchat: "googlechat", -}; - const normalizeChannelKey = (raw?: string | null): string | undefined => { const normalized = raw?.trim().toLowerCase(); return normalized || undefined; }; - -export function listChatChannels(): ChatChannelMeta[] { - return CHAT_CHANNEL_ORDER.map((id) => CHAT_CHANNEL_META[id]); -} - -export function listChatChannelAliases(): string[] { - return Object.keys(CHAT_CHANNEL_ALIASES); -} - -export function getChatChannelMeta(id: ChatChannelId): ChatChannelMeta { - return CHAT_CHANNEL_META[id]; -} - -export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null { - const normalized = normalizeChannelKey(raw); - if (!normalized) { - return null; - } - const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized; - return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null; -} +export { + CHAT_CHANNEL_ALIASES, + getChatChannelMeta, + listChatChannelAliases, + listChatChannels, + normalizeChatChannelId, +}; // Channel docking: prefer this helper in shared code. Importing from // `src/channels/plugins/*` can eagerly load channel implementations. diff --git a/src/plugin-sdk/channel-config-helpers.ts b/src/plugin-sdk/channel-config-helpers.ts index ccec4815f39..3c5721d563d 100644 --- a/src/plugin-sdk/channel-config-helpers.ts +++ b/src/plugin-sdk/channel-config-helpers.ts @@ -26,6 +26,10 @@ import type { OpenClawConfig } from "../config/config.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; import { normalizeStringEntries } from "../shared/string-normalization.js"; +// `node --import tsx` can emit `__name(...)` wrappers for function expressions. +// Keep a local no-op helper so direct TS loads (used by config-surface tooling) stay stable. +const __name = (value: T): T => value; + export { authorizeConfigWrite, canBypassConfigWritePolicy, diff --git a/src/plugin-sdk/channel-plugin-common.ts b/src/plugin-sdk/channel-plugin-common.ts index 3c5153733c0..d8779fbf898 100644 --- a/src/plugin-sdk/channel-plugin-common.ts +++ b/src/plugin-sdk/channel-plugin-common.ts @@ -22,4 +22,4 @@ export { export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; -export { getChatChannelMeta } from "../channels/registry.js"; +export { getChatChannelMeta } from "../channels/chat-meta.js"; diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index 7f2ca7260ca..99d6f631c8f 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -1,3 +1,4 @@ +import { getChatChannelMeta } from "../channels/chat-meta.js"; import { createScopedAccountReplyToModeResolver, createTopLevelChannelReplyToModeResolver, @@ -10,20 +11,18 @@ import type { import type { ChannelMessagingAdapter, ChannelOutboundSessionRoute, + ChannelPollResult, ChannelThreadingAdapter, } from "../channels/plugins/types.core.js"; import type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; -import { getChatChannelMeta } from "../channels/registry.js"; import type { OpenClawConfig } from "../config/config.js"; import type { ReplyToMode } from "../config/types.base.js"; import { buildOutboundBaseSessionKey } from "../infra/outbound/base-session-key.js"; +import type { OutboundDeliveryResult } from "../infra/outbound/deliver.js"; import { emptyPluginConfigSchema } from "../plugins/config-schema.js"; import type { PluginRuntime } from "../plugins/runtime/types.js"; import type { OpenClawPluginApi, OpenClawPluginConfigSchema } from "../plugins/types.js"; import { createScopedDmSecurityResolver } from "./channel-config-helpers.js"; -import { createTextPairingAdapter } from "./channel-pairing.js"; -import { createAttachedChannelResultAdapter } from "./channel-send-result.js"; -import { definePluginEntry } from "./plugin-entry.js"; export type { AnyAgentTool, @@ -75,6 +74,25 @@ export type { ChannelOutboundSessionRoute, ChannelMessagingAdapter, } from "../channels/plugins/types.core.js"; + +function createInlineTextPairingAdapter(params: { + idLabel: string; + message: string; + normalizeAllowEntry?: ChannelPairingAdapter["normalizeAllowEntry"]; + notify: ( + params: Parameters>[0] & { + message: string; + }, + ) => Promise | void; +}): ChannelPairingAdapter { + return { + idLabel: params.idLabel, + normalizeAllowEntry: params.normalizeAllowEntry, + notifyApproval: async (ctx) => { + await params.notify({ ...ctx, message: params.message }); + }, + }; +} export type { ProviderUsageSnapshot, UsageProviderId, @@ -103,7 +121,7 @@ export { formatPairingApproveHint, parseOptionalDelimitedEntries, } from "../channels/plugins/helpers.js"; -export { getChatChannelMeta } from "../channels/registry.js"; +export { getChatChannelMeta } from "../channels/chat-meta.js"; export { channelTargetSchema, channelTargetsSchema, @@ -200,7 +218,12 @@ type DefineChannelPluginEntryOptions = { registerFull?: (api: OpenClawPluginApi) => void; }; -type DefinedChannelPluginEntry = ReturnType & { +type DefinedChannelPluginEntry = { + id: string; + name: string; + description: string; + configSchema: OpenClawPluginConfigSchema; + register: (api: OpenClawPluginApi) => void; channelPlugin: TPlugin; setChannelRuntime?: (runtime: PluginRuntime) => void; }; @@ -257,11 +280,12 @@ export function defineChannelPluginEntry({ setRuntime, registerFull, }: DefineChannelPluginEntryOptions): DefinedChannelPluginEntry { - const entry = definePluginEntry({ + const resolvedConfigSchema = typeof configSchema === "function" ? configSchema() : configSchema; + const entry = { id, name, description, - configSchema, + configSchema: resolvedConfigSchema, register(api: OpenClawPluginApi) { setRuntime?.(api.runtime); api.registerChannel({ plugin: plugin as ChannelPlugin }); @@ -270,7 +294,7 @@ export function defineChannelPluginEntry({ } registerFull?.(api); }, - }); + }; return { ...entry, channelPlugin: plugin, @@ -320,7 +344,11 @@ type ChatChannelPairingOptions = { idLabel: string; message: string; normalizeAllowEntry?: ChannelPairingAdapter["normalizeAllowEntry"]; - notify: Parameters[0]["notify"]; + notify: ( + params: Parameters>[0] & { + message: string; + }, + ) => Promise | void; }; }; @@ -346,9 +374,47 @@ type ChatChannelThreadingOptions = type ChatChannelAttachedOutboundOptions = { base: Omit; - attachedResults: Parameters[0]; + attachedResults: { + channel: string; + sendText?: ( + ctx: Parameters>[0], + ) => MaybePromise>; + sendMedia?: ( + ctx: Parameters>[0], + ) => MaybePromise>; + sendPoll?: ( + ctx: Parameters>[0], + ) => MaybePromise>; + }; }; +type MaybePromise = T | Promise; + +function createInlineAttachedChannelResultAdapter( + params: ChatChannelAttachedOutboundOptions["attachedResults"], +) { + return { + sendText: params.sendText + ? async (ctx: Parameters>[0]) => ({ + channel: params.channel, + ...(await params.sendText!(ctx)), + }) + : undefined, + sendMedia: params.sendMedia + ? async (ctx: Parameters>[0]) => ({ + channel: params.channel, + ...(await params.sendMedia!(ctx)), + }) + : undefined, + sendPoll: params.sendPoll + ? async (ctx: Parameters>[0]) => ({ + channel: params.channel, + ...(await params.sendPoll!(ctx)), + }) + : undefined, + } satisfies Pick; +} + function resolveChatChannelSecurity( security: | ChannelSecurityAdapter @@ -376,7 +442,7 @@ function resolveChatChannelPairing( if (!("text" in pairing)) { return pairing; } - return createTextPairingAdapter(pairing.text); + return createInlineTextPairingAdapter(pairing.text); } function resolveChatChannelThreading( @@ -415,7 +481,7 @@ function resolveChatChannelOutbound( } return { ...outbound.base, - ...createAttachedChannelResultAdapter(outbound.attachedResults), + ...createInlineAttachedChannelResultAdapter(outbound.attachedResults), }; } diff --git a/src/plugin-sdk/mattermost.ts b/src/plugin-sdk/mattermost.ts index f7f9f37cdad..691b45772db 100644 --- a/src/plugin-sdk/mattermost.ts +++ b/src/plugin-sdk/mattermost.ts @@ -36,6 +36,7 @@ export { export { applyAccountNameToChannelSection, applySetupAccountConfigPatch, + createSetupInputPresenceValidator, migrateBaseNameToDefaultAccount, } from "../channels/plugins/setup-helpers.js"; export { createAccountStatusSink } from "./channel-lifecycle.js"; diff --git a/src/plugin-sdk/nextcloud-talk.ts b/src/plugin-sdk/nextcloud-talk.ts index c231cf49564..934ead7568b 100644 --- a/src/plugin-sdk/nextcloud-talk.ts +++ b/src/plugin-sdk/nextcloud-talk.ts @@ -27,11 +27,14 @@ export { } from "../channels/plugins/setup-wizard-helpers.js"; export { applyAccountNameToChannelSection, + createSetupInputPresenceValidator, patchScopedAccountConfig, } from "../channels/plugins/setup-helpers.js"; export { createAccountListHelpers } from "../channels/plugins/account-helpers.js"; export type { ChannelGroupContext, ChannelSetupInput } from "../channels/plugins/types.js"; +export type { ChannelSetupDmPolicy } from "../channels/plugins/setup-wizard-types.js"; export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; +export type { ChannelSetupWizard } from "../channels/plugins/setup-wizard.js"; export { createChannelReplyPipeline } from "./channel-reply-pipeline.js"; export type { OpenClawConfig } from "../config/config.js"; export { mapAllowFromEntries } from "./channel-config-helpers.js"; @@ -78,12 +81,12 @@ export type { PluginRuntime } from "../plugins/runtime/types.js"; export type { OpenClawPluginApi } from "../plugins/types.js"; export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; export type { RuntimeEnv } from "../runtime.js"; +export type { WizardPrompter } from "../wizard/prompts.js"; export { readStoreAllowFromForDmPolicy, resolveDmGroupAccessWithCommandGate, } from "../security/dm-policy-shared.js"; export { formatDocsLink } from "../terminal/links.js"; -export type { WizardPrompter } from "../wizard/prompts.js"; export { listConfiguredAccountIds, resolveAccountWithDefaultFallback, @@ -103,3 +106,9 @@ export { buildBaseChannelStatusSummary, buildRuntimeAccountStatusSnapshot, } from "./status-helpers.js"; +export { + createTopLevelChannelDmPolicy, + promptParsedAllowFromForAccount, + resolveSetupAccountId, + setSetupChannelEnabled, +} from "../channels/plugins/setup-wizard-helpers.js"; diff --git a/src/plugin-sdk/setup-runtime.ts b/src/plugin-sdk/setup-runtime.ts index b0d82655dea..6d4b61f56f0 100644 --- a/src/plugin-sdk/setup-runtime.ts +++ b/src/plugin-sdk/setup-runtime.ts @@ -5,22 +5,38 @@ export type { ChannelSetupDmPolicy } from "../channels/plugins/setup-wizard-type export type { ChannelSetupWizard, ChannelSetupWizardAllowFromEntry, + ChannelSetupWizardTextInput, } from "../channels/plugins/setup-wizard.js"; export { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js"; -export { createEnvPatchedAccountSetupAdapter } from "../channels/plugins/setup-helpers.js"; +export { + createEnvPatchedAccountSetupAdapter, + createPatchedAccountSetupAdapter, + createSetupInputPresenceValidator, +} from "../channels/plugins/setup-helpers.js"; export { createAccountScopedAllowFromSection, createAccountScopedGroupAccessSection, + createTopLevelChannelDmPolicy, createLegacyCompatChannelDmPolicy, createStandardChannelSetupStatus, + mergeAllowFromEntries, + parseSetupEntriesAllowingWildcard, parseMentionOrPrefixedId, patchChannelConfigForAccount, promptLegacyChannelAllowFromForAccount, + promptParsedAllowFromForAccount, resolveEntriesWithOptionalToken, + resolveSetupAccountId, + setAccountAllowFromForChannel, setSetupChannelEnabled, } from "../channels/plugins/setup-wizard-helpers.js"; export { createAllowlistSetupWizardProxy } from "../channels/plugins/setup-wizard-proxy.js"; +export { + createCliPathTextInput, + createDelegatedTextInputShouldPrompt, +} from "../channels/plugins/setup-wizard-binary.js"; +export { createDelegatedSetupWizardProxy } from "../channels/plugins/setup-wizard-proxy.js"; diff --git a/src/plugin-sdk/signal.ts b/src/plugin-sdk/signal.ts index e0c7486f2fc..dc2865d77f9 100644 --- a/src/plugin-sdk/signal.ts +++ b/src/plugin-sdk/signal.ts @@ -11,6 +11,11 @@ export type { OpenClawPluginApi, PluginRuntime, } from "./channel-plugin-common.js"; +export type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js"; +export type { + ChannelSetupWizard, + ChannelSetupWizardTextInput, +} from "../channels/plugins/setup-wizard.js"; export { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE, @@ -24,6 +29,10 @@ export { normalizeAccountId, setAccountEnabledInConfigSection, } from "./channel-plugin-common.js"; +export { + createPatchedAccountSetupAdapter, + createSetupInputPresenceValidator, +} from "../channels/plugins/setup-helpers.js"; export { formatCliCommand } from "../cli/command-format.js"; export { formatDocsLink } from "../terminal/links.js"; @@ -42,6 +51,18 @@ export { SignalConfigSchema } from "../config/zod-schema.providers-core.js"; export { normalizeE164 } from "../utils.js"; export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js"; +export { + createCliPathTextInput, + createDelegatedTextInputShouldPrompt, +} from "../channels/plugins/setup-wizard-binary.js"; +export { createDelegatedSetupWizardProxy } from "../channels/plugins/setup-wizard-proxy.js"; +export { + createTopLevelChannelDmPolicy, + parseSetupEntriesAllowingWildcard, + promptParsedAllowFromForAccount, + setAccountAllowFromForChannel, + setSetupChannelEnabled, +} from "../channels/plugins/setup-wizard-helpers.js"; export { buildBaseAccountStatusSnapshot,