From ec1b80809df2b717c321c4bc504ecfc4254e7a96 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 00:59:19 -0700 Subject: [PATCH] refactor: remove remaining extension core imports --- .../src/monitor/provider.registry.test.ts | 2 +- .../src/monitor/provider.test-support.ts | 460 +---------------- .../discord/src/monitor/provider.test.ts | 14 +- extensions/discord/src/setup-core.ts | 2 +- extensions/discord/src/setup-surface.ts | 2 +- extensions/discord/src/shared.ts | 10 +- extensions/imessage/src/setup-core.ts | 2 +- extensions/imessage/src/setup-surface.ts | 7 +- extensions/imessage/src/shared.ts | 16 +- extensions/signal/src/setup-core.ts | 4 +- extensions/signal/src/setup-surface.ts | 9 +- extensions/signal/src/shared.ts | 14 +- extensions/slack/src/channel.ts | 4 +- extensions/slack/src/setup-core.ts | 2 +- extensions/slack/src/setup-surface.ts | 2 +- extensions/slack/src/shared.ts | 20 +- extensions/telegram/src/setup-core.ts | 4 +- extensions/telegram/src/shared.ts | 14 +- .../discord-provider.test-support.ts | 472 ++++++++++++++++++ extensions/whatsapp/src/setup-surface.ts | 4 +- extensions/whatsapp/src/shared.ts | 22 +- src/plugin-sdk/slack.ts | 2 + 22 files changed, 560 insertions(+), 528 deletions(-) create mode 100644 extensions/test-utils/discord-provider.test-support.ts diff --git a/extensions/discord/src/monitor/provider.registry.test.ts b/extensions/discord/src/monitor/provider.registry.test.ts index 2187c851f69..1070bb744e3 100644 --- a/extensions/discord/src/monitor/provider.registry.test.ts +++ b/extensions/discord/src/monitor/provider.registry.test.ts @@ -5,7 +5,7 @@ import { baseRuntime, getProviderMonitorTestMocks, resetDiscordProviderMonitorMocks, -} from "./provider.test-support.js"; +} from "../../../test-utils/discord-provider.test-support.js"; const { createDiscordNativeCommandMock, clientHandleDeployRequestMock, monitorLifecycleMock } = getProviderMonitorTestMocks(); diff --git a/extensions/discord/src/monitor/provider.test-support.ts b/extensions/discord/src/monitor/provider.test-support.ts index 23ffb7da2f2..9eebb9ad38d 100644 --- a/extensions/discord/src/monitor/provider.test-support.ts +++ b/extensions/discord/src/monitor/provider.test-support.ts @@ -1,459 +1 @@ -import type { Mock } from "vitest"; -import { expect, vi } from "vitest"; -import type { OpenClawConfig } from "../../../../src/config/config.js"; -import type { RuntimeEnv } from "../../../../src/runtime.js"; - -export type NativeCommandSpecMock = { - name: string; - description: string; - acceptsArgs: boolean; -}; - -export type PluginCommandSpecMock = { - name: string; - description: string; - acceptsArgs: boolean; -}; - -type ProviderMonitorTestMocks = { - clientHandleDeployRequestMock: Mock<() => Promise>; - clientFetchUserMock: Mock<(target: string) => Promise<{ id: string }>>; - clientGetPluginMock: Mock<(name: string) => unknown>; - clientConstructorOptionsMock: Mock<(options?: unknown) => void>; - createDiscordAutoPresenceControllerMock: Mock<() => unknown>; - createDiscordNativeCommandMock: Mock<(params?: { command?: { name?: string } }) => unknown>; - createDiscordMessageHandlerMock: Mock<() => unknown>; - createNoopThreadBindingManagerMock: Mock<() => { stop: ReturnType }>; - createThreadBindingManagerMock: Mock<() => { stop: ReturnType }>; - reconcileAcpThreadBindingsOnStartupMock: Mock<() => unknown>; - createdBindingManagers: Array<{ stop: ReturnType }>; - getAcpSessionStatusMock: Mock< - (params: { - cfg: OpenClawConfig; - sessionKey: string; - signal?: AbortSignal; - }) => Promise<{ state: string }> - >; - getPluginCommandSpecsMock: Mock<() => PluginCommandSpecMock[]>; - listNativeCommandSpecsForConfigMock: Mock<() => NativeCommandSpecMock[]>; - listSkillCommandsForAgentsMock: Mock<() => unknown[]>; - monitorLifecycleMock: Mock<(params: { threadBindings: { stop: () => void } }) => Promise>; - resolveDiscordAccountMock: Mock<() => unknown>; - resolveDiscordAllowlistConfigMock: Mock<() => Promise>; - resolveNativeCommandsEnabledMock: Mock<() => boolean>; - resolveNativeSkillsEnabledMock: Mock<() => boolean>; - isVerboseMock: Mock<() => boolean>; - shouldLogVerboseMock: Mock<() => boolean>; - voiceRuntimeModuleLoadedMock: Mock<() => void>; -}; - -export function baseDiscordAccountConfig() { - return { - commands: { native: true, nativeSkills: false }, - voice: { enabled: false }, - agentComponents: { enabled: false }, - execApprovals: { enabled: false }, - }; -} - -const providerMonitorTestMocks: ProviderMonitorTestMocks = vi.hoisted(() => { - const createdBindingManagers: Array<{ stop: ReturnType }> = []; - const isVerboseMock = vi.fn(() => false); - const shouldLogVerboseMock = vi.fn(() => false); - - return { - clientHandleDeployRequestMock: vi.fn(async () => undefined), - clientFetchUserMock: vi.fn(async (_target: string) => ({ id: "bot-1" })), - clientGetPluginMock: vi.fn<(_name: string) => unknown>(() => undefined), - clientConstructorOptionsMock: vi.fn(), - createDiscordAutoPresenceControllerMock: vi.fn(() => ({ - enabled: false, - start: vi.fn(), - stop: vi.fn(), - refresh: vi.fn(), - runNow: vi.fn(), - })), - createDiscordNativeCommandMock: vi.fn((params?: { command?: { name?: string } }) => ({ - name: params?.command?.name ?? "mock-command", - })), - createDiscordMessageHandlerMock: vi.fn(() => - Object.assign( - vi.fn(async () => undefined), - { - deactivate: vi.fn(), - }, - ), - ), - createNoopThreadBindingManagerMock: vi.fn(() => { - const manager = { stop: vi.fn() }; - createdBindingManagers.push(manager); - return manager; - }), - createThreadBindingManagerMock: vi.fn(() => { - const manager = { stop: vi.fn() }; - createdBindingManagers.push(manager); - return manager; - }), - reconcileAcpThreadBindingsOnStartupMock: vi.fn(() => ({ - checked: 0, - removed: 0, - staleSessionKeys: [], - })), - createdBindingManagers, - getAcpSessionStatusMock: vi.fn( - async (_params: { cfg: OpenClawConfig; sessionKey: string; signal?: AbortSignal }) => ({ - state: "idle", - }), - ), - getPluginCommandSpecsMock: vi.fn<() => PluginCommandSpecMock[]>(() => []), - listNativeCommandSpecsForConfigMock: vi.fn<() => NativeCommandSpecMock[]>(() => [ - { name: "cmd", description: "built-in", acceptsArgs: false }, - ]), - listSkillCommandsForAgentsMock: vi.fn(() => []), - monitorLifecycleMock: vi.fn(async (params: { threadBindings: { stop: () => void } }) => { - params.threadBindings.stop(); - }), - resolveDiscordAccountMock: vi.fn(() => ({ - accountId: "default", - token: "cfg-token", - config: baseDiscordAccountConfig(), - })), - resolveDiscordAllowlistConfigMock: vi.fn(async () => ({ - guildEntries: undefined, - allowFrom: undefined, - })), - resolveNativeCommandsEnabledMock: vi.fn(() => true), - resolveNativeSkillsEnabledMock: vi.fn(() => false), - isVerboseMock, - shouldLogVerboseMock, - voiceRuntimeModuleLoadedMock: vi.fn(), - }; -}); - -const { - clientHandleDeployRequestMock, - clientFetchUserMock, - clientGetPluginMock, - clientConstructorOptionsMock, - createDiscordAutoPresenceControllerMock, - createDiscordNativeCommandMock, - createDiscordMessageHandlerMock, - createNoopThreadBindingManagerMock, - createThreadBindingManagerMock, - reconcileAcpThreadBindingsOnStartupMock, - createdBindingManagers, - getAcpSessionStatusMock, - getPluginCommandSpecsMock, - listNativeCommandSpecsForConfigMock, - listSkillCommandsForAgentsMock, - monitorLifecycleMock, - resolveDiscordAccountMock, - resolveDiscordAllowlistConfigMock, - resolveNativeCommandsEnabledMock, - resolveNativeSkillsEnabledMock, - isVerboseMock, - shouldLogVerboseMock, - voiceRuntimeModuleLoadedMock, -} = providerMonitorTestMocks; - -export function getProviderMonitorTestMocks(): typeof providerMonitorTestMocks { - return providerMonitorTestMocks; -} - -export function mockResolvedDiscordAccountConfig(overrides: Record) { - resolveDiscordAccountMock.mockImplementation(() => ({ - accountId: "default", - token: "cfg-token", - config: { - ...baseDiscordAccountConfig(), - ...overrides, - }, - })); -} - -export function getFirstDiscordMessageHandlerParams() { - expect(createDiscordMessageHandlerMock).toHaveBeenCalledTimes(1); - const firstCall = createDiscordMessageHandlerMock.mock.calls.at(0) as [T] | undefined; - return firstCall?.[0]; -} - -export function resetDiscordProviderMonitorMocks(params?: { - nativeCommands?: NativeCommandSpecMock[]; -}) { - clientHandleDeployRequestMock.mockClear().mockResolvedValue(undefined); - clientFetchUserMock.mockClear().mockResolvedValue({ id: "bot-1" }); - clientGetPluginMock.mockClear().mockReturnValue(undefined); - clientConstructorOptionsMock.mockClear(); - createDiscordAutoPresenceControllerMock.mockClear().mockImplementation(() => ({ - enabled: false, - start: vi.fn(), - stop: vi.fn(), - refresh: vi.fn(), - runNow: vi.fn(), - })); - createDiscordNativeCommandMock.mockClear().mockImplementation((input) => ({ - name: input?.command?.name ?? "mock-command", - })); - createDiscordMessageHandlerMock.mockClear().mockImplementation(() => - Object.assign( - vi.fn(async () => undefined), - { - deactivate: vi.fn(), - }, - ), - ); - createNoopThreadBindingManagerMock.mockClear(); - createThreadBindingManagerMock.mockClear(); - reconcileAcpThreadBindingsOnStartupMock.mockClear().mockReturnValue({ - checked: 0, - removed: 0, - staleSessionKeys: [], - }); - createdBindingManagers.length = 0; - getAcpSessionStatusMock.mockClear().mockResolvedValue({ state: "idle" }); - getPluginCommandSpecsMock.mockClear().mockReturnValue([]); - listNativeCommandSpecsForConfigMock - .mockClear() - .mockReturnValue( - params?.nativeCommands ?? [{ name: "cmd", description: "built-in", acceptsArgs: false }], - ); - listSkillCommandsForAgentsMock.mockClear().mockReturnValue([]); - monitorLifecycleMock.mockClear().mockImplementation(async (monitorParams) => { - monitorParams.threadBindings.stop(); - }); - resolveDiscordAccountMock.mockClear().mockReturnValue({ - accountId: "default", - token: "cfg-token", - config: baseDiscordAccountConfig(), - }); - resolveDiscordAllowlistConfigMock.mockClear().mockResolvedValue({ - guildEntries: undefined, - allowFrom: undefined, - }); - resolveNativeCommandsEnabledMock.mockClear().mockReturnValue(true); - resolveNativeSkillsEnabledMock.mockClear().mockReturnValue(false); - isVerboseMock.mockClear().mockReturnValue(false); - shouldLogVerboseMock.mockClear().mockReturnValue(false); - voiceRuntimeModuleLoadedMock.mockClear(); -} - -export const baseRuntime = (): RuntimeEnv => ({ - log: vi.fn(), - error: vi.fn(), - exit: vi.fn(), -}); - -export const baseConfig = (): OpenClawConfig => - ({ - channels: { - discord: { - accounts: { - default: {}, - }, - }, - }, - }) as OpenClawConfig; - -vi.mock("@buape/carbon", () => { - class ReadyListener {} - class RateLimitError extends Error { - status = 429; - discordCode?: number; - retryAfter: number; - scope: string | null; - bucket: string | null; - constructor( - response: Response, - body: { message: string; retry_after: number; global: boolean }, - ) { - super(body.message); - this.retryAfter = body.retry_after; - this.scope = body.global ? "global" : response.headers.get("X-RateLimit-Scope"); - this.bucket = response.headers.get("X-RateLimit-Bucket"); - } - } - class Client { - listeners: unknown[]; - rest: { put: ReturnType }; - options: unknown; - constructor(options: unknown, handlers: { listeners?: unknown[] }) { - this.options = options; - this.listeners = handlers.listeners ?? []; - this.rest = { put: vi.fn(async () => undefined) }; - clientConstructorOptionsMock(options); - } - async handleDeployRequest() { - return await clientHandleDeployRequestMock(); - } - async fetchUser(target: string) { - return await clientFetchUserMock(target); - } - getPlugin(name: string) { - return clientGetPluginMock(name); - } - } - return { Client, RateLimitError, ReadyListener }; -}); - -vi.mock("@buape/carbon/gateway", () => ({ - GatewayCloseCodes: { DisallowedIntents: 4014 }, -})); - -vi.mock("@buape/carbon/voice", () => ({ - VoicePlugin: class VoicePlugin {}, -})); - -vi.mock("../../../../src/acp/control-plane/manager.js", () => ({ - getAcpSessionManager: () => ({ - getSessionStatus: getAcpSessionStatusMock, - }), -})); - -vi.mock("../../../../src/auto-reply/chunk.js", () => ({ - resolveTextChunkLimit: () => 2000, -})); - -vi.mock("../../../../src/auto-reply/commands-registry.js", () => ({ - listNativeCommandSpecsForConfig: listNativeCommandSpecsForConfigMock, -})); - -vi.mock("../../../../src/auto-reply/skill-commands.js", () => ({ - listSkillCommandsForAgents: listSkillCommandsForAgentsMock, -})); - -vi.mock("../../../../src/config/commands.js", () => ({ - isNativeCommandsExplicitlyDisabled: () => false, - resolveNativeCommandsEnabled: resolveNativeCommandsEnabledMock, - resolveNativeSkillsEnabled: resolveNativeSkillsEnabledMock, -})); - -vi.mock("../../../../src/config/config.js", () => ({ - loadConfig: () => ({}), -})); - -vi.mock("../../../../src/globals.js", () => ({ - danger: (value: string) => value, - isVerbose: isVerboseMock, - logVerbose: vi.fn(), - shouldLogVerbose: shouldLogVerboseMock, - warn: (value: string) => value, -})); - -vi.mock("../../../../src/infra/errors.js", () => ({ - formatErrorMessage: (error: unknown) => String(error), -})); - -vi.mock("../../../../src/infra/retry-policy.js", () => ({ - createDiscordRetryRunner: () => async (run: () => Promise) => run(), -})); - -vi.mock("../../../../src/logging/subsystem.js", () => ({ - createSubsystemLogger: () => { - const logger = { - child: vi.fn(() => logger), - info: vi.fn(), - error: vi.fn(), - warn: vi.fn(), - debug: vi.fn(), - }; - return logger; - }, -})); - -vi.mock("../../../../src/runtime.js", () => ({ - createNonExitingRuntime: () => ({ log: vi.fn(), error: vi.fn(), exit: vi.fn() }), -})); - -vi.mock("../accounts.js", () => ({ - resolveDiscordAccount: resolveDiscordAccountMock, -})); - -vi.mock("../probe.js", () => ({ - fetchDiscordApplicationId: async () => "app-1", -})); - -vi.mock("../token.js", () => ({ - normalizeDiscordToken: (value?: string) => value, -})); - -vi.mock("../voice/command.js", () => ({ - createDiscordVoiceCommand: () => ({ name: "voice-command" }), -})); - -vi.mock("./agent-components.js", () => ({ - createAgentComponentButton: () => ({ id: "btn" }), - createAgentSelectMenu: () => ({ id: "menu" }), - createDiscordComponentButton: () => ({ id: "btn2" }), - createDiscordComponentChannelSelect: () => ({ id: "channel" }), - createDiscordComponentMentionableSelect: () => ({ id: "mentionable" }), - createDiscordComponentModal: () => ({ id: "modal" }), - createDiscordComponentRoleSelect: () => ({ id: "role" }), - createDiscordComponentStringSelect: () => ({ id: "string" }), - createDiscordComponentUserSelect: () => ({ id: "user" }), -})); - -vi.mock("./auto-presence.js", () => ({ - createDiscordAutoPresenceController: createDiscordAutoPresenceControllerMock, -})); - -vi.mock("./commands.js", () => ({ - resolveDiscordSlashCommandConfig: () => ({ ephemeral: false }), -})); - -vi.mock("./exec-approvals.js", () => ({ - createExecApprovalButton: () => ({ id: "exec-approval" }), - DiscordExecApprovalHandler: class DiscordExecApprovalHandler { - async start() { - return undefined; - } - async stop() { - return undefined; - } - }, -})); - -vi.mock("./gateway-plugin.js", () => ({ - createDiscordGatewayPlugin: () => ({ id: "gateway-plugin" }), -})); - -vi.mock("./listeners.js", () => ({ - DiscordMessageListener: class DiscordMessageListener {}, - DiscordPresenceListener: class DiscordPresenceListener {}, - DiscordReactionListener: class DiscordReactionListener {}, - DiscordReactionRemoveListener: class DiscordReactionRemoveListener {}, - DiscordThreadUpdateListener: class DiscordThreadUpdateListener {}, - registerDiscordListener: vi.fn(), -})); - -vi.mock("./message-handler.js", () => ({ - createDiscordMessageHandler: createDiscordMessageHandlerMock, -})); - -vi.mock("./native-command.js", () => ({ - createDiscordCommandArgFallbackButton: () => ({ id: "arg-fallback" }), - createDiscordModelPickerFallbackButton: () => ({ id: "model-fallback-btn" }), - createDiscordModelPickerFallbackSelect: () => ({ id: "model-fallback-select" }), - createDiscordNativeCommand: createDiscordNativeCommandMock, -})); - -vi.mock("./presence.js", () => ({ - resolveDiscordPresenceUpdate: () => undefined, -})); - -vi.mock("./provider.allowlist.js", () => ({ - resolveDiscordAllowlistConfig: resolveDiscordAllowlistConfigMock, -})); - -vi.mock("./provider.lifecycle.js", () => ({ - runDiscordGatewayLifecycle: monitorLifecycleMock, -})); - -vi.mock("./rest-fetch.js", () => ({ - resolveDiscordRestFetch: () => async () => undefined, -})); - -vi.mock("./thread-bindings.js", () => ({ - createNoopThreadBindingManager: createNoopThreadBindingManagerMock, - createThreadBindingManager: createThreadBindingManagerMock, - reconcileAcpThreadBindingsOnStartup: reconcileAcpThreadBindingsOnStartupMock, -})); +export * from "../../../test-utils/discord-provider.test-support.js"; diff --git a/extensions/discord/src/monitor/provider.test.ts b/extensions/discord/src/monitor/provider.test.ts index 14177aec001..4ff936f5519 100644 --- a/extensions/discord/src/monitor/provider.test.ts +++ b/extensions/discord/src/monitor/provider.test.ts @@ -9,7 +9,7 @@ import { getProviderMonitorTestMocks, mockResolvedDiscordAccountConfig, resetDiscordProviderMonitorMocks, -} from "./provider.test-support.js"; +} from "../../../test-utils/discord-provider.test-support.js"; const { clientConstructorOptionsMock, @@ -37,9 +37,15 @@ const { voiceRuntimeModuleLoadedMock, } = getProviderMonitorTestMocks(); -vi.mock("../../../../src/plugins/commands.js", () => ({ - getPluginCommandSpecs: getPluginCommandSpecsMock, -})); +vi.mock("openclaw/plugin-sdk/plugin-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/plugin-runtime", + ); + return { + ...actual, + getPluginCommandSpecs: getPluginCommandSpecsMock, + }; +}); vi.mock("../voice/manager.runtime.js", () => { voiceRuntimeModuleLoadedMock(); diff --git a/extensions/discord/src/setup-core.ts b/extensions/discord/src/setup-core.ts index 4b807f10a65..f7722a35af5 100644 --- a/extensions/discord/src/setup-core.ts +++ b/extensions/discord/src/setup-core.ts @@ -2,6 +2,7 @@ import type { DiscordGuildEntry } from "openclaw/plugin-sdk/config-runtime"; import { DEFAULT_ACCOUNT_ID, createEnvPatchedAccountSetupAdapter, + formatDocsLink, noteChannelLookupFailure, noteChannelLookupSummary, parseMentionOrPrefixedId, @@ -16,7 +17,6 @@ import { type ChannelSetupDmPolicy, type ChannelSetupWizard, } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "../../../src/terminal/links.js"; import { inspectDiscordAccount } from "./account-inspect.js"; import { listDiscordAccountIds, resolveDiscordAccount } from "./accounts.js"; diff --git a/extensions/discord/src/setup-surface.ts b/extensions/discord/src/setup-surface.ts index 66f7f8bbf4b..801f7bf7838 100644 --- a/extensions/discord/src/setup-surface.ts +++ b/extensions/discord/src/setup-surface.ts @@ -1,11 +1,11 @@ import { + formatDocsLink, type OpenClawConfig, promptLegacyChannelAllowFrom, resolveSetupAccountId, type WizardPrompter, } from "openclaw/plugin-sdk/setup"; import { type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "../../../src/terminal/links.js"; import { resolveDefaultDiscordAccountId, resolveDiscordAccount } from "./accounts.js"; import { normalizeDiscordSlug } from "./monitor/allow-list.js"; import { diff --git a/extensions/discord/src/shared.ts b/extensions/discord/src/shared.ts index 92e248066af..03174404bdb 100644 --- a/extensions/discord/src/shared.ts +++ b/extensions/discord/src/shared.ts @@ -3,10 +3,12 @@ import { createScopedAccountConfigAccessors, createScopedChannelConfigBase, } from "openclaw/plugin-sdk/channel-config-helpers"; -import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; -import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; -import { getChatChannelMeta } from "../../../src/channels/registry.js"; -import { DiscordConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; +import { + buildChannelConfigSchema, + DiscordConfigSchema, + getChatChannelMeta, + type ChannelPlugin, +} from "openclaw/plugin-sdk/discord"; import { inspectDiscordAccount } from "./account-inspect.js"; import { listDiscordAccountIds, diff --git a/extensions/imessage/src/setup-core.ts b/extensions/imessage/src/setup-core.ts index 17f1b7487d3..9da4e99b1ef 100644 --- a/extensions/imessage/src/setup-core.ts +++ b/extensions/imessage/src/setup-core.ts @@ -1,5 +1,6 @@ import { createPatchedAccountSetupAdapter, + formatDocsLink, parseSetupEntriesAllowingWildcard, promptParsedAllowFromForScopedChannel, setChannelDmPolicyWithAllowFrom, @@ -13,7 +14,6 @@ import type { ChannelSetupWizard, ChannelSetupWizardTextInput, } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "../../../src/terminal/links.js"; import { listIMessageAccountIds, resolveDefaultIMessageAccountId, diff --git a/extensions/imessage/src/setup-surface.ts b/extensions/imessage/src/setup-surface.ts index 27e8b256ada..54511d284c4 100644 --- a/extensions/imessage/src/setup-surface.ts +++ b/extensions/imessage/src/setup-surface.ts @@ -1,5 +1,8 @@ -import { setSetupChannelEnabled, type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; -import { detectBinary } from "../../../src/plugins/setup-binary.js"; +import { + detectBinary, + setSetupChannelEnabled, + type ChannelSetupWizard, +} from "openclaw/plugin-sdk/setup"; import { listIMessageAccountIds, resolveIMessageAccount } from "./accounts.js"; import { createIMessageCliPathTextInput, diff --git a/extensions/imessage/src/shared.ts b/extensions/imessage/src/shared.ts index 1ede2ad412d..935546721da 100644 --- a/extensions/imessage/src/shared.ts +++ b/extensions/imessage/src/shared.ts @@ -3,19 +3,17 @@ import { collectAllowlistProviderRestrictSendersWarnings, } from "openclaw/plugin-sdk/channel-policy"; import { + buildChannelConfigSchema, + DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, - setAccountEnabledInConfigSection, -} from "../../../src/channels/plugins/config-helpers.js"; -import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; -import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; -import { getChatChannelMeta } from "../../../src/channels/registry.js"; -import { IMessageConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; -import { formatTrimmedAllowFromEntries, + getChatChannelMeta, + IMessageConfigSchema, resolveIMessageConfigAllowFrom, resolveIMessageConfigDefaultTo, -} from "../../../src/plugin-sdk/channel-config-helpers.js"; -import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; + setAccountEnabledInConfigSection, + type ChannelPlugin, +} from "openclaw/plugin-sdk/imessage"; import { listIMessageAccountIds, resolveDefaultIMessageAccountId, diff --git a/extensions/signal/src/setup-core.ts b/extensions/signal/src/setup-core.ts index a1433a34f13..e0c4d5ec0a3 100644 --- a/extensions/signal/src/setup-core.ts +++ b/extensions/signal/src/setup-core.ts @@ -1,5 +1,7 @@ import { createPatchedAccountSetupAdapter, + formatCliCommand, + formatDocsLink, normalizeE164, parseSetupEntriesAllowingWildcard, promptParsedAllowFromForScopedChannel, @@ -14,8 +16,6 @@ import type { ChannelSetupWizard, ChannelSetupWizardTextInput, } from "openclaw/plugin-sdk/setup"; -import { formatCliCommand } from "../../../src/cli/command-format.js"; -import { formatDocsLink } from "../../../src/terminal/links.js"; import { listSignalAccountIds, resolveDefaultSignalAccountId, diff --git a/extensions/signal/src/setup-surface.ts b/extensions/signal/src/setup-surface.ts index edcea39d6b1..01ded866785 100644 --- a/extensions/signal/src/setup-surface.ts +++ b/extensions/signal/src/setup-surface.ts @@ -1,6 +1,9 @@ -import { setSetupChannelEnabled, type ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; -import { detectBinary } from "../../../src/plugins/setup-binary.js"; -import { installSignalCli } from "../../../src/plugins/signal-cli-install.js"; +import { + detectBinary, + installSignalCli, + setSetupChannelEnabled, + type ChannelSetupWizard, +} from "openclaw/plugin-sdk/setup"; import { listSignalAccountIds, resolveSignalAccount } from "./accounts.js"; import { createSignalCliPathTextInput, diff --git a/extensions/signal/src/shared.ts b/extensions/signal/src/shared.ts index 60dfd0ed010..3de5af7d57a 100644 --- a/extensions/signal/src/shared.ts +++ b/extensions/signal/src/shared.ts @@ -4,15 +4,15 @@ import { collectAllowlistProviderRestrictSendersWarnings, } from "openclaw/plugin-sdk/channel-policy"; import { + buildChannelConfigSchema, + DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, + getChatChannelMeta, + normalizeE164, setAccountEnabledInConfigSection, -} from "../../../src/channels/plugins/config-helpers.js"; -import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; -import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; -import { getChatChannelMeta } from "../../../src/channels/registry.js"; -import { SignalConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; -import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; -import { normalizeE164 } from "../../../src/utils.js"; + SignalConfigSchema, + type ChannelPlugin, +} from "openclaw/plugin-sdk/signal"; import { listSignalAccountIds, resolveDefaultSignalAccountId, diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index bafc5fc8c91..5e25f0187b1 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -22,11 +22,11 @@ import { resolveConfiguredFromRequiredCredentialStatuses, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, + createSlackActions, type ChannelPlugin, type OpenClawConfig, + type SlackActionContext, } from "openclaw/plugin-sdk/slack"; -import type { SlackActionContext } from "../../../src/agents/tools/slack-actions.js"; -import { createSlackActions } from "../../../src/channels/plugins/slack.actions.js"; import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js"; import { listEnabledSlackAccounts, diff --git a/extensions/slack/src/setup-core.ts b/extensions/slack/src/setup-core.ts index af71e5edc52..2a3aad980fa 100644 --- a/extensions/slack/src/setup-core.ts +++ b/extensions/slack/src/setup-core.ts @@ -2,6 +2,7 @@ import { createAllowlistSetupWizardProxy, DEFAULT_ACCOUNT_ID, createEnvPatchedAccountSetupAdapter, + formatDocsLink, hasConfiguredSecretInput, type OpenClawConfig, noteChannelLookupFailure, @@ -18,7 +19,6 @@ import { type ChannelSetupWizard, type ChannelSetupWizardAllowFromEntry, } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "../../../src/terminal/links.js"; import { inspectSlackAccount } from "./account-inspect.js"; import { listSlackAccountIds, resolveSlackAccount, type ResolvedSlackAccount } from "./accounts.js"; import { diff --git a/extensions/slack/src/setup-surface.ts b/extensions/slack/src/setup-surface.ts index f7a52a72888..d103a329c50 100644 --- a/extensions/slack/src/setup-surface.ts +++ b/extensions/slack/src/setup-surface.ts @@ -1,4 +1,5 @@ import { + formatDocsLink, noteChannelLookupFailure, noteChannelLookupSummary, type OpenClawConfig, @@ -11,7 +12,6 @@ import type { ChannelSetupWizard, ChannelSetupWizardAllowFromEntry, } from "openclaw/plugin-sdk/setup"; -import { formatDocsLink } from "../../../src/terminal/links.js"; import { resolveDefaultSlackAccountId, resolveSlackAccount } from "./accounts.js"; import { resolveSlackChannelAllowlist } from "./resolve-channels.js"; import { resolveSlackUserAllowlist } from "./resolve-users.js"; diff --git a/extensions/slack/src/shared.ts b/extensions/slack/src/shared.ts index 4471e851097..58dfae35c90 100644 --- a/extensions/slack/src/shared.ts +++ b/extensions/slack/src/shared.ts @@ -3,14 +3,18 @@ import { createScopedAccountConfigAccessors, createScopedChannelConfigBase, } from "openclaw/plugin-sdk/channel-config-helpers"; -import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; -import { patchChannelConfigForAccount } from "../../../src/channels/plugins/setup-wizard-helpers.js"; -import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; -import { getChatChannelMeta } from "../../../src/channels/registry.js"; -import type { OpenClawConfig } from "../../../src/config/config.js"; -import { hasConfiguredSecretInput } from "../../../src/config/types.secrets.js"; -import { SlackConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; -import { formatDocsLink } from "../../../src/terminal/links.js"; +import { + formatDocsLink, + hasConfiguredSecretInput, + patchChannelConfigForAccount, +} from "openclaw/plugin-sdk/setup"; +import { + buildChannelConfigSchema, + getChatChannelMeta, + SlackConfigSchema, + type ChannelPlugin, + type OpenClawConfig, +} from "openclaw/plugin-sdk/slack"; import { inspectSlackAccount } from "./account-inspect.js"; import { listSlackAccountIds, diff --git a/extensions/telegram/src/setup-core.ts b/extensions/telegram/src/setup-core.ts index 542fffc0500..7e73898f8b1 100644 --- a/extensions/telegram/src/setup-core.ts +++ b/extensions/telegram/src/setup-core.ts @@ -1,6 +1,8 @@ import { createEnvPatchedAccountSetupAdapter, DEFAULT_ACCOUNT_ID, + formatCliCommand, + formatDocsLink, patchChannelConfigForAccount, promptResolvedAllowFrom, splitSetupEntries, @@ -8,8 +10,6 @@ import { type WizardPrompter, } from "openclaw/plugin-sdk/setup"; import type { ChannelSetupAdapter, ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup"; -import { formatCliCommand } from "../../../src/cli/command-format.js"; -import { formatDocsLink } from "../../../src/terminal/links.js"; import { resolveDefaultTelegramAccountId, resolveTelegramAccount } from "./accounts.js"; import { fetchTelegramChatId } from "./api-fetch.js"; diff --git a/extensions/telegram/src/shared.ts b/extensions/telegram/src/shared.ts index 335213adead..644869dbc60 100644 --- a/extensions/telegram/src/shared.ts +++ b/extensions/telegram/src/shared.ts @@ -3,12 +3,14 @@ import { createScopedAccountConfigAccessors, createScopedChannelConfigBase, } from "openclaw/plugin-sdk/channel-config-helpers"; -import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; -import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; -import { getChatChannelMeta } from "../../../src/channels/registry.js"; -import type { OpenClawConfig } from "../../../src/config/config.js"; -import { TelegramConfigSchema } from "../../../src/config/zod-schema.providers-core.js"; -import { normalizeAccountId } from "../../../src/routing/session-key.js"; +import { + buildChannelConfigSchema, + getChatChannelMeta, + normalizeAccountId, + TelegramConfigSchema, + type ChannelPlugin, + type OpenClawConfig, +} from "openclaw/plugin-sdk/telegram"; import { inspectTelegramAccount } from "./account-inspect.js"; import { listTelegramAccountIds, diff --git a/extensions/test-utils/discord-provider.test-support.ts b/extensions/test-utils/discord-provider.test-support.ts new file mode 100644 index 00000000000..ca1d1fd0894 --- /dev/null +++ b/extensions/test-utils/discord-provider.test-support.ts @@ -0,0 +1,472 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/discord"; +import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env"; +import type { Mock } from "vitest"; +import { expect, vi } from "vitest"; + +export type NativeCommandSpecMock = { + name: string; + description: string; + acceptsArgs: boolean; +}; + +export type PluginCommandSpecMock = { + name: string; + description: string; + acceptsArgs: boolean; +}; + +type ProviderMonitorTestMocks = { + clientHandleDeployRequestMock: Mock<() => Promise>; + clientFetchUserMock: Mock<(target: string) => Promise<{ id: string }>>; + clientGetPluginMock: Mock<(name: string) => unknown>; + clientConstructorOptionsMock: Mock<(options?: unknown) => void>; + createDiscordAutoPresenceControllerMock: Mock<() => unknown>; + createDiscordNativeCommandMock: Mock<(params?: { command?: { name?: string } }) => unknown>; + createDiscordMessageHandlerMock: Mock<() => unknown>; + createNoopThreadBindingManagerMock: Mock<() => { stop: ReturnType }>; + createThreadBindingManagerMock: Mock<() => { stop: ReturnType }>; + reconcileAcpThreadBindingsOnStartupMock: Mock<() => unknown>; + createdBindingManagers: Array<{ stop: ReturnType }>; + getAcpSessionStatusMock: Mock< + (params: { + cfg: OpenClawConfig; + sessionKey: string; + signal?: AbortSignal; + }) => Promise<{ state: string }> + >; + getPluginCommandSpecsMock: Mock<() => PluginCommandSpecMock[]>; + listNativeCommandSpecsForConfigMock: Mock<() => NativeCommandSpecMock[]>; + listSkillCommandsForAgentsMock: Mock<() => unknown[]>; + monitorLifecycleMock: Mock<(params: { threadBindings: { stop: () => void } }) => Promise>; + resolveDiscordAccountMock: Mock<() => unknown>; + resolveDiscordAllowlistConfigMock: Mock<() => Promise>; + resolveNativeCommandsEnabledMock: Mock<() => boolean>; + resolveNativeSkillsEnabledMock: Mock<() => boolean>; + isVerboseMock: Mock<() => boolean>; + shouldLogVerboseMock: Mock<() => boolean>; + voiceRuntimeModuleLoadedMock: Mock<() => void>; +}; + +export function baseDiscordAccountConfig() { + return { + commands: { native: true, nativeSkills: false }, + voice: { enabled: false }, + agentComponents: { enabled: false }, + execApprovals: { enabled: false }, + }; +} + +const providerMonitorTestMocks: ProviderMonitorTestMocks = vi.hoisted(() => { + const createdBindingManagers: Array<{ stop: ReturnType }> = []; + const isVerboseMock = vi.fn(() => false); + const shouldLogVerboseMock = vi.fn(() => false); + + return { + clientHandleDeployRequestMock: vi.fn(async () => undefined), + clientFetchUserMock: vi.fn(async (_target: string) => ({ id: "bot-1" })), + clientGetPluginMock: vi.fn<(_name: string) => unknown>(() => undefined), + clientConstructorOptionsMock: vi.fn(), + createDiscordAutoPresenceControllerMock: vi.fn(() => ({ + enabled: false, + start: vi.fn(), + stop: vi.fn(), + refresh: vi.fn(), + runNow: vi.fn(), + })), + createDiscordNativeCommandMock: vi.fn((params?: { command?: { name?: string } }) => ({ + name: params?.command?.name ?? "mock-command", + })), + createDiscordMessageHandlerMock: vi.fn(() => + Object.assign( + vi.fn(async () => undefined), + { + deactivate: vi.fn(), + }, + ), + ), + createNoopThreadBindingManagerMock: vi.fn(() => { + const manager = { stop: vi.fn() }; + createdBindingManagers.push(manager); + return manager; + }), + createThreadBindingManagerMock: vi.fn(() => { + const manager = { stop: vi.fn() }; + createdBindingManagers.push(manager); + return manager; + }), + reconcileAcpThreadBindingsOnStartupMock: vi.fn(() => ({ + checked: 0, + removed: 0, + staleSessionKeys: [], + })), + createdBindingManagers, + getAcpSessionStatusMock: vi.fn( + async (_params: { cfg: OpenClawConfig; sessionKey: string; signal?: AbortSignal }) => ({ + state: "idle", + }), + ), + getPluginCommandSpecsMock: vi.fn<() => PluginCommandSpecMock[]>(() => []), + listNativeCommandSpecsForConfigMock: vi.fn<() => NativeCommandSpecMock[]>(() => [ + { name: "cmd", description: "built-in", acceptsArgs: false }, + ]), + listSkillCommandsForAgentsMock: vi.fn(() => []), + monitorLifecycleMock: vi.fn(async (params: { threadBindings: { stop: () => void } }) => { + params.threadBindings.stop(); + }), + resolveDiscordAccountMock: vi.fn(() => ({ + accountId: "default", + token: "cfg-token", + config: baseDiscordAccountConfig(), + })), + resolveDiscordAllowlistConfigMock: vi.fn(async () => ({ + guildEntries: undefined, + allowFrom: undefined, + })), + resolveNativeCommandsEnabledMock: vi.fn(() => true), + resolveNativeSkillsEnabledMock: vi.fn(() => false), + isVerboseMock, + shouldLogVerboseMock, + voiceRuntimeModuleLoadedMock: vi.fn(), + }; +}); + +const { + clientHandleDeployRequestMock, + clientFetchUserMock, + clientGetPluginMock, + clientConstructorOptionsMock, + createDiscordAutoPresenceControllerMock, + createDiscordNativeCommandMock, + createDiscordMessageHandlerMock, + createNoopThreadBindingManagerMock, + createThreadBindingManagerMock, + reconcileAcpThreadBindingsOnStartupMock, + createdBindingManagers, + getAcpSessionStatusMock, + getPluginCommandSpecsMock, + listNativeCommandSpecsForConfigMock, + listSkillCommandsForAgentsMock, + monitorLifecycleMock, + resolveDiscordAccountMock, + resolveDiscordAllowlistConfigMock, + resolveNativeCommandsEnabledMock, + resolveNativeSkillsEnabledMock, + isVerboseMock, + shouldLogVerboseMock, + voiceRuntimeModuleLoadedMock, +} = providerMonitorTestMocks; + +export function getProviderMonitorTestMocks(): typeof providerMonitorTestMocks { + return providerMonitorTestMocks; +} + +export function mockResolvedDiscordAccountConfig(overrides: Record) { + resolveDiscordAccountMock.mockImplementation(() => ({ + accountId: "default", + token: "cfg-token", + config: { + ...baseDiscordAccountConfig(), + ...overrides, + }, + })); +} + +export function getFirstDiscordMessageHandlerParams() { + expect(createDiscordMessageHandlerMock).toHaveBeenCalledTimes(1); + const firstCall = createDiscordMessageHandlerMock.mock.calls.at(0) as [T] | undefined; + return firstCall?.[0]; +} + +export function resetDiscordProviderMonitorMocks(params?: { + nativeCommands?: NativeCommandSpecMock[]; +}) { + clientHandleDeployRequestMock.mockClear().mockResolvedValue(undefined); + clientFetchUserMock.mockClear().mockResolvedValue({ id: "bot-1" }); + clientGetPluginMock.mockClear().mockReturnValue(undefined); + clientConstructorOptionsMock.mockClear(); + createDiscordAutoPresenceControllerMock.mockClear().mockImplementation(() => ({ + enabled: false, + start: vi.fn(), + stop: vi.fn(), + refresh: vi.fn(), + runNow: vi.fn(), + })); + createDiscordNativeCommandMock.mockClear().mockImplementation((input) => ({ + name: input?.command?.name ?? "mock-command", + })); + createDiscordMessageHandlerMock.mockClear().mockImplementation(() => + Object.assign( + vi.fn(async () => undefined), + { + deactivate: vi.fn(), + }, + ), + ); + createNoopThreadBindingManagerMock.mockClear(); + createThreadBindingManagerMock.mockClear(); + reconcileAcpThreadBindingsOnStartupMock.mockClear().mockReturnValue({ + checked: 0, + removed: 0, + staleSessionKeys: [], + }); + createdBindingManagers.length = 0; + getAcpSessionStatusMock.mockClear().mockResolvedValue({ state: "idle" }); + getPluginCommandSpecsMock.mockClear().mockReturnValue([]); + listNativeCommandSpecsForConfigMock + .mockClear() + .mockReturnValue( + params?.nativeCommands ?? [{ name: "cmd", description: "built-in", acceptsArgs: false }], + ); + listSkillCommandsForAgentsMock.mockClear().mockReturnValue([]); + monitorLifecycleMock.mockClear().mockImplementation(async (monitorParams) => { + monitorParams.threadBindings.stop(); + }); + resolveDiscordAccountMock.mockClear().mockReturnValue({ + accountId: "default", + token: "cfg-token", + config: baseDiscordAccountConfig(), + }); + resolveDiscordAllowlistConfigMock.mockClear().mockResolvedValue({ + guildEntries: undefined, + allowFrom: undefined, + }); + resolveNativeCommandsEnabledMock.mockClear().mockReturnValue(true); + resolveNativeSkillsEnabledMock.mockClear().mockReturnValue(false); + isVerboseMock.mockClear().mockReturnValue(false); + shouldLogVerboseMock.mockClear().mockReturnValue(false); + voiceRuntimeModuleLoadedMock.mockClear(); +} + +export const baseRuntime = (): RuntimeEnv => ({ + log: vi.fn(), + error: vi.fn(), + exit: vi.fn(), +}); + +export const baseConfig = (): OpenClawConfig => + ({ + channels: { + discord: { + accounts: { + default: {}, + }, + }, + }, + }) as OpenClawConfig; + +vi.mock("@buape/carbon", () => { + class ReadyListener {} + class RateLimitError extends Error { + status = 429; + discordCode?: number; + retryAfter: number; + scope: string | null; + bucket: string | null; + constructor( + response: Response, + body: { message: string; retry_after: number; global: boolean }, + ) { + super(body.message); + this.retryAfter = body.retry_after; + this.scope = body.global ? "global" : response.headers.get("X-RateLimit-Scope"); + this.bucket = response.headers.get("X-RateLimit-Bucket"); + } + } + class Client { + listeners: unknown[]; + rest: { put: ReturnType }; + options: unknown; + constructor(options: unknown, handlers: { listeners?: unknown[] }) { + this.options = options; + this.listeners = handlers.listeners ?? []; + this.rest = { put: vi.fn(async () => undefined) }; + clientConstructorOptionsMock(options); + } + async handleDeployRequest() { + return await clientHandleDeployRequestMock(); + } + async fetchUser(target: string) { + return await clientFetchUserMock(target); + } + getPlugin(name: string) { + return clientGetPluginMock(name); + } + } + return { Client, RateLimitError, ReadyListener }; +}); + +vi.mock("@buape/carbon/gateway", () => ({ + GatewayCloseCodes: { DisallowedIntents: 4014 }, +})); + +vi.mock("@buape/carbon/voice", () => ({ + VoicePlugin: class VoicePlugin {}, +})); + +vi.mock("openclaw/plugin-sdk/acp-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/acp-runtime", + ); + return { + ...actual, + getAcpSessionManager: () => ({ + getSessionStatus: getAcpSessionStatusMock, + }), + isAcpRuntimeError: (error: unknown): error is { code: string } => + error instanceof Error && "code" in error, + }; +}); + +vi.mock("openclaw/plugin-sdk/reply-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/reply-runtime", + ); + return { + ...actual, + resolveTextChunkLimit: () => 2000, + listNativeCommandSpecsForConfig: listNativeCommandSpecsForConfigMock, + listSkillCommandsForAgents: listSkillCommandsForAgentsMock, + }; +}); + +vi.mock("openclaw/plugin-sdk/config-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/config-runtime", + ); + return { + ...actual, + isNativeCommandsExplicitlyDisabled: () => false, + loadConfig: () => ({}), + resolveNativeCommandsEnabled: resolveNativeCommandsEnabledMock, + resolveNativeSkillsEnabled: resolveNativeSkillsEnabledMock, + }; +}); + +vi.mock("openclaw/plugin-sdk/runtime-env", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/runtime-env", + ); + return { + ...actual, + danger: (value: string) => value, + isVerbose: isVerboseMock, + logVerbose: vi.fn(), + shouldLogVerbose: shouldLogVerboseMock, + warn: (value: string) => value, + createSubsystemLogger: () => { + const logger = { + child: vi.fn(() => logger), + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + }; + return logger; + }, + createNonExitingRuntime: () => ({ log: vi.fn(), error: vi.fn(), exit: vi.fn() }), + }; +}); + +vi.mock("openclaw/plugin-sdk/infra-runtime", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/infra-runtime", + ); + return { + ...actual, + formatErrorMessage: (error: unknown) => String(error), + }; +}); + +vi.mock("../discord/src/accounts.js", () => ({ + resolveDiscordAccount: resolveDiscordAccountMock, +})); + +vi.mock("../discord/src/probe.js", () => ({ + fetchDiscordApplicationId: async () => "app-1", +})); + +vi.mock("../discord/src/token.js", () => ({ + normalizeDiscordToken: (value?: string) => value, +})); + +vi.mock("../discord/src/voice/command.js", () => ({ + createDiscordVoiceCommand: () => ({ name: "voice-command" }), +})); + +vi.mock("../discord/src/monitor/agent-components.js", () => ({ + createAgentComponentButton: () => ({ id: "btn" }), + createAgentSelectMenu: () => ({ id: "menu" }), + createDiscordComponentButton: () => ({ id: "btn2" }), + createDiscordComponentChannelSelect: () => ({ id: "channel" }), + createDiscordComponentMentionableSelect: () => ({ id: "mentionable" }), + createDiscordComponentModal: () => ({ id: "modal" }), + createDiscordComponentRoleSelect: () => ({ id: "role" }), + createDiscordComponentStringSelect: () => ({ id: "string" }), + createDiscordComponentUserSelect: () => ({ id: "user" }), +})); + +vi.mock("../discord/src/monitor/auto-presence.js", () => ({ + createDiscordAutoPresenceController: createDiscordAutoPresenceControllerMock, +})); + +vi.mock("../discord/src/monitor/commands.js", () => ({ + resolveDiscordSlashCommandConfig: () => ({ ephemeral: false }), +})); + +vi.mock("../discord/src/monitor/exec-approvals.js", () => ({ + createExecApprovalButton: () => ({ id: "exec-approval" }), + DiscordExecApprovalHandler: class DiscordExecApprovalHandler { + async start() { + return undefined; + } + async stop() { + return undefined; + } + }, +})); + +vi.mock("../discord/src/monitor/gateway-plugin.js", () => ({ + createDiscordGatewayPlugin: () => ({ id: "gateway-plugin" }), +})); + +vi.mock("../discord/src/monitor/listeners.js", () => ({ + DiscordMessageListener: class DiscordMessageListener {}, + DiscordPresenceListener: class DiscordPresenceListener {}, + DiscordReactionListener: class DiscordReactionListener {}, + DiscordReactionRemoveListener: class DiscordReactionRemoveListener {}, + DiscordThreadUpdateListener: class DiscordThreadUpdateListener {}, + registerDiscordListener: vi.fn(), +})); + +vi.mock("../discord/src/monitor/message-handler.js", () => ({ + createDiscordMessageHandler: createDiscordMessageHandlerMock, +})); + +vi.mock("../discord/src/monitor/native-command.js", () => ({ + createDiscordCommandArgFallbackButton: () => ({ id: "arg-fallback" }), + createDiscordModelPickerFallbackButton: () => ({ id: "model-fallback-btn" }), + createDiscordModelPickerFallbackSelect: () => ({ id: "model-fallback-select" }), + createDiscordNativeCommand: createDiscordNativeCommandMock, +})); + +vi.mock("../discord/src/monitor/presence.js", () => ({ + resolveDiscordPresenceUpdate: () => undefined, +})); + +vi.mock("../discord/src/monitor/provider.allowlist.js", () => ({ + resolveDiscordAllowlistConfig: resolveDiscordAllowlistConfigMock, +})); + +vi.mock("../discord/src/monitor/provider.lifecycle.js", () => ({ + runDiscordGatewayLifecycle: monitorLifecycleMock, +})); + +vi.mock("../discord/src/monitor/rest-fetch.js", () => ({ + resolveDiscordRestFetch: () => async () => undefined, +})); + +vi.mock("../discord/src/monitor/thread-bindings.js", () => ({ + createNoopThreadBindingManager: createNoopThreadBindingManagerMock, + createThreadBindingManager: createThreadBindingManagerMock, + reconcileAcpThreadBindingsOnStartup: reconcileAcpThreadBindingsOnStartupMock, +})); diff --git a/extensions/whatsapp/src/setup-surface.ts b/extensions/whatsapp/src/setup-surface.ts index 7bee33c2ef4..4a87ce4d0f8 100644 --- a/extensions/whatsapp/src/setup-surface.ts +++ b/extensions/whatsapp/src/setup-surface.ts @@ -1,6 +1,8 @@ import path from "node:path"; import { DEFAULT_ACCOUNT_ID, + formatCliCommand, + formatDocsLink, normalizeAccountId, normalizeAllowFromEntries, normalizeE164, @@ -11,8 +13,6 @@ import { type OpenClawConfig, } from "openclaw/plugin-sdk/setup"; import type { ChannelSetupWizard } from "openclaw/plugin-sdk/setup"; -import { formatCliCommand } from "../../../src/cli/command-format.js"; -import { formatDocsLink } from "../../../src/terminal/links.js"; import { listWhatsAppAccountIds, resolveWhatsAppAuthDir } from "./accounts.js"; import { loginWeb } from "./login.js"; import { whatsappSetupAdapter } from "./setup-core.js"; diff --git a/extensions/whatsapp/src/shared.ts b/extensions/whatsapp/src/shared.ts index 575954a516c..6616ac6911f 100644 --- a/extensions/whatsapp/src/shared.ts +++ b/extensions/whatsapp/src/shared.ts @@ -3,22 +3,20 @@ import { collectAllowlistProviderGroupPolicyWarnings, collectOpenGroupPolicyRouteAllowlistWarnings, } from "openclaw/plugin-sdk/channel-policy"; -import { buildChannelConfigSchema } from "../../../src/channels/plugins/config-schema.js"; -import { - resolveWhatsAppGroupRequireMention, - resolveWhatsAppGroupToolPolicy, -} from "../../../src/channels/plugins/group-mentions.js"; -import type { ChannelPlugin } from "../../../src/channels/plugins/types.plugin.js"; -import { resolveWhatsAppGroupIntroHint } from "../../../src/channels/plugins/whatsapp-shared.js"; -import { getChatChannelMeta } from "../../../src/channels/registry.js"; -import { WhatsAppConfigSchema } from "../../../src/config/zod-schema.providers-whatsapp.js"; import { + buildChannelConfigSchema, + DEFAULT_ACCOUNT_ID, formatWhatsAppConfigAllowFromEntries, + getChatChannelMeta, + normalizeE164, resolveWhatsAppConfigAllowFrom, resolveWhatsAppConfigDefaultTo, -} from "../../../src/plugin-sdk/channel-config-helpers.js"; -import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; -import { normalizeE164 } from "../../../src/utils.js"; + resolveWhatsAppGroupIntroHint, + resolveWhatsAppGroupRequireMention, + resolveWhatsAppGroupToolPolicy, + WhatsAppConfigSchema, + type ChannelPlugin, +} from "openclaw/plugin-sdk/whatsapp"; import { listWhatsAppAccountIds, resolveDefaultWhatsAppAccountId, diff --git a/src/plugin-sdk/slack.ts b/src/plugin-sdk/slack.ts index 8e6793543af..0e8ce16aef2 100644 --- a/src/plugin-sdk/slack.ts +++ b/src/plugin-sdk/slack.ts @@ -81,3 +81,5 @@ export { } from "../../extensions/slack/src/actions.js"; export { recordSlackThreadParticipation } from "../../extensions/slack/src/sent-thread-cache.js"; export { handleSlackMessageAction } from "./slack-message-actions.js"; +export { createSlackActions } from "../channels/plugins/slack.actions.js"; +export type { SlackActionContext } from "../agents/tools/slack-actions.js";