refactor: remove remaining extension core imports

This commit is contained in:
Peter Steinberger 2026-03-17 00:59:19 -07:00
parent 9648e7fecb
commit ec1b80809d
No known key found for this signature in database
22 changed files with 560 additions and 528 deletions

View File

@ -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();

View File

@ -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<void>>;
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<typeof vi.fn> }>;
createThreadBindingManagerMock: Mock<() => { stop: ReturnType<typeof vi.fn> }>;
reconcileAcpThreadBindingsOnStartupMock: Mock<() => unknown>;
createdBindingManagers: Array<{ stop: ReturnType<typeof vi.fn> }>;
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<void>>;
resolveDiscordAccountMock: Mock<() => unknown>;
resolveDiscordAllowlistConfigMock: Mock<() => Promise<unknown>>;
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<typeof vi.fn> }> = [];
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<string, unknown>) {
resolveDiscordAccountMock.mockImplementation(() => ({
accountId: "default",
token: "cfg-token",
config: {
...baseDiscordAccountConfig(),
...overrides,
},
}));
}
export function getFirstDiscordMessageHandlerParams<T extends object>() {
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<typeof vi.fn> };
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<unknown>) => 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";

View File

@ -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<typeof import("openclaw/plugin-sdk/plugin-runtime")>(
"openclaw/plugin-sdk/plugin-runtime",
);
return {
...actual,
getPluginCommandSpecs: getPluginCommandSpecsMock,
};
});
vi.mock("../voice/manager.runtime.js", () => {
voiceRuntimeModuleLoadedMock();

View File

@ -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";

View File

@ -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 {

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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 {

View File

@ -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";

View File

@ -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,

View File

@ -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";

View File

@ -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,

View File

@ -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<void>>;
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<typeof vi.fn> }>;
createThreadBindingManagerMock: Mock<() => { stop: ReturnType<typeof vi.fn> }>;
reconcileAcpThreadBindingsOnStartupMock: Mock<() => unknown>;
createdBindingManagers: Array<{ stop: ReturnType<typeof vi.fn> }>;
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<void>>;
resolveDiscordAccountMock: Mock<() => unknown>;
resolveDiscordAllowlistConfigMock: Mock<() => Promise<unknown>>;
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<typeof vi.fn> }> = [];
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<string, unknown>) {
resolveDiscordAccountMock.mockImplementation(() => ({
accountId: "default",
token: "cfg-token",
config: {
...baseDiscordAccountConfig(),
...overrides,
},
}));
}
export function getFirstDiscordMessageHandlerParams<T extends object>() {
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<typeof vi.fn> };
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<typeof import("openclaw/plugin-sdk/acp-runtime")>(
"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<typeof import("openclaw/plugin-sdk/reply-runtime")>(
"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<typeof import("openclaw/plugin-sdk/config-runtime")>(
"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<typeof import("openclaw/plugin-sdk/runtime-env")>(
"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<typeof import("openclaw/plugin-sdk/infra-runtime")>(
"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,
}));

View File

@ -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";

View File

@ -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,

View File

@ -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";