mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 07:01:40 +00:00
* feat(plugins): load native manifest MCP servers * fix(gateway): advertise proxied plugin surface ports * fix(codex): retain MCP App transcript previews * fix(codex): render native MCP apps inline * fix(mcp-apps): resolve harness-native views by session * fix(codex): normalize null MCP result metadata * fix(ui): give inline MCP apps full message width * test(codex): use generic native MCP App fixtures * chore(plugin-sdk): refresh harness runtime baseline * refactor(codex): isolate native MCP App contracts * fix(codex): satisfy native app CI contracts * fix(ci): scope automation app tokens * chore(ci): defer token scopes to current main
150 lines
5.1 KiB
TypeScript
150 lines
5.1 KiB
TypeScript
// Shares bundled plugin config merge behavior across setup and runtime code.
|
|
import { applyMergePatch } from "../config/merge-patch.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { matchRootFileOpenFailure, type RootFileOpenFailure } from "../infra/boundary-file-read.js";
|
|
import { readRootJsonObjectSync } from "../infra/json-files.js";
|
|
import { normalizePluginsConfig, resolveEffectivePluginActivationState } from "./config-state.js";
|
|
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
|
|
import type { PluginBundleFormat } from "./manifest-types.js";
|
|
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
|
|
|
type ReadBundleJsonResult =
|
|
| { ok: true; raw: Record<string, unknown> }
|
|
| { ok: false; error: string };
|
|
|
|
type BundleServerRuntimeSupport = {
|
|
hasSupportedServer: boolean;
|
|
supportedServerNames: string[];
|
|
unsupportedServerNames: string[];
|
|
diagnostics: string[];
|
|
};
|
|
|
|
export function readBundleJsonObject(params: {
|
|
rootDir: string;
|
|
relativePath: string;
|
|
onOpenFailure?: (failure: RootFileOpenFailure) => ReadBundleJsonResult;
|
|
}): ReadBundleJsonResult {
|
|
const result = readRootJsonObjectSync({
|
|
rootDir: params.rootDir,
|
|
relativePath: params.relativePath,
|
|
boundaryLabel: "plugin root",
|
|
rejectHardlinks: true,
|
|
});
|
|
if (result.ok) {
|
|
return { ok: true, raw: result.value };
|
|
}
|
|
if (result.reason === "open") {
|
|
return params.onOpenFailure?.(result.failure) ?? { ok: true, raw: {} };
|
|
}
|
|
return { ok: false, error: result.error };
|
|
}
|
|
|
|
export function resolveBundleJsonOpenFailure(params: {
|
|
failure: RootFileOpenFailure;
|
|
relativePath: string;
|
|
allowMissing?: boolean;
|
|
}): ReadBundleJsonResult {
|
|
return matchRootFileOpenFailure(params.failure, {
|
|
path: () => {
|
|
if (params.allowMissing) {
|
|
return { ok: true, raw: {} };
|
|
}
|
|
return { ok: false, error: `unable to read ${params.relativePath}: path` };
|
|
},
|
|
fallback: (failure) => ({
|
|
ok: false,
|
|
error: `unable to read ${params.relativePath}: ${failure.reason}`,
|
|
}),
|
|
});
|
|
}
|
|
|
|
export function inspectBundleServerRuntimeSupport<TConfig>(params: {
|
|
loaded: { config: TConfig; diagnostics: string[] };
|
|
resolveServers: (config: TConfig) => Record<string, Record<string, unknown>>;
|
|
}): BundleServerRuntimeSupport {
|
|
const supportedServerNames: string[] = [];
|
|
const unsupportedServerNames: string[] = [];
|
|
let hasSupportedServer = false;
|
|
for (const [serverName, server] of Object.entries(params.resolveServers(params.loaded.config))) {
|
|
if (typeof server.command === "string" && server.command.trim().length > 0) {
|
|
hasSupportedServer = true;
|
|
supportedServerNames.push(serverName);
|
|
continue;
|
|
}
|
|
unsupportedServerNames.push(serverName);
|
|
}
|
|
return {
|
|
hasSupportedServer,
|
|
supportedServerNames,
|
|
unsupportedServerNames,
|
|
diagnostics: params.loaded.diagnostics,
|
|
};
|
|
}
|
|
|
|
export function loadEnabledBundleConfig<TConfig, TDiagnostic>(params: {
|
|
workspaceDir: string;
|
|
cfg?: OpenClawConfig;
|
|
manifestRegistry?: Pick<PluginManifestRegistry, "plugins">;
|
|
createEmptyConfig: () => TConfig;
|
|
loadBundleConfig: (params: {
|
|
pluginId: string;
|
|
rootDir: string;
|
|
bundleFormat: PluginBundleFormat;
|
|
}) => { config: TConfig; diagnostics: string[] };
|
|
loadNativePluginConfig?: (params: {
|
|
record: PluginManifestRecord;
|
|
}) => { config: TConfig; diagnostics: string[] } | undefined;
|
|
createDiagnostic: (pluginId: string, message: string) => TDiagnostic;
|
|
}): { config: TConfig; diagnostics: TDiagnostic[] } {
|
|
const normalizedPlugins = normalizePluginsConfig(params.cfg?.plugins);
|
|
if (!normalizedPlugins.enabled) {
|
|
return { config: params.createEmptyConfig(), diagnostics: [] };
|
|
}
|
|
|
|
const registry =
|
|
params.manifestRegistry ??
|
|
loadPluginManifestRegistryForPluginRegistry({
|
|
workspaceDir: params.workspaceDir,
|
|
config: params.cfg,
|
|
includeDisabled: true,
|
|
});
|
|
const diagnostics: TDiagnostic[] = [];
|
|
let merged = params.createEmptyConfig();
|
|
|
|
for (const record of registry.plugins) {
|
|
const canLoadBundle = record.format === "bundle" && Boolean(record.bundleFormat);
|
|
const canLoadNative = record.format !== "bundle" && params.loadNativePluginConfig !== undefined;
|
|
if (!canLoadBundle && !canLoadNative) {
|
|
continue;
|
|
}
|
|
const activationState = resolveEffectivePluginActivationState({
|
|
id: record.id,
|
|
origin: record.origin,
|
|
config: normalizedPlugins,
|
|
rootConfig: params.cfg,
|
|
enabledByDefault: record.enabledByDefault,
|
|
});
|
|
if (!activationState.activated) {
|
|
continue;
|
|
}
|
|
|
|
const loaded =
|
|
canLoadBundle && record.bundleFormat
|
|
? params.loadBundleConfig({
|
|
pluginId: record.id,
|
|
rootDir: record.rootDir,
|
|
bundleFormat: record.bundleFormat,
|
|
})
|
|
: params.loadNativePluginConfig?.({ record });
|
|
if (!loaded) {
|
|
continue;
|
|
}
|
|
merged = applyMergePatch(merged, loaded.config) as TConfig;
|
|
for (const message of loaded.diagnostics) {
|
|
diagnostics.push(params.createDiagnostic(record.id, message));
|
|
}
|
|
}
|
|
|
|
return { config: merged, diagnostics };
|
|
}
|