From ef1784d264e02c4dfb7ef459b72de5ff11f72f57 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 27 Mar 2026 16:38:41 +0000 Subject: [PATCH] refactor: move bundled plugin policy into manifests --- .../amazon-bedrock/openclaw.plugin.json | 1 + extensions/anthropic/openclaw.plugin.json | 1 + .../brave/src/brave-web-search-provider.ts | 1 + extensions/byteplus/openclaw.plugin.json | 1 + .../openclaw.plugin.json | 1 + extensions/copilot-proxy/openclaw.plugin.json | 2 + extensions/deepseek/openclaw.plugin.json | 1 + extensions/device-pair/openclaw.plugin.json | 1 + extensions/fal/openclaw.plugin.json | 1 + .../src/firecrawl-search-provider.ts | 1 + .../github-copilot/openclaw.plugin.json | 1 + extensions/google/openclaw.plugin.json | 2 + .../google/src/gemini-web-search-provider.ts | 1 + extensions/huggingface/openclaw.plugin.json | 1 + extensions/kilocode/openclaw.plugin.json | 1 + extensions/kimi-coding/openclaw.plugin.json | 1 + extensions/litellm/openclaw.plugin.json | 1 + extensions/matrix/test-api.ts | 2 + extensions/memory-core/api.ts | 1 + extensions/memory-core/runtime-api.ts | 5 + .../src/memory/provider-adapters.ts | 43 +++ .../memory-core/src/tools.test-helpers.ts | 2 +- extensions/minimax/openclaw.plugin.json | 5 + extensions/mistral/openclaw.plugin.json | 1 + extensions/modelstudio/openclaw.plugin.json | 1 + extensions/moonshot/openclaw.plugin.json | 1 + .../moonshot/src/kimi-web-search-provider.ts | 1 + extensions/nvidia/openclaw.plugin.json | 1 + extensions/ollama/openclaw.plugin.json | 1 + .../openai/openai-codex-provider.test.ts | 15 ++ extensions/openai/openai-codex-provider.ts | 8 + extensions/openai/openclaw.plugin.json | 1 + extensions/opencode-go/openclaw.plugin.json | 1 + extensions/opencode/openclaw.plugin.json | 1 + extensions/openrouter/openclaw.plugin.json | 1 + .../src/perplexity-web-search-provider.ts | 1 + extensions/phone-control/openclaw.plugin.json | 1 + extensions/qianfan/openclaw.plugin.json | 1 + extensions/sglang/openclaw.plugin.json | 1 + extensions/synthetic/openclaw.plugin.json | 1 + extensions/talk-voice/openclaw.plugin.json | 1 + .../tavily/src/tavily-search-provider.ts | 1 + extensions/together/openclaw.plugin.json | 1 + extensions/venice/openclaw.plugin.json | 1 + .../vercel-ai-gateway/openclaw.plugin.json | 1 + extensions/vllm/openclaw.plugin.json | 1 + extensions/volcengine/openclaw.plugin.json | 1 + extensions/xai/openclaw.plugin.json | 1 + extensions/xai/web-search.ts | 1 + extensions/xiaomi/openclaw.plugin.json | 1 + extensions/zai/openclaw.plugin.json | 1 + scripts/generate-bundled-plugin-metadata.mjs | 66 +++++ src/commands/configure.wizard.test.ts | 145 +++------- src/commands/configure.wizard.ts | 130 +-------- src/commands/doctor-auth.ts | 52 ++-- src/commands/doctor-memory-search.test.ts | 31 +++ src/commands/doctor-memory-search.ts | 42 +-- .../local/auth-choice.api-key-providers.ts | 67 ----- .../local/auth-choice.test.ts | 28 ++ .../local/auth-choice.ts | 48 +--- src/commands/onboard-search.providers.test.ts | 1 + src/config/plugin-auto-enable.test.ts | 46 ++++ src/config/plugin-auto-enable.ts | 30 ++- src/flows/search-setup.ts | 18 +- src/infra/update-global.test.ts | 2 +- src/infra/update-runner.test.ts | 2 +- src/library.ts | 4 +- src/plugin-sdk/memory-core-engine-runtime.ts | 3 + src/plugin-sdk/provider-env-vars.ts | 1 + src/plugins/bundled-capability-metadata.ts | 25 ++ .../bundled-plugin-metadata.generated.ts | 252 ++++++++++++++++++ src/plugins/bundled-plugin-metadata.test.ts | 58 ++++ src/plugins/bundled-plugin-metadata.ts | 2 + src/plugins/config-state.test.ts | 26 +- src/plugins/config-state.ts | 55 +--- src/plugins/manifest-registry.ts | 2 + src/plugins/manifest.ts | 16 ++ src/plugins/provider-auth-choices.test.ts | 27 ++ src/plugins/provider-auth-choices.ts | 19 ++ src/plugins/types.ts | 9 + 80 files changed, 874 insertions(+), 459 deletions(-) create mode 100644 extensions/matrix/test-api.ts create mode 100644 extensions/memory-core/api.ts delete mode 100644 src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts diff --git a/extensions/amazon-bedrock/openclaw.plugin.json b/extensions/amazon-bedrock/openclaw.plugin.json index 9239ea19146..c49f2ba34bb 100644 --- a/extensions/amazon-bedrock/openclaw.plugin.json +++ b/extensions/amazon-bedrock/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "amazon-bedrock", + "enabledByDefault": true, "providers": ["amazon-bedrock"], "configSchema": { "type": "object", diff --git a/extensions/anthropic/openclaw.plugin.json b/extensions/anthropic/openclaw.plugin.json index 941c19cff48..60349da823e 100644 --- a/extensions/anthropic/openclaw.plugin.json +++ b/extensions/anthropic/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "anthropic", + "enabledByDefault": true, "providers": ["anthropic"], "cliBackends": ["claude-cli"], "providerAuthEnvVars": { diff --git a/extensions/brave/src/brave-web-search-provider.ts b/extensions/brave/src/brave-web-search-provider.ts index d4e3bd4ce03..4877e33ccd4 100644 --- a/extensions/brave/src/brave-web-search-provider.ts +++ b/extensions/brave/src/brave-web-search-provider.ts @@ -580,6 +580,7 @@ export function createBraveWebSearchProvider(): WebSearchProviderPlugin { id: "brave", label: "Brave Search", hint: "Structured results · country/language/time filters", + onboardingScopes: ["text-inference"], credentialLabel: "Brave Search API key", envVars: ["BRAVE_API_KEY"], placeholder: "BSA...", diff --git a/extensions/byteplus/openclaw.plugin.json b/extensions/byteplus/openclaw.plugin.json index f24abe730a3..9e20d5aada7 100644 --- a/extensions/byteplus/openclaw.plugin.json +++ b/extensions/byteplus/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "byteplus", + "enabledByDefault": true, "providers": ["byteplus", "byteplus-plan"], "providerAuthEnvVars": { "byteplus": ["BYTEPLUS_API_KEY"] diff --git a/extensions/cloudflare-ai-gateway/openclaw.plugin.json b/extensions/cloudflare-ai-gateway/openclaw.plugin.json index daa9d363b54..0fa44404e56 100644 --- a/extensions/cloudflare-ai-gateway/openclaw.plugin.json +++ b/extensions/cloudflare-ai-gateway/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "cloudflare-ai-gateway", + "enabledByDefault": true, "providers": ["cloudflare-ai-gateway"], "providerAuthEnvVars": { "cloudflare-ai-gateway": ["CLOUDFLARE_AI_GATEWAY_API_KEY"] diff --git a/extensions/copilot-proxy/openclaw.plugin.json b/extensions/copilot-proxy/openclaw.plugin.json index 88b5ee0e7b3..3e2091943e2 100644 --- a/extensions/copilot-proxy/openclaw.plugin.json +++ b/extensions/copilot-proxy/openclaw.plugin.json @@ -1,6 +1,8 @@ { "id": "copilot-proxy", + "enabledByDefault": true, "providers": ["copilot-proxy"], + "autoEnableWhenConfiguredProviders": ["copilot-proxy"], "providerAuthChoices": [ { "provider": "copilot-proxy", diff --git a/extensions/deepseek/openclaw.plugin.json b/extensions/deepseek/openclaw.plugin.json index 55c9c2779e8..8981657c4e5 100644 --- a/extensions/deepseek/openclaw.plugin.json +++ b/extensions/deepseek/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "deepseek", + "enabledByDefault": true, "providers": ["deepseek"], "providerAuthEnvVars": { "deepseek": ["DEEPSEEK_API_KEY"] diff --git a/extensions/device-pair/openclaw.plugin.json b/extensions/device-pair/openclaw.plugin.json index b72a075bd49..1ab1d874da5 100644 --- a/extensions/device-pair/openclaw.plugin.json +++ b/extensions/device-pair/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "device-pair", + "enabledByDefault": true, "name": "Device Pairing", "description": "Generate setup codes and approve device pairing requests.", "configSchema": { diff --git a/extensions/fal/openclaw.plugin.json b/extensions/fal/openclaw.plugin.json index e2226b6098e..32038ed538b 100644 --- a/extensions/fal/openclaw.plugin.json +++ b/extensions/fal/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "fal", + "enabledByDefault": true, "providers": ["fal"], "providerAuthEnvVars": { "fal": ["FAL_KEY"] diff --git a/extensions/firecrawl/src/firecrawl-search-provider.ts b/extensions/firecrawl/src/firecrawl-search-provider.ts index f5fbe4c134b..6ba5cefa4ed 100644 --- a/extensions/firecrawl/src/firecrawl-search-provider.ts +++ b/extensions/firecrawl/src/firecrawl-search-provider.ts @@ -28,6 +28,7 @@ export function createFirecrawlWebSearchProvider(): WebSearchProviderPlugin { id: "firecrawl", label: "Firecrawl Search", hint: "Structured results with optional result scraping", + onboardingScopes: ["text-inference"], credentialLabel: "Firecrawl API key", envVars: ["FIRECRAWL_API_KEY"], placeholder: "fc-...", diff --git a/extensions/github-copilot/openclaw.plugin.json b/extensions/github-copilot/openclaw.plugin.json index ec05c52b48c..6502677860a 100644 --- a/extensions/github-copilot/openclaw.plugin.json +++ b/extensions/github-copilot/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "github-copilot", + "enabledByDefault": true, "providers": ["github-copilot"], "providerAuthEnvVars": { "github-copilot": ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"] diff --git a/extensions/google/openclaw.plugin.json b/extensions/google/openclaw.plugin.json index a70686f3f78..e07ffb304a8 100644 --- a/extensions/google/openclaw.plugin.json +++ b/extensions/google/openclaw.plugin.json @@ -1,6 +1,8 @@ { "id": "google", + "enabledByDefault": true, "providers": ["google", "google-gemini-cli"], + "autoEnableWhenConfiguredProviders": ["google-gemini-cli"], "cliBackends": ["google-gemini-cli"], "providerAuthEnvVars": { "google": ["GEMINI_API_KEY", "GOOGLE_API_KEY"] diff --git a/extensions/google/src/gemini-web-search-provider.ts b/extensions/google/src/gemini-web-search-provider.ts index 64224220265..ae863fe300b 100644 --- a/extensions/google/src/gemini-web-search-provider.ts +++ b/extensions/google/src/gemini-web-search-provider.ts @@ -247,6 +247,7 @@ export function createGeminiWebSearchProvider(): WebSearchProviderPlugin { id: "gemini", label: "Gemini (Google Search)", hint: "Requires Google Gemini API key · Google Search grounding", + onboardingScopes: ["text-inference"], credentialLabel: "Google Gemini API key", envVars: ["GEMINI_API_KEY"], placeholder: "AIza...", diff --git a/extensions/huggingface/openclaw.plugin.json b/extensions/huggingface/openclaw.plugin.json index 84d8fd0e6bf..5308872c8f1 100644 --- a/extensions/huggingface/openclaw.plugin.json +++ b/extensions/huggingface/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "huggingface", + "enabledByDefault": true, "providers": ["huggingface"], "providerAuthEnvVars": { "huggingface": ["HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"] diff --git a/extensions/kilocode/openclaw.plugin.json b/extensions/kilocode/openclaw.plugin.json index 3aa51875bb6..ef3d29a20df 100644 --- a/extensions/kilocode/openclaw.plugin.json +++ b/extensions/kilocode/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "kilocode", + "enabledByDefault": true, "providers": ["kilocode"], "providerAuthEnvVars": { "kilocode": ["KILOCODE_API_KEY"] diff --git a/extensions/kimi-coding/openclaw.plugin.json b/extensions/kimi-coding/openclaw.plugin.json index 3558a545d5e..fc8b31701ae 100644 --- a/extensions/kimi-coding/openclaw.plugin.json +++ b/extensions/kimi-coding/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "kimi", + "enabledByDefault": true, "providers": ["kimi", "kimi-coding"], "providerAuthEnvVars": { "kimi": ["KIMI_API_KEY", "KIMICODE_API_KEY"], diff --git a/extensions/litellm/openclaw.plugin.json b/extensions/litellm/openclaw.plugin.json index e8a7b600569..d36cdb1e228 100644 --- a/extensions/litellm/openclaw.plugin.json +++ b/extensions/litellm/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "litellm", + "enabledByDefault": true, "providers": ["litellm"], "providerAuthEnvVars": { "litellm": ["LITELLM_API_KEY"] diff --git a/extensions/matrix/test-api.ts b/extensions/matrix/test-api.ts new file mode 100644 index 00000000000..396034c820a --- /dev/null +++ b/extensions/matrix/test-api.ts @@ -0,0 +1,2 @@ +export { matrixPlugin } from "./src/channel.js"; +export { setMatrixRuntime } from "./src/runtime.js"; diff --git a/extensions/memory-core/api.ts b/extensions/memory-core/api.ts new file mode 100644 index 00000000000..841031465bb --- /dev/null +++ b/extensions/memory-core/api.ts @@ -0,0 +1 @@ +export type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core"; diff --git a/extensions/memory-core/runtime-api.ts b/extensions/memory-core/runtime-api.ts index e31dcf5fb30..d4ec52aa942 100644 --- a/extensions/memory-core/runtime-api.ts +++ b/extensions/memory-core/runtime-api.ts @@ -1 +1,6 @@ export { getMemorySearchManager, MemoryIndexManager } from "./src/memory/index.js"; +export { + getBuiltinMemoryEmbeddingProviderDoctorMetadata, + listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata, +} from "./src/memory/provider-adapters.js"; +export type { BuiltinMemoryEmbeddingProviderDoctorMetadata } from "./src/memory/provider-adapters.js"; diff --git a/extensions/memory-core/src/memory/provider-adapters.ts b/extensions/memory-core/src/memory/provider-adapters.ts index c31ac08dc03..e802b13ec84 100644 --- a/extensions/memory-core/src/memory/provider-adapters.ts +++ b/extensions/memory-core/src/memory/provider-adapters.ts @@ -22,6 +22,15 @@ import { type MemoryEmbeddingProviderAdapter, } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings"; import { resolveUserPath } from "openclaw/plugin-sdk/memory-core-host-engine-foundation"; +import { getProviderEnvVars } from "openclaw/plugin-sdk/provider-env-vars"; + +export type BuiltinMemoryEmbeddingProviderDoctorMetadata = { + providerId: string; + authProviderId: string; + envVars: string[]; + transport: "local" | "remote"; + autoSelectPriority?: number; +}; function formatErrorMessage(err: unknown): string { return err instanceof Error ? err.message : String(err); @@ -107,6 +116,10 @@ function supportsGeminiMultimodalEmbeddings(model: string): boolean { return normalized === "gemini-embedding-2-preview"; } +function resolveMemoryEmbeddingAuthProviderId(providerId: string): string { + return providerId === "gemini" ? "google" : providerId; +} + const openAiAdapter: MemoryEmbeddingProviderAdapter = { id: "openai", defaultModel: DEFAULT_OPENAI_EMBEDDING_MODEL, @@ -356,6 +369,36 @@ export function registerBuiltInMemoryEmbeddingProviders(register: { } } +export function getBuiltinMemoryEmbeddingProviderDoctorMetadata( + providerId: string, +): BuiltinMemoryEmbeddingProviderDoctorMetadata | null { + const adapter = getBuiltinMemoryEmbeddingProviderAdapter(providerId); + if (!adapter) { + return null; + } + const authProviderId = resolveMemoryEmbeddingAuthProviderId(adapter.id); + return { + providerId: adapter.id, + authProviderId, + envVars: getProviderEnvVars(authProviderId), + transport: adapter.transport === "local" ? "local" : "remote", + autoSelectPriority: adapter.autoSelectPriority, + }; +} + +export function listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata(): Array { + return builtinMemoryEmbeddingProviderAdapters + .filter((adapter) => typeof adapter.autoSelectPriority === "number") + .toSorted((a, b) => (a.autoSelectPriority ?? 0) - (b.autoSelectPriority ?? 0)) + .map((adapter) => ({ + providerId: adapter.id, + authProviderId: resolveMemoryEmbeddingAuthProviderId(adapter.id), + envVars: getProviderEnvVars(resolveMemoryEmbeddingAuthProviderId(adapter.id)), + transport: adapter.transport === "local" ? "local" : "remote", + autoSelectPriority: adapter.autoSelectPriority, + })); +} + export { DEFAULT_GEMINI_EMBEDDING_MODEL, DEFAULT_LOCAL_MODEL, diff --git a/extensions/memory-core/src/tools.test-helpers.ts b/extensions/memory-core/src/tools.test-helpers.ts index e8c8d41f82f..cede4ed0cf5 100644 --- a/extensions/memory-core/src/tools.test-helpers.ts +++ b/extensions/memory-core/src/tools.test-helpers.ts @@ -1,5 +1,5 @@ -import type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core"; import { expect } from "vitest"; +import type { OpenClawConfig } from "../api.js"; import { createMemoryGetTool, createMemorySearchTool } from "./tools.js"; export function asOpenClawConfig(config: Partial): OpenClawConfig { diff --git a/extensions/minimax/openclaw.plugin.json b/extensions/minimax/openclaw.plugin.json index 39bd22bcdfb..8f0da029322 100644 --- a/extensions/minimax/openclaw.plugin.json +++ b/extensions/minimax/openclaw.plugin.json @@ -1,6 +1,9 @@ { "id": "minimax", + "enabledByDefault": true, + "legacyPluginIds": ["minimax-portal-auth"], "providers": ["minimax", "minimax-portal"], + "autoEnableWhenConfiguredProviders": ["minimax-portal"], "providerAuthEnvVars": { "minimax": ["MINIMAX_API_KEY"], "minimax-portal": ["MINIMAX_OAUTH_TOKEN", "MINIMAX_API_KEY"] @@ -20,6 +23,7 @@ "provider": "minimax", "method": "api-global", "choiceId": "minimax-global-api", + "deprecatedChoiceIds": ["minimax", "minimax-api", "minimax-cloud", "minimax-api-lightning"], "choiceLabel": "MiniMax API key (Global)", "choiceHint": "Global endpoint - api.minimax.io", "groupId": "minimax", @@ -44,6 +48,7 @@ "provider": "minimax", "method": "api-cn", "choiceId": "minimax-cn-api", + "deprecatedChoiceIds": ["minimax-api-key-cn"], "choiceLabel": "MiniMax API key (CN)", "choiceHint": "CN endpoint - api.minimaxi.com", "groupId": "minimax", diff --git a/extensions/mistral/openclaw.plugin.json b/extensions/mistral/openclaw.plugin.json index e1919db748d..6cf38a73a27 100644 --- a/extensions/mistral/openclaw.plugin.json +++ b/extensions/mistral/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "mistral", + "enabledByDefault": true, "providers": ["mistral"], "providerAuthEnvVars": { "mistral": ["MISTRAL_API_KEY"] diff --git a/extensions/modelstudio/openclaw.plugin.json b/extensions/modelstudio/openclaw.plugin.json index af75b8ef426..8e546d4f69b 100644 --- a/extensions/modelstudio/openclaw.plugin.json +++ b/extensions/modelstudio/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "modelstudio", + "enabledByDefault": true, "providers": ["modelstudio"], "providerAuthEnvVars": { "modelstudio": ["MODELSTUDIO_API_KEY"] diff --git a/extensions/moonshot/openclaw.plugin.json b/extensions/moonshot/openclaw.plugin.json index 5923a53c082..0670bc70479 100644 --- a/extensions/moonshot/openclaw.plugin.json +++ b/extensions/moonshot/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "moonshot", + "enabledByDefault": true, "providers": ["moonshot"], "providerAuthEnvVars": { "moonshot": ["MOONSHOT_API_KEY"] diff --git a/extensions/moonshot/src/kimi-web-search-provider.ts b/extensions/moonshot/src/kimi-web-search-provider.ts index 94d5acda3bf..d0d39b729d9 100644 --- a/extensions/moonshot/src/kimi-web-search-provider.ts +++ b/extensions/moonshot/src/kimi-web-search-provider.ts @@ -318,6 +318,7 @@ export function createKimiWebSearchProvider(): WebSearchProviderPlugin { id: "kimi", label: "Kimi (Moonshot)", hint: "Requires Moonshot / Kimi API key · Moonshot web search", + onboardingScopes: ["text-inference"], credentialLabel: "Moonshot / Kimi API key", envVars: ["KIMI_API_KEY", "MOONSHOT_API_KEY"], placeholder: "sk-...", diff --git a/extensions/nvidia/openclaw.plugin.json b/extensions/nvidia/openclaw.plugin.json index 3b46534911b..f08e51443b0 100644 --- a/extensions/nvidia/openclaw.plugin.json +++ b/extensions/nvidia/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "nvidia", + "enabledByDefault": true, "providers": ["nvidia"], "providerAuthEnvVars": { "nvidia": ["NVIDIA_API_KEY"] diff --git a/extensions/ollama/openclaw.plugin.json b/extensions/ollama/openclaw.plugin.json index bace5f2816f..3c6c38176f3 100644 --- a/extensions/ollama/openclaw.plugin.json +++ b/extensions/ollama/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "ollama", + "enabledByDefault": true, "providers": ["ollama"], "providerAuthEnvVars": { "ollama": ["OLLAMA_API_KEY"] diff --git a/extensions/openai/openai-codex-provider.test.ts b/extensions/openai/openai-codex-provider.test.ts index 509963842bb..8eaf0a589cb 100644 --- a/extensions/openai/openai-codex-provider.test.ts +++ b/extensions/openai/openai-codex-provider.test.ts @@ -69,4 +69,19 @@ describe("openai codex provider", () => { expires: expect.any(Number), }); }); + + it("returns deprecated-profile doctor guidance for legacy Codex CLI ids", () => { + const provider = buildOpenAICodexProviderPlugin(); + + expect( + provider.buildAuthDoctorHint?.({ + provider: "openai-codex", + profileId: "openai-codex:codex-cli", + config: undefined, + store: { version: 1, profiles: {} }, + }), + ).toBe( + "Deprecated profile. Run `openclaw models auth login --provider openai-codex` or `openclaw configure`.", + ); + }); }); diff --git a/extensions/openai/openai-codex-provider.ts b/extensions/openai/openai-codex-provider.ts index b1c723471fd..3808218826f 100644 --- a/extensions/openai/openai-codex-provider.ts +++ b/extensions/openai/openai-codex-provider.ts @@ -195,6 +195,13 @@ async function runOpenAICodexOAuth(ctx: ProviderAuthContext) { }); } +function buildOpenAICodexAuthDoctorHint(ctx: { profileId?: string }) { + if (ctx.profileId !== CODEX_CLI_PROFILE_ID) { + return undefined; + } + return "Deprecated profile. Run `openclaw models auth login --provider openai-codex` or `openclaw configure`."; +} + export function buildOpenAICodexProviderPlugin(): ProviderPlugin { return { id: PROVIDER_ID, @@ -233,6 +240,7 @@ export function buildOpenAICodexProviderPlugin(): ProviderPlugin { }, }, resolveDynamicModel: (ctx) => resolveCodexForwardCompatModel(ctx), + buildAuthDoctorHint: (ctx) => buildOpenAICodexAuthDoctorHint(ctx), capabilities: { providerFamily: "openai", }, diff --git a/extensions/openai/openclaw.plugin.json b/extensions/openai/openclaw.plugin.json index a0153a31804..d129118c87e 100644 --- a/extensions/openai/openclaw.plugin.json +++ b/extensions/openai/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "openai", + "enabledByDefault": true, "providers": ["openai", "openai-codex"], "cliBackends": ["codex-cli"], "providerAuthEnvVars": { diff --git a/extensions/opencode-go/openclaw.plugin.json b/extensions/opencode-go/openclaw.plugin.json index 9972dc633ff..555c8a5de64 100644 --- a/extensions/opencode-go/openclaw.plugin.json +++ b/extensions/opencode-go/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "opencode-go", + "enabledByDefault": true, "providers": ["opencode-go"], "providerAuthEnvVars": { "opencode-go": ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"] diff --git a/extensions/opencode/openclaw.plugin.json b/extensions/opencode/openclaw.plugin.json index 9352896340b..d1f7a6890b2 100644 --- a/extensions/opencode/openclaw.plugin.json +++ b/extensions/opencode/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "opencode", + "enabledByDefault": true, "providers": ["opencode"], "providerAuthEnvVars": { "opencode": ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"] diff --git a/extensions/openrouter/openclaw.plugin.json b/extensions/openrouter/openclaw.plugin.json index 2a153632343..00a9633678d 100644 --- a/extensions/openrouter/openclaw.plugin.json +++ b/extensions/openrouter/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "openrouter", + "enabledByDefault": true, "providers": ["openrouter"], "providerAuthEnvVars": { "openrouter": ["OPENROUTER_API_KEY"] diff --git a/extensions/perplexity/src/perplexity-web-search-provider.ts b/extensions/perplexity/src/perplexity-web-search-provider.ts index a60324257e7..709d1b0ebbc 100644 --- a/extensions/perplexity/src/perplexity-web-search-provider.ts +++ b/extensions/perplexity/src/perplexity-web-search-provider.ts @@ -654,6 +654,7 @@ export function createPerplexityWebSearchProvider(): WebSearchProviderPlugin { id: "perplexity", label: "Perplexity Search", hint: "Requires Perplexity API key or OpenRouter API key · structured results", + onboardingScopes: ["text-inference"], credentialLabel: "Perplexity API key", envVars: ["PERPLEXITY_API_KEY", "OPENROUTER_API_KEY"], placeholder: "pplx-...", diff --git a/extensions/phone-control/openclaw.plugin.json b/extensions/phone-control/openclaw.plugin.json index 4d73c85e43b..3936aed06c2 100644 --- a/extensions/phone-control/openclaw.plugin.json +++ b/extensions/phone-control/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "phone-control", + "enabledByDefault": true, "name": "Phone Control", "description": "Arm/disarm high-risk phone node commands (camera/screen/writes) with an optional auto-expiry.", "configSchema": { diff --git a/extensions/qianfan/openclaw.plugin.json b/extensions/qianfan/openclaw.plugin.json index d2ac243e261..29f77396982 100644 --- a/extensions/qianfan/openclaw.plugin.json +++ b/extensions/qianfan/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "qianfan", + "enabledByDefault": true, "providers": ["qianfan"], "providerAuthEnvVars": { "qianfan": ["QIANFAN_API_KEY"] diff --git a/extensions/sglang/openclaw.plugin.json b/extensions/sglang/openclaw.plugin.json index d9f7f92eb52..39d25f66cfa 100644 --- a/extensions/sglang/openclaw.plugin.json +++ b/extensions/sglang/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "sglang", + "enabledByDefault": true, "providers": ["sglang"], "providerAuthEnvVars": { "sglang": ["SGLANG_API_KEY"] diff --git a/extensions/synthetic/openclaw.plugin.json b/extensions/synthetic/openclaw.plugin.json index a0c519a72b2..85ab77c626c 100644 --- a/extensions/synthetic/openclaw.plugin.json +++ b/extensions/synthetic/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "synthetic", + "enabledByDefault": true, "providers": ["synthetic"], "providerAuthEnvVars": { "synthetic": ["SYNTHETIC_API_KEY"] diff --git a/extensions/talk-voice/openclaw.plugin.json b/extensions/talk-voice/openclaw.plugin.json index 88ef17397d2..0979662dc4a 100644 --- a/extensions/talk-voice/openclaw.plugin.json +++ b/extensions/talk-voice/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "talk-voice", + "enabledByDefault": true, "name": "Talk Voice", "description": "Manage Talk voice selection (list/set).", "configSchema": { diff --git a/extensions/tavily/src/tavily-search-provider.ts b/extensions/tavily/src/tavily-search-provider.ts index 25f1cf405a2..6fe230521b9 100644 --- a/extensions/tavily/src/tavily-search-provider.ts +++ b/extensions/tavily/src/tavily-search-provider.ts @@ -28,6 +28,7 @@ export function createTavilyWebSearchProvider(): WebSearchProviderPlugin { id: "tavily", label: "Tavily Search", hint: "Structured results with domain filters and AI answer summaries", + onboardingScopes: ["text-inference"], credentialLabel: "Tavily API key", envVars: ["TAVILY_API_KEY"], placeholder: "tvly-...", diff --git a/extensions/together/openclaw.plugin.json b/extensions/together/openclaw.plugin.json index f185492aa54..52f4dc2714b 100644 --- a/extensions/together/openclaw.plugin.json +++ b/extensions/together/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "together", + "enabledByDefault": true, "providers": ["together"], "providerAuthEnvVars": { "together": ["TOGETHER_API_KEY"] diff --git a/extensions/venice/openclaw.plugin.json b/extensions/venice/openclaw.plugin.json index 6667907b5e4..43daec4829c 100644 --- a/extensions/venice/openclaw.plugin.json +++ b/extensions/venice/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "venice", + "enabledByDefault": true, "providers": ["venice"], "providerAuthEnvVars": { "venice": ["VENICE_API_KEY"] diff --git a/extensions/vercel-ai-gateway/openclaw.plugin.json b/extensions/vercel-ai-gateway/openclaw.plugin.json index e86c3943c9a..865c7d9765c 100644 --- a/extensions/vercel-ai-gateway/openclaw.plugin.json +++ b/extensions/vercel-ai-gateway/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "vercel-ai-gateway", + "enabledByDefault": true, "providers": ["vercel-ai-gateway"], "providerAuthEnvVars": { "vercel-ai-gateway": ["AI_GATEWAY_API_KEY"] diff --git a/extensions/vllm/openclaw.plugin.json b/extensions/vllm/openclaw.plugin.json index bc3ad68c407..cf9ebdb5087 100644 --- a/extensions/vllm/openclaw.plugin.json +++ b/extensions/vllm/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "vllm", + "enabledByDefault": true, "providers": ["vllm"], "providerAuthEnvVars": { "vllm": ["VLLM_API_KEY"] diff --git a/extensions/volcengine/openclaw.plugin.json b/extensions/volcengine/openclaw.plugin.json index 4fd6d192281..1fd1d11e7c3 100644 --- a/extensions/volcengine/openclaw.plugin.json +++ b/extensions/volcengine/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "volcengine", + "enabledByDefault": true, "providers": ["volcengine", "volcengine-plan"], "providerAuthEnvVars": { "volcengine": ["VOLCANO_ENGINE_API_KEY"] diff --git a/extensions/xai/openclaw.plugin.json b/extensions/xai/openclaw.plugin.json index ef1da4587af..cdbe4bad558 100644 --- a/extensions/xai/openclaw.plugin.json +++ b/extensions/xai/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "xai", + "enabledByDefault": true, "providers": ["xai"], "providerAuthEnvVars": { "xai": ["XAI_API_KEY"] diff --git a/extensions/xai/web-search.ts b/extensions/xai/web-search.ts index 132806b4d28..1bbdfeb190b 100644 --- a/extensions/xai/web-search.ts +++ b/extensions/xai/web-search.ts @@ -72,6 +72,7 @@ export function createXaiWebSearchProvider(): WebSearchProviderPlugin { id: "grok", label: "Grok (xAI)", hint: "Requires xAI API key · xAI web-grounded responses", + onboardingScopes: ["text-inference"], credentialLabel: "xAI API key", envVars: ["XAI_API_KEY"], placeholder: "xai-...", diff --git a/extensions/xiaomi/openclaw.plugin.json b/extensions/xiaomi/openclaw.plugin.json index 61bec4e1473..981e4bcb9ed 100644 --- a/extensions/xiaomi/openclaw.plugin.json +++ b/extensions/xiaomi/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "xiaomi", + "enabledByDefault": true, "providers": ["xiaomi"], "providerAuthEnvVars": { "xiaomi": ["XIAOMI_API_KEY"] diff --git a/extensions/zai/openclaw.plugin.json b/extensions/zai/openclaw.plugin.json index a54c2bfd26c..f7d5838f54d 100644 --- a/extensions/zai/openclaw.plugin.json +++ b/extensions/zai/openclaw.plugin.json @@ -1,5 +1,6 @@ { "id": "zai", + "enabledByDefault": true, "providers": ["zai"], "providerAuthEnvVars": { "zai": ["ZAI_API_KEY", "Z_AI_API_KEY"] diff --git a/scripts/generate-bundled-plugin-metadata.mjs b/scripts/generate-bundled-plugin-metadata.mjs index 8797f3b64ff..c320006e805 100644 --- a/scripts/generate-bundled-plugin-metadata.mjs +++ b/scripts/generate-bundled-plugin-metadata.mjs @@ -1,6 +1,7 @@ import { execFileSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; +import { collectBundledPluginBuildEntries } from "./lib/bundled-plugin-build-entries.mjs"; import { collectBundledPluginSources } from "./lib/bundled-plugin-source-utils.mjs"; import { formatGeneratedModule } from "./lib/format-generated-module.mjs"; import { writeGeneratedOutput } from "./lib/generated-output-utils.mjs"; @@ -26,6 +27,12 @@ const DEFAULT_BUNDLED_CHANNEL_ENTRY_IDS = [ ]; const MANIFEST_KEY = "openclaw"; const FORMATTER_CWD = path.resolve(import.meta.dirname, ".."); +const RUNTIME_SIDECAR_PUBLIC_SURFACE_BASENAMES = new Set([ + "helper-api.js", + "light-runtime-api.js", + "runtime-api.js", + "thread-bindings-runtime.js", +]); function rewriteEntryToBuiltPath(entry) { if (typeof entry !== "string" || entry.trim().length === 0) { @@ -133,6 +140,16 @@ function normalizePluginManifest(raw) { id: raw.id.trim(), configSchema: raw.configSchema, ...(raw.enabledByDefault === true ? { enabledByDefault: true } : {}), + ...(normalizeStringList(raw.legacyPluginIds) + ? { legacyPluginIds: normalizeStringList(raw.legacyPluginIds) } + : {}), + ...(normalizeStringList(raw.autoEnableWhenConfiguredProviders) + ? { + autoEnableWhenConfiguredProviders: normalizeStringList( + raw.autoEnableWhenConfiguredProviders, + ), + } + : {}), ...(typeof raw.kind === "string" ? { kind: raw.kind.trim() } : {}), ...(normalizeStringList(raw.channels) ? { channels: normalizeStringList(raw.channels) } : {}), ...(normalizeStringList(raw.providers) @@ -179,6 +196,10 @@ function resolvePackageChannelMeta(packageJson) { function resolveChannelConfigSchemaModulePath(rootDir) { const candidates = [ + path.join(rootDir, "src", "config-surface.ts"), + path.join(rootDir, "src", "config-surface.js"), + path.join(rootDir, "src", "config-surface.mts"), + path.join(rootDir, "src", "config-surface.mjs"), path.join(rootDir, "src", "config-schema.ts"), path.join(rootDir, "src", "config-schema.js"), path.join(rootDir, "src", "config-schema.mts"), @@ -311,6 +332,25 @@ function normalizeGeneratedImportPath(dirName, builtPath) { return `../../extensions/${dirName}/${String(builtPath).replace(/^\.\//u, "")}`; } +function normalizeEntryPath(entry) { + return String(entry).replace(/^\.\//u, ""); +} + +function isPublicSurfaceArtifactSourceEntry(entry) { + const baseName = path.posix.basename(normalizeEntryPath(entry)); + if (baseName.startsWith("test-")) { + return false; + } + if (baseName.includes(".test-")) { + return false; + } + return !baseName.endsWith(".test.ts") && !baseName.endsWith(".test.js"); +} + +function isRuntimeSidecarPublicSurfaceArtifact(artifact) { + return RUNTIME_SIDECAR_PUBLIC_SURFACE_BASENAMES.has(path.posix.basename(String(artifact))); +} + function resolveBundledChannelEntries(entries) { const orderById = new Map(DEFAULT_BUNDLED_CHANNEL_ENTRY_IDS.map((id, index) => [id, index])); return entries @@ -329,6 +369,9 @@ function resolveBundledChannelEntries(entries) { export async function collectBundledPluginMetadata(params = {}) { const repoRoot = path.resolve(params.repoRoot ?? process.cwd()); + const buildEntriesById = new Map( + collectBundledPluginBuildEntries({ cwd: repoRoot }).map((entry) => [entry.id, entry]), + ); const entries = []; for (const source of collectBundledPluginSources({ repoRoot, requirePackageJson: true })) { const manifest = normalizePluginManifest(source.manifest); @@ -358,6 +401,27 @@ export async function collectBundledPluginMetadata(params = {}) { built: rewriteEntryToBuiltPath(packageManifest.setupEntry.trim()), } : undefined; + const publicSurfaceArtifacts = (() => { + const buildEntry = buildEntriesById.get(source.dirName); + if (!buildEntry) { + return undefined; + } + const excludedEntries = new Set( + [sourceEntry, setupEntry?.source] + .filter((entry) => typeof entry === "string" && entry.trim().length > 0) + .map(normalizeEntryPath), + ); + const artifacts = buildEntry.sourceEntries + .map(normalizeEntryPath) + .filter((entry) => !excludedEntries.has(entry)) + .filter(isPublicSurfaceArtifactSourceEntry) + .map(rewriteEntryToBuiltPath) + .filter((entry) => typeof entry === "string" && entry.length > 0) + .toSorted((left, right) => left.localeCompare(right)); + return artifacts.length > 0 ? artifacts : undefined; + })(); + const runtimeSidecarArtifacts = + publicSurfaceArtifacts?.filter(isRuntimeSidecarPublicSurfaceArtifact) ?? undefined; const channelConfigs = await collectBundledChannelConfigsForSource({ source, manifest }); if (channelConfigs) { manifest.channelConfigs = channelConfigs; @@ -378,6 +442,8 @@ export async function collectBundledPluginMetadata(params = {}) { ...(setupEntry?.built ? { setupSource: { source: setupEntry.source, built: setupEntry.built } } : {}), + ...(publicSurfaceArtifacts ? { publicSurfaceArtifacts } : {}), + ...(runtimeSidecarArtifacts?.length ? { runtimeSidecarArtifacts } : {}), ...(typeof packageJson.name === "string" ? { packageName: packageJson.name.trim() } : {}), ...(typeof packageJson.version === "string" ? { packageVersion: packageJson.version.trim() } diff --git a/src/commands/configure.wizard.test.ts b/src/commands/configure.wizard.test.ts index 736be4678d1..6c83aa05a1e 100644 --- a/src/commands/configure.wizard.test.ts +++ b/src/commands/configure.wizard.test.ts @@ -7,12 +7,8 @@ const mocks = vi.hoisted(() => ({ clackSelect: vi.fn(), clackText: vi.fn(), clackConfirm: vi.fn(), - applySearchKey: vi.fn(), - applySearchProviderSelection: vi.fn(), - hasExistingKey: vi.fn(), - hasKeyInEnv: vi.fn(), - resolveExistingKey: vi.fn(), resolveSearchProviderOptions: vi.fn(), + setupSearch: vi.fn(), readConfigFileSnapshot: vi.fn(), writeConfigFile: vi.fn(), resolveGatewayPort: vi.fn(), @@ -103,23 +99,7 @@ vi.mock("./onboard-channels.js", () => ({ vi.mock("./onboard-search.js", () => ({ resolveSearchProviderOptions: mocks.resolveSearchProviderOptions, - SEARCH_PROVIDER_OPTIONS: [ - { - id: "firecrawl", - label: "Firecrawl Search", - hint: "Structured results with optional result scraping", - credentialLabel: "Firecrawl API key", - envVars: ["FIRECRAWL_API_KEY"], - placeholder: "fc-...", - signupUrl: "https://www.firecrawl.dev/", - credentialPath: "plugins.entries.firecrawl.config.webSearch.apiKey", - }, - ], - resolveExistingKey: mocks.resolveExistingKey, - hasExistingKey: mocks.hasExistingKey, - applySearchKey: mocks.applySearchKey, - applySearchProviderSelection: mocks.applySearchProviderSelection, - hasKeyInEnv: mocks.hasKeyInEnv, + setupSearch: mocks.setupSearch, })); import { WizardCancelledError } from "../wizard/prompts.js"; @@ -173,7 +153,16 @@ function setupBaseWizardState() { mocks.probeGatewayReachable.mockResolvedValue({ ok: false }); mocks.resolveControlUiLinks.mockReturnValue({ wsUrl: "ws://127.0.0.1:18789" }); mocks.summarizeExistingConfig.mockReturnValue(""); - mocks.createClackPrompter.mockReturnValue({}); + mocks.createClackPrompter.mockReturnValue({ + intro: vi.fn(async () => {}), + outro: vi.fn(async () => {}), + note: vi.fn(async () => {}), + select: vi.fn(async () => "firecrawl"), + multiselect: vi.fn(async () => []), + text: vi.fn(async () => ""), + confirm: vi.fn(async () => true), + progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })), + }); } function queueWizardPrompts(params: { select: string[]; confirm: boolean[]; text?: string }) { @@ -194,9 +183,6 @@ describe("runConfigureWizard", () => { beforeEach(() => { vi.clearAllMocks(); mocks.ensureControlUiAssetsBuilt.mockResolvedValue({ ok: true }); - mocks.resolveExistingKey.mockReturnValue(undefined); - mocks.hasExistingKey.mockReturnValue(false); - mocks.hasKeyInEnv.mockReturnValue(false); mocks.resolveSearchProviderOptions.mockReturnValue([ { id: "firecrawl", @@ -209,9 +195,8 @@ describe("runConfigureWizard", () => { credentialPath: "plugins.entries.firecrawl.config.webSearch.apiKey", }, ]); - mocks.applySearchKey.mockReset(); - mocks.applySearchProviderSelection.mockReset(); - mocks.applySearchProviderSelection.mockImplementation((cfg: OpenClawConfig) => cfg); + mocks.setupSearch.mockReset(); + mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => cfg); }); it("persists gateway.mode=local when only the run mode is selected", async () => { @@ -240,21 +225,17 @@ describe("runConfigureWizard", () => { expect(runtime.exit).toHaveBeenCalledWith(1); }); - it("persists provider-owned web search config changes returned by applySearchKey", async () => { + it("persists provider-owned web search config changes returned by setupSearch", async () => { setupBaseWizardState(); - mocks.resolveExistingKey.mockReturnValue(undefined); - mocks.hasExistingKey.mockReturnValue(false); - mocks.hasKeyInEnv.mockReturnValue(false); - mocks.applySearchKey.mockImplementation((cfg: OpenClawConfig, provider: string, key: string) => - createEnabledWebSearchConfig(provider, { + mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => + createEnabledWebSearchConfig("firecrawl", { enabled: true, - config: { webSearch: { apiKey: key } }, + config: { webSearch: { apiKey: "fc-entered-key" } }, })(cfg), ); queueWizardPrompts({ - select: ["local", "firecrawl"], + select: ["local"], confirm: [true, false], - text: "fc-entered-key", }); await runWebConfigureWizard(); @@ -281,35 +262,29 @@ describe("runConfigureWizard", () => { }), }), ); - expect(mocks.clackText).toHaveBeenCalledWith( - expect.objectContaining({ - message: "Firecrawl API key (paste it here; leave blank to use FIRECRAWL_API_KEY)", - }), - ); + expect(mocks.setupSearch).toHaveBeenCalledOnce(); }); - it("applies provider selection side effects when a key already exists via secret ref or env", async () => { + it("delegates provider selection to the shared search setup flow", async () => { setupBaseWizardState(); - mocks.resolveExistingKey.mockReturnValue(undefined); - mocks.hasExistingKey.mockReturnValue(true); - mocks.hasKeyInEnv.mockReturnValue(false); - mocks.applySearchProviderSelection.mockImplementation((cfg: OpenClawConfig, provider: string) => - createEnabledWebSearchConfig(provider, { + mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => + createEnabledWebSearchConfig("firecrawl", { enabled: true, })(cfg), ); queueWizardPrompts({ - select: ["local", "firecrawl"], + select: ["local"], confirm: [true, false], }); await runWebConfigureWizard(); - expect(mocks.applySearchProviderSelection).toHaveBeenCalledWith( + expect(mocks.setupSearch).toHaveBeenCalledWith( expect.objectContaining({ gateway: expect.objectContaining({ mode: "local" }), }), - "firecrawl", + expect.anything(), + expect.anything(), ); expect(mocks.writeConfigFile).toHaveBeenCalledWith( expect.objectContaining({ @@ -322,53 +297,6 @@ describe("runConfigureWizard", () => { }), }), ); - expect(mocks.clackText).toHaveBeenCalledWith( - expect.objectContaining({ - message: "Firecrawl API key (leave blank to keep current or use FIRECRAWL_API_KEY)", - }), - ); - }); - - it("uses provider-specific credential copy for Gemini web search", async () => { - const originalGeminiApiKey = process.env.GEMINI_API_KEY; - delete process.env.GEMINI_API_KEY; - try { - setupBaseWizardState(); - mocks.resolveSearchProviderOptions.mockReturnValue([ - createSearchProviderOption({ - id: "gemini", - label: "Gemini (Google Search)", - hint: "Requires Google Gemini API key · Google Search grounding", - credentialLabel: "Google Gemini API key", - envVars: ["GEMINI_API_KEY"], - placeholder: "AIza...", - signupUrl: "https://aistudio.google.com/apikey", - credentialPath: "plugins.entries.google.config.webSearch.apiKey", - }), - ]); - queueWizardPrompts({ - select: ["local", "gemini"], - confirm: [true, false], - }); - - await runWebConfigureWizard(); - - expect(mocks.clackText).toHaveBeenCalledWith( - expect.objectContaining({ - message: expect.stringContaining("Google Gemini API key"), - }), - ); - expect(mocks.note).toHaveBeenCalledWith( - expect.stringContaining("Store your Google Gemini API key here or set GEMINI_API_KEY"), - "Web search", - ); - } finally { - if (originalGeminiApiKey === undefined) { - delete process.env.GEMINI_API_KEY; - } else { - process.env.GEMINI_API_KEY = originalGeminiApiKey; - } - } }); it("does not crash when web search providers are unavailable under plugin policy", async () => { @@ -400,7 +328,7 @@ describe("runConfigureWizard", () => { ); }); - it("skips the API key prompt for keyless web search providers", async () => { + it("still supports keyless web search providers through the shared setup flow", async () => { setupBaseWizardState(); mocks.resolveSearchProviderOptions.mockReturnValue([ createSearchProviderOption({ @@ -415,28 +343,19 @@ describe("runConfigureWizard", () => { credentialPath: "", }), ]); - mocks.applySearchProviderSelection.mockImplementation((cfg: OpenClawConfig, provider: string) => - createEnabledWebSearchConfig(provider, { + mocks.setupSearch.mockImplementation(async (cfg: OpenClawConfig) => + createEnabledWebSearchConfig("duckduckgo", { enabled: true, })(cfg), ); queueWizardPrompts({ - select: ["local", "duckduckgo"], + select: ["local"], confirm: [true, false], }); await runWebConfigureWizard(); expect(mocks.clackText).not.toHaveBeenCalled(); - expect(mocks.applySearchProviderSelection).toHaveBeenCalledWith( - expect.objectContaining({ - gateway: expect.objectContaining({ mode: "local" }), - }), - "duckduckgo", - ); - expect(mocks.note).toHaveBeenCalledWith( - expect.stringContaining("works without an API key"), - "Web search", - ); + expect(mocks.setupSearch).toHaveBeenCalledOnce(); }); }); diff --git a/src/commands/configure.wizard.ts b/src/commands/configure.wizard.ts index 7150cee2067..bd47b51cdab 100644 --- a/src/commands/configure.wizard.ts +++ b/src/commands/configure.wizard.ts @@ -158,53 +158,17 @@ async function promptChannelMode(runtime: RuntimeEnv): Promise, ): Promise { const existingSearch = nextConfig.tools?.web?.search; const existingFetch = nextConfig.tools?.web?.fetch; - const { - resolveSearchProviderOptions, - resolveExistingKey, - hasExistingKey, - applySearchKey, - applySearchProviderSelection, - hasKeyInEnv, - } = await import("./onboard-search.js"); + const { resolveSearchProviderOptions, setupSearch } = await import("./onboard-search.js"); const searchProviderOptions = resolveSearchProviderOptions(nextConfig); - const defaultProvider = searchProviderOptions[0]?.id; - - const hasKeyForProvider = (provider: string): boolean => { - const entry = searchProviderOptions.find((e) => e.id === provider); - if (!entry) { - return false; - } - if (entry.requiresCredential === false) { - return true; - } - return hasExistingKey(nextConfig, provider) || hasKeyInEnv(entry); - }; - - const existingProvider = (() => { - const stored = existingSearch?.provider; - if (stored && searchProviderOptions.some((e) => e.id === stored)) { - return stored; - } - return searchProviderOptions.find((e) => hasKeyForProvider(e.id))?.id ?? defaultProvider; - })(); - - note( - [ - "Web search lets your agent look things up online using the `web_search` tool.", - "Choose a provider. Some providers need an API key, and some work key-free.", - "Docs: https://docs.openclaw.ai/tools/web", - ].join("\n"), - "Web search", - ); const enableSearch = guardCancel( await confirm({ message: "Enable web_search?", - initialValue: - existingSearch?.enabled ?? searchProviderOptions.some((e) => hasKeyForProvider(e.id)), + initialValue: existingSearch?.enabled ?? searchProviderOptions.length > 0, }), runtime, ); @@ -230,85 +194,11 @@ async function promptWebToolsConfig( enabled: false, }; } else { - const providerOptions = searchProviderOptions.map((entry) => { - const configured = hasKeyForProvider(entry.id); - return { - value: entry.id, - label: entry.label, - hint: - entry.requiresCredential === false - ? `${entry.hint} · key-free` - : configured - ? `${entry.hint} · configured` - : entry.hint, - }; - }); - - const providerChoice = guardCancel( - await select({ - message: "Choose web search provider", - options: providerOptions, - initialValue: existingProvider, - }), - runtime, - ); - - nextSearch = { ...nextSearch, provider: providerChoice }; - - const entry = searchProviderOptions.find((e) => e.id === providerChoice)!; - const credentialLabel = entry.credentialLabel?.trim() || `${entry.label} API key`; - const existingKey = resolveExistingKey(nextConfig, providerChoice); - const keyConfigured = hasExistingKey(nextConfig, providerChoice); - const envAvailable = entry.envVars.some((k) => Boolean(process.env[k]?.trim())); - const envVarNames = entry.envVars.join(" / "); - const needsCredential = entry.requiresCredential !== false; - - if (!needsCredential) { - workingConfig = applySearchProviderSelection(workingConfig, providerChoice); - nextSearch = { ...workingConfig.tools?.web?.search }; - note( - [ - `${entry.label} works without an API key.`, - "OpenClaw enabled the plugin and selected it as your web_search provider.", - `Docs: ${entry.docsUrl ?? "https://docs.openclaw.ai/tools/web"}`, - ].join("\n"), - "Web search", - ); - } else { - const keyInput = guardCancel( - await text({ - message: keyConfigured - ? envAvailable - ? `${credentialLabel} (leave blank to keep current or use ${envVarNames})` - : `${credentialLabel} (leave blank to keep current)` - : envAvailable - ? `${credentialLabel} (paste it here; leave blank to use ${envVarNames})` - : credentialLabel, - placeholder: keyConfigured ? "Leave blank to keep current" : entry.placeholder, - }), - runtime, - ); - const key = String(keyInput ?? "").trim(); - - if (key || existingKey) { - workingConfig = applySearchKey(workingConfig, providerChoice, (key || existingKey)!); - nextSearch = { ...workingConfig.tools?.web?.search }; - } else if (keyConfigured || envAvailable) { - workingConfig = applySearchProviderSelection(workingConfig, providerChoice); - nextSearch = { ...workingConfig.tools?.web?.search }; - } else { - nextSearch = { ...nextSearch, provider: providerChoice }; - note( - [ - "No key stored yet — web_search won't work until a key is available.", - `Store your ${credentialLabel} here or set ${envVarNames} in the Gateway environment.`, - `Get your API key at: ${entry.signupUrl}`, - "Docs: https://docs.openclaw.ai/tools/web", - ].join("\n"), - "Web search", - ); - } - } + workingConfig = await setupSearch(workingConfig, runtime, prompter); + nextSearch = { + ...workingConfig.tools?.web?.search, + enabled: workingConfig.tools?.web?.search?.provider ? true : existingSearch?.enabled, + }; } } @@ -555,7 +445,7 @@ export async function runConfigureWizard( } if (selected.includes("web")) { - nextConfig = await promptWebToolsConfig(nextConfig, runtime); + nextConfig = await promptWebToolsConfig(nextConfig, runtime, prompter); } if (selected.includes("gateway")) { @@ -608,7 +498,7 @@ export async function runConfigureWizard( } if (choice === "web") { - nextConfig = await promptWebToolsConfig(nextConfig, runtime); + nextConfig = await promptWebToolsConfig(nextConfig, runtime, prompter); await persistConfig(); } diff --git a/src/commands/doctor-auth.ts b/src/commands/doctor-auth.ts index c8335267071..fc2ad6f840a 100644 --- a/src/commands/doctor-auth.ts +++ b/src/commands/doctor-auth.ts @@ -5,13 +5,12 @@ import { } from "../agents/auth-health.js"; import { type AuthCredentialReasonCode, - CLAUDE_CLI_PROFILE_ID, - CODEX_CLI_PROFILE_ID, ensureAuthProfileStore, repairOAuthProfileIdMismatch, resolveApiKeyForProfile, resolveProfileUnusableUntilForDisplay, } from "../agents/auth-profiles.js"; +import { formatAuthDoctorHint } from "../agents/auth-profiles/doctor.js"; import { updateAuthProfileStoreWithLock } from "../agents/auth-profiles/store.js"; import { formatCliCommand } from "../cli/command-format.js"; import type { OpenClawConfig } from "../config/config.js"; @@ -234,29 +233,36 @@ export function resolveUnusableProfileHint(params: { return "Wait for cooldown or switch provider."; } -function formatAuthIssueHint(issue: AuthIssue): string | null { +export async function resolveAuthIssueHint( + issue: AuthIssue, + cfg: OpenClawConfig, + store: ReturnType, +): Promise { if (issue.reasonCode === "invalid_expires") { return "Invalid token expires metadata. Set a future Unix ms timestamp or remove expires."; } - if (issue.provider === "anthropic" && issue.profileId === CLAUDE_CLI_PROFILE_ID) { - return `Deprecated profile. ${buildProviderAuthRecoveryHint({ - provider: "anthropic", - })}`; - } - if (issue.provider === "openai-codex" && issue.profileId === CODEX_CLI_PROFILE_ID) { - return `Deprecated profile. ${buildProviderAuthRecoveryHint({ - provider: "openai-codex", - })}`; + const providerHint = await formatAuthDoctorHint({ + cfg, + store, + provider: issue.provider, + profileId: issue.profileId, + }); + if (providerHint.trim()) { + return providerHint; } return buildProviderAuthRecoveryHint({ provider: issue.provider, }).replace(/^Run /, "Re-auth via "); } -function formatAuthIssueLine(issue: AuthIssue): string { +async function formatAuthIssueLine( + issue: AuthIssue, + cfg: OpenClawConfig, + store: ReturnType, +): Promise { const remaining = issue.remainingMs !== undefined ? ` (${formatRemainingShort(issue.remainingMs)})` : ""; - const hint = formatAuthIssueHint(issue); + const hint = await resolveAuthIssueHint(issue, cfg, store); const reason = issue.reasonCode ? ` [${issue.reasonCode}]` : ""; return `- ${issue.profileId}: ${issue.status}${reason}${remaining}${hint ? ` — ${hint}` : ""}`; } @@ -352,19 +358,21 @@ export async function noteAuthProfileHealth(params: { } if (issues.length > 0) { - note( - issues - .map((issue) => - formatAuthIssueLine({ + const issueLines = await Promise.all( + issues.map((issue) => + formatAuthIssueLine( + { profileId: issue.profileId, provider: issue.provider, status: issue.status, reasonCode: issue.reasonCode, remainingMs: issue.remainingMs, - }), - ) - .join("\n"), - "Model auth", + }, + params.cfg, + store, + ), + ), ); + note(issueLines.join("\n"), "Model auth"); } } diff --git a/src/commands/doctor-memory-search.test.ts b/src/commands/doctor-memory-search.test.ts index a816bb454b4..7c3c48aec45 100644 --- a/src/commands/doctor-memory-search.test.ts +++ b/src/commands/doctor-memory-search.test.ts @@ -291,6 +291,37 @@ describe("noteMemorySearchHealth", () => { const providersChecked = providerCalls.map(([arg]) => arg.provider); expect(providersChecked).toEqual(["openai", "google", "voyage", "mistral"]); }); + + it("uses runtime-derived env var hints for explicit providers", async () => { + resolveMemorySearchConfig.mockReturnValue({ + provider: "gemini", + local: {}, + remote: {}, + }); + + await noteMemorySearchHealth(cfg); + + const message = String(note.mock.calls[0]?.[0] ?? ""); + expect(message).toContain("GEMINI_API_KEY"); + expect(message).toContain('provider is set to "gemini"'); + }); + + it("uses runtime-derived env var hints in auto mode", async () => { + resolveMemorySearchConfig.mockReturnValue({ + provider: "auto", + local: {}, + remote: {}, + }); + + await noteMemorySearchHealth(cfg); + + const message = String(note.mock.calls[0]?.[0] ?? ""); + expect(message).toContain("OPENAI_API_KEY"); + expect(message).toContain("GEMINI_API_KEY"); + expect(message).toContain("GOOGLE_API_KEY"); + expect(message).toContain("VOYAGE_API_KEY"); + expect(message).toContain("MISTRAL_API_KEY"); + }); }); describe("detectLegacyWorkspaceDirs", () => { diff --git a/src/commands/doctor-memory-search.ts b/src/commands/doctor-memory-search.ts index 090fc942b5c..80fcd0cc3f8 100644 --- a/src/commands/doctor-memory-search.ts +++ b/src/commands/doctor-memory-search.ts @@ -4,6 +4,10 @@ import { resolveMemorySearchConfig } from "../agents/memory-search.js"; import { resolveApiKeyForProvider } from "../agents/model-auth.js"; import { formatCliCommand } from "../cli/command-format.js"; import type { OpenClawConfig } from "../config/config.js"; +import { + getBuiltinMemoryEmbeddingProviderDoctorMetadata, + listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata, +} from "../plugin-sdk/memory-core-engine-runtime.js"; import { DEFAULT_LOCAL_MODEL } from "../plugin-sdk/memory-core-host-engine-embeddings.js"; import { hasConfiguredMemorySecretInput } from "../plugin-sdk/memory-core-host-secret.js"; import { resolveActiveMemoryBackendConfig } from "../plugins/memory-runtime.js"; @@ -99,7 +103,7 @@ export async function noteMemorySearchHealth( return; } const gatewayProbeWarning = buildGatewayProbeWarning(opts?.gatewayMemoryProbe); - const envVar = providerEnvVar(resolved.provider); + const envVar = resolvePrimaryMemoryProviderEnvVar(resolved.provider); note( [ `Memory search provider is set to "${resolved.provider}" but no API key was found.`, @@ -122,8 +126,11 @@ export async function noteMemorySearchHealth( if (hasLocalEmbeddings(resolved.local)) { return; } - for (const provider of ["openai", "gemini", "voyage", "mistral"] as const) { - if (hasRemoteApiKey || (await hasApiKeyForProvider(provider, cfg, agentDir))) { + const autoSelectProviders = listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata().filter( + (provider) => provider.transport === "remote", + ); + for (const provider of autoSelectProviders) { + if (hasRemoteApiKey || (await hasApiKeyForProvider(provider.authProviderId, cfg, agentDir))) { return; } } @@ -148,7 +155,7 @@ export async function noteMemorySearchHealth( gatewayProbeWarning ? gatewayProbeWarning : null, "", "Fix (pick one):", - "- Set OPENAI_API_KEY, GEMINI_API_KEY, VOYAGE_API_KEY, or MISTRAL_API_KEY in your environment", + `- Set ${formatMemoryProviderEnvVarList(autoSelectProviders)} in your environment`, `- Configure credentials: ${formatCliCommand("openclaw configure --section model")}`, `- For local embeddings: configure agents.defaults.memorySearch.provider and local model path`, `- To disable: ${formatCliCommand("openclaw config set agents.defaults.memorySearch.enabled false")}`, @@ -195,27 +202,26 @@ async function hasApiKeyForProvider( cfg: OpenClawConfig, agentDir: string, ): Promise { - // Map embedding provider names to model-auth provider names - const authProvider = provider === "gemini" ? "google" : provider; + const metadata = getBuiltinMemoryEmbeddingProviderDoctorMetadata(provider); try { - await resolveApiKeyForProvider({ provider: authProvider, cfg, agentDir }); + await resolveApiKeyForProvider({ + provider: metadata?.authProviderId ?? provider, + cfg, + agentDir, + }); return true; } catch { return false; } } -function providerEnvVar(provider: string): string { - switch (provider) { - case "openai": - return "OPENAI_API_KEY"; - case "gemini": - return "GEMINI_API_KEY"; - case "voyage": - return "VOYAGE_API_KEY"; - default: - return `${provider.toUpperCase()}_API_KEY`; - } +function resolvePrimaryMemoryProviderEnvVar(provider: string): string { + const metadata = getBuiltinMemoryEmbeddingProviderDoctorMetadata(provider); + return metadata?.envVars[0] ?? `${provider.toUpperCase()}_API_KEY`; +} + +function formatMemoryProviderEnvVarList(providers: Array<{ envVars: string[] }>): string { + return [...new Set(providers.flatMap((provider) => provider.envVars).filter(Boolean))].join(", "); } function buildGatewayProbeWarning( 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 deleted file mode 100644 index 48aad9dd936..00000000000 --- a/src/commands/onboard-non-interactive/local/auth-choice.api-key-providers.ts +++ /dev/null @@ -1,67 +0,0 @@ -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/commands/onboard-non-interactive/local/auth-choice.test.ts b/src/commands/onboard-non-interactive/local/auth-choice.test.ts index e3ad83a24bf..918995bfb9b 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.test.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.test.ts @@ -12,6 +12,11 @@ vi.mock("../api-keys.js", () => ({ resolveNonInteractiveApiKey, })); +const resolveManifestDeprecatedProviderAuthChoice = vi.hoisted(() => vi.fn(() => undefined)); +vi.mock("../../../plugins/provider-auth-choices.js", () => ({ + resolveManifestDeprecatedProviderAuthChoice, +})); + beforeEach(() => { vi.clearAllMocks(); }); @@ -42,4 +47,27 @@ describe("applyNonInteractiveAuthChoice", () => { expect(result).toBe(resolvedConfig); expect(applyNonInteractivePluginProviderChoice).toHaveBeenCalledOnce(); }); + + it("fails with manifest-owned replacement guidance for deprecated auth choices", async () => { + const runtime = createRuntime(); + const nextConfig = { agents: { defaults: {} } } as OpenClawConfig; + resolveManifestDeprecatedProviderAuthChoice.mockReturnValueOnce({ + choiceId: "minimax-global-api", + } as never); + + const result = await applyNonInteractiveAuthChoice({ + nextConfig, + authChoice: "minimax", + opts: {} as never, + runtime: runtime as never, + baseConfig: nextConfig, + }); + + expect(result).toBeNull(); + expect(runtime.error).toHaveBeenCalledWith( + '"minimax" is no longer supported. Use --auth-choice minimax-global-api instead.', + ); + expect(runtime.exit).toHaveBeenCalledWith(1); + expect(applyNonInteractivePluginProviderChoice).toHaveBeenCalledOnce(); + }); }); diff --git a/src/commands/onboard-non-interactive/local/auth-choice.ts b/src/commands/onboard-non-interactive/local/auth-choice.ts index a63556b87e9..981255b1b86 100644 --- a/src/commands/onboard-non-interactive/local/auth-choice.ts +++ b/src/commands/onboard-non-interactive/local/auth-choice.ts @@ -1,6 +1,7 @@ import type { ApiKeyCredential } from "../../../agents/auth-profiles/types.js"; import type { OpenClawConfig } from "../../../config/config.js"; import type { SecretInput } from "../../../config/types.secrets.js"; +import { resolveManifestDeprecatedProviderAuthChoice } from "../../../plugins/provider-auth-choices.js"; import type { RuntimeEnv } from "../../../runtime.js"; import { resolveDefaultSecretProviderAlias } from "../../../secrets/ref-contract.js"; import { @@ -17,7 +18,6 @@ import { } from "../../onboard-custom.js"; import type { AuthChoice, OnboardOptions } from "../../onboard-types.js"; import { resolveNonInteractiveApiKey } from "../api-keys.js"; -import { applySimpleNonInteractiveApiKeyChoice } from "./auth-choice.api-key-providers.js"; import { applyNonInteractivePluginProviderChoice } from "./auth-choice.plugin-providers.js"; type ResolvedNonInteractiveApiKey = NonNullable< @@ -45,9 +45,6 @@ export async function applyNonInteractiveAuthChoice(params: { runtime.exit(1); return null; } - const apiKeyStorageOptions = requestedSecretInputMode - ? { secretInputMode: requestedSecretInputMode } - : undefined; const toStoredSecretInput = (resolved: ResolvedNonInteractiveApiKey): SecretInput | null => { const storePlaintextSecret = requestedSecretInputMode !== "ref"; // pragma: allowlist secret if (storePlaintextSecret) { @@ -79,20 +76,6 @@ export async function applyNonInteractiveAuthChoice(params: { ...input, secretInputMode: requestedSecretInputMode, }); - const maybeSetResolvedApiKey = async ( - resolved: ResolvedNonInteractiveApiKey, - setter: (value: SecretInput) => Promise | void, - ): Promise => { - if (resolved.source === "profile") { - return true; - } - const stored = toStoredSecretInput(resolved); - if (!stored) { - return false; - } - await setter(stored); - return true; - }; const toApiKeyCredential = (params: { provider: string; resolved: ResolvedNonInteractiveApiKey; @@ -168,32 +151,13 @@ export async function applyNonInteractiveAuthChoice(params: { return pluginProviderChoice; } - const simpleApiKeyChoice = await applySimpleNonInteractiveApiKeyChoice({ - authChoice, - nextConfig, - baseConfig, - opts, - runtime, - apiKeyStorageOptions, - resolveApiKey, - maybeSetResolvedApiKey, + const deprecatedChoice = resolveManifestDeprecatedProviderAuthChoice(authChoice as string, { + config: nextConfig, + env: process.env, }); - if (simpleApiKeyChoice !== undefined) { - return simpleApiKeyChoice; - } - // Legacy aliases: these choice values were removed; fail with an actionable message so - // existing CI automation gets a clear error instead of silently exiting 0 with no auth. - const REMOVED_MINIMAX_CHOICES: Record = { - minimax: "minimax-global-api", - "minimax-api": "minimax-global-api", - "minimax-cloud": "minimax-global-api", - "minimax-api-lightning": "minimax-global-api", - "minimax-api-key-cn": "minimax-cn-api", - }; - if (Object.prototype.hasOwnProperty.call(REMOVED_MINIMAX_CHOICES, authChoice as string)) { - const replacement = REMOVED_MINIMAX_CHOICES[authChoice as string]; + if (deprecatedChoice) { runtime.error( - `"${authChoice as string}" is no longer supported. Use --auth-choice ${replacement} instead.`, + `"${authChoice as string}" is no longer supported. Use --auth-choice ${deprecatedChoice.choiceId} instead.`, ); runtime.exit(1); return null; diff --git a/src/commands/onboard-search.providers.test.ts b/src/commands/onboard-search.providers.test.ts index c4094ceebd8..6677871f915 100644 --- a/src/commands/onboard-search.providers.test.ts +++ b/src/commands/onboard-search.providers.test.ts @@ -59,6 +59,7 @@ function createBundledFirecrawlEntry(): PluginWebSearchProviderEntry { pluginId: "firecrawl", label: "Firecrawl Search", hint: "Structured results", + onboardingScopes: ["text-inference"], envVars: ["FIRECRAWL_API_KEY"], placeholder: "fc-...", signupUrl: "https://example.com/firecrawl", diff --git a/src/config/plugin-auto-enable.test.ts b/src/config/plugin-auto-enable.test.ts index 353f4163ca6..80903d7fff6 100644 --- a/src/config/plugin-auto-enable.test.ts +++ b/src/config/plugin-auto-enable.test.ts @@ -43,6 +43,7 @@ function makeRegistry( plugins: Array<{ id: string; channels: string[]; + autoEnableWhenConfiguredProviders?: string[]; channelConfigs?: Record; preferOver?: string[] }>; }>, ): PluginManifestRegistry { @@ -50,6 +51,7 @@ function makeRegistry( plugins: plugins.map((p) => ({ id: p.id, channels: p.channels, + autoEnableWhenConfiguredProviders: p.autoEnableWhenConfiguredProviders, channelConfigs: p.channelConfigs, providers: [], cliBackends: [], @@ -376,6 +378,50 @@ describe("applyPluginAutoEnable", () => { expect(result.config.plugins?.entries?.["minimax-portal-auth"]).toBeUndefined(); }); + it("does not auto-enable unrelated provider plugins just because auth profiles exist", () => { + const result = applyPluginAutoEnable({ + config: { + auth: { + profiles: { + "openai:default": { + provider: "openai", + mode: "api_key", + }, + }, + }, + }, + env: {}, + }); + + expect(result.config.plugins?.entries?.openai).toBeUndefined(); + expect(result.changes).toEqual([]); + }); + + it("uses manifest-owned provider auto-enable metadata for third-party plugins", () => { + const result = applyPluginAutoEnable({ + config: { + auth: { + profiles: { + "acme-oauth:default": { + provider: "acme-oauth", + mode: "oauth", + }, + }, + }, + }, + env: {}, + manifestRegistry: makeRegistry([ + { + id: "acme", + channels: [], + autoEnableWhenConfiguredProviders: ["acme-oauth"], + }, + ]), + }); + + expect(result.config.plugins?.entries?.acme?.enabled).toBe(true); + }); + it("auto-enables acpx plugin when ACP is configured", () => { const result = applyPluginAutoEnable({ config: { diff --git a/src/config/plugin-auto-enable.ts b/src/config/plugin-auto-enable.ts index a4e6036fb08..7f275484d0b 100644 --- a/src/config/plugin-auto-enable.ts +++ b/src/config/plugin-auto-enable.ts @@ -6,6 +6,7 @@ import { listChatChannels, normalizeChatChannelId, } from "../channels/registry.js"; +import { BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS } from "../plugins/bundled-capability-metadata.js"; import { loadPluginManifestRegistry, type PluginManifestRegistry, @@ -30,13 +31,22 @@ const EMPTY_PLUGIN_MANIFEST_REGISTRY: PluginManifestRegistry = { diagnostics: [], }; -const PROVIDER_PLUGIN_IDS: Array<{ pluginId: string; providerId: string }> = [ - { pluginId: "google", providerId: "google-gemini-cli" }, - { pluginId: "copilot-proxy", providerId: "copilot-proxy" }, - { pluginId: "minimax", providerId: "minimax-portal" }, -]; const ENV_CATALOG_PATHS = ["OPENCLAW_PLUGIN_CATALOG_PATHS", "OPENCLAW_MPM_CATALOG_PATHS"]; +function resolveAutoEnableProviderPluginIds( + registry: PluginManifestRegistry, +): Readonly> { + const entries = new Map(Object.entries(BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS)); + for (const plugin of registry.plugins) { + for (const providerId of plugin.autoEnableWhenConfiguredProviders ?? []) { + if (!entries.has(providerId)) { + entries.set(providerId, plugin.id); + } + } + } + return Object.fromEntries(entries); +} + function collectModelRefs(cfg: OpenClawConfig): string[] { const refs: string[] = []; const pushModelRef = (value: unknown) => { @@ -286,11 +296,13 @@ function resolveConfiguredPlugins( } } - for (const mapping of PROVIDER_PLUGIN_IDS) { - if (isProviderConfigured(cfg, mapping.providerId)) { + for (const [providerId, pluginId] of Object.entries( + resolveAutoEnableProviderPluginIds(registry), + )) { + if (isProviderConfigured(cfg, providerId)) { changes.push({ - pluginId: mapping.pluginId, - reason: `${mapping.providerId} auth configured`, + pluginId, + reason: `${providerId} auth configured`, }); } } diff --git a/src/flows/search-setup.ts b/src/flows/search-setup.ts index 0647e84e593..2e2c3cafdc6 100644 --- a/src/flows/search-setup.ts +++ b/src/flows/search-setup.ts @@ -47,19 +47,15 @@ function resolveSearchProviderCredentialLabel( return entry.credentialLabel?.trim() || `${entry.label} API key`; } -const DEFAULT_ONBOARD_SEARCH_PROVIDER_IDS = new Set([ - "brave", - "firecrawl", - "gemini", - "grok", - "kimi", - "perplexity", - "tavily", -]); - export const SEARCH_PROVIDER_OPTIONS: readonly PluginWebSearchProviderEntry[] = resolveSearchProviderSetupContributions().map((contribution) => contribution.provider); +function showsSearchProviderInSetup( + entry: Pick, +): boolean { + return entry.onboardingScopes?.includes("text-inference") ?? false; +} + function canRepairBundledProviderSelection( config: OpenClawConfig, provider: Pick, @@ -107,7 +103,7 @@ export function resolveSearchProviderSetupContributions( if (!config) { return sortFlowContributionsByLabel( sortWebSearchProviders(listBundledWebSearchProviders()) - .filter((entry) => DEFAULT_ONBOARD_SEARCH_PROVIDER_IDS.has(entry.id)) + .filter(showsSearchProviderInSetup) .map((provider) => buildSearchProviderSetupContribution({ provider, source: "bundled" })), ); } diff --git a/src/infra/update-global.test.ts b/src/infra/update-global.test.ts index 7297ba1169f..bfd01260666 100644 --- a/src/infra/update-global.test.ts +++ b/src/infra/update-global.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; -import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../extensions/public-artifacts.js"; +import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../plugins/public-artifacts.js"; import { captureEnv } from "../test-utils/env.js"; import { canResolveRegistryVersionForPackageTarget, diff --git a/src/infra/update-runner.test.ts b/src/infra/update-runner.test.ts index 1a9709551ea..be6fa9d8c94 100644 --- a/src/infra/update-runner.test.ts +++ b/src/infra/update-runner.test.ts @@ -2,7 +2,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../extensions/public-artifacts.js"; +import { BUNDLED_RUNTIME_SIDECAR_PATHS } from "../plugins/public-artifacts.js"; import { withEnvAsync } from "../test-utils/env.js"; import { pathExists } from "../utils.js"; import { resolveStableNodePath } from "./stable-node-path.js"; diff --git a/src/library.ts b/src/library.ts index 0e110eb1790..7a6590eadc1 100644 --- a/src/library.ts +++ b/src/library.ts @@ -53,9 +53,7 @@ export const ensureBinary: BinariesRuntimeModule["ensureBinary"] = async (...arg (await loadBinariesRuntime()).ensureBinary(...args); export const runExec: ExecRuntimeModule["runExec"] = async (...args) => (await loadExecRuntime()).runExec(...args); -export const runCommandWithTimeout: ExecRuntimeModule["runCommandWithTimeout"] = async ( - ...args -) => +export const runCommandWithTimeout: ExecRuntimeModule["runCommandWithTimeout"] = async (...args) => (await loadExecRuntime()).runCommandWithTimeout(...args); export const monitorWebChannel: WhatsAppRuntimeModule["monitorWebChannel"] = async (...args) => (await loadWhatsAppRuntime()).monitorWebChannel(...args); diff --git a/src/plugin-sdk/memory-core-engine-runtime.ts b/src/plugin-sdk/memory-core-engine-runtime.ts index 41b137f97ca..c546f9b463a 100644 --- a/src/plugin-sdk/memory-core-engine-runtime.ts +++ b/src/plugin-sdk/memory-core-engine-runtime.ts @@ -2,6 +2,9 @@ // Keep extension-owned engine exports isolated behind a dedicated SDK subpath. export { + getBuiltinMemoryEmbeddingProviderDoctorMetadata, getMemorySearchManager, + listBuiltinAutoSelectMemoryEmbeddingProviderDoctorMetadata, MemoryIndexManager, } from "../../extensions/memory-core/runtime-api.js"; +export type { BuiltinMemoryEmbeddingProviderDoctorMetadata } from "../../extensions/memory-core/runtime-api.js"; diff --git a/src/plugin-sdk/provider-env-vars.ts b/src/plugin-sdk/provider-env-vars.ts index fb4d0271bf1..039ec220c62 100644 --- a/src/plugin-sdk/provider-env-vars.ts +++ b/src/plugin-sdk/provider-env-vars.ts @@ -1,6 +1,7 @@ // Public provider auth environment variable helpers for plugin runtimes. export { + getProviderEnvVars, listKnownProviderAuthEnvVarNames, omitEnvKeysCaseInsensitive, } from "../secrets/provider-env-vars.js"; diff --git a/src/plugins/bundled-capability-metadata.ts b/src/plugins/bundled-capability-metadata.ts index 982196e8915..42ad2a4a973 100644 --- a/src/plugins/bundled-capability-metadata.ts +++ b/src/plugins/bundled-capability-metadata.ts @@ -90,3 +90,28 @@ export const BUNDLED_WEB_SEARCH_PROVIDER_PLUGIN_IDS = Object.fromEntries( entry.webSearchProviderIds.map((providerId) => [providerId, entry.pluginId] as const), ).toSorted(([left], [right]) => left.localeCompare(right)), ) as Readonly>; + +export const BUNDLED_PROVIDER_PLUGIN_ID_ALIASES = Object.fromEntries( + BUNDLED_PLUGIN_CONTRACT_SNAPSHOTS.flatMap((entry) => + entry.providerIds + .filter((providerId) => providerId !== entry.pluginId) + .map((providerId) => [providerId, entry.pluginId] as const), + ).toSorted(([left], [right]) => left.localeCompare(right)), +) as Readonly>; + +export const BUNDLED_LEGACY_PLUGIN_ID_ALIASES = Object.fromEntries( + BUNDLED_PLUGIN_METADATA.flatMap(({ manifest }) => + (manifest.legacyPluginIds ?? []).map( + (legacyPluginId) => [legacyPluginId, manifest.id] as const, + ), + ).toSorted(([left], [right]) => left.localeCompare(right)), +) as Readonly>; + +export const BUNDLED_AUTO_ENABLE_PROVIDER_PLUGIN_IDS = Object.fromEntries( + BUNDLED_PLUGIN_METADATA.flatMap(({ manifest }) => + (manifest.autoEnableWhenConfiguredProviders ?? []).map((providerId) => [ + providerId, + manifest.id, + ]), + ).toSorted(([left], [right]) => left.localeCompare(right)), +) as Readonly>; diff --git a/src/plugins/bundled-plugin-metadata.generated.ts b/src/plugins/bundled-plugin-metadata.generated.ts index eecc6addcb6..9449f267283 100644 --- a/src/plugins/bundled-plugin-metadata.generated.ts +++ b/src/plugins/bundled-plugin-metadata.generated.ts @@ -8,6 +8,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/acpx", packageVersion: "2026.3.26", packageDescription: "OpenClaw ACP runtime backend via acpx", @@ -149,6 +151,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["amazon-bedrock"], }, }, @@ -159,6 +162,12 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "cli-backend.js", + "cli-migration.js", + "cli-shared.js", + "media-understanding-provider.js", + ], packageName: "@openclaw/anthropic-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Anthropic provider plugin", @@ -172,6 +181,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["anthropic"], cliBackends: ["claude-cli"], providerAuthEnvVars: { @@ -228,6 +238,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "channel-config-api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/bluebubbles", packageVersion: "2026.3.26", packageDescription: "OpenClaw BlueBubbles channel plugin", @@ -777,6 +789,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["web-search-provider.js"], packageName: "@openclaw/brave-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw Brave plugin", @@ -831,6 +844,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["browser-runtime-api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/browser-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw browser tool plugin", @@ -854,6 +869,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["provider-catalog.js"], packageName: "@openclaw/byteplus-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw BytePlus provider plugin", @@ -867,6 +883,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["byteplus", "byteplus-plan"], providerAuthEnvVars: { byteplus: ["BYTEPLUS_API_KEY"], @@ -895,6 +912,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/chutes-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Chutes.ai provider plugin", @@ -948,6 +966,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js"], packageName: "@openclaw/cloudflare-ai-gateway-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Cloudflare AI Gateway provider plugin", @@ -961,6 +980,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["cloudflare-ai-gateway"], providerAuthEnvVars: { "cloudflare-ai-gateway": ["CLOUDFLARE_AI_GATEWAY_API_KEY"], @@ -990,6 +1010,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/copilot-proxy", packageVersion: "2026.3.26", packageDescription: "OpenClaw Copilot Proxy provider plugin", @@ -1003,6 +1025,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, + autoEnableWhenConfiguredProviders: ["copilot-proxy"], providers: ["copilot-proxy"], providerAuthChoices: [ { @@ -1025,6 +1049,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["audio.js", "media-understanding-provider.js"], packageName: "@openclaw/deepgram-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Deepgram media-understanding provider", @@ -1050,6 +1075,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/deepseek-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw DeepSeek provider plugin", @@ -1063,6 +1089,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["deepseek"], providerAuthEnvVars: { deepseek: ["DEEPSEEK_API_KEY"], @@ -1091,6 +1118,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["api.js"], packageName: "@openclaw/diagnostics-otel", packageVersion: "2026.3.26", packageDescription: "OpenClaw diagnostics OpenTelemetry exporter", @@ -1113,6 +1141,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["api.js"], packageName: "@openclaw/diffs", packageVersion: "2026.3.26", packageDescription: "OpenClaw diff viewer plugin", @@ -1313,6 +1342,15 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: [ + "action-runtime-api.js", + "api.js", + "channel-config-api.js", + "runtime-api.js", + "session-key-api.js", + "timeouts.js", + ], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/discord", packageVersion: "2026.3.26", packageDescription: "OpenClaw Discord channel plugin", @@ -3977,6 +4015,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["web-search-provider.js"], packageName: "@openclaw/duckduckgo-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw DuckDuckGo plugin", @@ -4026,6 +4065,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["speech-provider.js", "tts.js"], packageName: "@openclaw/elevenlabs-speech", packageVersion: "2026.3.26", packageDescription: "OpenClaw ElevenLabs speech plugin", @@ -4051,6 +4091,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["web-search-provider.js"], packageName: "@openclaw/exa-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw Exa plugin", @@ -4097,6 +4138,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["image-generation-provider.js", "onboard.js"], packageName: "@openclaw/fal-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw fal provider plugin", @@ -4110,6 +4152,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["fal"], providerAuthEnvVars: { fal: ["FAL_KEY"], @@ -4146,6 +4189,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js", "setup-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/feishu", packageVersion: "2026.3.26", packageDescription: "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)", @@ -5301,6 +5346,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["web-search-provider.js"], packageName: "@openclaw/firecrawl-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw Firecrawl plugin", @@ -5355,6 +5401,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["login.js", "models-defaults.js", "models.js", "token.js", "usage.js"], packageName: "@openclaw/github-copilot-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw GitHub Copilot provider plugin", @@ -5368,6 +5415,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["github-copilot"], providerAuthEnvVars: { "github-copilot": ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"], @@ -5393,6 +5441,24 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "cli-backend.js", + "gemini-cli-provider.js", + "image-generation-provider.js", + "media-understanding-provider.js", + "oauth.credentials.js", + "oauth.flow.js", + "oauth.http.js", + "oauth.js", + "oauth.project.js", + "oauth.runtime.js", + "oauth.shared.js", + "oauth.token.js", + "provider-models.js", + "runtime-api.js", + "web-search-provider.js", + ], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/google-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw Google plugin", @@ -5419,6 +5485,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ }, }, }, + enabledByDefault: true, + autoEnableWhenConfiguredProviders: ["google-gemini-cli"], providers: ["google", "google-gemini-cli"], cliBackends: ["google-gemini-cli"], providerAuthEnvVars: { @@ -5479,6 +5547,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "channel-config-api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/googlechat", packageVersion: "2026.3.26", packageDescription: "OpenClaw Google Chat channel plugin", @@ -6298,6 +6368,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["media-understanding-provider.js"], packageName: "@openclaw/groq-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Groq media-understanding provider", @@ -6323,6 +6394,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/huggingface-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Hugging Face provider plugin", @@ -6336,6 +6408,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["huggingface"], providerAuthEnvVars: { huggingface: ["HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"], @@ -6369,6 +6442,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "channel-config-api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/imessage", packageVersion: "2026.3.26", packageDescription: "OpenClaw iMessage channel plugin", @@ -6992,6 +7067,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "channel-config-api.js"], packageName: "@openclaw/irc", packageVersion: "2026.3.26", packageDescription: "OpenClaw IRC channel plugin", @@ -7653,6 +7729,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js", "shared.js"], packageName: "@openclaw/kilocode-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Kilo Gateway provider plugin", @@ -7666,6 +7743,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["kilocode"], providerAuthEnvVars: { kilocode: ["KILOCODE_API_KEY"], @@ -7695,6 +7773,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/kimi-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Kimi provider plugin", @@ -7708,6 +7787,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["kimi", "kimi-coding"], providerAuthEnvVars: { kimi: ["KIMI_API_KEY", "KIMICODE_API_KEY"], @@ -7741,6 +7821,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js", "setup-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/line", packageVersion: "2026.3.26", packageDescription: "OpenClaw LINE channel plugin", @@ -8017,6 +8099,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/litellm-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw LiteLLM provider plugin", @@ -8030,6 +8113,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["litellm"], providerAuthEnvVars: { litellm: ["LITELLM_API_KEY"], @@ -8059,6 +8143,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["api.js"], packageName: "@openclaw/llm-task", packageVersion: "2026.3.26", packageDescription: "OpenClaw JSON-only LLM task plugin", @@ -8106,6 +8191,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/lobster", packageVersion: "2026.3.26", packageDescription: "Lobster workflow tool plugin (typed pipelines + resumable approvals)", @@ -8134,6 +8221,14 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: [ + "api.js", + "helper-api.js", + "legacy-crypto-inspector.js", + "runtime-api.js", + "thread-bindings-runtime.js", + ], + runtimeSidecarArtifacts: ["helper-api.js", "runtime-api.js", "thread-bindings-runtime.js"], packageName: "@openclaw/matrix", packageVersion: "2026.3.26", packageDescription: "OpenClaw Matrix channel plugin", @@ -8631,6 +8726,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/mattermost", packageVersion: "2026.3.26", packageDescription: "OpenClaw Mattermost channel plugin", @@ -9246,6 +9343,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/memory-core", packageVersion: "2026.3.26", packageDescription: "OpenClaw core memory search plugin", @@ -9269,6 +9368,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["api.js", "config.js", "lancedb-runtime.js"], packageName: "@openclaw/memory-lancedb", packageVersion: "2026.3.26", packageDescription: "OpenClaw LanceDB-backed long-term memory plugin with auto-recall/capture", @@ -9377,6 +9477,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["speech-provider.js", "tts.js"], packageName: "@openclaw/microsoft-speech", packageVersion: "2026.3.26", packageDescription: "OpenClaw Microsoft speech plugin", @@ -9402,6 +9503,15 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "auth.js", + "cli.js", + "onboard.js", + "provider.js", + "runtime.js", + "shared-runtime.js", + "shared.js", + ], packageName: "@openclaw/microsoft-foundry", packageVersion: "2026.3.26", packageDescription: "OpenClaw Microsoft Foundry provider plugin", @@ -9451,6 +9561,15 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "image-generation-provider.js", + "media-understanding-provider.js", + "model-definitions.js", + "oauth.js", + "oauth.runtime.js", + "onboard.js", + "provider-catalog.js", + ], packageName: "@openclaw/minimax-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw MiniMax provider and OAuth plugin", @@ -9464,6 +9583,9 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, + legacyPluginIds: ["minimax-portal-auth"], + autoEnableWhenConfiguredProviders: ["minimax-portal"], providers: ["minimax", "minimax-portal"], providerAuthEnvVars: { minimax: ["MINIMAX_API_KEY"], @@ -9484,6 +9606,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ provider: "minimax", method: "api-global", choiceId: "minimax-global-api", + deprecatedChoiceIds: ["minimax", "minimax-api", "minimax-cloud", "minimax-api-lightning"], choiceLabel: "MiniMax API key (Global)", choiceHint: "Global endpoint - api.minimax.io", groupId: "minimax", @@ -9508,6 +9631,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ provider: "minimax", method: "api-cn", choiceId: "minimax-cn-api", + deprecatedChoiceIds: ["minimax-api-key-cn"], choiceLabel: "MiniMax API key (CN)", choiceHint: "CN endpoint - api.minimaxi.com", groupId: "minimax", @@ -9532,6 +9656,12 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "media-understanding-provider.js", + "model-definitions.js", + "onboard.js", + "provider-catalog.js", + ], packageName: "@openclaw/mistral-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Mistral provider plugin", @@ -9545,6 +9675,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["mistral"], providerAuthEnvVars: { mistral: ["MISTRAL_API_KEY"], @@ -9576,6 +9707,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["model-definitions.js", "onboard.js", "provider-catalog.js"], packageName: "@openclaw/modelstudio-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Model Studio provider plugin", @@ -9589,6 +9721,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["modelstudio"], providerAuthEnvVars: { modelstudio: ["MODELSTUDIO_API_KEY"], @@ -9660,6 +9793,12 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "media-understanding-provider.js", + "onboard.js", + "provider-catalog.js", + "web-search-provider.js", + ], packageName: "@openclaw/moonshot-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Moonshot provider plugin", @@ -9689,6 +9828,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ }, }, }, + enabledByDefault: true, providers: ["moonshot"], providerAuthEnvVars: { moonshot: ["MOONSHOT_API_KEY"], @@ -9753,6 +9893,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "channel-config-api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/msteams", packageVersion: "2026.3.26", packageDescription: "OpenClaw Microsoft Teams channel plugin", @@ -10236,6 +10378,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/nextcloud-talk", packageVersion: "2026.3.26", packageDescription: "OpenClaw Nextcloud Talk channel plugin", @@ -10957,6 +11101,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js", "setup-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/nostr", packageVersion: "2026.3.26", packageDescription: "OpenClaw Nostr channel plugin for NIP-04 encrypted DMs", @@ -11091,6 +11237,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["provider-catalog.js"], packageName: "@openclaw/nvidia-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw NVIDIA provider plugin", @@ -11104,6 +11251,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["nvidia"], providerAuthEnvVars: { nvidia: ["NVIDIA_API_KEY"], @@ -11117,6 +11265,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/ollama-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Ollama provider plugin", @@ -11130,6 +11280,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["ollama"], providerAuthEnvVars: { ollama: ["OLLAMA_API_KEY"], @@ -11155,6 +11306,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/open-prose", packageVersion: "2026.3.26", packageDescription: "OpenProse VM skill pack plugin (slash command + telemetry).", @@ -11180,6 +11333,19 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "cli-backend.js", + "image-generation-provider.js", + "media-understanding-provider.js", + "openai-codex-auth-identity.js", + "openai-codex-catalog.js", + "openai-codex-provider.js", + "openai-codex-provider.runtime.js", + "openai-provider.js", + "shared.js", + "speech-provider.js", + "tts.js", + ], packageName: "@openclaw/openai-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw OpenAI provider plugins", @@ -11193,6 +11359,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["openai", "openai-codex"], cliBackends: ["codex-cli"], providerAuthEnvVars: { @@ -11237,6 +11404,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js"], packageName: "@openclaw/opencode-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw OpenCode Zen provider plugin", @@ -11250,6 +11418,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["opencode"], providerAuthEnvVars: { opencode: ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"], @@ -11278,6 +11447,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js"], packageName: "@openclaw/opencode-go-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw OpenCode Go provider plugin", @@ -11291,6 +11461,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["opencode-go"], providerAuthEnvVars: { "opencode-go": ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"], @@ -11319,6 +11490,11 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "media-understanding-provider.js", + "onboard.js", + "provider-catalog.js", + ], packageName: "@openclaw/openrouter-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw OpenRouter provider plugin", @@ -11332,6 +11508,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["openrouter"], providerAuthEnvVars: { openrouter: ["OPENROUTER_API_KEY"], @@ -11493,6 +11670,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["web-search-provider.js"], packageName: "@openclaw/perplexity-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw Perplexity plugin", @@ -11553,6 +11731,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/qianfan-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Qianfan provider plugin", @@ -11566,6 +11745,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["qianfan"], providerAuthEnvVars: { qianfan: ["QIANFAN_API_KEY"], @@ -11607,6 +11787,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["sglang"], providerAuthEnvVars: { sglang: ["SGLANG_API_KEY"], @@ -11636,6 +11817,13 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: [ + "api.js", + "channel-config-api.js", + "reaction-runtime-api.js", + "runtime-api.js", + ], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/signal", packageVersion: "2026.3.26", packageDescription: "OpenClaw Signal channel plugin", @@ -12330,6 +12518,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "channel-config-api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/slack", packageVersion: "2026.3.26", packageDescription: "OpenClaw Slack channel plugin", @@ -14075,6 +14265,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["setup-api.js"], packageName: "@openclaw/synology-chat", packageVersion: "2026.3.26", packageDescription: "Synology Chat channel plugin for OpenClaw", @@ -14133,6 +14324,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/synthetic-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Synthetic provider plugin", @@ -14146,6 +14338,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["synthetic"], providerAuthEnvVars: { synthetic: ["SYNTHETIC_API_KEY"], @@ -14174,6 +14367,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["web-search-provider.js"], packageName: "@openclaw/tavily-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw Tavily plugin", @@ -14233,6 +14427,14 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: [ + "allow-from.js", + "api.js", + "channel-config-api.js", + "runtime-api.js", + "update-offset-runtime-api.js", + ], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/telegram", packageVersion: "2026.3.26", packageDescription: "OpenClaw Telegram channel plugin", @@ -16314,6 +16516,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js", "setup-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/tlon", packageVersion: "2026.3.26", packageDescription: "OpenClaw Tlon/Urbit channel plugin", @@ -16520,6 +16724,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/together-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Together provider plugin", @@ -16533,6 +16738,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["together"], providerAuthEnvVars: { together: ["TOGETHER_API_KEY"], @@ -16561,6 +16767,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/twitch", packageVersion: "2026.3.26", packageDescription: "OpenClaw Twitch channel plugin", @@ -16793,6 +17001,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/venice-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Venice provider plugin", @@ -16806,6 +17015,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["venice"], providerAuthEnvVars: { venice: ["VENICE_API_KEY"], @@ -16834,6 +17044,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/vercel-ai-gateway-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Vercel AI Gateway provider plugin", @@ -16847,6 +17058,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["vercel-ai-gateway"], providerAuthEnvVars: { "vercel-ai-gateway": ["AI_GATEWAY_API_KEY"], @@ -16888,6 +17100,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["vllm"], providerAuthEnvVars: { vllm: ["VLLM_API_KEY"], @@ -16913,6 +17126,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js", "runtime-entry.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/voice-call", packageVersion: "2026.3.26", packageDescription: "OpenClaw voice-call plugin", @@ -17540,6 +17755,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["provider-catalog.js"], packageName: "@openclaw/volcengine-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Volcengine provider plugin", @@ -17553,6 +17769,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["volcengine", "volcengine-plan"], providerAuthEnvVars: { volcengine: ["VOLCANO_ENGINE_API_KEY"], @@ -17585,6 +17802,17 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: [ + "action-runtime-api.js", + "action-runtime.runtime.js", + "api.js", + "auth-presence.js", + "channel-config-api.js", + "light-runtime-api.js", + "login-qr-api.js", + "runtime-api.js", + ], + runtimeSidecarArtifacts: ["light-runtime-api.js", "runtime-api.js"], packageName: "@openclaw/whatsapp", packageVersion: "2026.3.26", packageDescription: "OpenClaw WhatsApp channel plugin", @@ -18183,6 +18411,14 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "model-definitions.js", + "onboard.js", + "provider-catalog.js", + "provider-models.js", + "stream.js", + "web-search.js", + ], packageName: "@openclaw/xai-plugin", packageVersion: "2026.3.26", packageDescription: "OpenClaw xAI plugin", @@ -18212,6 +18448,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ }, }, }, + enabledByDefault: true, providers: ["xai"], providerAuthEnvVars: { xai: ["XAI_API_KEY"], @@ -18258,6 +18495,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: ["onboard.js", "provider-catalog.js"], packageName: "@openclaw/xiaomi-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Xiaomi provider plugin", @@ -18271,6 +18509,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["xiaomi"], providerAuthEnvVars: { xiaomi: ["XIAOMI_API_KEY"], @@ -18299,6 +18538,14 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./index.ts", built: "index.js", }, + publicSurfaceArtifacts: [ + "detect.js", + "media-understanding-provider.js", + "model-definitions.js", + "onboard.js", + "runtime-api.js", + ], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/zai-provider", packageVersion: "2026.3.26", packageDescription: "OpenClaw Z.AI provider plugin", @@ -18312,6 +18559,7 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ additionalProperties: false, properties: {}, }, + enabledByDefault: true, providers: ["zai"], providerAuthEnvVars: { zai: ["ZAI_API_KEY", "Z_AI_API_KEY"], @@ -18403,6 +18651,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/zalo", packageVersion: "2026.3.26", packageDescription: "OpenClaw Zalo channel plugin", @@ -18874,6 +19124,8 @@ export const GENERATED_BUNDLED_PLUGIN_METADATA = [ source: "./setup-entry.ts", built: "setup-entry.js", }, + publicSurfaceArtifacts: ["api.js", "runtime-api.js"], + runtimeSidecarArtifacts: ["runtime-api.js"], packageName: "@openclaw/zalouser", packageVersion: "2026.3.26", packageDescription: "OpenClaw Zalo Personal Account plugin via native zca-js integration", diff --git a/src/plugins/bundled-plugin-metadata.test.ts b/src/plugins/bundled-plugin-metadata.test.ts index 7665686c4d0..3d11078f035 100644 --- a/src/plugins/bundled-plugin-metadata.test.ts +++ b/src/plugins/bundled-plugin-metadata.test.ts @@ -35,6 +35,11 @@ describe("bundled plugin metadata", () => { const discord = BUNDLED_PLUGIN_METADATA.find((entry) => entry.dirName === "discord"); expect(discord?.source).toEqual({ source: "./index.ts", built: "index.js" }); expect(discord?.setupSource).toEqual({ source: "./setup-entry.ts", built: "setup-entry.js" }); + expect(discord?.publicSurfaceArtifacts).toContain("api.js"); + expect(discord?.publicSurfaceArtifacts).toContain("runtime-api.js"); + expect(discord?.publicSurfaceArtifacts).toContain("session-key-api.js"); + expect(discord?.publicSurfaceArtifacts).not.toContain("test-api.js"); + expect(discord?.runtimeSidecarArtifacts).toContain("runtime-api.js"); expect(discord?.manifest.id).toBe("discord"); expect(discord?.manifest.channelConfigs?.discord).toEqual( expect.objectContaining({ @@ -43,6 +48,16 @@ describe("bundled plugin metadata", () => { ); }); + it("excludes test-only public surface artifacts", () => { + for (const entry of BUNDLED_PLUGIN_METADATA) { + for (const artifact of entry.publicSurfaceArtifacts ?? []) { + expect(artifact).not.toMatch(/^test-/); + expect(artifact).not.toContain(".test-"); + expect(artifact).not.toMatch(/\.test\.js$/); + } + } + }); + it("prefers built generated paths when present and falls back to source paths", () => { const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-metadata-"); @@ -183,4 +198,47 @@ describe("bundled plugin metadata", () => { }, }); }); + + it("captures top-level public surface artifacts without duplicating the primary entrypoints", async () => { + const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-public-artifacts-"); + + writeJson(path.join(tempRoot, "extensions", "alpha", "package.json"), { + name: "@openclaw/alpha", + version: "0.0.1", + openclaw: { + extensions: ["./index.ts"], + setupEntry: "./setup-entry.ts", + }, + }); + writeJson(path.join(tempRoot, "extensions", "alpha", "openclaw.plugin.json"), { + id: "alpha", + configSchema: { type: "object" }, + }); + fs.writeFileSync( + path.join(tempRoot, "extensions", "alpha", "index.ts"), + "export {};\n", + "utf8", + ); + fs.writeFileSync( + path.join(tempRoot, "extensions", "alpha", "setup-entry.ts"), + "export {};\n", + "utf8", + ); + fs.writeFileSync(path.join(tempRoot, "extensions", "alpha", "api.ts"), "export {};\n", "utf8"); + fs.writeFileSync( + path.join(tempRoot, "extensions", "alpha", "runtime-api.ts"), + "export {};\n", + "utf8", + ); + + const entries = await collectBundledPluginMetadata({ repoRoot: tempRoot }); + const firstEntry = entries[0] as + | { + publicSurfaceArtifacts?: string[]; + runtimeSidecarArtifacts?: string[]; + } + | undefined; + expect(firstEntry?.publicSurfaceArtifacts).toEqual(["api.js", "runtime-api.js"]); + expect(firstEntry?.runtimeSidecarArtifacts).toEqual(["runtime-api.js"]); + }); }); diff --git a/src/plugins/bundled-plugin-metadata.ts b/src/plugins/bundled-plugin-metadata.ts index 489a648a020..1db292781d8 100644 --- a/src/plugins/bundled-plugin-metadata.ts +++ b/src/plugins/bundled-plugin-metadata.ts @@ -13,6 +13,8 @@ export type GeneratedBundledPluginMetadata = { idHint: string; source: GeneratedBundledPluginPathPair; setupSource?: GeneratedBundledPluginPathPair; + publicSurfaceArtifacts?: readonly string[]; + runtimeSidecarArtifacts?: readonly string[]; packageName?: string; packageVersion?: string; packageDescription?: string; diff --git a/src/plugins/config-state.test.ts b/src/plugins/config-state.test.ts index 0b40e4418ad..5565f9653d3 100644 --- a/src/plugins/config-state.test.ts +++ b/src/plugins/config-state.test.ts @@ -132,21 +132,25 @@ describe("normalizePluginsConfig", () => { it("normalizes legacy plugin ids to their merged bundled plugin id", () => { const result = normalizePluginsConfig({ - allow: ["openai-codex", "minimax-portal-auth"], - deny: ["openai-codex", "minimax-portal-auth"], + allow: ["openai-codex", "google-gemini-cli", "minimax-portal-auth"], + deny: ["openai-codex", "google-gemini-cli", "minimax-portal-auth"], entries: { "openai-codex": { enabled: true, }, + "google-gemini-cli": { + enabled: true, + }, "minimax-portal-auth": { enabled: false, }, }, }); - expect(result.allow).toEqual(["openai", "minimax"]); - expect(result.deny).toEqual(["openai", "minimax"]); + expect(result.allow).toEqual(["openai", "google", "minimax"]); + expect(result.deny).toEqual(["openai", "google", "minimax"]); expect(result.entries.openai?.enabled).toBe(true); + expect(result.entries.google?.enabled).toBe(true); expect(result.entries.minimax?.enabled).toBe(false); }); }); @@ -189,6 +193,16 @@ describe("resolveEffectiveEnableState", () => { }); describe("resolveEnableState", () => { + it("enables bundled plugins only when manifest metadata marks them enabled by default", () => { + expect(resolveEnableState("openai", "bundled", normalizePluginsConfig({}))).toEqual({ + enabled: false, + reason: "bundled (disabled by default)", + }); + expect(resolveEnableState("openai", "bundled", normalizePluginsConfig({}), true)).toEqual({ + enabled: true, + }); + }); + it("keeps the selected memory slot plugin enabled even when omitted from plugins.allow", () => { const state = resolveEnableState( "memory-core", @@ -266,8 +280,8 @@ describe("resolveEnableState", () => { }); }); - it("keeps bundled provider plugins enabled when they are bundled-default providers", () => { - const state = resolveEnableState("google", "bundled", normalizePluginsConfig({})); + it("keeps bundled plugins enabled when manifest metadata marks them enabled by default", () => { + const state = resolveEnableState("google", "bundled", normalizePluginsConfig({}), true); expect(state).toEqual({ enabled: true }); }); diff --git a/src/plugins/config-state.ts b/src/plugins/config-state.ts index f723048b8e9..9d5d4217c68 100644 --- a/src/plugins/config-state.ts +++ b/src/plugins/config-state.ts @@ -1,5 +1,9 @@ import { normalizeChatChannelId } from "../channels/registry.js"; import type { OpenClawConfig } from "../config/config.js"; +import { + BUNDLED_LEGACY_PLUGIN_ID_ALIASES, + BUNDLED_PROVIDER_PLUGIN_ID_ALIASES, +} from "./bundled-capability-metadata.js"; import type { PluginRecord } from "./registry.js"; import { defaultSlotIdForKey } from "./slots.js"; @@ -28,52 +32,13 @@ export type NormalizedPluginsConfig = { >; }; -export const BUNDLED_ENABLED_BY_DEFAULT = new Set([ - "amazon-bedrock", - "anthropic", - "byteplus", - "cloudflare-ai-gateway", - "deepseek", - "device-pair", - "github-copilot", - "google", - "huggingface", - "kilocode", - "kimi", - "minimax", - "mistral", - "modelstudio", - "moonshot", - "nvidia", - "ollama", - "openai", - "opencode", - "opencode-go", - "openrouter", - "phone-control", - "qianfan", - "sglang", - "synthetic", - "talk-voice", - "together", - "venice", - "vercel-ai-gateway", - "vllm", - "volcengine", - "xai", - "xiaomi", - "zai", -]); - -const PLUGIN_ID_ALIASES: Readonly> = { - "openai-codex": "openai", - "kimi-coding": "kimi", - "minimax-portal-auth": "minimax", -}; - export function normalizePluginId(id: string): string { const trimmed = id.trim(); - return PLUGIN_ID_ALIASES[trimmed] ?? trimmed; + return ( + BUNDLED_LEGACY_PLUGIN_ID_ALIASES[trimmed] ?? + BUNDLED_PROVIDER_PLUGIN_ID_ALIASES[trimmed] ?? + trimmed + ); } const normalizeList = (value: unknown): string[] => { @@ -299,7 +264,7 @@ export function resolveEnableState( if (entry?.enabled === true) { return { enabled: true }; } - if (origin === "bundled" && (enabledByDefault ?? BUNDLED_ENABLED_BY_DEFAULT.has(id))) { + if (origin === "bundled" && enabledByDefault === true) { return { enabled: true }; } if (origin === "bundled") { diff --git a/src/plugins/manifest-registry.ts b/src/plugins/manifest-registry.ts index 44dcba50eb6..297d21314f4 100644 --- a/src/plugins/manifest-registry.ts +++ b/src/plugins/manifest-registry.ts @@ -45,6 +45,7 @@ export type PluginManifestRecord = { description?: string; version?: string; enabledByDefault?: boolean; + autoEnableWhenConfiguredProviders?: string[]; format?: PluginFormat; bundleFormat?: PluginBundleFormat; bundleCapabilities?: string[]; @@ -220,6 +221,7 @@ function buildRecord(params: { normalizeManifestLabel(params.manifest.description) ?? params.candidate.packageDescription, version: normalizeManifestLabel(params.manifest.version) ?? params.candidate.packageVersion, enabledByDefault: params.manifest.enabledByDefault === true ? true : undefined, + autoEnableWhenConfiguredProviders: params.manifest.autoEnableWhenConfiguredProviders, format: params.candidate.format ?? "openclaw", bundleFormat: params.candidate.bundleFormat, kind: params.manifest.kind, diff --git a/src/plugins/manifest.ts b/src/plugins/manifest.ts index 2a3a1a109ef..516a3d96af6 100644 --- a/src/plugins/manifest.ts +++ b/src/plugins/manifest.ts @@ -20,6 +20,10 @@ export type PluginManifest = { id: string; configSchema: Record; enabledByDefault?: boolean; + /** Legacy plugin ids that should normalize to this plugin id. */ + legacyPluginIds?: string[]; + /** Provider ids that should auto-enable this plugin when referenced in auth/config/models. */ + autoEnableWhenConfiguredProviders?: string[]; kind?: PluginKind; channels?: string[]; providers?: string[]; @@ -63,6 +67,8 @@ export type PluginManifestProviderAuthChoice = { /** Optional user-facing choice label/hint for grouped onboarding UI. */ choiceLabel?: string; choiceHint?: string; + /** Legacy choice ids that should point users at this replacement choice. */ + deprecatedChoiceIds?: string[]; /** Optional grouping metadata for auth-choice pickers. */ groupId?: string; groupLabel?: string; @@ -151,6 +157,7 @@ function normalizeProviderAuthChoices( } const choiceLabel = typeof entry.choiceLabel === "string" ? entry.choiceLabel.trim() : ""; const choiceHint = typeof entry.choiceHint === "string" ? entry.choiceHint.trim() : ""; + const deprecatedChoiceIds = normalizeStringList(entry.deprecatedChoiceIds); const groupId = typeof entry.groupId === "string" ? entry.groupId.trim() : ""; const groupLabel = typeof entry.groupLabel === "string" ? entry.groupLabel.trim() : ""; const groupHint = typeof entry.groupHint === "string" ? entry.groupHint.trim() : ""; @@ -169,6 +176,7 @@ function normalizeProviderAuthChoices( choiceId, ...(choiceLabel ? { choiceLabel } : {}), ...(choiceHint ? { choiceHint } : {}), + ...(deprecatedChoiceIds.length > 0 ? { deprecatedChoiceIds } : {}), ...(groupId ? { groupId } : {}), ...(groupLabel ? { groupLabel } : {}), ...(groupHint ? { groupHint } : {}), @@ -276,6 +284,10 @@ export function loadPluginManifest( const kind = typeof raw.kind === "string" ? (raw.kind as PluginKind) : undefined; const enabledByDefault = raw.enabledByDefault === true; + const legacyPluginIds = normalizeStringList(raw.legacyPluginIds); + const autoEnableWhenConfiguredProviders = normalizeStringList( + raw.autoEnableWhenConfiguredProviders, + ); const name = typeof raw.name === "string" ? raw.name.trim() : undefined; const description = typeof raw.description === "string" ? raw.description.trim() : undefined; const version = typeof raw.version === "string" ? raw.version.trim() : undefined; @@ -299,6 +311,10 @@ export function loadPluginManifest( id, configSchema, ...(enabledByDefault ? { enabledByDefault } : {}), + ...(legacyPluginIds.length > 0 ? { legacyPluginIds } : {}), + ...(autoEnableWhenConfiguredProviders.length > 0 + ? { autoEnableWhenConfiguredProviders } + : {}), kind, channels, providers, diff --git a/src/plugins/provider-auth-choices.test.ts b/src/plugins/provider-auth-choices.test.ts index 25b4a017a2b..9cc4ec4951a 100644 --- a/src/plugins/provider-auth-choices.test.ts +++ b/src/plugins/provider-auth-choices.test.ts @@ -7,6 +7,7 @@ vi.mock("./manifest-registry.js", () => ({ })); import { + resolveManifestDeprecatedProviderAuthChoice, resolveManifestProviderAuthChoice, resolveManifestProviderAuthChoices, resolveManifestProviderOnboardAuthFlags, @@ -91,4 +92,30 @@ describe("provider auth choice manifest helpers", () => { }, ]); }); + + it("resolves deprecated auth-choice aliases through manifest metadata", () => { + loadPluginManifestRegistry.mockReturnValue({ + plugins: [ + { + id: "minimax", + providerAuthChoices: [ + { + provider: "minimax", + method: "api-global", + choiceId: "minimax-global-api", + deprecatedChoiceIds: ["minimax", "minimax-api"], + }, + ], + }, + ], + }); + + expect(resolveManifestDeprecatedProviderAuthChoice("minimax")?.choiceId).toBe( + "minimax-global-api", + ); + expect(resolveManifestDeprecatedProviderAuthChoice("minimax-api")?.choiceId).toBe( + "minimax-global-api", + ); + expect(resolveManifestDeprecatedProviderAuthChoice("openai")).toBeUndefined(); + }); }); diff --git a/src/plugins/provider-auth-choices.ts b/src/plugins/provider-auth-choices.ts index fd52c7e80a5..e9fca47ea1a 100644 --- a/src/plugins/provider-auth-choices.ts +++ b/src/plugins/provider-auth-choices.ts @@ -9,6 +9,7 @@ export type ProviderAuthChoiceMetadata = { choiceId: string; choiceLabel: string; choiceHint?: string; + deprecatedChoiceIds?: string[]; groupId?: string; groupLabel?: string; groupHint?: string; @@ -46,6 +47,7 @@ export function resolveManifestProviderAuthChoices(params?: { choiceId: choice.choiceId, choiceLabel: choice.choiceLabel ?? choice.choiceId, ...(choice.choiceHint ? { choiceHint: choice.choiceHint } : {}), + ...(choice.deprecatedChoiceIds ? { deprecatedChoiceIds: choice.deprecatedChoiceIds } : {}), ...(choice.groupId ? { groupId: choice.groupId } : {}), ...(choice.groupLabel ? { groupLabel: choice.groupLabel } : {}), ...(choice.groupHint ? { groupHint: choice.groupHint } : {}), @@ -94,6 +96,23 @@ export function resolveManifestProviderApiKeyChoice(params: { }); } +export function resolveManifestDeprecatedProviderAuthChoice( + choiceId: string, + params?: { + config?: OpenClawConfig; + workspaceDir?: string; + env?: NodeJS.ProcessEnv; + }, +): ProviderAuthChoiceMetadata | undefined { + const normalized = choiceId.trim(); + if (!normalized) { + return undefined; + } + return resolveManifestProviderAuthChoices(params).find((choice) => + choice.deprecatedChoiceIds?.includes(normalized), + ); +} + export function resolveManifestProviderOnboardAuthFlags(params?: { config?: OpenClawConfig; workspaceDir?: string; diff --git a/src/plugins/types.ts b/src/plugins/types.ts index 7af9bff66c8..fa0509b2db1 100644 --- a/src/plugins/types.ts +++ b/src/plugins/types.ts @@ -1052,6 +1052,15 @@ export type WebSearchProviderPlugin = { id: WebSearchProviderId; label: string; hint: string; + /** + * Interactive onboarding surfaces where this search provider should appear + * when OpenClaw has no config-aware runtime context yet. + * + * Unlike provider auth, search setup historically exposed only a curated + * quickstart subset. Keep this plugin-owned so core does not hardcode the + * default bundled provider list. + */ + onboardingScopes?: Array<"text-inference">; requiresCredential?: boolean; credentialLabel?: string; envVars: string[];