diff --git a/src/channels/plugins/setup-wizard-binary.ts b/src/channels/plugins/setup-wizard-binary.ts index d199caf3bb9..0d0711fc0c6 100644 --- a/src/channels/plugins/setup-wizard-binary.ts +++ b/src/channels/plugins/setup-wizard-binary.ts @@ -23,6 +23,7 @@ export function createDetectedBinaryStatus(params: { detectBinary?: (path: string) => Promise; }): ChannelSetupWizardStatus { const detectBinary = params.detectBinary ?? defaultDetectBinary; + return { configuredLabel: params.configuredLabel, unconfiguredLabel: params.unconfiguredLabel, @@ -31,7 +32,7 @@ export function createDetectedBinaryStatus(params: { configuredScore: params.configuredScore, unconfiguredScore: params.unconfiguredScore, resolveConfigured: params.resolveConfigured, - resolveStatusLines: async ({ cfg, configured }: SetupStatusParams) => { + async resolveStatusLines({ cfg, configured }: SetupStatusParams): Promise { const binaryPath = params.resolveBinaryPath({ cfg }); const detected = await detectBinary(binaryPath); return [ @@ -39,14 +40,26 @@ export function createDetectedBinaryStatus(params: { `${params.binaryLabel}: ${detected ? "found" : "missing"} (${binaryPath})`, ]; }, - resolveSelectionHint: async ({ cfg }) => - (await detectBinary(params.resolveBinaryPath({ cfg }))) + async resolveSelectionHint({ + cfg, + }: { + cfg: OpenClawConfig; + configured: boolean; + }): Promise { + return (await detectBinary(params.resolveBinaryPath({ cfg }))) ? params.configuredHint - : params.unconfiguredHint, - resolveQuickstartScore: async ({ cfg }) => - (await detectBinary(params.resolveBinaryPath({ cfg }))) + : params.unconfiguredHint; + }, + async resolveQuickstartScore({ + cfg, + }: { + cfg: OpenClawConfig; + configured: boolean; + }): Promise { + return (await detectBinary(params.resolveBinaryPath({ cfg }))) ? params.configuredScore - : params.unconfiguredScore, + : params.unconfiguredScore; + }, }; } @@ -78,12 +91,15 @@ export function createDelegatedSetupWizardStatusResolvers( "resolveStatusLines" | "resolveSelectionHint" | "resolveQuickstartScore" > { return { - resolveStatusLines: async (params) => - (await loadWizard()).status.resolveStatusLines?.(params) ?? [], - resolveSelectionHint: async (params) => - await (await loadWizard()).status.resolveSelectionHint?.(params), - resolveQuickstartScore: async (params) => - await (await loadWizard()).status.resolveQuickstartScore?.(params), + async resolveStatusLines(params) { + return (await loadWizard()).status.resolveStatusLines?.(params) ?? []; + }, + async resolveSelectionHint(params) { + return await (await loadWizard()).status.resolveSelectionHint?.(params); + }, + async resolveQuickstartScore(params) { + return await (await loadWizard()).status.resolveQuickstartScore?.(params); + }, }; } diff --git a/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts b/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts new file mode 100644 index 00000000000..48aad9dd936 --- /dev/null +++ b/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts @@ -0,0 +1,67 @@ +import type { OpenClawConfig } from "../../../config/config.js"; +import type { SecretInput } from "../../../config/types.secrets.js"; +import { applyLitellmConfig } from "../../../plugin-sdk/litellm.js"; +import { applyAuthProfileConfig } from "../../../plugins/provider-auth-helpers.js"; +import { setLitellmApiKey } from "../../../plugins/provider-auth-storage.js"; +import type { RuntimeEnv } from "../../../runtime.js"; +import type { AuthChoice, OnboardOptions } from "../../onboard-types.js"; + +type ApiKeyStorageOptions = { + secretInputMode: "plaintext" | "ref"; +}; + +type ResolvedNonInteractiveApiKey = { + key: string; + source: "profile" | "env" | "flag"; +}; + +export async function applySimpleNonInteractiveApiKeyChoice(params: { + authChoice: AuthChoice; + nextConfig: OpenClawConfig; + baseConfig: OpenClawConfig; + opts: OnboardOptions; + runtime: RuntimeEnv; + apiKeyStorageOptions?: ApiKeyStorageOptions; + resolveApiKey: (input: { + provider: string; + cfg: OpenClawConfig; + flagValue?: string; + flagName: `--${string}`; + envVar: string; + runtime: RuntimeEnv; + }) => Promise; + maybeSetResolvedApiKey: ( + resolved: ResolvedNonInteractiveApiKey, + setter: (value: SecretInput) => Promise | void, + ) => Promise; +}): Promise { + if (params.authChoice !== "litellm-api-key") { + return undefined; + } + + const resolved = await params.resolveApiKey({ + provider: "litellm", + cfg: params.baseConfig, + flagValue: params.opts.litellmApiKey, + flagName: "--litellm-api-key", + envVar: "LITELLM_API_KEY", + runtime: params.runtime, + }); + if (!resolved) { + return null; + } + if ( + !(await params.maybeSetResolvedApiKey(resolved, (value) => + setLitellmApiKey(value, undefined, params.apiKeyStorageOptions), + )) + ) { + return null; + } + return applyLitellmConfig( + applyAuthProfileConfig(params.nextConfig, { + profileId: "litellm:default", + provider: "litellm", + mode: "api_key", + }), + ); +} diff --git a/src/logging/subsystem.ts b/src/logging/subsystem.ts index 6438cbba516..0b74b645859 100644 --- a/src/logging/subsystem.ts +++ b/src/logging/subsystem.ts @@ -307,103 +307,345 @@ function logToFile( export function createSubsystemLogger(subsystem: string): SubsystemLogger { let fileLogger: TsLogger | null = null; - const getFileLogger = (): TsLogger => { - if (!fileLogger) { - fileLogger = getChildLogger({ subsystem }); - } - return fileLogger; - }; - const emit = (level: LogLevel, message: string, meta?: Record): void => { - const consoleSettings = getConsoleSettings(); - const consoleEnabled = - shouldLogToConsole(level, { level: consoleSettings.level }) && - shouldLogSubsystemToConsole(subsystem); - const fileEnabled = isFileLogLevelEnabled(level); - if (!consoleEnabled && !fileEnabled) { - return; - } - let consoleMessageOverride: string | undefined; - let fileMeta = meta; - if (meta && Object.keys(meta).length > 0) { - const { consoleMessage, ...rest } = meta as Record & { - consoleMessage?: unknown; - }; - if (typeof consoleMessage === "string") { - consoleMessageOverride = consoleMessage; - } - fileMeta = Object.keys(rest).length > 0 ? rest : undefined; - } - if (fileEnabled) { - logToFile(getFileLogger(), level, message, fileMeta); - } - if (!consoleEnabled) { - return; - } - const consoleMessage = consoleMessageOverride ?? message; - if ( - shouldSuppressProbeConsoleLine({ - level, - subsystem, - message: consoleMessage, - meta: fileMeta, - }) - ) { - return; - } - const line = formatConsoleLine({ - level, - subsystem, - message: consoleSettings.style === "json" ? message : consoleMessage, - style: consoleSettings.style, - meta: fileMeta, - }); - writeConsoleLine(level, line); - }; - const isConsoleEnabled = (level: LogLevel): boolean => { - const consoleSettings = getConsoleSettings(); - return ( - shouldLogToConsole(level, { level: consoleSettings.level }) && - shouldLogSubsystemToConsole(subsystem) - ); - }; - const isFileEnabled = (level: LogLevel): boolean => { - return isFileLogLevelEnabled(level); - }; const logger: SubsystemLogger = { subsystem, isEnabled(level, target = "any") { + const isConsoleEnabled = + shouldLogToConsole(level, { level: getConsoleSettings().level }) && + shouldLogSubsystemToConsole(subsystem); + const isFileEnabled = isFileLogLevelEnabled(level); if (target === "console") { - return isConsoleEnabled(level); + return isConsoleEnabled; } if (target === "file") { - return isFileEnabled(level); + return isFileEnabled; } - return isConsoleEnabled(level) || isFileEnabled(level); + return isConsoleEnabled || isFileEnabled; }, trace(message, meta) { - emit("trace", message, meta); + const level: LogLevel = "trace"; + const consoleSettings = getConsoleSettings(); + const consoleEnabled = + shouldLogToConsole(level, { level: consoleSettings.level }) && + shouldLogSubsystemToConsole(subsystem); + const fileEnabled = isFileLogLevelEnabled(level); + if (!consoleEnabled && !fileEnabled) { + return; + } + let consoleMessageOverride: string | undefined; + let fileMeta = meta; + if (meta && Object.keys(meta).length > 0) { + const { consoleMessage, ...rest } = meta as Record & { + consoleMessage?: unknown; + }; + if (typeof consoleMessage === "string") { + consoleMessageOverride = consoleMessage; + } + fileMeta = Object.keys(rest).length > 0 ? rest : undefined; + } + if (fileEnabled) { + if (!fileLogger) { + fileLogger = getChildLogger({ subsystem }); + } + logToFile(fileLogger, level, message, fileMeta); + } + if (!consoleEnabled) { + return; + } + const consoleMessage = consoleMessageOverride ?? message; + if ( + shouldSuppressProbeConsoleLine({ + level, + subsystem, + message: consoleMessage, + meta: fileMeta, + }) + ) { + return; + } + writeConsoleLine( + level, + formatConsoleLine({ + level, + subsystem, + message: consoleSettings.style === "json" ? message : consoleMessage, + style: consoleSettings.style, + meta: fileMeta, + }), + ); }, debug(message, meta) { - emit("debug", message, meta); + const level: LogLevel = "debug"; + const consoleSettings = getConsoleSettings(); + const consoleEnabled = + shouldLogToConsole(level, { level: consoleSettings.level }) && + shouldLogSubsystemToConsole(subsystem); + const fileEnabled = isFileLogLevelEnabled(level); + if (!consoleEnabled && !fileEnabled) { + return; + } + let consoleMessageOverride: string | undefined; + let fileMeta = meta; + if (meta && Object.keys(meta).length > 0) { + const { consoleMessage, ...rest } = meta as Record & { + consoleMessage?: unknown; + }; + if (typeof consoleMessage === "string") { + consoleMessageOverride = consoleMessage; + } + fileMeta = Object.keys(rest).length > 0 ? rest : undefined; + } + if (fileEnabled) { + if (!fileLogger) { + fileLogger = getChildLogger({ subsystem }); + } + logToFile(fileLogger, level, message, fileMeta); + } + if (!consoleEnabled) { + return; + } + const consoleMessage = consoleMessageOverride ?? message; + if ( + shouldSuppressProbeConsoleLine({ + level, + subsystem, + message: consoleMessage, + meta: fileMeta, + }) + ) { + return; + } + writeConsoleLine( + level, + formatConsoleLine({ + level, + subsystem, + message: consoleSettings.style === "json" ? message : consoleMessage, + style: consoleSettings.style, + meta: fileMeta, + }), + ); }, info(message, meta) { - emit("info", message, meta); + const level: LogLevel = "info"; + const consoleSettings = getConsoleSettings(); + const consoleEnabled = + shouldLogToConsole(level, { level: consoleSettings.level }) && + shouldLogSubsystemToConsole(subsystem); + const fileEnabled = isFileLogLevelEnabled(level); + if (!consoleEnabled && !fileEnabled) { + return; + } + let consoleMessageOverride: string | undefined; + let fileMeta = meta; + if (meta && Object.keys(meta).length > 0) { + const { consoleMessage, ...rest } = meta as Record & { + consoleMessage?: unknown; + }; + if (typeof consoleMessage === "string") { + consoleMessageOverride = consoleMessage; + } + fileMeta = Object.keys(rest).length > 0 ? rest : undefined; + } + if (fileEnabled) { + if (!fileLogger) { + fileLogger = getChildLogger({ subsystem }); + } + logToFile(fileLogger, level, message, fileMeta); + } + if (!consoleEnabled) { + return; + } + const consoleMessage = consoleMessageOverride ?? message; + if ( + shouldSuppressProbeConsoleLine({ + level, + subsystem, + message: consoleMessage, + meta: fileMeta, + }) + ) { + return; + } + writeConsoleLine( + level, + formatConsoleLine({ + level, + subsystem, + message: consoleSettings.style === "json" ? message : consoleMessage, + style: consoleSettings.style, + meta: fileMeta, + }), + ); }, warn(message, meta) { - emit("warn", message, meta); + const level: LogLevel = "warn"; + const consoleSettings = getConsoleSettings(); + const consoleEnabled = + shouldLogToConsole(level, { level: consoleSettings.level }) && + shouldLogSubsystemToConsole(subsystem); + const fileEnabled = isFileLogLevelEnabled(level); + if (!consoleEnabled && !fileEnabled) { + return; + } + let consoleMessageOverride: string | undefined; + let fileMeta = meta; + if (meta && Object.keys(meta).length > 0) { + const { consoleMessage, ...rest } = meta as Record & { + consoleMessage?: unknown; + }; + if (typeof consoleMessage === "string") { + consoleMessageOverride = consoleMessage; + } + fileMeta = Object.keys(rest).length > 0 ? rest : undefined; + } + if (fileEnabled) { + if (!fileLogger) { + fileLogger = getChildLogger({ subsystem }); + } + logToFile(fileLogger, level, message, fileMeta); + } + if (!consoleEnabled) { + return; + } + const consoleMessage = consoleMessageOverride ?? message; + if ( + shouldSuppressProbeConsoleLine({ + level, + subsystem, + message: consoleMessage, + meta: fileMeta, + }) + ) { + return; + } + writeConsoleLine( + level, + formatConsoleLine({ + level, + subsystem, + message: consoleSettings.style === "json" ? message : consoleMessage, + style: consoleSettings.style, + meta: fileMeta, + }), + ); }, error(message, meta) { - emit("error", message, meta); + const level: LogLevel = "error"; + const consoleSettings = getConsoleSettings(); + const consoleEnabled = + shouldLogToConsole(level, { level: consoleSettings.level }) && + shouldLogSubsystemToConsole(subsystem); + const fileEnabled = isFileLogLevelEnabled(level); + if (!consoleEnabled && !fileEnabled) { + return; + } + let consoleMessageOverride: string | undefined; + let fileMeta = meta; + if (meta && Object.keys(meta).length > 0) { + const { consoleMessage, ...rest } = meta as Record & { + consoleMessage?: unknown; + }; + if (typeof consoleMessage === "string") { + consoleMessageOverride = consoleMessage; + } + fileMeta = Object.keys(rest).length > 0 ? rest : undefined; + } + if (fileEnabled) { + if (!fileLogger) { + fileLogger = getChildLogger({ subsystem }); + } + logToFile(fileLogger, level, message, fileMeta); + } + if (!consoleEnabled) { + return; + } + const consoleMessage = consoleMessageOverride ?? message; + if ( + shouldSuppressProbeConsoleLine({ + level, + subsystem, + message: consoleMessage, + meta: fileMeta, + }) + ) { + return; + } + writeConsoleLine( + level, + formatConsoleLine({ + level, + subsystem, + message: consoleSettings.style === "json" ? message : consoleMessage, + style: consoleSettings.style, + meta: fileMeta, + }), + ); }, fatal(message, meta) { - emit("fatal", message, meta); + const level: LogLevel = "fatal"; + const consoleSettings = getConsoleSettings(); + const consoleEnabled = + shouldLogToConsole(level, { level: consoleSettings.level }) && + shouldLogSubsystemToConsole(subsystem); + const fileEnabled = isFileLogLevelEnabled(level); + if (!consoleEnabled && !fileEnabled) { + return; + } + let consoleMessageOverride: string | undefined; + let fileMeta = meta; + if (meta && Object.keys(meta).length > 0) { + const { consoleMessage, ...rest } = meta as Record & { + consoleMessage?: unknown; + }; + if (typeof consoleMessage === "string") { + consoleMessageOverride = consoleMessage; + } + fileMeta = Object.keys(rest).length > 0 ? rest : undefined; + } + if (fileEnabled) { + if (!fileLogger) { + fileLogger = getChildLogger({ subsystem }); + } + logToFile(fileLogger, level, message, fileMeta); + } + if (!consoleEnabled) { + return; + } + const consoleMessage = consoleMessageOverride ?? message; + if ( + shouldSuppressProbeConsoleLine({ + level, + subsystem, + message: consoleMessage, + meta: fileMeta, + }) + ) { + return; + } + writeConsoleLine( + level, + formatConsoleLine({ + level, + subsystem, + message: consoleSettings.style === "json" ? message : consoleMessage, + style: consoleSettings.style, + meta: fileMeta, + }), + ); }, raw(message) { - if (isFileEnabled("info")) { - logToFile(getFileLogger(), "info", message, { raw: true }); + if (isFileLogLevelEnabled("info")) { + if (!fileLogger) { + fileLogger = getChildLogger({ subsystem }); + } + logToFile(fileLogger, "info", message, { raw: true }); } - if (isConsoleEnabled("info")) { + if ( + shouldLogToConsole("info", { level: getConsoleSettings().level }) && + shouldLogSubsystemToConsole(subsystem) + ) { if (shouldSuppressProbeConsoleLine({ level: "info", subsystem, message })) { return; } @@ -421,16 +663,27 @@ export function runtimeForLogger( logger: SubsystemLogger, exit: RuntimeEnv["exit"] = defaultRuntime.exit, ): OutputRuntimeEnv { - const formatArgs = (...args: unknown[]) => - args - .map((arg) => formatRuntimeArg(arg)) - .join(" ") - .trim(); return { - log: (...args: unknown[]) => logger.info(formatArgs(...args)), - error: (...args: unknown[]) => logger.error(formatArgs(...args)), - writeStdout: (value: string) => logger.info(value), - writeJson: (value: unknown, space = 2) => { + log(...args) { + logger.info( + args + .map((arg) => formatRuntimeArg(arg)) + .join(" ") + .trim(), + ); + }, + error(...args) { + logger.error( + args + .map((arg) => formatRuntimeArg(arg)) + .join(" ") + .trim(), + ); + }, + writeStdout(value) { + logger.info(value); + }, + writeJson(value: unknown, space = 2) { logger.info(JSON.stringify(value, null, space > 0 ? space : undefined)); }, exit, diff --git a/src/plugin-sdk/litellm.ts b/src/plugin-sdk/litellm.ts new file mode 100644 index 00000000000..8f0fa7ebb06 --- /dev/null +++ b/src/plugin-sdk/litellm.ts @@ -0,0 +1,8 @@ +export { + applyLitellmConfig, + applyLitellmProviderConfig, + buildLitellmModelDefinition, + LITELLM_BASE_URL, + LITELLM_DEFAULT_MODEL_ID, + LITELLM_DEFAULT_MODEL_REF, +} from "../../extensions/litellm/onboard.js"; diff --git a/src/plugin-sdk/plugin-entry.ts b/src/plugin-sdk/plugin-entry.ts index 343bd258886..ee100b0784d 100644 --- a/src/plugin-sdk/plugin-entry.ts +++ b/src/plugin-sdk/plugin-entry.ts @@ -1,10 +1,48 @@ +import type { OpenClawConfig } from "../config/config.js"; import { emptyPluginConfigSchema } from "../plugins/config-schema.js"; import type { + AnyAgentTool, + MediaUnderstandingProviderPlugin, OpenClawPluginApi, OpenClawPluginCommandDefinition, OpenClawPluginConfigSchema, OpenClawPluginDefinition, + OpenClawPluginService, + OpenClawPluginServiceContext, + OpenClawPluginToolContext, + OpenClawPluginToolFactory, PluginInteractiveTelegramHandlerContext, + PluginLogger, + ProviderAugmentModelCatalogContext, + ProviderAuthContext, + ProviderAuthDoctorHintContext, + ProviderAuthMethod, + ProviderAuthMethodNonInteractiveContext, + ProviderAuthResult, + ProviderBuildMissingAuthMessageContext, + ProviderBuildUnknownModelHintContext, + ProviderBuiltInModelSuppressionContext, + ProviderBuiltInModelSuppressionResult, + ProviderCacheTtlEligibilityContext, + ProviderCatalogContext, + ProviderCatalogResult, + ProviderDefaultThinkingPolicyContext, + ProviderDiscoveryContext, + ProviderFetchUsageSnapshotContext, + ProviderModernModelPolicyContext, + ProviderNormalizeResolvedModelContext, + ProviderPrepareDynamicModelContext, + ProviderPrepareExtraParamsContext, + ProviderPrepareRuntimeAuthContext, + ProviderPreparedRuntimeAuth, + ProviderResolvedUsageAuth, + ProviderResolveDynamicModelContext, + ProviderResolveUsageAuthContext, + ProviderRuntimeModel, + ProviderThinkingPolicyContext, + ProviderWrapStreamFnContext, + SpeechProviderPlugin, + PluginCommandContext, } from "../plugins/types.js"; export type { @@ -50,8 +88,8 @@ export type { OpenClawPluginDefinition, PluginLogger, PluginInteractiveTelegramHandlerContext, -} from "../plugins/types.js"; -export type { OpenClawConfig } from "../config/config.js"; +}; +export type { OpenClawConfig }; export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; diff --git a/src/plugin-sdk/root-alias.cjs b/src/plugin-sdk/root-alias.cjs index 4879db2eb24..7c4375c3324 100644 --- a/src/plugin-sdk/root-alias.cjs +++ b/src/plugin-sdk/root-alias.cjs @@ -7,7 +7,12 @@ let monolithicSdk = null; let diagnosticEventsModule = null; const jitiLoaders = new Map(); const pluginSdkSubpathsCache = new Map(); -const shouldPreferSourceInTests = Boolean(process.env.VITEST) || process.env.NODE_ENV === "test"; +const shouldPreferSourceInTests = + process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST === "1" + ? false + : Boolean(process.env.VITEST) || + process.env.NODE_ENV === "test" || + process.env.OPENCLAW_PLUGIN_SDK_SOURCE_IN_TESTS === "1"; function emptyPluginConfigSchema() { function error(message) { diff --git a/src/plugins/cli.browser-plugin.integration.test.ts b/src/plugins/cli.browser-plugin.integration.test.ts index 9d6a4897a08..fb23b2bad87 100644 --- a/src/plugins/cli.browser-plugin.integration.test.ts +++ b/src/plugins/cli.browser-plugin.integration.test.ts @@ -1,11 +1,14 @@ import { Command } from "commander"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { afterAll, afterEach, beforeEach, describe, expect, it } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { registerPluginCliCommands } from "./cli.js"; import { clearPluginLoaderCache } from "./loader.js"; import { clearPluginManifestRegistryCache } from "./manifest-registry.js"; import { resetPluginRuntimeStateForTest } from "./runtime.js"; +const previousPreferDistPluginSdk = process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST; +process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST = "1"; + function resetPluginState() { clearPluginLoaderCache(); clearPluginManifestRegistryCache(); @@ -21,6 +24,14 @@ describe("registerPluginCliCommands browser plugin integration", () => { resetPluginState(); }); + afterAll(() => { + if (previousPreferDistPluginSdk === undefined) { + delete process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST; + } else { + process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST = previousPreferDistPluginSdk; + } + }); + it("registers the browser command from the bundled browser plugin", () => { const program = new Command(); registerPluginCliCommands(program, { diff --git a/src/plugins/contracts/web-search-provider.contract.test.ts b/src/plugins/contracts/web-search-provider.contract.test.ts index ca51d97862e..582608d9676 100644 --- a/src/plugins/contracts/web-search-provider.contract.test.ts +++ b/src/plugins/contracts/web-search-provider.contract.test.ts @@ -1,6 +1,18 @@ -import { describe, expect, it } from "vitest"; -import { webSearchProviderContractRegistry } from "./registry.js"; -import { installWebSearchProviderContractSuite } from "./suites.js"; +import { afterAll, describe, expect, it } from "vitest"; + +const previousPreferDistPluginSdk = process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST; +process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST = "1"; + +const { webSearchProviderContractRegistry } = await import("./registry.js"); +const { installWebSearchProviderContractSuite } = await import("./suites.js"); + +afterAll(() => { + if (previousPreferDistPluginSdk === undefined) { + delete process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST; + } else { + process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST = previousPreferDistPluginSdk; + } +}); describe("web search provider contract registry load", () => { it("loads bundled web search providers", () => { diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index c87f8d46f6f..9064a6f4373 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -54,6 +54,8 @@ function mkdirSafe(dir: string) { const fixtureRoot = mkdtempSafe(path.join(os.tmpdir(), "openclaw-plugin-")); let tempDirIndex = 0; const prevBundledDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; +const prevPreferDistPluginSdk = process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST; +process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST = "1"; const EMPTY_PLUGIN_SCHEMA = { type: "object", additionalProperties: false, properties: {} }; let cachedBundledTelegramDir = ""; let cachedBundledMemoryDir = ""; @@ -721,6 +723,11 @@ afterAll(() => { } finally { cachedBundledTelegramDir = ""; cachedBundledMemoryDir = ""; + if (prevPreferDistPluginSdk === undefined) { + delete process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST; + } else { + process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST = prevPreferDistPluginSdk; + } } }); diff --git a/src/plugins/sdk-alias.ts b/src/plugins/sdk-alias.ts index a8df6a903e2..1b9475984a8 100644 --- a/src/plugins/sdk-alias.ts +++ b/src/plugins/sdk-alias.ts @@ -161,7 +161,10 @@ export function resolvePluginSdkAliasCandidateOrder(params: { }): PluginSdkAliasCandidateKind[] { const normalizedModulePath = params.modulePath.replace(/\\/g, "/"); const isDistRuntime = normalizedModulePath.includes("/dist/"); - return isDistRuntime || params.isProduction ? ["dist", "src"] : ["src", "dist"]; + const preferDistInTests = process.env.OPENCLAW_PLUGIN_SDK_PREFER_DIST === "1"; + return isDistRuntime || params.isProduction || preferDistInTests + ? ["dist", "src"] + : ["src", "dist"]; } export function listPluginSdkAliasCandidates(params: {