mirror of https://github.com/openclaw/openclaw.git
refactor: move plugin setup and memory capabilities to registries
This commit is contained in:
parent
695c9c887b
commit
629baf5fa7
|
|
@ -711,6 +711,13 @@
|
|||
"lines"
|
||||
]
|
||||
},
|
||||
"update_plan": {
|
||||
"emoji": "🗺️",
|
||||
"title": "Update Plan",
|
||||
"detailKeys": [
|
||||
"explanation"
|
||||
]
|
||||
},
|
||||
"web_search": {
|
||||
"emoji": "🔎",
|
||||
"title": "Web Search",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "acpx",
|
||||
name: "ACPX Setup",
|
||||
description: "Lightweight ACPX setup hooks",
|
||||
register(api) {
|
||||
api.registerAutoEnableProbe(({ config }) => {
|
||||
const backendRaw =
|
||||
typeof config.acp?.backend === "string" ? config.acp.backend.trim().toLowerCase() : "";
|
||||
const configured =
|
||||
config.acp?.enabled === true ||
|
||||
config.acp?.dispatch?.enabled === true ||
|
||||
backendRaw === "acpx";
|
||||
return configured && (!backendRaw || backendRaw === "acpx") ? "ACP runtime configured" : null;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { migrateAmazonBedrockLegacyConfig } from "./config-api.js";
|
||||
import { resolveBedrockConfigApiKey } from "./discovery.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "amazon-bedrock",
|
||||
name: "Amazon Bedrock Setup",
|
||||
description: "Lightweight Amazon Bedrock setup hooks",
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: "amazon-bedrock",
|
||||
label: "Amazon Bedrock",
|
||||
auth: [],
|
||||
resolveConfigApiKey: ({ env }) => resolveBedrockConfigApiKey(env),
|
||||
});
|
||||
api.registerConfigMigration((config) => migrateAmazonBedrockLegacyConfig(config));
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { resolveAnthropicVertexConfigApiKey } from "./region.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "anthropic-vertex",
|
||||
name: "Anthropic Vertex Setup",
|
||||
description: "Lightweight Anthropic Vertex setup hooks",
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: "anthropic-vertex",
|
||||
label: "Anthropic Vertex",
|
||||
auth: [],
|
||||
resolveConfigApiKey: ({ env }) => resolveAnthropicVertexConfigApiKey(env),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { buildAnthropicCliBackend } from "./cli-backend.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "anthropic",
|
||||
name: "Anthropic Setup",
|
||||
description: "Lightweight Anthropic setup hooks",
|
||||
register(api) {
|
||||
api.registerCliBackend(buildAnthropicCliBackend());
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function listContainsBrowser(value: unknown): boolean {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.some((entry) => typeof entry === "string" && entry.trim().toLowerCase() === "browser")
|
||||
);
|
||||
}
|
||||
|
||||
function toolPolicyReferencesBrowser(value: unknown): boolean {
|
||||
return (
|
||||
isRecord(value) && (listContainsBrowser(value.allow) || listContainsBrowser(value.alsoAllow))
|
||||
);
|
||||
}
|
||||
|
||||
function hasBrowserToolReference(config: OpenClawConfig): boolean {
|
||||
if (toolPolicyReferencesBrowser(config.tools)) {
|
||||
return true;
|
||||
}
|
||||
const agentList = config.agents?.list;
|
||||
return Array.isArray(agentList)
|
||||
? agentList.some((entry) => isRecord(entry) && toolPolicyReferencesBrowser(entry.tools))
|
||||
: false;
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "browser",
|
||||
name: "Browser Setup",
|
||||
description: "Lightweight Browser setup hooks",
|
||||
register(api) {
|
||||
api.registerAutoEnableProbe(({ config }) => {
|
||||
if (
|
||||
config.browser?.enabled === false ||
|
||||
config.plugins?.entries?.browser?.enabled === false
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(config, "browser")) {
|
||||
return "browser configured";
|
||||
}
|
||||
if (
|
||||
config.plugins?.entries &&
|
||||
Object.prototype.hasOwnProperty.call(config.plugins.entries, "browser")
|
||||
) {
|
||||
return "browser plugin configured";
|
||||
}
|
||||
if (hasBrowserToolReference(config)) {
|
||||
return "browser tool referenced";
|
||||
}
|
||||
return null;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { migrateElevenLabsLegacyTalkConfig } from "./config-compat.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "elevenlabs",
|
||||
name: "ElevenLabs Setup",
|
||||
description: "Lightweight ElevenLabs setup hooks",
|
||||
register(api) {
|
||||
api.registerLegacyConfigMigration((raw, changes) => {
|
||||
const migrated = migrateElevenLabsLegacyTalkConfig(raw);
|
||||
if (migrated.changes.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const key of Object.keys(raw)) {
|
||||
delete raw[key];
|
||||
}
|
||||
Object.assign(raw, migrated.config);
|
||||
changes.push(...migrated.changes);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { normalizeGoogleProviderConfig } from "./api.js";
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "google",
|
||||
name: "Google Setup",
|
||||
description: "Lightweight Google setup hooks",
|
||||
register(api) {
|
||||
api.registerProvider({
|
||||
id: "google",
|
||||
label: "Google AI Studio",
|
||||
hookAliases: ["google-antigravity", "google-vertex"],
|
||||
auth: [],
|
||||
normalizeConfig: ({ provider, providerConfig }) =>
|
||||
normalizeGoogleProviderConfig(provider, providerConfig),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -12,10 +12,7 @@ import {
|
|||
type MemoryEmbeddingProviderCreateOptions,
|
||||
type MemoryEmbeddingProviderRuntime,
|
||||
} from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||
import {
|
||||
canAutoSelectLocal,
|
||||
getBuiltinMemoryEmbeddingProviderAdapter,
|
||||
} from "./provider-adapters.js";
|
||||
import { canAutoSelectLocal } from "./provider-adapters.js";
|
||||
|
||||
export {
|
||||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
|
|
@ -61,8 +58,11 @@ function shouldContinueAutoSelection(
|
|||
return adapter.shouldContinueAutoSelection?.(err) ?? false;
|
||||
}
|
||||
|
||||
function getAdapter(id: string): MemoryEmbeddingProviderAdapter {
|
||||
const adapter = getMemoryEmbeddingProvider(id);
|
||||
function getAdapter(
|
||||
id: string,
|
||||
config?: MemoryEmbeddingProviderCreateOptions["config"],
|
||||
): MemoryEmbeddingProviderAdapter {
|
||||
const adapter = getMemoryEmbeddingProvider(id, config);
|
||||
if (!adapter) {
|
||||
throw new Error(`Unknown memory embedding provider: ${id}`);
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ function getAdapter(id: string): MemoryEmbeddingProviderAdapter {
|
|||
function listAutoSelectAdapters(
|
||||
options: CreateEmbeddingProviderOptions,
|
||||
): MemoryEmbeddingProviderAdapter[] {
|
||||
return listMemoryEmbeddingProviders()
|
||||
return listMemoryEmbeddingProviders(options.config)
|
||||
.filter((adapter) => typeof adapter.autoSelectPriority === "number")
|
||||
.filter((adapter) =>
|
||||
adapter.id === "local" ? canAutoSelectLocal(options.local?.modelPath) : true,
|
||||
|
|
@ -98,9 +98,9 @@ function resolveProviderModel(
|
|||
export function resolveEmbeddingProviderFallbackModel(
|
||||
providerId: string,
|
||||
fallbackSourceModel: string,
|
||||
config?: MemoryEmbeddingProviderCreateOptions["config"],
|
||||
): string {
|
||||
const adapter =
|
||||
getMemoryEmbeddingProvider(providerId) ?? getBuiltinMemoryEmbeddingProviderAdapter(providerId);
|
||||
const adapter = getMemoryEmbeddingProvider(providerId, config);
|
||||
return adapter?.defaultModel ?? fallbackSourceModel;
|
||||
}
|
||||
|
||||
|
|
@ -153,13 +153,13 @@ export async function createEmbeddingProvider(
|
|||
};
|
||||
}
|
||||
|
||||
const primaryAdapter = getAdapter(options.provider);
|
||||
const primaryAdapter = getAdapter(options.provider, options.config);
|
||||
try {
|
||||
return await createWithAdapter(primaryAdapter, options);
|
||||
} catch (primaryErr) {
|
||||
const reason = formatProviderError(primaryAdapter, primaryErr);
|
||||
if (options.fallback && options.fallback !== "none" && options.fallback !== options.provider) {
|
||||
const fallbackAdapter = getAdapter(options.fallback);
|
||||
const fallbackAdapter = getAdapter(options.fallback, options.config);
|
||||
try {
|
||||
const fallbackResult = await createWithAdapter(fallbackAdapter, {
|
||||
...options,
|
||||
|
|
|
|||
|
|
@ -1119,7 +1119,11 @@ export abstract class MemoryManagerSyncOps {
|
|||
}
|
||||
const fallbackFrom = this.provider.id as EmbeddingProviderId;
|
||||
|
||||
const fallbackModel = resolveEmbeddingProviderFallbackModel(fallback, this.settings.model);
|
||||
const fallbackModel = resolveEmbeddingProviderFallbackModel(
|
||||
fallback,
|
||||
this.settings.model,
|
||||
this.cfg,
|
||||
);
|
||||
|
||||
const fallbackResult = await createEmbeddingProvider({
|
||||
config: this.cfg,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import {
|
|||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
DEFAULT_LOCAL_MODEL,
|
||||
DEFAULT_MISTRAL_EMBEDDING_MODEL,
|
||||
DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
DEFAULT_OPENAI_EMBEDDING_MODEL,
|
||||
DEFAULT_VOYAGE_EMBEDDING_MODEL,
|
||||
OPENAI_BATCH_ENDPOINT,
|
||||
|
|
@ -11,7 +10,6 @@ import {
|
|||
createGeminiEmbeddingProvider,
|
||||
createLocalEmbeddingProvider,
|
||||
createMistralEmbeddingProvider,
|
||||
createOllamaEmbeddingProvider,
|
||||
createOpenAiEmbeddingProvider,
|
||||
createVoyageEmbeddingProvider,
|
||||
hasNonTextEmbeddingParts,
|
||||
|
|
@ -289,29 +287,6 @@ const mistralAdapter: MemoryEmbeddingProviderAdapter = {
|
|||
},
|
||||
};
|
||||
|
||||
const ollamaAdapter: MemoryEmbeddingProviderAdapter = {
|
||||
id: "ollama",
|
||||
defaultModel: DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
transport: "remote",
|
||||
create: async (options) => {
|
||||
const { provider, client } = await createOllamaEmbeddingProvider({
|
||||
...options,
|
||||
provider: "ollama",
|
||||
fallback: "none",
|
||||
});
|
||||
return {
|
||||
provider,
|
||||
runtime: {
|
||||
id: "ollama",
|
||||
cacheKeyData: {
|
||||
provider: "ollama",
|
||||
model: client.model,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const localAdapter: MemoryEmbeddingProviderAdapter = {
|
||||
id: "local",
|
||||
defaultModel: DEFAULT_LOCAL_MODEL,
|
||||
|
|
@ -344,7 +319,6 @@ export const builtinMemoryEmbeddingProviderAdapters = [
|
|||
geminiAdapter,
|
||||
voyageAdapter,
|
||||
mistralAdapter,
|
||||
ollamaAdapter,
|
||||
] as const;
|
||||
|
||||
const builtinMemoryEmbeddingProviderAdapterById = new Map(
|
||||
|
|
@ -403,7 +377,6 @@ export {
|
|||
DEFAULT_GEMINI_EMBEDDING_MODEL,
|
||||
DEFAULT_LOCAL_MODEL,
|
||||
DEFAULT_MISTRAL_EMBEDDING_MODEL,
|
||||
DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
DEFAULT_OPENAI_EMBEDDING_MODEL,
|
||||
DEFAULT_VOYAGE_EMBEDDING_MODEL,
|
||||
canAutoSelectLocal,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
createOllamaEmbeddingProvider,
|
||||
} from "./src/embedding-provider.js";
|
||||
import { ollamaMemoryEmbeddingProviderAdapter } from "./src/memory-embedding-adapter.js";
|
||||
import { resolveOllamaApiBase } from "./src/provider-models.js";
|
||||
import {
|
||||
createConfiguredOllamaCompatStreamWrapper,
|
||||
|
|
@ -57,6 +58,7 @@ export default definePluginEntry({
|
|||
name: "Ollama Provider",
|
||||
description: "Bundled Ollama provider plugin",
|
||||
register(api: OpenClawPluginApi) {
|
||||
api.registerMemoryEmbeddingProvider(ollamaMemoryEmbeddingProviderAdapter);
|
||||
const pluginConfig = (api.pluginConfig ?? {}) as OllamaPluginConfig;
|
||||
api.registerWebSearchProvider(createOllamaWebSearchProvider());
|
||||
api.registerProvider({
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
}
|
||||
],
|
||||
"contracts": {
|
||||
"memoryEmbeddingProviders": ["ollama"],
|
||||
"webSearchProviders": ["ollama"]
|
||||
},
|
||||
"configSchema": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import type { MemoryEmbeddingProviderAdapter } from "openclaw/plugin-sdk/memory-core-host-engine-embeddings";
|
||||
import {
|
||||
DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
createOllamaEmbeddingProvider,
|
||||
} from "./embedding-provider.js";
|
||||
|
||||
export const ollamaMemoryEmbeddingProviderAdapter: MemoryEmbeddingProviderAdapter = {
|
||||
id: "ollama",
|
||||
defaultModel: DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
transport: "remote",
|
||||
create: async (options) => {
|
||||
const { provider, client } = await createOllamaEmbeddingProvider({
|
||||
...options,
|
||||
provider: "ollama",
|
||||
fallback: "none",
|
||||
});
|
||||
return {
|
||||
provider,
|
||||
runtime: {
|
||||
id: "ollama",
|
||||
cacheKeyData: {
|
||||
provider: "ollama",
|
||||
model: client.model,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
import { migrateVoiceCallLegacyConfigInput } from "./config-api.js";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function migrateVoiceCallPluginConfig(config: OpenClawConfig): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
} | null {
|
||||
const rawVoiceCallConfig = config.plugins?.entries?.["voice-call"]?.config;
|
||||
if (!isRecord(rawVoiceCallConfig)) {
|
||||
return null;
|
||||
}
|
||||
const migration = migrateVoiceCallLegacyConfigInput({
|
||||
value: rawVoiceCallConfig,
|
||||
configPathPrefix: "plugins.entries.voice-call.config",
|
||||
});
|
||||
if (migration.changes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const plugins = structuredClone(config.plugins ?? {});
|
||||
const entries = { ...plugins.entries };
|
||||
const existingVoiceCallEntry = isRecord(entries["voice-call"])
|
||||
? (entries["voice-call"] as Record<string, unknown>)
|
||||
: {};
|
||||
entries["voice-call"] = {
|
||||
...existingVoiceCallEntry,
|
||||
config: migration.config,
|
||||
};
|
||||
plugins.entries = entries;
|
||||
return {
|
||||
config: {
|
||||
...config,
|
||||
plugins,
|
||||
},
|
||||
changes: migration.changes,
|
||||
};
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "voice-call",
|
||||
name: "Voice Call Setup",
|
||||
description: "Lightweight Voice Call setup hooks",
|
||||
register(api) {
|
||||
api.registerConfigMigration((config) => migrateVoiceCallPluginConfig(config));
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
export default definePluginEntry({
|
||||
id: "xai",
|
||||
name: "xAI Setup",
|
||||
description: "Lightweight xAI setup hooks",
|
||||
register(api) {
|
||||
api.registerAutoEnableProbe(({ config }) => {
|
||||
const pluginConfig = config.plugins?.entries?.xai?.config;
|
||||
const web = config.tools?.web as Record<string, unknown> | undefined;
|
||||
if (
|
||||
isRecord(web?.x_search) ||
|
||||
(isRecord(pluginConfig) &&
|
||||
(isRecord(pluginConfig.xSearch) || isRecord(pluginConfig.codeExecution)))
|
||||
) {
|
||||
return "xai tool configured";
|
||||
}
|
||||
return null;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
@ -4,7 +4,7 @@ import { streamAnthropic, type AnthropicOptions, type Model } from "@mariozechne
|
|||
import {
|
||||
resolveAnthropicVertexClientRegion,
|
||||
resolveAnthropicVertexProjectId,
|
||||
} from "../../extensions/anthropic-vertex/api.js";
|
||||
} from "../plugin-sdk/anthropic-vertex.js";
|
||||
import {
|
||||
applyAnthropicPayloadPolicyToParams,
|
||||
resolveAnthropicPayloadPolicy,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
import {
|
||||
CLAUDE_CLI_BACKEND_ID,
|
||||
buildAnthropicCliBackend,
|
||||
normalizeClaudeBackendConfig,
|
||||
} from "../../extensions/anthropic/cli-backend-api.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { CliBackendConfig } from "../config/types.js";
|
||||
import { resolveRuntimeCliBackends } from "../plugins/cli-backends.runtime.js";
|
||||
import { resolvePluginSetupCliBackend } from "../plugins/setup-registry.js";
|
||||
import { normalizeProviderId } from "./model-selection.js";
|
||||
|
||||
export type ResolvedCliBackend = {
|
||||
|
|
@ -15,7 +11,10 @@ export type ResolvedCliBackend = {
|
|||
pluginId?: string;
|
||||
};
|
||||
|
||||
export { normalizeClaudeBackendConfig };
|
||||
export function normalizeClaudeBackendConfig(config: CliBackendConfig): CliBackendConfig {
|
||||
const normalizeConfig = resolveFallbackCliBackendPolicy("claude-cli")?.normalizeConfig;
|
||||
return normalizeConfig ? normalizeConfig(config) : config;
|
||||
}
|
||||
|
||||
type FallbackCliBackendPolicy = {
|
||||
bundleMcp: boolean;
|
||||
|
|
@ -23,19 +22,26 @@ type FallbackCliBackendPolicy = {
|
|||
normalizeConfig?: (config: CliBackendConfig) => CliBackendConfig;
|
||||
};
|
||||
|
||||
const FALLBACK_CLI_BACKEND_POLICIES: Record<string, FallbackCliBackendPolicy> = {
|
||||
[CLAUDE_CLI_BACKEND_ID]: {
|
||||
// Claude CLI consumes explicit MCP config overlays even when the runtime
|
||||
// plugin registry is not initialized yet (for example direct runner tests
|
||||
// or narrow non-gateway entrypoints).
|
||||
bundleMcp: true,
|
||||
baseConfig: buildAnthropicCliBackend().config,
|
||||
normalizeConfig: normalizeClaudeBackendConfig,
|
||||
},
|
||||
};
|
||||
const FALLBACK_CLI_BACKEND_POLICIES: Record<string, FallbackCliBackendPolicy> = {};
|
||||
|
||||
function resolveSetupCliBackendPolicy(provider: string): FallbackCliBackendPolicy | undefined {
|
||||
const entry = resolvePluginSetupCliBackend({
|
||||
backend: provider,
|
||||
});
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
// Setup-registered backends keep narrow CLI paths generic even when the
|
||||
// runtime plugin registry has not booted yet.
|
||||
bundleMcp: entry.backend.bundleMcp === true,
|
||||
baseConfig: entry.backend.config,
|
||||
normalizeConfig: entry.backend.normalizeConfig,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveFallbackCliBackendPolicy(provider: string): FallbackCliBackendPolicy | undefined {
|
||||
return FALLBACK_CLI_BACKEND_POLICIES[provider];
|
||||
return FALLBACK_CLI_BACKEND_POLICIES[provider] ?? resolveSetupCliBackendPolicy(provider);
|
||||
}
|
||||
|
||||
function normalizeBackendKey(key: string): string {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { isClaudeCliProvider } from "../../extensions/anthropic/api.js";
|
||||
import type { CliBackendConfig } from "../config/types.js";
|
||||
import { isClaudeCliProvider } from "../plugin-sdk/anthropic-cli.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
|
||||
type CliUsage = {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import path from "node:path";
|
|||
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
import type { ImageContent } from "@mariozechner/pi-ai";
|
||||
import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue";
|
||||
import { isClaudeCliProvider } from "../../../extensions/anthropic/api.js";
|
||||
import type { ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { CliBackendConfig } from "../../config/types.js";
|
||||
import { resolvePreferredOpenClawTmpDir } from "../../infra/tmp-openclaw-dir.js";
|
||||
import { MAX_IMAGE_BYTES } from "../../media/constants.js";
|
||||
import { extensionForMime } from "../../media/mime.js";
|
||||
import { isClaudeCliProvider } from "../../plugin-sdk/anthropic-cli.js";
|
||||
import { buildTtsSystemPromptHint } from "../../tts/tts.js";
|
||||
import { buildModelAliasLines } from "../model-alias-lines.js";
|
||||
import { resolveDefaultModelForAgent } from "../model-selection.js";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
supportsMemoryMultimodalEmbeddings,
|
||||
type MemoryMultimodalSettings,
|
||||
} from "../memory-host-sdk/multimodal.js";
|
||||
import { getMemoryEmbeddingProvider } from "../plugins/memory-embedding-providers.js";
|
||||
import { getMemoryEmbeddingProvider } from "../plugins/memory-embedding-provider-runtime.js";
|
||||
import { clampInt, clampNumber, resolveUserPath } from "../utils.js";
|
||||
import { resolveAgentConfig } from "./agent-scope.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { getEnvApiKey } from "@mariozechner/pi-ai";
|
||||
import { hasAnthropicVertexAvailableAuth } from "../../extensions/anthropic-vertex/api.js";
|
||||
import { getShellEnvAppliedKeys } from "../infra/shell-env.js";
|
||||
import { resolvePluginSetupProvider } from "../plugins/setup-registry.js";
|
||||
import { normalizeOptionalSecretInput } from "../utils/normalize-secret-input.js";
|
||||
import { resolveProviderEnvApiKeyCandidates } from "./model-auth-env-vars.js";
|
||||
import { GCP_VERTEX_CREDENTIALS_MARKER } from "./model-auth-markers.js";
|
||||
|
|
@ -44,13 +44,21 @@ export function resolveEnvApiKey(
|
|||
return { apiKey: envKey, source: "gcloud adc" };
|
||||
}
|
||||
|
||||
if (normalized === "anthropic-vertex") {
|
||||
// Vertex AI uses GCP credentials (SA JSON or ADC), not API keys.
|
||||
// Return a sentinel so the model resolver still treats this provider as available.
|
||||
if (hasAnthropicVertexAvailableAuth(env)) {
|
||||
return { apiKey: GCP_VERTEX_CREDENTIALS_MARKER, source: "gcloud adc" };
|
||||
const setupProvider = resolvePluginSetupProvider({
|
||||
provider: normalized,
|
||||
env,
|
||||
});
|
||||
if (setupProvider?.resolveConfigApiKey) {
|
||||
const resolved = setupProvider.resolveConfigApiKey({
|
||||
provider: normalized,
|
||||
env,
|
||||
});
|
||||
if (resolved?.trim()) {
|
||||
return {
|
||||
apiKey: resolved,
|
||||
source: resolved === GCP_VERTEX_CREDENTIALS_MARKER ? "gcloud adc" : "env",
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
import { resolveMantleBearerToken } from "../../extensions/amazon-bedrock-mantle/discovery.js";
|
||||
import { resolveBedrockConfigApiKey } from "../../extensions/amazon-bedrock/api.js";
|
||||
import { resolveAnthropicVertexConfigApiKey } from "../../extensions/anthropic-vertex/region.js";
|
||||
import {
|
||||
normalizeGoogleProviderConfig,
|
||||
shouldNormalizeGoogleProviderConfig,
|
||||
} from "../../extensions/google/api.js";
|
||||
import { MODEL_APIS } from "../config/types.models.js";
|
||||
import { resolveMantleBearerToken } from "../plugin-sdk/amazon-bedrock-mantle.js";
|
||||
import {
|
||||
applyProviderNativeStreamingUsageCompatWithPlugin,
|
||||
normalizeProviderConfigWithPlugin,
|
||||
} from "../plugins/provider-runtime.js";
|
||||
import { resolvePluginSetupProvider } from "../plugins/setup-registry.js";
|
||||
import type { ProviderConfig } from "./models-config.providers.secrets.js";
|
||||
|
||||
const GENERIC_PROVIDER_APIS = new Set<string>([
|
||||
|
|
@ -59,8 +54,15 @@ export function normalizeProviderSpecificConfig(
|
|||
providerKey: string,
|
||||
provider: ProviderConfig,
|
||||
): ProviderConfig {
|
||||
if (shouldNormalizeGoogleProviderConfig(providerKey, provider)) {
|
||||
return normalizeGoogleProviderConfig(providerKey, provider);
|
||||
const setupProvider = resolvePluginSetupProvider({
|
||||
provider: resolveProviderPluginLookupKey(providerKey, provider),
|
||||
});
|
||||
const setupNormalized = setupProvider?.normalizeConfig?.({
|
||||
provider: providerKey,
|
||||
providerConfig: provider,
|
||||
});
|
||||
if (setupNormalized && setupNormalized !== provider) {
|
||||
return setupNormalized;
|
||||
}
|
||||
const runtimeProviderKey = resolveProviderPluginLookupKey(providerKey, provider);
|
||||
if (!PROVIDERS_WITH_RUNTIME_NORMALIZE_CONFIG.has(runtimeProviderKey)) {
|
||||
|
|
@ -84,19 +86,20 @@ export function resolveProviderConfigApiKeyResolver(
|
|||
providerKey: string,
|
||||
provider?: ProviderConfig,
|
||||
): ((env: NodeJS.ProcessEnv) => string | undefined) | undefined {
|
||||
if (providerKey.trim() === "amazon-bedrock") {
|
||||
const setupProvider = resolvePluginSetupProvider({
|
||||
provider: resolveProviderPluginLookupKey(providerKey, provider),
|
||||
});
|
||||
const resolveSetupConfigApiKey = setupProvider?.resolveConfigApiKey;
|
||||
if (resolveSetupConfigApiKey) {
|
||||
return (env) => {
|
||||
const resolved = resolveBedrockConfigApiKey(env);
|
||||
const resolved = resolveSetupConfigApiKey({
|
||||
provider: providerKey,
|
||||
env,
|
||||
});
|
||||
return resolved?.trim() || undefined;
|
||||
};
|
||||
}
|
||||
const runtimeProviderKey = resolveProviderPluginLookupKey(providerKey, provider).trim();
|
||||
if (runtimeProviderKey === "anthropic-vertex") {
|
||||
return (env) => {
|
||||
const resolved = resolveAnthropicVertexConfigApiKey(env);
|
||||
return resolved?.trim() || undefined;
|
||||
};
|
||||
}
|
||||
if (runtimeProviderKey === "amazon-bedrock-mantle") {
|
||||
return (env) =>
|
||||
resolveMantleBearerToken(env)?.trim() ? "AWS_BEARER_TOKEN_BEDROCK" : undefined;
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ export type {
|
|||
} from "./models-config.providers.secrets.js";
|
||||
export { applyNativeStreamingUsageCompat } from "./models-config.providers.policy.js";
|
||||
export { enforceSourceManagedProviderSecrets } from "./models-config.providers.source-managed.js";
|
||||
export { resolveOllamaApiBase } from "../../extensions/ollama/api.js";
|
||||
export { resolveOllamaApiBase } from "../plugin-sdk/ollama.js";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { BrowserBridge } from "../../../extensions/browser/runtime-api.js";
|
||||
import type { BrowserBridge } from "../../plugin-sdk/browser-bridge.js";
|
||||
|
||||
export const BROWSER_BRIDGES = new Map<
|
||||
string,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import crypto from "node:crypto";
|
||||
import { deriveDefaultBrowserCdpPortRange } from "../../config/port-defaults.js";
|
||||
import {
|
||||
startBrowserBridgeServer,
|
||||
stopBrowserBridgeServer,
|
||||
} from "../../plugin-sdk/browser-bridge.js";
|
||||
import {
|
||||
DEFAULT_BROWSER_EVALUATE_ENABLED,
|
||||
DEFAULT_OPENCLAW_BROWSER_COLOR,
|
||||
DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME,
|
||||
resolveProfile,
|
||||
startBrowserBridgeServer,
|
||||
stopBrowserBridgeServer,
|
||||
type ResolvedBrowserConfig,
|
||||
} from "../../../extensions/browser/runtime-api.js";
|
||||
import { deriveDefaultBrowserCdpPortRange } from "../../config/port-defaults.js";
|
||||
} from "../../plugin-sdk/browser-config.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { BROWSER_BRIDGES } from "./browser-bridges.js";
|
||||
import { computeSandboxBrowserConfigHash } from "./config-hash.js";
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import fs from "node:fs/promises";
|
||||
import {
|
||||
DEFAULT_BROWSER_EVALUATE_ENABLED,
|
||||
ensureBrowserControlAuth,
|
||||
resolveBrowserControlAuth,
|
||||
} from "../../../extensions/browser/runtime-api.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
|
||||
import { DEFAULT_BROWSER_EVALUATE_ENABLED } from "../../plugin-sdk/browser-config.js";
|
||||
import {
|
||||
ensureBrowserControlAuth,
|
||||
resolveBrowserControlAuth,
|
||||
} from "../../plugin-sdk/browser-control-auth.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import { syncSkillsToWorkspace } from "../skills.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { stopBrowserBridgeServer } from "../../../extensions/browser/runtime-api.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { stopBrowserBridgeServer } from "../../plugin-sdk/browser-bridge.js";
|
||||
import { getSandboxBackendManager } from "./backend.js";
|
||||
import { BROWSER_BRIDGES } from "./browser-bridges.js";
|
||||
import { dockerSandboxBackendManager } from "./docker-backend.js";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { stopBrowserBridgeServer } from "../../../extensions/browser/runtime-api.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { stopBrowserBridgeServer } from "../../plugin-sdk/browser-bridge.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { getSandboxBackendManager } from "./backend.js";
|
||||
import { BROWSER_BRIDGES } from "./browser-bridges.js";
|
||||
|
|
|
|||
|
|
@ -471,6 +471,11 @@ export const TOOL_DISPLAY_CONFIG: ToolDisplayConfig = {
|
|||
title: "Memory Get",
|
||||
detailKeys: ["path", "from", "lines"],
|
||||
},
|
||||
update_plan: {
|
||||
emoji: "🗺️",
|
||||
title: "Update Plan",
|
||||
detailKeys: ["explanation"],
|
||||
},
|
||||
web_search: {
|
||||
emoji: "🔎",
|
||||
title: "Web Search",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import {
|
||||
parseBrowserMajorVersion,
|
||||
readBrowserVersion,
|
||||
resolveGoogleChromeExecutableForPlatform,
|
||||
} from "../../extensions/browser/runtime-api.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
} from "../plugin-sdk/browser-host-inspection.js";
|
||||
import { note } from "../terminal/note.js";
|
||||
|
||||
const CHROME_MCP_MIN_MAJOR = 144;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import { isDeepStrictEqual } from "node:util";
|
||||
import { migrateAmazonBedrockLegacyConfig } from "../../extensions/amazon-bedrock/config-api.js";
|
||||
import { migrateVoiceCallLegacyConfigInput } from "../../extensions/voice-call/config-api.js";
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import { shouldMoveSingleAccountChannelKey } from "../channels/plugins/setup-helpers.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
|
|
@ -10,7 +8,11 @@ import { migrateLegacyWebSearchConfig } from "../config/legacy-web-search.js";
|
|||
import { migrateLegacyXSearchConfig } from "../config/legacy-x-search.js";
|
||||
import { normalizeTalkSection } from "../config/talk.js";
|
||||
import { DEFAULT_GOOGLE_API_BASE_URL } from "../infra/google-api-base-url.js";
|
||||
import { normalizeCompatibilityConfig as normalizeElevenLabsCompatibilityConfig } from "../plugin-sdk/elevenlabs.js";
|
||||
import {
|
||||
ELEVENLABS_TALK_PROVIDER_ID,
|
||||
normalizeCompatibilityConfig as normalizeElevenLabsCompatibilityConfig,
|
||||
} from "../plugin-sdk/elevenlabs.js";
|
||||
import { runPluginSetupConfigMigrations } from "../plugins/setup-registry.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
||||
|
||||
export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): {
|
||||
|
|
@ -86,37 +88,6 @@ export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): {
|
|||
};
|
||||
};
|
||||
|
||||
const normalizeVoiceCallLegacyConfig = () => {
|
||||
const rawVoiceCallConfig = next.plugins?.entries?.["voice-call"]?.config;
|
||||
if (!isRecord(rawVoiceCallConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const migration = migrateVoiceCallLegacyConfigInput({
|
||||
value: rawVoiceCallConfig,
|
||||
configPathPrefix: "plugins.entries.voice-call.config",
|
||||
});
|
||||
if (migration.changes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const plugins = structuredClone(next.plugins ?? {});
|
||||
const entries = { ...plugins.entries };
|
||||
const existingVoiceCallEntry = isRecord(entries["voice-call"])
|
||||
? (entries["voice-call"] as Record<string, unknown>)
|
||||
: {};
|
||||
entries["voice-call"] = {
|
||||
...existingVoiceCallEntry,
|
||||
config: migration.config,
|
||||
};
|
||||
plugins.entries = entries;
|
||||
next = {
|
||||
...next,
|
||||
plugins,
|
||||
};
|
||||
changes.push(...migration.changes);
|
||||
};
|
||||
|
||||
const seedMissingDefaultAccountsFromSingleAccountBase = () => {
|
||||
const channels = next.channels as Record<string, unknown> | undefined;
|
||||
if (!channels) {
|
||||
|
|
@ -188,11 +159,12 @@ export function normalizeCompatibilityConfigValues(cfg: OpenClawConfig): {
|
|||
|
||||
seedMissingDefaultAccountsFromSingleAccountBase();
|
||||
normalizeLegacyBrowserProfiles();
|
||||
normalizeVoiceCallLegacyConfig();
|
||||
const bedrockMigration = migrateAmazonBedrockLegacyConfig(next);
|
||||
if (bedrockMigration.changes.length > 0) {
|
||||
next = bedrockMigration.config;
|
||||
changes.push(...bedrockMigration.changes);
|
||||
const setupMigration = runPluginSetupConfigMigrations({
|
||||
config: next,
|
||||
});
|
||||
if (setupMigration.changes.length > 0) {
|
||||
next = setupMigration.config;
|
||||
changes.push(...setupMigration.changes);
|
||||
}
|
||||
const webSearchMigration = migrateLegacyWebSearchConfig(next);
|
||||
if (webSearchMigration.changes.length > 0) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import {
|
||||
ELEVENLABS_TALK_LEGACY_CONFIG_RULES,
|
||||
migrateElevenLabsLegacyTalkConfig,
|
||||
hasLegacyTalkFields,
|
||||
} from "../plugin-sdk/elevenlabs.js";
|
||||
import { runPluginSetupLegacyConfigMigrations } from "../plugins/setup-registry.js";
|
||||
import {
|
||||
buildDefaultControlUiAllowedOrigins,
|
||||
hasConfiguredControlUiAllowedOrigins,
|
||||
|
|
@ -154,19 +155,6 @@ function hasLegacyAgentListSandboxPerSession(value: unknown): boolean {
|
|||
}
|
||||
return value.some((agent) => hasLegacySandboxPerSession(getRecord(agent)?.sandbox));
|
||||
}
|
||||
|
||||
function migrateLegacyTalkFields(raw: Record<string, unknown>, changes: string[]): void {
|
||||
const migrated = migrateElevenLabsLegacyTalkConfig(raw);
|
||||
if (migrated.changes.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const key of Object.keys(raw)) {
|
||||
delete raw[key];
|
||||
}
|
||||
Object.assign(raw, migrated.config);
|
||||
changes.push(...migrated.changes);
|
||||
}
|
||||
|
||||
function hasLegacyPluginEntryTtsProviderKeys(value: unknown): boolean {
|
||||
const entries = getRecord(value);
|
||||
if (!entries) {
|
||||
|
|
@ -346,7 +334,10 @@ export const LEGACY_CONFIG_MIGRATIONS_RUNTIME: LegacyConfigMigrationSpec[] = [
|
|||
describe: "Move legacy Talk flat fields into talk.providers.<provider>",
|
||||
legacyRules: ELEVENLABS_TALK_LEGACY_CONFIG_RULES,
|
||||
apply: (raw, changes) => {
|
||||
migrateLegacyTalkFields(raw, changes);
|
||||
if (!hasLegacyTalkFields(raw.talk)) {
|
||||
return;
|
||||
}
|
||||
runPluginSetupLegacyConfigMigrations({ raw, changes });
|
||||
},
|
||||
}),
|
||||
defineLegacyConfigMigration({
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
type PluginManifestRegistry,
|
||||
} from "../plugins/manifest-registry.js";
|
||||
import { resolveOwningPluginIdsForModelRef } from "../plugins/providers.js";
|
||||
import { resolvePluginSetupAutoEnableReasons } from "../plugins/setup-registry.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { isChannelConfigured } from "./channel-configured.js";
|
||||
import type { OpenClawConfig } from "./config.js";
|
||||
|
|
@ -23,11 +24,6 @@ export type PluginAutoEnableCandidate =
|
|||
kind: "channel-configured";
|
||||
channelId: string;
|
||||
}
|
||||
| {
|
||||
pluginId: "browser";
|
||||
kind: "browser-configured";
|
||||
source: "browser-configured" | "browser-plugin-configured" | "browser-tool-referenced";
|
||||
}
|
||||
| {
|
||||
pluginId: string;
|
||||
kind: "provider-auth-configured";
|
||||
|
|
@ -56,8 +52,9 @@ export type PluginAutoEnableCandidate =
|
|||
kind: "plugin-tool-configured";
|
||||
}
|
||||
| {
|
||||
pluginId: "acpx";
|
||||
kind: "acp-runtime-configured";
|
||||
pluginId: string;
|
||||
kind: "setup-auto-enable";
|
||||
reason: string;
|
||||
};
|
||||
|
||||
export type PluginAutoEnableResult = {
|
||||
|
|
@ -182,15 +179,15 @@ function hasPluginOwnedWebFetchConfig(cfg: OpenClawConfig, pluginId: string): bo
|
|||
}
|
||||
|
||||
function hasPluginOwnedToolConfig(cfg: OpenClawConfig, pluginId: string): boolean {
|
||||
if (pluginId !== "xai") {
|
||||
return false;
|
||||
}
|
||||
const pluginConfig = cfg.plugins?.entries?.xai?.config;
|
||||
const web = cfg.tools?.web as Record<string, unknown> | undefined;
|
||||
return Boolean(
|
||||
isRecord(web?.x_search) ||
|
||||
(isRecord(pluginConfig) &&
|
||||
(isRecord(pluginConfig.xSearch) || isRecord(pluginConfig.codeExecution))),
|
||||
return (
|
||||
pluginId === "xai" &&
|
||||
Boolean(
|
||||
isRecord(web?.x_search) ||
|
||||
(isRecord(pluginConfig) &&
|
||||
(isRecord(pluginConfig.xSearch) || isRecord(pluginConfig.codeExecution))),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -278,53 +275,6 @@ function hasConfiguredWebFetchPluginEntry(cfg: OpenClawConfig): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
function listContainsBrowser(value: unknown): boolean {
|
||||
return (
|
||||
Array.isArray(value) &&
|
||||
value.some((entry) => typeof entry === "string" && entry.trim().toLowerCase() === "browser")
|
||||
);
|
||||
}
|
||||
|
||||
function toolPolicyReferencesBrowser(value: unknown): boolean {
|
||||
return (
|
||||
isRecord(value) && (listContainsBrowser(value.allow) || listContainsBrowser(value.alsoAllow))
|
||||
);
|
||||
}
|
||||
|
||||
function hasBrowserToolReference(cfg: OpenClawConfig): boolean {
|
||||
if (toolPolicyReferencesBrowser(cfg.tools)) {
|
||||
return true;
|
||||
}
|
||||
const agentList = cfg.agents?.list;
|
||||
return Array.isArray(agentList)
|
||||
? agentList.some((entry) => isRecord(entry) && toolPolicyReferencesBrowser(entry.tools))
|
||||
: false;
|
||||
}
|
||||
|
||||
function hasExplicitBrowserPluginEntry(cfg: OpenClawConfig): boolean {
|
||||
return Boolean(
|
||||
cfg.plugins?.entries && Object.prototype.hasOwnProperty.call(cfg.plugins.entries, "browser"),
|
||||
);
|
||||
}
|
||||
|
||||
function resolveBrowserAutoEnableSource(
|
||||
cfg: OpenClawConfig,
|
||||
): Extract<PluginAutoEnableCandidate, { kind: "browser-configured" }>["source"] | null {
|
||||
if (cfg.browser?.enabled === false || cfg.plugins?.entries?.browser?.enabled === false) {
|
||||
return null;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(cfg, "browser")) {
|
||||
return "browser-configured";
|
||||
}
|
||||
if (hasExplicitBrowserPluginEntry(cfg)) {
|
||||
return "browser-plugin-configured";
|
||||
}
|
||||
if (hasBrowserToolReference(cfg)) {
|
||||
return "browser-tool-referenced";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function configMayNeedPluginManifestRegistry(cfg: OpenClawConfig): boolean {
|
||||
const pluginEntries = cfg.plugins?.entries;
|
||||
if (
|
||||
|
|
@ -364,15 +314,6 @@ export function configMayNeedPluginAutoEnable(
|
|||
if (hasPotentialConfiguredChannels(cfg, env)) {
|
||||
return true;
|
||||
}
|
||||
if (resolveBrowserAutoEnableSource(cfg)) {
|
||||
return true;
|
||||
}
|
||||
if (cfg.acp?.enabled === true || cfg.acp?.dispatch?.enabled === true) {
|
||||
return true;
|
||||
}
|
||||
if (typeof cfg.acp?.backend === "string" && cfg.acp.backend.trim().length > 0) {
|
||||
return true;
|
||||
}
|
||||
if (cfg.auth?.profiles && Object.keys(cfg.auth.profiles).length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -383,11 +324,18 @@ export function configMayNeedPluginAutoEnable(
|
|||
return true;
|
||||
}
|
||||
const web = cfg.tools?.web as Record<string, unknown> | undefined;
|
||||
return (
|
||||
if (
|
||||
isRecord(web?.x_search) ||
|
||||
isRecord(cfg.plugins?.entries?.xai?.config) ||
|
||||
hasConfiguredWebSearchPluginEntry(cfg) ||
|
||||
hasConfiguredWebFetchPluginEntry(cfg)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
resolvePluginSetupAutoEnableReasons({
|
||||
config: cfg,
|
||||
env,
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -397,16 +345,6 @@ export function resolvePluginAutoEnableCandidateReason(
|
|||
switch (candidate.kind) {
|
||||
case "channel-configured":
|
||||
return `${candidate.channelId} configured`;
|
||||
case "browser-configured":
|
||||
switch (candidate.source) {
|
||||
case "browser-configured":
|
||||
return "browser configured";
|
||||
case "browser-plugin-configured":
|
||||
return "browser plugin configured";
|
||||
case "browser-tool-referenced":
|
||||
return "browser tool referenced";
|
||||
}
|
||||
break;
|
||||
case "provider-auth-configured":
|
||||
return `${candidate.providerId} auth configured`;
|
||||
case "provider-model-configured":
|
||||
|
|
@ -419,8 +357,8 @@ export function resolvePluginAutoEnableCandidateReason(
|
|||
return `${candidate.pluginId} web fetch configured`;
|
||||
case "plugin-tool-configured":
|
||||
return `${candidate.pluginId} tool configured`;
|
||||
case "acp-runtime-configured":
|
||||
return "ACP runtime configured";
|
||||
case "setup-auto-enable":
|
||||
return candidate.reason;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -438,11 +376,6 @@ export function resolveConfiguredPluginAutoEnableCandidates(params: {
|
|||
}
|
||||
}
|
||||
|
||||
const browserSource = resolveBrowserAutoEnableSource(params.config);
|
||||
if (browserSource) {
|
||||
changes.push({ pluginId: "browser", kind: "browser-configured", source: browserSource });
|
||||
}
|
||||
|
||||
for (const [providerId, pluginId] of Object.entries(
|
||||
resolveAutoEnableProviderPluginIds(params.registry),
|
||||
)) {
|
||||
|
|
@ -498,16 +431,15 @@ export function resolveConfiguredPluginAutoEnableCandidates(params: {
|
|||
}
|
||||
}
|
||||
|
||||
const backendRaw =
|
||||
typeof params.config.acp?.backend === "string"
|
||||
? params.config.acp.backend.trim().toLowerCase()
|
||||
: "";
|
||||
const acpConfigured =
|
||||
params.config.acp?.enabled === true ||
|
||||
params.config.acp?.dispatch?.enabled === true ||
|
||||
backendRaw === "acpx";
|
||||
if (acpConfigured && (!backendRaw || backendRaw === "acpx")) {
|
||||
changes.push({ pluginId: "acpx", kind: "acp-runtime-configured" });
|
||||
for (const entry of resolvePluginSetupAutoEnableReasons({
|
||||
config: params.config,
|
||||
env: params.env,
|
||||
})) {
|
||||
changes.push({
|
||||
pluginId: entry.pluginId,
|
||||
kind: "setup-auto-enable",
|
||||
reason: entry.reason,
|
||||
});
|
||||
}
|
||||
|
||||
return changes;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
clearPluginManifestRegistryCache,
|
||||
type PluginManifestRegistry,
|
||||
} from "../plugins/manifest-registry.js";
|
||||
import { clearPluginSetupRegistryCache } from "../plugins/setup-registry.js";
|
||||
import {
|
||||
cleanupTrackedTempDirs,
|
||||
makeTrackedTempDir,
|
||||
|
|
@ -16,6 +17,7 @@ const tempDirs: string[] = [];
|
|||
export function resetPluginAutoEnableTestState(): void {
|
||||
clearPluginDiscoveryCache();
|
||||
clearPluginManifestRegistryCache();
|
||||
clearPluginSetupRegistryCache();
|
||||
cleanupTrackedTempDirs(tempDirs);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -270,6 +270,9 @@ describe("config schema", () => {
|
|||
planTool: true,
|
||||
},
|
||||
});
|
||||
if (!parsed) {
|
||||
throw new Error("expected parsed tools config");
|
||||
}
|
||||
|
||||
expect(parsed?.experimental?.planTool).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import { logWarn } from "../logger.js";
|
|||
import {
|
||||
getMemoryEmbeddingProvider,
|
||||
listMemoryEmbeddingProviders,
|
||||
type MemoryEmbeddingProvider,
|
||||
type MemoryEmbeddingProviderAdapter,
|
||||
} from "../plugins/memory-embedding-provider-runtime.js";
|
||||
import type {
|
||||
MemoryEmbeddingProvider,
|
||||
MemoryEmbeddingProviderAdapter,
|
||||
} from "../plugins/memory-embedding-providers.js";
|
||||
import type { AuthRateLimiter } from "./auth-rate-limit.js";
|
||||
import type { ResolvedGatewayAuth } from "./auth.js";
|
||||
|
|
@ -87,9 +89,9 @@ function formatErrorMessage(err: unknown): string {
|
|||
return err instanceof Error ? err.message : String(err);
|
||||
}
|
||||
|
||||
function resolveAutoExplicitProviders(): Set<string> {
|
||||
function resolveAutoExplicitProviders(cfg: ReturnType<typeof loadConfig>): Set<string> {
|
||||
return new Set(
|
||||
listMemoryEmbeddingProviders()
|
||||
listMemoryEmbeddingProviders(cfg)
|
||||
.filter((adapter) => adapter.allowExplicitWhenConfiguredAuto)
|
||||
.map((adapter) => adapter.id),
|
||||
);
|
||||
|
|
@ -131,7 +133,7 @@ async function createConfiguredEmbeddingProvider(params: {
|
|||
};
|
||||
|
||||
if (params.provider === "auto") {
|
||||
const adapters = listMemoryEmbeddingProviders()
|
||||
const adapters = listMemoryEmbeddingProviders(params.cfg)
|
||||
.filter((adapter) => typeof adapter.autoSelectPriority === "number")
|
||||
.toSorted(
|
||||
(a, b) =>
|
||||
|
|
@ -154,7 +156,7 @@ async function createConfiguredEmbeddingProvider(params: {
|
|||
throw new Error("No embeddings provider available.");
|
||||
}
|
||||
|
||||
const adapter = getMemoryEmbeddingProvider(params.provider);
|
||||
const adapter = getMemoryEmbeddingProvider(params.provider, params.cfg);
|
||||
if (!adapter) {
|
||||
throw new Error(`Unknown memory embedding provider: ${params.provider}`);
|
||||
}
|
||||
|
|
@ -168,6 +170,7 @@ async function createConfiguredEmbeddingProvider(params: {
|
|||
function resolveEmbeddingsTarget(params: {
|
||||
requestModel: string;
|
||||
configuredProvider: EmbeddingProviderRequest;
|
||||
cfg: ReturnType<typeof loadConfig>;
|
||||
}): { provider: EmbeddingProviderRequest; model: string } | { errorMessage: string } {
|
||||
const raw = params.requestModel.trim();
|
||||
const slash = raw.indexOf("/");
|
||||
|
|
@ -182,7 +185,7 @@ function resolveEmbeddingsTarget(params: {
|
|||
}
|
||||
|
||||
if (params.configuredProvider === "auto") {
|
||||
const safeAutoExplicitProviders = resolveAutoExplicitProviders();
|
||||
const safeAutoExplicitProviders = resolveAutoExplicitProviders(params.cfg);
|
||||
if (provider === "auto") {
|
||||
return { provider: "auto", model };
|
||||
}
|
||||
|
|
@ -268,7 +271,11 @@ export async function handleOpenAiEmbeddingsHttpRequest(
|
|||
const memorySearch = resolveMemorySearchConfig(cfg, agentId);
|
||||
const configuredProvider = memorySearch?.provider ?? "openai";
|
||||
const overrideModel = getHeader(req, "x-openclaw-model")?.trim() || memorySearch?.model || "";
|
||||
const target = resolveEmbeddingsTarget({ requestModel: overrideModel, configuredProvider });
|
||||
const target = resolveEmbeddingsTarget({
|
||||
requestModel: overrideModel,
|
||||
configuredProvider,
|
||||
cfg,
|
||||
});
|
||||
if ("errorMessage" in target) {
|
||||
sendJson(res, 400, {
|
||||
error: {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { movePathToTrash } from "../../../extensions/browser/runtime-api.js";
|
||||
import {
|
||||
listAgentIds,
|
||||
resolveAgentDir,
|
||||
|
|
@ -36,6 +35,7 @@ import {
|
|||
} from "../../infra/fs-safe.js";
|
||||
import { assertNoPathAliasEscape } from "../../infra/path-alias-guards.js";
|
||||
import { isNotFoundPathError } from "../../infra/path-guards.js";
|
||||
import { movePathToTrash } from "../../plugin-sdk/browser-maintenance.js";
|
||||
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../routing/session-key.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ const createRegistry = (diagnostics: PluginDiagnostic[]): PluginRegistry => ({
|
|||
videoGenerationProviders: [],
|
||||
webFetchProviders: [],
|
||||
webSearchProviders: [],
|
||||
memoryEmbeddingProviders: [],
|
||||
gatewayHandlers: {},
|
||||
httpRoutes: [],
|
||||
cliRegistrars: [],
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import { randomUUID } from "node:crypto";
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { CURRENT_SESSION_VERSION } from "@mariozechner/pi-coding-agent";
|
||||
import { closeTrackedBrowserTabsForSessions } from "../../extensions/browser/runtime-api.js";
|
||||
import { getAcpSessionManager } from "../acp/control-plane/manager.js";
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { clearBootstrapSnapshot } from "../agents/bootstrap-cache.js";
|
||||
|
|
@ -23,6 +22,7 @@ import { resolveSessionFilePath, resolveSessionFilePathOptions } from "../config
|
|||
import { logVerbose } from "../globals.js";
|
||||
import { createInternalHookEvent, triggerInternalHook } from "../hooks/internal-hooks.js";
|
||||
import { getSessionBindingService } from "../infra/outbound/session-binding-service.js";
|
||||
import { closeTrackedBrowserTabsForSessions } from "../plugin-sdk/browser-maintenance.js";
|
||||
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
|
||||
import {
|
||||
isSubagentSessionKey,
|
||||
|
|
|
|||
|
|
@ -313,6 +313,7 @@ const createStubPluginRegistry = (): PluginRegistry => ({
|
|||
videoGenerationProviders: [],
|
||||
webFetchProviders: [],
|
||||
webSearchProviders: [],
|
||||
memoryEmbeddingProviders: [],
|
||||
gatewayHandlers: {},
|
||||
httpRoutes: [],
|
||||
cliRegistrars: [],
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
export {
|
||||
getMemoryEmbeddingProvider,
|
||||
listMemoryEmbeddingProviders,
|
||||
} from "../plugins/memory-embedding-providers.js";
|
||||
} from "../plugins/memory-embedding-provider-runtime.js";
|
||||
export type {
|
||||
MemoryEmbeddingBatchChunk,
|
||||
MemoryEmbeddingBatchOptions,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export type { OllamaEmbeddingClient } from "../../../extensions/ollama/runtime-api.js";
|
||||
export type { OllamaEmbeddingClient } from "../../plugin-sdk/ollama-runtime.js";
|
||||
export {
|
||||
createOllamaEmbeddingProvider,
|
||||
DEFAULT_OLLAMA_EMBEDDING_MODEL,
|
||||
} from "../../../extensions/ollama/runtime-api.js";
|
||||
} from "../../plugin-sdk/ollama-runtime.js";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { spawn, spawnSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { runBrowserProxyCommand } from "../../extensions/browser/runtime-api.js";
|
||||
import { GatewayClient } from "../gateway/client.js";
|
||||
import {
|
||||
ensureExecApprovals,
|
||||
|
|
@ -20,6 +19,7 @@ import {
|
|||
type ExecHostResponse,
|
||||
} from "../infra/exec-host.js";
|
||||
import { sanitizeHostExecEnv } from "../infra/host-env-security.js";
|
||||
import { runBrowserProxyCommand } from "../plugin-sdk/browser-node-host.js";
|
||||
import { buildSystemRunApprovalPlan, handleSystemRunInvoke } from "./invoke-system-run.js";
|
||||
import type {
|
||||
ExecEventPayload,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { resolveBrowserConfig } from "../../extensions/browser/runtime-api.js";
|
||||
import { loadConfig, type OpenClawConfig } from "../config/config.js";
|
||||
import { GatewayClient } from "../gateway/client.js";
|
||||
import { resolveGatewayConnectionAuth } from "../gateway/connection-auth.js";
|
||||
|
|
@ -12,6 +11,7 @@ import {
|
|||
NODE_SYSTEM_RUN_COMMANDS,
|
||||
} from "../infra/node-commands.js";
|
||||
import { ensureOpenClawCliOnPath } from "../infra/path-env.js";
|
||||
import { resolveBrowserConfig } from "../plugin-sdk/browser-config.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { ensureNodeHostConfig, saveNodeHostConfig, type NodeHostGatewayConfig } from "./config.js";
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export { resolveMantleBearerToken } from "../../extensions/amazon-bedrock-mantle/discovery.js";
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
type FacadeModule = typeof import("@openclaw/anthropic-vertex/api.js");
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
|
||||
function loadFacadeModule(): FacadeModule {
|
||||
return loadBundledPluginPublicSurfaceModuleSync<FacadeModule>({
|
||||
dirName: "anthropic-vertex",
|
||||
artifactBasename: "api.js",
|
||||
});
|
||||
}
|
||||
|
||||
export const resolveAnthropicVertexClientRegion: FacadeModule["resolveAnthropicVertexClientRegion"] =
|
||||
((...args) =>
|
||||
loadFacadeModule().resolveAnthropicVertexClientRegion(
|
||||
...args,
|
||||
)) as FacadeModule["resolveAnthropicVertexClientRegion"];
|
||||
|
||||
export const resolveAnthropicVertexProjectId: FacadeModule["resolveAnthropicVertexProjectId"] = ((
|
||||
...args
|
||||
) =>
|
||||
loadFacadeModule().resolveAnthropicVertexProjectId(
|
||||
...args,
|
||||
)) as FacadeModule["resolveAnthropicVertexProjectId"];
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import type { Server } from "node:http";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const loadActivatedBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./facade-runtime.js", () => ({
|
||||
loadActivatedBundledPluginPublicSurfaceModuleSync,
|
||||
}));
|
||||
|
||||
describe("browser bridge facade", () => {
|
||||
beforeEach(() => {
|
||||
loadActivatedBundledPluginPublicSurfaceModuleSync.mockReset();
|
||||
});
|
||||
|
||||
it("stays cold until a bridge function is called", async () => {
|
||||
await import("./browser-bridge.js");
|
||||
|
||||
expect(loadActivatedBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("delegates bridge lifecycle calls through the activated runtime facade", async () => {
|
||||
const bridge = {
|
||||
server: {} as Server,
|
||||
port: 19001,
|
||||
baseUrl: "http://127.0.0.1:19001",
|
||||
state: {
|
||||
resolved: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const startBrowserBridgeServer = vi.fn(async () => bridge);
|
||||
const stopBrowserBridgeServer = vi.fn(async () => undefined);
|
||||
loadActivatedBundledPluginPublicSurfaceModuleSync.mockReturnValue({
|
||||
startBrowserBridgeServer,
|
||||
stopBrowserBridgeServer,
|
||||
});
|
||||
|
||||
const facade = await import("./browser-bridge.js");
|
||||
|
||||
await expect(
|
||||
facade.startBrowserBridgeServer({
|
||||
resolved: bridge.state.resolved as never,
|
||||
authToken: "token",
|
||||
}),
|
||||
).resolves.toEqual(bridge);
|
||||
await expect(facade.stopBrowserBridgeServer(bridge.server)).resolves.toBeUndefined();
|
||||
expect(loadActivatedBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({
|
||||
dirName: "browser",
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
expect(startBrowserBridgeServer).toHaveBeenCalledWith({
|
||||
resolved: bridge.state.resolved,
|
||||
authToken: "token",
|
||||
});
|
||||
expect(stopBrowserBridgeServer).toHaveBeenCalledWith(bridge.server);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,5 +1,42 @@
|
|||
export type { BrowserBridge } from "../../extensions/browser/browser-bridge.js";
|
||||
export {
|
||||
startBrowserBridgeServer,
|
||||
stopBrowserBridgeServer,
|
||||
} from "../../extensions/browser/browser-bridge.js";
|
||||
import type { Server } from "node:http";
|
||||
import type { ResolvedBrowserConfig } from "./browser-config.js";
|
||||
import { loadActivatedBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
|
||||
export type BrowserBridge = {
|
||||
server: Server;
|
||||
port: number;
|
||||
baseUrl: string;
|
||||
state: {
|
||||
resolved: ResolvedBrowserConfig;
|
||||
};
|
||||
};
|
||||
|
||||
type BrowserBridgeFacadeModule = {
|
||||
startBrowserBridgeServer(params: {
|
||||
resolved: ResolvedBrowserConfig;
|
||||
host?: string;
|
||||
port?: number;
|
||||
authToken?: string;
|
||||
authPassword?: string;
|
||||
onEnsureAttachTarget?: (profile: unknown) => Promise<void>;
|
||||
resolveSandboxNoVncToken?: (token: string) => { noVncPort: number; password?: string } | null;
|
||||
}): Promise<BrowserBridge>;
|
||||
stopBrowserBridgeServer(server: Server): Promise<void>;
|
||||
};
|
||||
|
||||
function loadFacadeModule(): BrowserBridgeFacadeModule {
|
||||
return loadActivatedBundledPluginPublicSurfaceModuleSync<BrowserBridgeFacadeModule>({
|
||||
dirName: "browser",
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
}
|
||||
|
||||
export async function startBrowserBridgeServer(
|
||||
params: Parameters<BrowserBridgeFacadeModule["startBrowserBridgeServer"]>[0],
|
||||
): Promise<BrowserBridge> {
|
||||
return await loadFacadeModule().startBrowserBridgeServer(params);
|
||||
}
|
||||
|
||||
export async function stopBrowserBridgeServer(server: Server): Promise<void> {
|
||||
await loadFacadeModule().stopBrowserBridgeServer(server);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import { execFileSync } from "node:child_process";
|
|||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { BrowserExecutable } from "../../extensions/browser/browser-runtime-api.js";
|
||||
|
||||
export type BrowserExecutable = {
|
||||
kind: "chrome" | "chromium" | "edge" | "canary";
|
||||
path: string;
|
||||
};
|
||||
|
||||
const CHROME_VERSION_RE = /\b(\d+)(?:\.\d+){1,3}\b/g;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const loadActivatedBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./facade-runtime.js", () => ({
|
||||
loadActivatedBundledPluginPublicSurfaceModuleSync,
|
||||
}));
|
||||
|
||||
describe("browser node-host facade", () => {
|
||||
beforeEach(() => {
|
||||
loadActivatedBundledPluginPublicSurfaceModuleSync.mockReset();
|
||||
});
|
||||
|
||||
it("stays cold until the proxy command is called", async () => {
|
||||
await import("./browser-node-host.js");
|
||||
|
||||
expect(loadActivatedBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("delegates the proxy command through the activated runtime facade", async () => {
|
||||
const runBrowserProxyCommand = vi.fn(async () => '{"ok":true}');
|
||||
loadActivatedBundledPluginPublicSurfaceModuleSync.mockReturnValue({
|
||||
runBrowserProxyCommand,
|
||||
});
|
||||
|
||||
const facade = await import("./browser-node-host.js");
|
||||
|
||||
await expect(facade.runBrowserProxyCommand('{"path":"/"}')).resolves.toBe('{"ok":true}');
|
||||
expect(loadActivatedBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({
|
||||
dirName: "browser",
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
expect(runBrowserProxyCommand).toHaveBeenCalledWith('{"path":"/"}');
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { loadActivatedBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
|
||||
type BrowserNodeHostFacadeModule = {
|
||||
runBrowserProxyCommand(paramsJSON?: string | null): Promise<string>;
|
||||
};
|
||||
|
||||
function loadFacadeModule(): BrowserNodeHostFacadeModule {
|
||||
return loadActivatedBundledPluginPublicSurfaceModuleSync<BrowserNodeHostFacadeModule>({
|
||||
dirName: "browser",
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
}
|
||||
|
||||
export async function runBrowserProxyCommand(paramsJSON?: string | null): Promise<string> {
|
||||
return await loadFacadeModule().runBrowserProxyCommand(paramsJSON);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
type FacadeModule = typeof import("@openclaw/ollama/runtime-api.js");
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
|
||||
function loadFacadeModule(): FacadeModule {
|
||||
return loadBundledPluginPublicSurfaceModuleSync<FacadeModule>({
|
||||
dirName: "ollama",
|
||||
artifactBasename: "runtime-api.js",
|
||||
});
|
||||
}
|
||||
|
||||
export type OllamaEmbeddingClient = import("@openclaw/ollama/runtime-api.js").OllamaEmbeddingClient;
|
||||
export const DEFAULT_OLLAMA_EMBEDDING_MODEL: FacadeModule["DEFAULT_OLLAMA_EMBEDDING_MODEL"] =
|
||||
loadFacadeModule().DEFAULT_OLLAMA_EMBEDDING_MODEL;
|
||||
|
||||
export const createOllamaEmbeddingProvider: FacadeModule["createOllamaEmbeddingProvider"] = ((
|
||||
...args
|
||||
) =>
|
||||
loadFacadeModule().createOllamaEmbeddingProvider(
|
||||
...args,
|
||||
)) as FacadeModule["createOllamaEmbeddingProvider"];
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
type FacadeModule = typeof import("@openclaw/ollama/api.js");
|
||||
import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-runtime.js";
|
||||
|
||||
function loadFacadeModule(): FacadeModule {
|
||||
return loadBundledPluginPublicSurfaceModuleSync<FacadeModule>({
|
||||
dirName: "ollama",
|
||||
artifactBasename: "api.js",
|
||||
});
|
||||
}
|
||||
|
||||
export const resolveOllamaApiBase: FacadeModule["resolveOllamaApiBase"] = ((...args) =>
|
||||
loadFacadeModule().resolveOllamaApiBase(...args)) as FacadeModule["resolveOllamaApiBase"];
|
||||
|
|
@ -26,6 +26,9 @@ export type BuildPluginApiParams = {
|
|||
| "registerCli"
|
||||
| "registerService"
|
||||
| "registerCliBackend"
|
||||
| "registerConfigMigration"
|
||||
| "registerLegacyConfigMigration"
|
||||
| "registerAutoEnableProbe"
|
||||
| "registerProvider"
|
||||
| "registerSpeechProvider"
|
||||
| "registerRealtimeTranscriptionProvider"
|
||||
|
|
@ -56,6 +59,10 @@ const noopRegisterGatewayMethod: OpenClawPluginApi["registerGatewayMethod"] = ()
|
|||
const noopRegisterCli: OpenClawPluginApi["registerCli"] = () => {};
|
||||
const noopRegisterService: OpenClawPluginApi["registerService"] = () => {};
|
||||
const noopRegisterCliBackend: OpenClawPluginApi["registerCliBackend"] = () => {};
|
||||
const noopRegisterConfigMigration: OpenClawPluginApi["registerConfigMigration"] = () => {};
|
||||
const noopRegisterLegacyConfigMigration: OpenClawPluginApi["registerLegacyConfigMigration"] =
|
||||
() => {};
|
||||
const noopRegisterAutoEnableProbe: OpenClawPluginApi["registerAutoEnableProbe"] = () => {};
|
||||
const noopRegisterProvider: OpenClawPluginApi["registerProvider"] = () => {};
|
||||
const noopRegisterSpeechProvider: OpenClawPluginApi["registerSpeechProvider"] = () => {};
|
||||
const noopRegisterRealtimeTranscriptionProvider: OpenClawPluginApi["registerRealtimeTranscriptionProvider"] =
|
||||
|
|
@ -104,6 +111,10 @@ export function buildPluginApi(params: BuildPluginApiParams): OpenClawPluginApi
|
|||
registerCli: handlers.registerCli ?? noopRegisterCli,
|
||||
registerService: handlers.registerService ?? noopRegisterService,
|
||||
registerCliBackend: handlers.registerCliBackend ?? noopRegisterCliBackend,
|
||||
registerConfigMigration: handlers.registerConfigMigration ?? noopRegisterConfigMigration,
|
||||
registerLegacyConfigMigration:
|
||||
handlers.registerLegacyConfigMigration ?? noopRegisterLegacyConfigMigration,
|
||||
registerAutoEnableProbe: handlers.registerAutoEnableProbe ?? noopRegisterAutoEnableProbe,
|
||||
registerProvider: handlers.registerProvider ?? noopRegisterProvider,
|
||||
registerSpeechProvider: handlers.registerSpeechProvider ?? noopRegisterSpeechProvider,
|
||||
registerRealtimeTranscriptionProvider:
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ function createCapabilityPluginRecord(params: {
|
|||
videoGenerationProviderIds: [],
|
||||
webFetchProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
memoryEmbeddingProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
|
|
@ -292,6 +293,9 @@ export function loadBundledCapabilityRuntimeRegistry(params: {
|
|||
);
|
||||
record.webFetchProviderIds.push(...captured.webFetchProviders.map((entry) => entry.id));
|
||||
record.webSearchProviderIds.push(...captured.webSearchProviders.map((entry) => entry.id));
|
||||
record.memoryEmbeddingProviderIds.push(
|
||||
...captured.memoryEmbeddingProviders.map((entry) => entry.id),
|
||||
);
|
||||
record.toolNames.push(...captured.tools.map((entry) => entry.name));
|
||||
|
||||
registry.cliBackends?.push(
|
||||
|
|
@ -384,6 +388,15 @@ export function loadBundledCapabilityRuntimeRegistry(params: {
|
|||
rootDir: record.rootDir,
|
||||
})),
|
||||
);
|
||||
registry.memoryEmbeddingProviders.push(
|
||||
...captured.memoryEmbeddingProviders.map((provider) => ({
|
||||
pluginId: record.id,
|
||||
pluginName: record.name,
|
||||
provider,
|
||||
source: record.source,
|
||||
rootDir: record.rootDir,
|
||||
})),
|
||||
);
|
||||
registry.tools.push(
|
||||
...captured.tools.map((tool) => ({
|
||||
pluginId: record.id,
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ function setBundledCapabilityFixture(contractKey: string) {
|
|||
|
||||
function expectCompatChainApplied(params: {
|
||||
key:
|
||||
| "memoryEmbeddingProviders"
|
||||
| "speechProviders"
|
||||
| "realtimeTranscriptionProviders"
|
||||
| "realtimeVoiceProviders"
|
||||
|
|
@ -205,6 +206,7 @@ describe("resolvePluginCapabilityProviders", () => {
|
|||
});
|
||||
|
||||
it.each([
|
||||
["memoryEmbeddingProviders", "memoryEmbeddingProviders"],
|
||||
["speechProviders", "speechProviders"],
|
||||
["realtimeTranscriptionProviders", "realtimeTranscriptionProviders"],
|
||||
["realtimeVoiceProviders", "realtimeVoiceProviders"],
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
|||
import type { PluginRegistry } from "./registry.js";
|
||||
|
||||
type CapabilityProviderRegistryKey =
|
||||
| "memoryEmbeddingProviders"
|
||||
| "speechProviders"
|
||||
| "realtimeTranscriptionProviders"
|
||||
| "realtimeVoiceProviders"
|
||||
|
|
@ -16,6 +17,7 @@ type CapabilityProviderRegistryKey =
|
|||
| "videoGenerationProviders";
|
||||
|
||||
type CapabilityContractKey =
|
||||
| "memoryEmbeddingProviders"
|
||||
| "speechProviders"
|
||||
| "realtimeTranscriptionProviders"
|
||||
| "realtimeVoiceProviders"
|
||||
|
|
@ -27,6 +29,7 @@ type CapabilityProviderForKey<K extends CapabilityProviderRegistryKey> =
|
|||
PluginRegistry[K][number] extends { provider: infer T } ? T : never;
|
||||
|
||||
const CAPABILITY_CONTRACT_KEY: Record<CapabilityProviderRegistryKey, CapabilityContractKey> = {
|
||||
memoryEmbeddingProviders: "memoryEmbeddingProviders",
|
||||
speechProviders: "speechProviders",
|
||||
realtimeTranscriptionProviders: "realtimeTranscriptionProviders",
|
||||
realtimeVoiceProviders: "realtimeVoiceProviders",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { buildPluginApi } from "./api-builder.js";
|
||||
import type { MemoryEmbeddingProviderAdapter } from "./memory-embedding-providers.js";
|
||||
import type { PluginRuntime } from "./runtime/types.js";
|
||||
import type {
|
||||
AnyAgentTool,
|
||||
|
|
@ -37,6 +38,7 @@ export type CapturedPluginRegistration = {
|
|||
videoGenerationProviders: VideoGenerationProviderPlugin[];
|
||||
webFetchProviders: WebFetchProviderPlugin[];
|
||||
webSearchProviders: WebSearchProviderPlugin[];
|
||||
memoryEmbeddingProviders: MemoryEmbeddingProviderAdapter[];
|
||||
tools: AnyAgentTool[];
|
||||
};
|
||||
|
||||
|
|
@ -55,6 +57,7 @@ export function createCapturedPluginRegistration(params?: {
|
|||
const videoGenerationProviders: VideoGenerationProviderPlugin[] = [];
|
||||
const webFetchProviders: WebFetchProviderPlugin[] = [];
|
||||
const webSearchProviders: WebSearchProviderPlugin[] = [];
|
||||
const memoryEmbeddingProviders: MemoryEmbeddingProviderAdapter[] = [];
|
||||
const tools: AnyAgentTool[] = [];
|
||||
const noopLogger = {
|
||||
info() {},
|
||||
|
|
@ -75,6 +78,7 @@ export function createCapturedPluginRegistration(params?: {
|
|||
videoGenerationProviders,
|
||||
webFetchProviders,
|
||||
webSearchProviders,
|
||||
memoryEmbeddingProviders,
|
||||
tools,
|
||||
api: buildPluginApi({
|
||||
id: "captured-plugin-registration",
|
||||
|
|
@ -139,6 +143,9 @@ export function createCapturedPluginRegistration(params?: {
|
|||
registerWebSearchProvider(provider: WebSearchProviderPlugin) {
|
||||
webSearchProviders.push(provider);
|
||||
},
|
||||
registerMemoryEmbeddingProvider(adapter: MemoryEmbeddingProviderAdapter) {
|
||||
memoryEmbeddingProviders.push(adapter);
|
||||
},
|
||||
registerTool(tool) {
|
||||
if (typeof tool !== "function") {
|
||||
tools.push(tool);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ function hasRuntimeContractSurface(plugin: PluginManifestRecord): boolean {
|
|||
plugin.contracts?.videoGenerationProviders?.length ||
|
||||
plugin.contracts?.webFetchProviders?.length ||
|
||||
plugin.contracts?.webSearchProviders?.length ||
|
||||
plugin.contracts?.memoryEmbeddingProviders?.length ||
|
||||
hasKind(plugin.kind, "memory"),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { getRegisteredMemoryEmbeddingProvider } from "../memory-embedding-provid
|
|||
import { createPluginRegistryFixture, registerVirtualTestPlugin } from "./testkit.js";
|
||||
|
||||
describe("memory embedding provider registration", () => {
|
||||
it("only allows memory plugins to register adapters", () => {
|
||||
it("rejects non-memory plugins that did not declare the capability contract", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
|
||||
registerVirtualTestPlugin({
|
||||
|
|
@ -24,12 +24,38 @@ describe("memory embedding provider registration", () => {
|
|||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
pluginId: "not-memory",
|
||||
message: "only memory plugins can register memory embedding providers",
|
||||
message:
|
||||
"plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: forbidden",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("allows non-memory plugins that declare the capability contract", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
|
||||
registerVirtualTestPlugin({
|
||||
registry,
|
||||
config,
|
||||
id: "ollama",
|
||||
name: "Ollama",
|
||||
contracts: {
|
||||
memoryEmbeddingProviders: ["ollama"],
|
||||
},
|
||||
register(api) {
|
||||
api.registerMemoryEmbeddingProvider({
|
||||
id: "ollama",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
expect(getRegisteredMemoryEmbeddingProvider("ollama")).toEqual({
|
||||
adapter: expect.objectContaining({ id: "ollama" }),
|
||||
ownerPluginId: "ollama",
|
||||
});
|
||||
});
|
||||
|
||||
it("records the owning memory plugin id for registered adapters", () => {
|
||||
const { config, registry } = createPluginRegistryFixture();
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ export function registerVirtualTestPlugin(params: {
|
|||
name: string;
|
||||
source?: string;
|
||||
kind?: PluginRecord["kind"];
|
||||
contracts?: PluginRecord["contracts"];
|
||||
register(this: void, api: OpenClawPluginApi): void;
|
||||
}) {
|
||||
registerTestPlugin({
|
||||
|
|
@ -99,6 +100,7 @@ export function registerVirtualTestPlugin(params: {
|
|||
name: params.name,
|
||||
source: params.source ?? `/virtual/${params.id}/index.ts`,
|
||||
...(params.kind ? { kind: params.kind } : {}),
|
||||
...(params.contracts ? { contracts: params.contracts } : {}),
|
||||
}),
|
||||
register: params.register,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import { discoverOpenClawPlugins } from "./discovery.js";
|
|||
import { initializeGlobalHookRunner } from "./hook-runner-global.js";
|
||||
import { clearPluginInteractiveHandlers } from "./interactive-registry.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import type { PluginManifestContracts } from "./manifest.js";
|
||||
import {
|
||||
clearMemoryEmbeddingProviders,
|
||||
listRegisteredMemoryEmbeddingProviders,
|
||||
|
|
@ -565,6 +566,7 @@ function createPluginRecord(params: {
|
|||
enabled: boolean;
|
||||
activationState?: PluginActivationState;
|
||||
configSchema: boolean;
|
||||
contracts?: PluginManifestContracts;
|
||||
}): PluginRecord {
|
||||
return {
|
||||
id: params.id,
|
||||
|
|
@ -597,6 +599,7 @@ function createPluginRecord(params: {
|
|||
videoGenerationProviderIds: [],
|
||||
webFetchProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
memoryEmbeddingProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
|
|
@ -606,6 +609,7 @@ function createPluginRecord(params: {
|
|||
configSchema: params.configSchema,
|
||||
configUiHints: undefined,
|
||||
configJsonSchema: undefined,
|
||||
contracts: params.contracts,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1185,6 +1189,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||
enabled: false,
|
||||
activationState,
|
||||
configSchema: Boolean(manifestRecord.configSchema),
|
||||
contracts: manifestRecord.contracts,
|
||||
});
|
||||
record.status = "disabled";
|
||||
record.error = `overridden by ${existingOrigin} plugin`;
|
||||
|
|
@ -1217,6 +1222,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
|||
enabled: enableState.enabled,
|
||||
activationState,
|
||||
configSchema: Boolean(manifestRecord.configSchema),
|
||||
contracts: manifestRecord.contracts,
|
||||
});
|
||||
record.kind = manifestRecord.kind;
|
||||
record.configUiHints = manifestRecord.configUiHints;
|
||||
|
|
@ -1743,6 +1749,7 @@ export async function loadOpenClawPluginCliRegistry(
|
|||
enabled: false,
|
||||
activationState,
|
||||
configSchema: Boolean(manifestRecord.configSchema),
|
||||
contracts: manifestRecord.contracts,
|
||||
});
|
||||
record.status = "disabled";
|
||||
record.error = `overridden by ${existingOrigin} plugin`;
|
||||
|
|
@ -1775,6 +1782,7 @@ export async function loadOpenClawPluginCliRegistry(
|
|||
enabled: enableState.enabled,
|
||||
activationState,
|
||||
configSchema: Boolean(manifestRecord.configSchema),
|
||||
contracts: manifestRecord.contracts,
|
||||
});
|
||||
record.kind = manifestRecord.kind;
|
||||
record.configUiHints = manifestRecord.configUiHints;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,10 @@ import type {
|
|||
PluginOrigin,
|
||||
} from "./types.js";
|
||||
|
||||
type PluginManifestContractListKey = "webFetchProviders" | "webSearchProviders";
|
||||
type PluginManifestContractListKey =
|
||||
| "memoryEmbeddingProviders"
|
||||
| "webFetchProviders"
|
||||
| "webSearchProviders";
|
||||
|
||||
type SeenIdEntry = {
|
||||
candidate: PluginCandidate;
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export type PluginManifest = {
|
|||
};
|
||||
|
||||
export type PluginManifestContracts = {
|
||||
memoryEmbeddingProviders?: string[];
|
||||
speechProviders?: string[];
|
||||
realtimeTranscriptionProviders?: string[];
|
||||
realtimeVoiceProviders?: string[];
|
||||
|
|
@ -151,6 +152,7 @@ function normalizeManifestContracts(value: unknown): PluginManifestContracts | u
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const memoryEmbeddingProviders = normalizeStringList(value.memoryEmbeddingProviders);
|
||||
const speechProviders = normalizeStringList(value.speechProviders);
|
||||
const realtimeTranscriptionProviders = normalizeStringList(value.realtimeTranscriptionProviders);
|
||||
const realtimeVoiceProviders = normalizeStringList(value.realtimeVoiceProviders);
|
||||
|
|
@ -161,6 +163,7 @@ function normalizeManifestContracts(value: unknown): PluginManifestContracts | u
|
|||
const webSearchProviders = normalizeStringList(value.webSearchProviders);
|
||||
const tools = normalizeStringList(value.tools);
|
||||
const contracts = {
|
||||
...(memoryEmbeddingProviders.length > 0 ? { memoryEmbeddingProviders } : {}),
|
||||
...(speechProviders.length > 0 ? { speechProviders } : {}),
|
||||
...(realtimeTranscriptionProviders.length > 0 ? { realtimeTranscriptionProviders } : {}),
|
||||
...(realtimeVoiceProviders.length > 0 ? { realtimeVoiceProviders } : {}),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
clearMemoryEmbeddingProviders,
|
||||
registerMemoryEmbeddingProvider,
|
||||
type MemoryEmbeddingProviderAdapter,
|
||||
} from "./memory-embedding-providers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
resolvePluginCapabilityProviders: vi.fn<
|
||||
typeof import("./capability-provider-runtime.js").resolvePluginCapabilityProviders
|
||||
>(() => []),
|
||||
}));
|
||||
|
||||
vi.mock("./capability-provider-runtime.js", () => ({
|
||||
resolvePluginCapabilityProviders: mocks.resolvePluginCapabilityProviders,
|
||||
}));
|
||||
|
||||
let runtimeModule: typeof import("./memory-embedding-provider-runtime.js");
|
||||
|
||||
function createCapabilityAdapter(id: string): MemoryEmbeddingProviderAdapter {
|
||||
return {
|
||||
id,
|
||||
create: async () => ({ provider: null }),
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
clearMemoryEmbeddingProviders();
|
||||
mocks.resolvePluginCapabilityProviders.mockReset();
|
||||
mocks.resolvePluginCapabilityProviders.mockReturnValue([]);
|
||||
runtimeModule = await import("./memory-embedding-provider-runtime.js");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
clearMemoryEmbeddingProviders();
|
||||
});
|
||||
|
||||
describe("memory embedding provider runtime resolution", () => {
|
||||
it("prefers registered adapters over capability fallback adapters", () => {
|
||||
registerMemoryEmbeddingProvider({
|
||||
id: "registered",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
mocks.resolvePluginCapabilityProviders.mockReturnValue([createCapabilityAdapter("capability")]);
|
||||
|
||||
expect(runtimeModule.listMemoryEmbeddingProviders().map((adapter) => adapter.id)).toEqual([
|
||||
"registered",
|
||||
]);
|
||||
expect(runtimeModule.getMemoryEmbeddingProvider("registered")?.id).toBe("registered");
|
||||
expect(mocks.resolvePluginCapabilityProviders).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("falls back to declared capability adapters when the registry is cold", () => {
|
||||
mocks.resolvePluginCapabilityProviders.mockReturnValue([createCapabilityAdapter("ollama")]);
|
||||
|
||||
expect(runtimeModule.listMemoryEmbeddingProviders().map((adapter) => adapter.id)).toEqual([
|
||||
"ollama",
|
||||
]);
|
||||
expect(runtimeModule.getMemoryEmbeddingProvider("ollama")?.id).toBe("ollama");
|
||||
expect(mocks.resolvePluginCapabilityProviders).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("does not consult capability fallback once runtime adapters are registered", () => {
|
||||
registerMemoryEmbeddingProvider({
|
||||
id: "openai",
|
||||
create: async () => ({ provider: null }),
|
||||
});
|
||||
mocks.resolvePluginCapabilityProviders.mockReturnValue([createCapabilityAdapter("ollama")]);
|
||||
|
||||
expect(runtimeModule.getMemoryEmbeddingProvider("ollama")).toBeUndefined();
|
||||
expect(mocks.resolvePluginCapabilityProviders).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolvePluginCapabilityProviders } from "./capability-provider-runtime.js";
|
||||
import {
|
||||
getRegisteredMemoryEmbeddingProvider,
|
||||
listRegisteredMemoryEmbeddingProviders,
|
||||
type MemoryEmbeddingProviderAdapter,
|
||||
} from "./memory-embedding-providers.js";
|
||||
|
||||
export function listMemoryEmbeddingProviders(
|
||||
cfg?: OpenClawConfig,
|
||||
): MemoryEmbeddingProviderAdapter[] {
|
||||
const registered = listRegisteredMemoryEmbeddingProviders();
|
||||
if (registered.length > 0) {
|
||||
return registered.map((entry) => entry.adapter);
|
||||
}
|
||||
return resolvePluginCapabilityProviders({
|
||||
key: "memoryEmbeddingProviders",
|
||||
cfg,
|
||||
});
|
||||
}
|
||||
|
||||
export function getMemoryEmbeddingProvider(
|
||||
id: string,
|
||||
cfg?: OpenClawConfig,
|
||||
): MemoryEmbeddingProviderAdapter | undefined {
|
||||
const registered = getRegisteredMemoryEmbeddingProvider(id);
|
||||
if (registered) {
|
||||
return registered.adapter;
|
||||
}
|
||||
if (listRegisteredMemoryEmbeddingProviders().length > 0) {
|
||||
return undefined;
|
||||
}
|
||||
return listMemoryEmbeddingProviders(cfg).find((adapter) => adapter.id === id);
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ export function createEmptyPluginRegistry(): PluginRegistry {
|
|||
videoGenerationProviders: [],
|
||||
webFetchProviders: [],
|
||||
webSearchProviders: [],
|
||||
memoryEmbeddingProviders: [],
|
||||
gatewayHandlers: {},
|
||||
gatewayMethodScopes: {},
|
||||
httpRoutes: [],
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ import type { PluginActivationSource } from "./config-state.js";
|
|||
import { normalizePluginHttpPath } from "./http-path.js";
|
||||
import { findOverlappingPluginHttpRoute } from "./http-route-overlap.js";
|
||||
import { registerPluginInteractiveHandler } from "./interactive-registry.js";
|
||||
import type { PluginManifestContracts } from "./manifest.js";
|
||||
import {
|
||||
getRegisteredMemoryEmbeddingProvider,
|
||||
type MemoryEmbeddingProviderAdapter,
|
||||
registerMemoryEmbeddingProvider,
|
||||
} from "./memory-embedding-providers.js";
|
||||
import {
|
||||
|
|
@ -160,6 +162,8 @@ export type PluginWebFetchProviderRegistration =
|
|||
PluginOwnedProviderRegistration<WebFetchProviderPlugin>;
|
||||
export type PluginWebSearchProviderRegistration =
|
||||
PluginOwnedProviderRegistration<WebSearchProviderPlugin>;
|
||||
export type PluginMemoryEmbeddingProviderRegistration =
|
||||
PluginOwnedProviderRegistration<MemoryEmbeddingProviderAdapter>;
|
||||
|
||||
export type PluginHookRegistration = {
|
||||
pluginId: string;
|
||||
|
|
@ -230,6 +234,7 @@ export type PluginRecord = {
|
|||
videoGenerationProviderIds: string[];
|
||||
webFetchProviderIds: string[];
|
||||
webSearchProviderIds: string[];
|
||||
memoryEmbeddingProviderIds: string[];
|
||||
gatewayMethods: string[];
|
||||
cliCommands: string[];
|
||||
services: string[];
|
||||
|
|
@ -239,6 +244,7 @@ export type PluginRecord = {
|
|||
configSchema: boolean;
|
||||
configUiHints?: Record<string, PluginConfigUiHint>;
|
||||
configJsonSchema?: Record<string, unknown>;
|
||||
contracts?: PluginManifestContracts;
|
||||
memorySlotSelected?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -259,6 +265,7 @@ export type PluginRegistry = {
|
|||
videoGenerationProviders: PluginVideoGenerationProviderRegistration[];
|
||||
webFetchProviders: PluginWebFetchProviderRegistration[];
|
||||
webSearchProviders: PluginWebSearchProviderRegistration[];
|
||||
memoryEmbeddingProviders: PluginMemoryEmbeddingProviderRegistration[];
|
||||
gatewayHandlers: GatewayRequestHandlers;
|
||||
gatewayMethodScopes?: Partial<Record<string, OperatorScope>>;
|
||||
httpRoutes: PluginHttpRouteRegistration[];
|
||||
|
|
@ -1208,26 +1215,29 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||
registerMemoryRuntime(runtime);
|
||||
},
|
||||
registerMemoryEmbeddingProvider: (adapter) => {
|
||||
if (!hasKind(record.kind, "memory")) {
|
||||
if (hasKind(record.kind, "memory")) {
|
||||
if (
|
||||
Array.isArray(record.kind) &&
|
||||
record.kind.length > 1 &&
|
||||
!record.memorySlotSelected
|
||||
) {
|
||||
pushDiagnostic({
|
||||
level: "warn",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message:
|
||||
"dual-kind plugin not selected for memory slot; skipping memory embedding provider registration",
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (
|
||||
!(record.contracts?.memoryEmbeddingProviders ?? []).includes(adapter.id)
|
||||
) {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: "only memory plugins can register memory embedding providers",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
Array.isArray(record.kind) &&
|
||||
record.kind.length > 1 &&
|
||||
!record.memorySlotSelected
|
||||
) {
|
||||
pushDiagnostic({
|
||||
level: "warn",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message:
|
||||
"dual-kind plugin not selected for memory slot; skipping memory embedding provider registration",
|
||||
message: `plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: ${adapter.id}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -1247,6 +1257,13 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
|||
registerMemoryEmbeddingProvider(adapter, {
|
||||
ownerPluginId: record.id,
|
||||
});
|
||||
registry.memoryEmbeddingProviders.push({
|
||||
pluginId: record.id,
|
||||
pluginName: record.name,
|
||||
provider: adapter,
|
||||
source: record.source,
|
||||
rootDir: record.rootDir,
|
||||
});
|
||||
},
|
||||
on: (hookName, handler, opts) =>
|
||||
registerTypedHook(record, hookName, handler, opts, params.hookPolicy),
|
||||
|
|
|
|||
|
|
@ -206,6 +206,7 @@ describe("setActivePluginRegistry", () => {
|
|||
videoGenerationProviderIds: [],
|
||||
webFetchProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
memoryEmbeddingProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
|
|
@ -235,6 +236,7 @@ describe("setActivePluginRegistry", () => {
|
|||
videoGenerationProviderIds: [],
|
||||
webFetchProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
memoryEmbeddingProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,377 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createJiti } from "jiti";
|
||||
import { normalizeProviderId } from "../agents/provider-id.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { buildPluginApi } from "./api-builder.js";
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import { resolvePluginCacheInputs } from "./roots.js";
|
||||
import type { PluginRuntime } from "./runtime/types.js";
|
||||
import {
|
||||
buildPluginLoaderAliasMap,
|
||||
buildPluginLoaderJitiOptions,
|
||||
shouldPreferNativeJiti,
|
||||
} from "./sdk-alias.js";
|
||||
import type {
|
||||
CliBackendPlugin,
|
||||
OpenClawPluginModule,
|
||||
PluginConfigMigration,
|
||||
PluginLegacyConfigMigration,
|
||||
PluginLogger,
|
||||
PluginSetupAutoEnableProbe,
|
||||
ProviderPlugin,
|
||||
} from "./types.js";
|
||||
|
||||
const SETUP_API_EXTENSIONS = [".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"] as const;
|
||||
const CURRENT_MODULE_PATH = fileURLToPath(import.meta.url);
|
||||
const RUNNING_FROM_BUILT_ARTIFACT =
|
||||
CURRENT_MODULE_PATH.includes(`${path.sep}dist${path.sep}`) ||
|
||||
CURRENT_MODULE_PATH.includes(`${path.sep}dist-runtime${path.sep}`);
|
||||
|
||||
type SetupProviderEntry = {
|
||||
pluginId: string;
|
||||
provider: ProviderPlugin;
|
||||
};
|
||||
|
||||
type SetupCliBackendEntry = {
|
||||
pluginId: string;
|
||||
backend: CliBackendPlugin;
|
||||
};
|
||||
|
||||
type SetupConfigMigrationEntry = {
|
||||
pluginId: string;
|
||||
migrate: PluginConfigMigration;
|
||||
};
|
||||
|
||||
type SetupLegacyConfigMigrationEntry = {
|
||||
pluginId: string;
|
||||
migrate: PluginLegacyConfigMigration;
|
||||
};
|
||||
|
||||
type SetupAutoEnableProbeEntry = {
|
||||
pluginId: string;
|
||||
probe: PluginSetupAutoEnableProbe;
|
||||
};
|
||||
|
||||
type PluginSetupRegistry = {
|
||||
providers: SetupProviderEntry[];
|
||||
cliBackends: SetupCliBackendEntry[];
|
||||
configMigrations: SetupConfigMigrationEntry[];
|
||||
legacyConfigMigrations: SetupLegacyConfigMigrationEntry[];
|
||||
autoEnableProbes: SetupAutoEnableProbeEntry[];
|
||||
};
|
||||
|
||||
type SetupAutoEnableReason = {
|
||||
pluginId: string;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
const EMPTY_RUNTIME = {} as PluginRuntime;
|
||||
const NOOP_LOGGER: PluginLogger = {
|
||||
info() {},
|
||||
warn() {},
|
||||
error() {},
|
||||
};
|
||||
|
||||
const jitiLoaders = new Map<string, ReturnType<typeof createJiti>>();
|
||||
const setupRegistryCache = new Map<string, PluginSetupRegistry>();
|
||||
|
||||
export function clearPluginSetupRegistryCache(): void {
|
||||
setupRegistryCache.clear();
|
||||
}
|
||||
|
||||
function getJiti(modulePath: string) {
|
||||
const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url);
|
||||
const cacheKey = JSON.stringify({
|
||||
tryNative: shouldPreferNativeJiti(modulePath),
|
||||
aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)),
|
||||
});
|
||||
const cached = jitiLoaders.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const loader = createJiti(modulePath, buildPluginLoaderJitiOptions(aliasMap));
|
||||
jitiLoaders.set(cacheKey, loader);
|
||||
return loader;
|
||||
}
|
||||
|
||||
function buildSetupRegistryCacheKey(params: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): string {
|
||||
const { roots, loadPaths } = resolvePluginCacheInputs({
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
return JSON.stringify({
|
||||
roots,
|
||||
loadPaths,
|
||||
});
|
||||
}
|
||||
|
||||
function resolveSetupApiPath(rootDir: string): string | null {
|
||||
const orderedExtensions = RUNNING_FROM_BUILT_ARTIFACT
|
||||
? SETUP_API_EXTENSIONS
|
||||
: ([...SETUP_API_EXTENSIONS.slice(3), ...SETUP_API_EXTENSIONS.slice(0, 3)] as const);
|
||||
for (const extension of orderedExtensions) {
|
||||
const candidate = path.join(rootDir, `setup-api${extension}`);
|
||||
if (fs.existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveRegister(mod: OpenClawPluginModule): {
|
||||
definition?: { id?: string };
|
||||
register?: (api: ReturnType<typeof buildPluginApi>) => void | Promise<void>;
|
||||
} {
|
||||
if (typeof mod === "function") {
|
||||
return { register: mod };
|
||||
}
|
||||
if (mod && typeof mod === "object" && typeof mod.register === "function") {
|
||||
return {
|
||||
definition: mod as { id?: string },
|
||||
register: mod.register.bind(mod),
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function matchesProvider(provider: ProviderPlugin, providerId: string): boolean {
|
||||
const normalized = normalizeProviderId(providerId);
|
||||
if (normalizeProviderId(provider.id) === normalized) {
|
||||
return true;
|
||||
}
|
||||
return [...(provider.aliases ?? []), ...(provider.hookAliases ?? [])].some(
|
||||
(alias) => normalizeProviderId(alias) === normalized,
|
||||
);
|
||||
}
|
||||
|
||||
export function resolvePluginSetupRegistry(params?: {
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): PluginSetupRegistry {
|
||||
const env = params?.env ?? process.env;
|
||||
const cacheKey = buildSetupRegistryCacheKey({
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env,
|
||||
});
|
||||
const cached = setupRegistryCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const providers: SetupProviderEntry[] = [];
|
||||
const cliBackends: SetupCliBackendEntry[] = [];
|
||||
const configMigrations: SetupConfigMigrationEntry[] = [];
|
||||
const legacyConfigMigrations: SetupLegacyConfigMigrationEntry[] = [];
|
||||
const autoEnableProbes: SetupAutoEnableProbeEntry[] = [];
|
||||
const providerKeys = new Set<string>();
|
||||
const cliBackendKeys = new Set<string>();
|
||||
|
||||
const discovery = discoverOpenClawPlugins({
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env,
|
||||
cache: true,
|
||||
});
|
||||
const manifestRegistry = loadPluginManifestRegistry({
|
||||
workspaceDir: params?.workspaceDir,
|
||||
env,
|
||||
cache: true,
|
||||
candidates: discovery.candidates,
|
||||
diagnostics: discovery.diagnostics,
|
||||
});
|
||||
|
||||
for (const record of manifestRegistry.plugins) {
|
||||
const setupSource = resolveSetupApiPath(record.rootDir);
|
||||
if (!setupSource) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mod: OpenClawPluginModule;
|
||||
try {
|
||||
mod = getJiti(setupSource)(setupSource) as OpenClawPluginModule;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
const resolved = resolveRegister((mod as { default?: OpenClawPluginModule }).default ?? mod);
|
||||
if (!resolved.register) {
|
||||
continue;
|
||||
}
|
||||
if (resolved.definition?.id && resolved.definition.id !== record.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const api = buildPluginApi({
|
||||
id: record.id,
|
||||
name: record.name ?? record.id,
|
||||
version: record.version,
|
||||
description: record.description,
|
||||
source: setupSource,
|
||||
rootDir: record.rootDir,
|
||||
registrationMode: "setup-only",
|
||||
config: {} as OpenClawConfig,
|
||||
runtime: EMPTY_RUNTIME,
|
||||
logger: NOOP_LOGGER,
|
||||
resolvePath: (input) => input,
|
||||
handlers: {
|
||||
registerProvider(provider) {
|
||||
const key = `${record.id}:${normalizeProviderId(provider.id)}`;
|
||||
if (providerKeys.has(key)) {
|
||||
return;
|
||||
}
|
||||
providerKeys.add(key);
|
||||
providers.push({
|
||||
pluginId: record.id,
|
||||
provider,
|
||||
});
|
||||
},
|
||||
registerCliBackend(backend) {
|
||||
const key = `${record.id}:${normalizeProviderId(backend.id)}`;
|
||||
if (cliBackendKeys.has(key)) {
|
||||
return;
|
||||
}
|
||||
cliBackendKeys.add(key);
|
||||
cliBackends.push({
|
||||
pluginId: record.id,
|
||||
backend,
|
||||
});
|
||||
},
|
||||
registerConfigMigration(migrate) {
|
||||
configMigrations.push({
|
||||
pluginId: record.id,
|
||||
migrate,
|
||||
});
|
||||
},
|
||||
registerLegacyConfigMigration(migrate) {
|
||||
legacyConfigMigrations.push({
|
||||
pluginId: record.id,
|
||||
migrate,
|
||||
});
|
||||
},
|
||||
registerAutoEnableProbe(probe) {
|
||||
autoEnableProbes.push({
|
||||
pluginId: record.id,
|
||||
probe,
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const result = resolved.register(api);
|
||||
if (result && typeof result.then === "function") {
|
||||
// Keep setup registration sync-only.
|
||||
}
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const registry = {
|
||||
providers,
|
||||
cliBackends,
|
||||
configMigrations,
|
||||
legacyConfigMigrations,
|
||||
autoEnableProbes,
|
||||
} satisfies PluginSetupRegistry;
|
||||
setupRegistryCache.set(cacheKey, registry);
|
||||
return registry;
|
||||
}
|
||||
|
||||
export function resolvePluginSetupProvider(params: {
|
||||
provider: string;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): ProviderPlugin | undefined {
|
||||
return resolvePluginSetupRegistry(params).providers.find((entry) =>
|
||||
matchesProvider(entry.provider, params.provider),
|
||||
)?.provider;
|
||||
}
|
||||
|
||||
export function resolvePluginSetupCliBackend(params: {
|
||||
backend: string;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): SetupCliBackendEntry | undefined {
|
||||
const normalized = normalizeProviderId(params.backend);
|
||||
return resolvePluginSetupRegistry(params).cliBackends.find(
|
||||
(entry) => normalizeProviderId(entry.backend.id) === normalized,
|
||||
);
|
||||
}
|
||||
|
||||
export function runPluginSetupConfigMigrations(params: {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
} {
|
||||
let next = params.config;
|
||||
const changes: string[] = [];
|
||||
|
||||
for (const entry of resolvePluginSetupRegistry(params).configMigrations) {
|
||||
const migration = entry.migrate(next);
|
||||
if (!migration || migration.changes.length === 0) {
|
||||
continue;
|
||||
}
|
||||
next = migration.config;
|
||||
changes.push(...migration.changes);
|
||||
}
|
||||
|
||||
return { config: next, changes };
|
||||
}
|
||||
|
||||
export function runPluginSetupLegacyConfigMigrations(params: {
|
||||
raw: Record<string, unknown>;
|
||||
changes: string[];
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): void {
|
||||
for (const entry of resolvePluginSetupRegistry(params).legacyConfigMigrations) {
|
||||
entry.migrate(params.raw, params.changes);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolvePluginSetupAutoEnableReasons(params: {
|
||||
config: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
}): SetupAutoEnableReason[] {
|
||||
const env = params.env ?? process.env;
|
||||
const reasons: SetupAutoEnableReason[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const entry of resolvePluginSetupRegistry({
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
}).autoEnableProbes) {
|
||||
const raw = entry.probe({
|
||||
config: params.config,
|
||||
env,
|
||||
});
|
||||
const values = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
||||
for (const reason of values) {
|
||||
const normalized = reason.trim();
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
const key = `${entry.pluginId}:${normalized}`;
|
||||
if (seen.has(key)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(key);
|
||||
reasons.push({
|
||||
pluginId: entry.pluginId,
|
||||
reason: normalized,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return reasons;
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ export function createPluginRecord(
|
|||
videoGenerationProviderIds: [],
|
||||
webFetchProviderIds: [],
|
||||
webSearchProviderIds: [],
|
||||
memoryEmbeddingProviderIds: [],
|
||||
gatewayMethods: [],
|
||||
cliCommands: [],
|
||||
services: [],
|
||||
|
|
@ -123,6 +124,7 @@ export function createPluginLoadResult(
|
|||
videoGenerationProviders: [],
|
||||
webFetchProviders: [],
|
||||
webSearchProviders: [],
|
||||
memoryEmbeddingProviders: [],
|
||||
tools: [],
|
||||
hooks: [],
|
||||
typedHooks: [],
|
||||
|
|
|
|||
|
|
@ -1988,6 +1988,25 @@ export type OpenClawPluginModule =
|
|||
|
||||
export type PluginRegistrationMode = "full" | "setup-only" | "setup-runtime" | "cli-metadata";
|
||||
|
||||
export type PluginConfigMigration = (config: OpenClawConfig) =>
|
||||
| {
|
||||
config: OpenClawConfig;
|
||||
changes: string[];
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
|
||||
export type PluginLegacyConfigMigration = (raw: Record<string, unknown>, changes: string[]) => void;
|
||||
|
||||
export type PluginSetupAutoEnableContext = {
|
||||
config: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
};
|
||||
|
||||
export type PluginSetupAutoEnableProbe = (
|
||||
ctx: PluginSetupAutoEnableContext,
|
||||
) => string | string[] | null | undefined;
|
||||
|
||||
/** Main registration API injected into native plugin entry files. */
|
||||
export type OpenClawPluginApi = {
|
||||
id: string;
|
||||
|
|
@ -2049,6 +2068,12 @@ export type OpenClawPluginApi = {
|
|||
registerService: (service: OpenClawPluginService) => void;
|
||||
/** Register a text-only CLI backend used by the local CLI runner. */
|
||||
registerCliBackend: (backend: CliBackendPlugin) => void;
|
||||
/** Register a lightweight config migration that can run before plugin runtime loads. */
|
||||
registerConfigMigration: (migrate: PluginConfigMigration) => void;
|
||||
/** Register a lightweight raw legacy-config migration for pre-schema config repair. */
|
||||
registerLegacyConfigMigration: (migrate: PluginLegacyConfigMigration) => void;
|
||||
/** Register a lightweight config probe that can auto-enable this plugin generically. */
|
||||
registerAutoEnableProbe: (probe: PluginSetupAutoEnableProbe) => void;
|
||||
/** Register a native model/provider plugin (text inference capability). */
|
||||
registerProvider: (provider: ProviderPlugin) => void;
|
||||
/** Register a speech synthesis provider (speech capability). */
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
import { isIP } from "node:net";
|
||||
import path from "node:path";
|
||||
import {
|
||||
redactCdpUrl,
|
||||
resolveBrowserConfig,
|
||||
resolveBrowserControlAuth,
|
||||
resolveProfile,
|
||||
} from "../../extensions/browser/runtime-api.js";
|
||||
import { resolveSandboxConfigForAgent } from "../agents/sandbox.js";
|
||||
import { hasPotentialConfiguredChannels } from "../channels/config-presence.js";
|
||||
import type { listChannelPlugins } from "../channels/plugins/index.js";
|
||||
|
|
@ -23,6 +17,12 @@ import {
|
|||
import { listRiskyConfiguredSafeBins } from "../infra/exec-safe-bin-semantics.js";
|
||||
import { normalizeTrustedSafeBinDirs } from "../infra/exec-safe-bin-trust.js";
|
||||
import { isBlockedHostnameOrIp, isPrivateNetworkAllowedByPolicy } from "../infra/net/ssrf.js";
|
||||
import {
|
||||
redactCdpUrl,
|
||||
resolveBrowserConfig,
|
||||
resolveProfile,
|
||||
} from "../plugin-sdk/browser-config.js";
|
||||
import { resolveBrowserControlAuth } from "../plugin-sdk/browser-control-auth.js";
|
||||
import { DEFAULT_AGENT_ID } from "../routing/session-key.js";
|
||||
import {
|
||||
formatPermissionDetail,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const createTestRegistry = (channels: TestChannelRegistration[] = []): Pl
|
|||
videoGenerationProviders: [],
|
||||
webFetchProviders: [],
|
||||
webSearchProviders: [],
|
||||
memoryEmbeddingProviders: [],
|
||||
gatewayHandlers: {},
|
||||
httpRoutes: [],
|
||||
cliRegistrars: [],
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi
|
|||
registerCli() {},
|
||||
registerService() {},
|
||||
registerCliBackend() {},
|
||||
registerConfigMigration() {},
|
||||
registerLegacyConfigMigration() {},
|
||||
registerAutoEnableProbe() {},
|
||||
registerProvider() {},
|
||||
registerSpeechProvider() {},
|
||||
registerRealtimeTranscriptionProvider() {},
|
||||
|
|
|
|||
Loading…
Reference in New Issue