mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-28 10:22:32 +00:00
refactor: move bundled plugin policy into manifests
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "amazon-bedrock",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["amazon-bedrock"],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "anthropic",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["anthropic"],
|
||||
"cliBackends": ["claude-cli"],
|
||||
"providerAuthEnvVars": {
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "byteplus",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["byteplus", "byteplus-plan"],
|
||||
"providerAuthEnvVars": {
|
||||
"byteplus": ["BYTEPLUS_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "cloudflare-ai-gateway",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["cloudflare-ai-gateway"],
|
||||
"providerAuthEnvVars": {
|
||||
"cloudflare-ai-gateway": ["CLOUDFLARE_AI_GATEWAY_API_KEY"]
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{
|
||||
"id": "copilot-proxy",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["copilot-proxy"],
|
||||
"autoEnableWhenConfiguredProviders": ["copilot-proxy"],
|
||||
"providerAuthChoices": [
|
||||
{
|
||||
"provider": "copilot-proxy",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "deepseek",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["deepseek"],
|
||||
"providerAuthEnvVars": {
|
||||
"deepseek": ["DEEPSEEK_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "device-pair",
|
||||
"enabledByDefault": true,
|
||||
"name": "Device Pairing",
|
||||
"description": "Generate setup codes and approve device pairing requests.",
|
||||
"configSchema": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "fal",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["fal"],
|
||||
"providerAuthEnvVars": {
|
||||
"fal": ["FAL_KEY"]
|
||||
|
||||
@@ -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-...",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "github-copilot",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["github-copilot"],
|
||||
"providerAuthEnvVars": {
|
||||
"github-copilot": ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"]
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "huggingface",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["huggingface"],
|
||||
"providerAuthEnvVars": {
|
||||
"huggingface": ["HUGGINGFACE_HUB_TOKEN", "HF_TOKEN"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "kilocode",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["kilocode"],
|
||||
"providerAuthEnvVars": {
|
||||
"kilocode": ["KILOCODE_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "kimi",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["kimi", "kimi-coding"],
|
||||
"providerAuthEnvVars": {
|
||||
"kimi": ["KIMI_API_KEY", "KIMICODE_API_KEY"],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "litellm",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["litellm"],
|
||||
"providerAuthEnvVars": {
|
||||
"litellm": ["LITELLM_API_KEY"]
|
||||
|
||||
2
extensions/matrix/test-api.ts
Normal file
2
extensions/matrix/test-api.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { matrixPlugin } from "./src/channel.js";
|
||||
export { setMatrixRuntime } from "./src/runtime.js";
|
||||
1
extensions/memory-core/api.ts
Normal file
1
extensions/memory-core/api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core";
|
||||
@@ -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";
|
||||
|
||||
@@ -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<BuiltinMemoryEmbeddingProviderDoctorMetadata> {
|
||||
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,
|
||||
|
||||
@@ -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>): OpenClawConfig {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "mistral",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["mistral"],
|
||||
"providerAuthEnvVars": {
|
||||
"mistral": ["MISTRAL_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "modelstudio",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["modelstudio"],
|
||||
"providerAuthEnvVars": {
|
||||
"modelstudio": ["MODELSTUDIO_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "moonshot",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["moonshot"],
|
||||
"providerAuthEnvVars": {
|
||||
"moonshot": ["MOONSHOT_API_KEY"]
|
||||
|
||||
@@ -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-...",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "nvidia",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["nvidia"],
|
||||
"providerAuthEnvVars": {
|
||||
"nvidia": ["NVIDIA_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "ollama",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["ollama"],
|
||||
"providerAuthEnvVars": {
|
||||
"ollama": ["OLLAMA_API_KEY"]
|
||||
|
||||
@@ -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`.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "openai",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["openai", "openai-codex"],
|
||||
"cliBackends": ["codex-cli"],
|
||||
"providerAuthEnvVars": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "opencode-go",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["opencode-go"],
|
||||
"providerAuthEnvVars": {
|
||||
"opencode-go": ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "opencode",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["opencode"],
|
||||
"providerAuthEnvVars": {
|
||||
"opencode": ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "openrouter",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["openrouter"],
|
||||
"providerAuthEnvVars": {
|
||||
"openrouter": ["OPENROUTER_API_KEY"]
|
||||
|
||||
@@ -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-...",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "qianfan",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["qianfan"],
|
||||
"providerAuthEnvVars": {
|
||||
"qianfan": ["QIANFAN_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "sglang",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["sglang"],
|
||||
"providerAuthEnvVars": {
|
||||
"sglang": ["SGLANG_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "synthetic",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["synthetic"],
|
||||
"providerAuthEnvVars": {
|
||||
"synthetic": ["SYNTHETIC_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "talk-voice",
|
||||
"enabledByDefault": true,
|
||||
"name": "Talk Voice",
|
||||
"description": "Manage Talk voice selection (list/set).",
|
||||
"configSchema": {
|
||||
|
||||
@@ -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-...",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "together",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["together"],
|
||||
"providerAuthEnvVars": {
|
||||
"together": ["TOGETHER_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "venice",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["venice"],
|
||||
"providerAuthEnvVars": {
|
||||
"venice": ["VENICE_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "vercel-ai-gateway",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["vercel-ai-gateway"],
|
||||
"providerAuthEnvVars": {
|
||||
"vercel-ai-gateway": ["AI_GATEWAY_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "vllm",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["vllm"],
|
||||
"providerAuthEnvVars": {
|
||||
"vllm": ["VLLM_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "volcengine",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["volcengine", "volcengine-plan"],
|
||||
"providerAuthEnvVars": {
|
||||
"volcengine": ["VOLCANO_ENGINE_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "xai",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["xai"],
|
||||
"providerAuthEnvVars": {
|
||||
"xai": ["XAI_API_KEY"]
|
||||
|
||||
@@ -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-...",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "xiaomi",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["xiaomi"],
|
||||
"providerAuthEnvVars": {
|
||||
"xiaomi": ["XIAOMI_API_KEY"]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "zai",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["zai"],
|
||||
"providerAuthEnvVars": {
|
||||
"zai": ["ZAI_API_KEY", "Z_AI_API_KEY"]
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -158,53 +158,17 @@ async function promptChannelMode(runtime: RuntimeEnv): Promise<ChannelsWizardMod
|
||||
async function promptWebToolsConfig(
|
||||
nextConfig: OpenClawConfig,
|
||||
runtime: RuntimeEnv,
|
||||
prompter: ReturnType<typeof createClackPrompter>,
|
||||
): Promise<OpenClawConfig> {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<typeof ensureAuthProfileStore>,
|
||||
): Promise<string | null> {
|
||||
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<typeof ensureAuthProfileStore>,
|
||||
): Promise<string> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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<boolean> {
|
||||
// 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(
|
||||
|
||||
@@ -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<ResolvedNonInteractiveApiKey | null>;
|
||||
maybeSetResolvedApiKey: (
|
||||
resolved: ResolvedNonInteractiveApiKey,
|
||||
setter: (value: SecretInput) => Promise<void> | void,
|
||||
) => Promise<boolean>;
|
||||
}): Promise<OpenClawConfig | null | undefined> {
|
||||
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",
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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> | void,
|
||||
): Promise<boolean> => {
|
||||
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<string, string> = {
|
||||
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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -43,6 +43,7 @@ function makeRegistry(
|
||||
plugins: Array<{
|
||||
id: string;
|
||||
channels: string[];
|
||||
autoEnableWhenConfiguredProviders?: string[];
|
||||
channelConfigs?: Record<string, { schema: Record<string, unknown>; 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: {
|
||||
|
||||
@@ -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<Record<string, string>> {
|
||||
const entries = new Map<string, string>(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`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,19 +47,15 @@ function resolveSearchProviderCredentialLabel(
|
||||
return entry.credentialLabel?.trim() || `${entry.label} API key`;
|
||||
}
|
||||
|
||||
const DEFAULT_ONBOARD_SEARCH_PROVIDER_IDS = new Set<SearchProvider>([
|
||||
"brave",
|
||||
"firecrawl",
|
||||
"gemini",
|
||||
"grok",
|
||||
"kimi",
|
||||
"perplexity",
|
||||
"tavily",
|
||||
]);
|
||||
|
||||
export const SEARCH_PROVIDER_OPTIONS: readonly PluginWebSearchProviderEntry[] =
|
||||
resolveSearchProviderSetupContributions().map((contribution) => contribution.provider);
|
||||
|
||||
function showsSearchProviderInSetup(
|
||||
entry: Pick<PluginWebSearchProviderEntry, "onboardingScopes">,
|
||||
): boolean {
|
||||
return entry.onboardingScopes?.includes("text-inference") ?? false;
|
||||
}
|
||||
|
||||
function canRepairBundledProviderSelection(
|
||||
config: OpenClawConfig,
|
||||
provider: Pick<PluginWebSearchProviderEntry, "id" | "pluginId">,
|
||||
@@ -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" })),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Public provider auth environment variable helpers for plugin runtimes.
|
||||
|
||||
export {
|
||||
getProviderEnvVars,
|
||||
listKnownProviderAuthEnvVarNames,
|
||||
omitEnvKeysCaseInsensitive,
|
||||
} from "../secrets/provider-env-vars.js";
|
||||
|
||||
@@ -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<Record<string, string>>;
|
||||
|
||||
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<Record<string, string>>;
|
||||
|
||||
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<Record<string, string>>;
|
||||
|
||||
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<Record<string, string>>;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
|
||||
|
||||
@@ -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<string>([
|
||||
"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<Record<string, string>> = {
|
||||
"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") {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -20,6 +20,10 @@ export type PluginManifest = {
|
||||
id: string;
|
||||
configSchema: Record<string, unknown>;
|
||||
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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
|
||||
Reference in New Issue
Block a user