mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:10:51 +00:00
refactor: unify plugin metadata cache paths
This commit is contained in:
@@ -1,11 +1,85 @@
|
||||
import {
|
||||
listBundledPluginMetadata,
|
||||
resolveBundledPluginGeneratedPath,
|
||||
resolveBundledPluginWorkspaceSourcePath,
|
||||
type BundledPluginMetadata,
|
||||
} from "./bundled-plugin-metadata.js";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { resolveBundledPluginGeneratedPath } from "./bundled-plugin-metadata.js";
|
||||
import type { PluginManifestRecord } from "./manifest-registry.js";
|
||||
import type { OpenClawPackageManifest } from "./manifest.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
||||
|
||||
export type BundledChannelPluginMetadata = BundledPluginMetadata;
|
||||
type BundledChannelEntryPathPair = {
|
||||
source: string;
|
||||
built: string;
|
||||
};
|
||||
|
||||
export type BundledChannelPluginMetadata = {
|
||||
dirName: string;
|
||||
source: BundledChannelEntryPathPair;
|
||||
setupSource?: BundledChannelEntryPathPair;
|
||||
manifest: {
|
||||
id: string;
|
||||
channels?: readonly string[];
|
||||
};
|
||||
packageManifest?: OpenClawPackageManifest;
|
||||
rootDir: string;
|
||||
};
|
||||
|
||||
function resolveBundledMetadataEnv(params?: {
|
||||
rootDir?: string;
|
||||
scanDir?: string;
|
||||
}): NodeJS.ProcessEnv | undefined {
|
||||
const overrideDir = params?.scanDir
|
||||
? path.resolve(params.scanDir)
|
||||
: params?.rootDir
|
||||
? resolveBundledPluginsDirForRoot(params.rootDir)
|
||||
: undefined;
|
||||
if (!overrideDir) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...process.env,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: overrideDir,
|
||||
OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR: "1",
|
||||
};
|
||||
}
|
||||
|
||||
function resolveBundledPluginsDirForRoot(rootDir: string): string | undefined {
|
||||
const candidates = [
|
||||
path.join(rootDir, "extensions"),
|
||||
path.join(rootDir, "dist-runtime", "extensions"),
|
||||
path.join(rootDir, "dist", "extensions"),
|
||||
];
|
||||
return candidates.find((candidate) => fs.existsSync(candidate));
|
||||
}
|
||||
|
||||
function toBundledChannelEntryPair(source: string | undefined): BundledChannelEntryPathPair | null {
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
return { source, built: source };
|
||||
}
|
||||
|
||||
function toBundledChannelPluginMetadata(
|
||||
record: PluginManifestRecord,
|
||||
): BundledChannelPluginMetadata | null {
|
||||
if (record.origin !== "bundled") {
|
||||
return null;
|
||||
}
|
||||
const source = toBundledChannelEntryPair(record.source);
|
||||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
const setupSource = toBundledChannelEntryPair(record.setupSource);
|
||||
return {
|
||||
dirName: path.basename(record.rootDir),
|
||||
source,
|
||||
...(setupSource ? { setupSource } : {}),
|
||||
manifest: {
|
||||
id: record.id,
|
||||
channels: record.channels,
|
||||
},
|
||||
...(record.packageManifest ? { packageManifest: record.packageManifest } : {}),
|
||||
rootDir: record.rootDir,
|
||||
};
|
||||
}
|
||||
|
||||
export function listBundledChannelPluginMetadata(params?: {
|
||||
rootDir?: string;
|
||||
@@ -13,12 +87,15 @@ export function listBundledChannelPluginMetadata(params?: {
|
||||
includeChannelConfigs?: boolean;
|
||||
includeSyntheticChannelConfigs?: boolean;
|
||||
}): readonly BundledChannelPluginMetadata[] {
|
||||
return listBundledPluginMetadata(params);
|
||||
return loadPluginManifestRegistryForPluginRegistry({
|
||||
env: resolveBundledMetadataEnv(params),
|
||||
includeDisabled: true,
|
||||
}).plugins.flatMap((record) => toBundledChannelPluginMetadata(record) ?? []);
|
||||
}
|
||||
|
||||
export function resolveBundledChannelGeneratedPath(
|
||||
rootDir: string,
|
||||
entry: BundledPluginMetadata["source"] | BundledPluginMetadata["setupSource"],
|
||||
entry: BundledChannelPluginMetadata["source"] | BundledChannelPluginMetadata["setupSource"],
|
||||
pluginDirName?: string,
|
||||
scanDir?: string,
|
||||
): string | null {
|
||||
@@ -30,5 +107,12 @@ export function resolveBundledChannelWorkspacePath(params: {
|
||||
scanDir?: string;
|
||||
pluginId: string;
|
||||
}): string | null {
|
||||
return resolveBundledPluginWorkspaceSourcePath(params);
|
||||
return (
|
||||
listBundledChannelPluginMetadata({
|
||||
rootDir: params.rootDir,
|
||||
...(params.scanDir ? { scanDir: params.scanDir } : {}),
|
||||
includeChannelConfigs: false,
|
||||
includeSyntheticChannelConfigs: false,
|
||||
}).find((metadata) => metadata.manifest.id === params.pluginId)?.rootDir ?? null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ 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}`);
|
||||
const DEFAULT_ROOT_METADATA_CACHE = new Map<string, readonly BundledPluginMetadata[]>();
|
||||
|
||||
type BundledPluginPathPair = {
|
||||
source: string;
|
||||
@@ -186,18 +185,6 @@ export function listBundledPluginMetadata(params?: {
|
||||
const includeChannelConfigs = params?.includeChannelConfigs ?? !RUNNING_FROM_BUILT_ARTIFACT;
|
||||
const includeSyntheticChannelConfigs =
|
||||
params?.includeSyntheticChannelConfigs ?? includeChannelConfigs;
|
||||
const cacheKey =
|
||||
!params?.rootDir && !scanDir
|
||||
? JSON.stringify({
|
||||
resolvedScanDir,
|
||||
includeChannelConfigs,
|
||||
includeSyntheticChannelConfigs,
|
||||
})
|
||||
: undefined;
|
||||
const cached = cacheKey ? DEFAULT_ROOT_METADATA_CACHE.get(cacheKey) : undefined;
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
const metadata = Object.freeze(
|
||||
collectBundledPluginMetadata(
|
||||
resolvedScanDir,
|
||||
@@ -205,9 +192,6 @@ export function listBundledPluginMetadata(params?: {
|
||||
includeSyntheticChannelConfigs,
|
||||
),
|
||||
);
|
||||
if (cacheKey) {
|
||||
DEFAULT_ROOT_METADATA_CACHE.set(cacheKey, metadata);
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,15 +4,20 @@ import type { PluginManifestRegistry } from "./manifest-registry.js";
|
||||
const mocks = vi.hoisted(() => {
|
||||
const loadManifestRegistry = vi.fn();
|
||||
return {
|
||||
findBundledPluginMetadataById: vi.fn(),
|
||||
discoverOpenClawPlugins: vi.fn(() => ({ candidates: [], diagnostics: [] })),
|
||||
loadBundledManifestRegistry: vi.fn(),
|
||||
loadPluginManifestRegistryForInstalledIndex: loadManifestRegistry,
|
||||
loadPluginManifestRegistryForPluginRegistry: loadManifestRegistry,
|
||||
loadPluginRegistrySnapshot: vi.fn(() => ({ plugins: [] })),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./bundled-plugin-metadata.js", () => ({
|
||||
findBundledPluginMetadataById: mocks.findBundledPluginMetadataById,
|
||||
vi.mock("./discovery.js", () => ({
|
||||
discoverOpenClawPlugins: mocks.discoverOpenClawPlugins,
|
||||
}));
|
||||
|
||||
vi.mock("./manifest-registry.js", () => ({
|
||||
loadPluginManifestRegistry: mocks.loadBundledManifestRegistry,
|
||||
}));
|
||||
|
||||
vi.mock("./manifest-registry-installed.js", () => ({
|
||||
@@ -77,14 +82,17 @@ function createPluginRecord(
|
||||
|
||||
describe("resolvePluginConfigContractsById", () => {
|
||||
beforeEach(() => {
|
||||
mocks.findBundledPluginMetadataById.mockReset();
|
||||
mocks.discoverOpenClawPlugins.mockReset();
|
||||
mocks.discoverOpenClawPlugins.mockReturnValue({ candidates: [], diagnostics: [] });
|
||||
mocks.loadBundledManifestRegistry.mockReset();
|
||||
mocks.loadBundledManifestRegistry.mockReturnValue(createRegistry([]));
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReset();
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(createRegistry([]));
|
||||
mocks.loadPluginRegistrySnapshot.mockReset();
|
||||
mocks.loadPluginRegistrySnapshot.mockReturnValue({ plugins: [] });
|
||||
});
|
||||
|
||||
it("does not fall back to bundled metadata when registry already resolved a plugin without config contracts", () => {
|
||||
it("does not fall back to bundled registry when registry already resolved a plugin without config contracts", () => {
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
|
||||
createRegistry([
|
||||
createPluginRecord({
|
||||
@@ -99,10 +107,10 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
pluginIds: ["brave"],
|
||||
}),
|
||||
).toEqual(new Map());
|
||||
expect(mocks.findBundledPluginMetadataById).not.toHaveBeenCalled();
|
||||
expect(mocks.loadBundledManifestRegistry).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("can hydrate missing contracts from bundled metadata for resolved bundled plugins", () => {
|
||||
it("can hydrate missing contracts from bundled registry for resolved bundled plugins", () => {
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
|
||||
createRegistry([
|
||||
createPluginRecord({
|
||||
@@ -114,15 +122,19 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
}),
|
||||
]),
|
||||
);
|
||||
mocks.findBundledPluginMetadataById.mockReturnValue({
|
||||
manifest: {
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [{ path: "twilio.authToken", expected: "string" }],
|
||||
mocks.loadBundledManifestRegistry.mockReturnValue(
|
||||
createRegistry([
|
||||
createPluginRecord({
|
||||
id: "voice-call",
|
||||
origin: "bundled",
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [{ path: "twilio.authToken", expected: "string" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(
|
||||
resolvePluginConfigContractsById({
|
||||
@@ -147,7 +159,7 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("refreshes stale bundled SecretInput contracts from bundled metadata", () => {
|
||||
it("refreshes stale bundled SecretInput contracts from bundled registry", () => {
|
||||
mocks.loadPluginManifestRegistryForInstalledIndex.mockReturnValue(
|
||||
createRegistry([
|
||||
createPluginRecord({
|
||||
@@ -162,18 +174,22 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
}),
|
||||
]),
|
||||
);
|
||||
mocks.findBundledPluginMetadataById.mockReturnValue({
|
||||
manifest: {
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [
|
||||
{ path: "twilio.authToken", expected: "string" },
|
||||
{ path: "realtime.providers.*.apiKey", expected: "string" },
|
||||
],
|
||||
mocks.loadBundledManifestRegistry.mockReturnValue(
|
||||
createRegistry([
|
||||
createPluginRecord({
|
||||
id: "voice-call",
|
||||
origin: "bundled",
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [
|
||||
{ path: "twilio.authToken", expected: "string" },
|
||||
{ path: "realtime.providers.*.apiKey", expected: "string" },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(
|
||||
resolvePluginConfigContractsById({
|
||||
@@ -210,15 +226,19 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
}),
|
||||
]),
|
||||
);
|
||||
mocks.findBundledPluginMetadataById.mockReturnValue({
|
||||
manifest: {
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [{ path: "tts.providers.*.apiKey", expected: "string" }],
|
||||
mocks.loadBundledManifestRegistry.mockReturnValue(
|
||||
createRegistry([
|
||||
createPluginRecord({
|
||||
id: "voice-call",
|
||||
origin: "bundled",
|
||||
configContracts: {
|
||||
secretInputs: {
|
||||
paths: [{ path: "tts.providers.*.apiKey", expected: "string" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
expect(
|
||||
resolvePluginConfigContractsById({
|
||||
@@ -249,6 +269,6 @@ describe("resolvePluginConfigContractsById", () => {
|
||||
fallbackToBundledMetadata: false,
|
||||
}),
|
||||
).toEqual(new Map());
|
||||
expect(mocks.findBundledPluginMetadataById).not.toHaveBeenCalled();
|
||||
expect(mocks.loadBundledManifestRegistry).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { findBundledPluginMetadataById } from "./bundled-plugin-metadata.js";
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import { loadPluginManifestRegistry } from "./manifest-registry.js";
|
||||
import type { PluginManifestConfigContracts } from "./manifest.js";
|
||||
import type { PluginOrigin } from "./plugin-origin.types.js";
|
||||
import { loadPluginManifestRegistryForPluginRegistry } from "./plugin-registry.js";
|
||||
@@ -116,6 +117,32 @@ export function resolvePluginConfigContractsById(params: {
|
||||
const fallbackBundledPluginIds = new Set(
|
||||
(params.fallbackBundledPluginIds ?? []).map((pluginId) => pluginId.trim()).filter(Boolean),
|
||||
);
|
||||
const bundledContractFallbacks = new Map<string, PluginManifestConfigContracts | undefined>();
|
||||
const findBundledConfigContracts = (
|
||||
pluginId: string,
|
||||
): PluginManifestConfigContracts | undefined => {
|
||||
if (bundledContractFallbacks.has(pluginId)) {
|
||||
return bundledContractFallbacks.get(pluginId);
|
||||
}
|
||||
const discovery = discoverOpenClawPlugins({
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
});
|
||||
const registry = loadPluginManifestRegistry({
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env: params.env,
|
||||
candidates: discovery.candidates.filter((candidate) => candidate.origin === "bundled"),
|
||||
diagnostics: discovery.diagnostics,
|
||||
});
|
||||
for (const plugin of registry.plugins) {
|
||||
bundledContractFallbacks.set(plugin.id, plugin.configContracts);
|
||||
}
|
||||
if (!bundledContractFallbacks.has(pluginId)) {
|
||||
bundledContractFallbacks.set(pluginId, undefined);
|
||||
}
|
||||
return bundledContractFallbacks.get(pluginId);
|
||||
};
|
||||
|
||||
const resolvedPluginOrigins = new Map<string, PluginOrigin>();
|
||||
const registry = loadPluginManifestRegistryForPluginRegistry({
|
||||
@@ -146,18 +173,15 @@ export function resolvePluginConfigContractsById(params: {
|
||||
((params.fallbackToBundledMetadataForResolvedBundled && existing.origin === "bundled") ||
|
||||
fallbackBundledPluginIds.has(pluginId));
|
||||
if (shouldHydrateBundledMatch) {
|
||||
const bundled = findBundledPluginMetadataById(pluginId, {
|
||||
includeChannelConfigs: false,
|
||||
includeSyntheticChannelConfigs: false,
|
||||
});
|
||||
if (bundled?.manifest.configContracts) {
|
||||
const bundledConfigContracts = findBundledConfigContracts(pluginId);
|
||||
if (bundledConfigContracts) {
|
||||
matches.set(pluginId, {
|
||||
origin: fallbackBundledPluginIds.has(pluginId) ? "bundled" : existing.origin,
|
||||
configContracts: {
|
||||
...bundled.manifest.configContracts,
|
||||
...bundledConfigContracts,
|
||||
...existing.configContracts,
|
||||
...(bundled.manifest.configContracts.secretInputs
|
||||
? { secretInputs: bundled.manifest.configContracts.secretInputs }
|
||||
...(bundledConfigContracts.secretInputs
|
||||
? { secretInputs: bundledConfigContracts.secretInputs }
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
@@ -175,16 +199,13 @@ export function resolvePluginConfigContractsById(params: {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const bundled = findBundledPluginMetadataById(pluginId, {
|
||||
includeChannelConfigs: false,
|
||||
includeSyntheticChannelConfigs: false,
|
||||
});
|
||||
if (!bundled?.manifest.configContracts) {
|
||||
const bundledConfigContracts = findBundledConfigContracts(pluginId);
|
||||
if (!bundledConfigContracts) {
|
||||
continue;
|
||||
}
|
||||
matches.set(pluginId, {
|
||||
origin: "bundled",
|
||||
configContracts: bundled.manifest.configContracts,
|
||||
configContracts: bundledConfigContracts,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,12 +155,13 @@ describe("normalizePluginsConfig", () => {
|
||||
expect(result.entries.minimax?.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it("reuses the bundled alias scan during one config normalization", async () => {
|
||||
it("reuses the plugin alias discovery during one config normalization", async () => {
|
||||
vi.resetModules();
|
||||
const bundledPluginMetadata = await import("./bundled-plugin-metadata.js");
|
||||
const listBundledMetadata = vi.spyOn(bundledPluginMetadata, "listBundledPluginMetadata");
|
||||
const discovery = await import("./discovery.js");
|
||||
const discoverPlugins = vi.spyOn(discovery, "discoverOpenClawPlugins");
|
||||
const { normalizePluginsConfig: normalizeFreshPluginsConfig } =
|
||||
await import("./config-state.js");
|
||||
discoverPlugins.mockClear();
|
||||
|
||||
const result = normalizeFreshPluginsConfig({
|
||||
allow: ["unknown-plugin-one", "unknown-plugin-two"],
|
||||
@@ -175,7 +176,7 @@ describe("normalizePluginsConfig", () => {
|
||||
expect(result.allow).toEqual(["unknown-plugin-one", "unknown-plugin-two"]);
|
||||
expect(result.deny).toEqual(["unknown-plugin-three"]);
|
||||
expect(result.entries["unknown-plugin-four"]?.enabled).toBe(true);
|
||||
expect(listBundledMetadata).toHaveBeenCalledTimes(1);
|
||||
expect(discoverPlugins).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { listBundledPluginMetadata } from "./bundled-plugin-metadata.js";
|
||||
import {
|
||||
createEffectiveEnableStateResolver,
|
||||
createPluginEnableStateResolver,
|
||||
@@ -21,6 +20,8 @@ import {
|
||||
type NormalizePluginId,
|
||||
type NormalizedPluginsConfig as SharedNormalizedPluginsConfig,
|
||||
} from "./config-normalization-shared.js";
|
||||
import { discoverOpenClawPlugins } from "./discovery.js";
|
||||
import { loadPluginManifest } from "./manifest.js";
|
||||
import type { PluginOrigin } from "./plugin-origin.types.js";
|
||||
import { defaultSlotIdForKey } from "./slots.js";
|
||||
|
||||
@@ -45,36 +46,38 @@ const BUILT_IN_PLUGIN_ALIAS_LOOKUP = new Map<string, string>([
|
||||
...BUILT_IN_PLUGIN_ALIAS_FALLBACKS.map(([, pluginId]) => [pluginId, pluginId] as const),
|
||||
]);
|
||||
|
||||
let bundledPluginAliasLookup: ReadonlyMap<string, string> | undefined;
|
||||
|
||||
function getBundledPluginAliasLookup(): ReadonlyMap<string, string> {
|
||||
if (bundledPluginAliasLookup) {
|
||||
return bundledPluginAliasLookup;
|
||||
}
|
||||
const lookup = new Map<string, string>();
|
||||
for (const plugin of listBundledPluginMetadata({ includeChannelConfigs: false })) {
|
||||
const pluginId = normalizeOptionalLowercaseString(plugin.manifest.id);
|
||||
if (pluginId) {
|
||||
lookup.set(pluginId, plugin.manifest.id);
|
||||
for (const candidate of discoverOpenClawPlugins({}).candidates) {
|
||||
const manifestResult =
|
||||
candidate.origin === "bundled" && candidate.bundledManifest
|
||||
? { ok: true as const, manifest: candidate.bundledManifest }
|
||||
: loadPluginManifest(candidate.rootDir, candidate.origin !== "bundled");
|
||||
if (!manifestResult.ok) {
|
||||
continue;
|
||||
}
|
||||
for (const providerId of plugin.manifest.providers ?? []) {
|
||||
const manifest = manifestResult.manifest;
|
||||
const pluginId = normalizeOptionalLowercaseString(manifest.id);
|
||||
if (pluginId) {
|
||||
lookup.set(pluginId, manifest.id);
|
||||
}
|
||||
for (const providerId of manifest.providers ?? []) {
|
||||
const normalizedProviderId = normalizeOptionalLowercaseString(providerId);
|
||||
if (normalizedProviderId) {
|
||||
lookup.set(normalizedProviderId, plugin.manifest.id);
|
||||
lookup.set(normalizedProviderId, manifest.id);
|
||||
}
|
||||
}
|
||||
for (const legacyPluginId of plugin.manifest.legacyPluginIds ?? []) {
|
||||
for (const legacyPluginId of manifest.legacyPluginIds ?? []) {
|
||||
const normalizedLegacyPluginId = normalizeOptionalLowercaseString(legacyPluginId);
|
||||
if (normalizedLegacyPluginId) {
|
||||
lookup.set(normalizedLegacyPluginId, plugin.manifest.id);
|
||||
lookup.set(normalizedLegacyPluginId, manifest.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [alias, pluginId] of BUILT_IN_PLUGIN_ALIAS_FALLBACKS) {
|
||||
lookup.set(alias, pluginId);
|
||||
}
|
||||
bundledPluginAliasLookup = lookup;
|
||||
return bundledPluginAliasLookup;
|
||||
return lookup;
|
||||
}
|
||||
|
||||
function normalizePluginIdWithLookup(
|
||||
|
||||
@@ -135,19 +135,6 @@ function normalizeStringField(value: unknown): string | undefined {
|
||||
return normalized ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizeStringListField(value: unknown): readonly string[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = value
|
||||
.flatMap((entry) => {
|
||||
const normalizedEntry = normalizeStringField(entry);
|
||||
return normalizedEntry ? [normalizedEntry] : [];
|
||||
})
|
||||
.filter((entry, index, all) => all.indexOf(entry) === index);
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizePackageChannel(
|
||||
channel: PluginPackageChannel | undefined,
|
||||
): InstalledPluginPackageChannelInfo | undefined {
|
||||
@@ -155,30 +142,9 @@ function normalizePackageChannel(
|
||||
if (!id) {
|
||||
return undefined;
|
||||
}
|
||||
const label = normalizeStringField(channel?.label);
|
||||
const blurb = normalizeStringField(channel?.blurb);
|
||||
const preferOver = normalizeStringListField(channel?.preferOver);
|
||||
const commands =
|
||||
channel?.commands &&
|
||||
typeof channel.commands === "object" &&
|
||||
!Array.isArray(channel.commands) &&
|
||||
(typeof channel.commands.nativeCommandsAutoEnabled === "boolean" ||
|
||||
typeof channel.commands.nativeSkillsAutoEnabled === "boolean")
|
||||
? {
|
||||
...(typeof channel.commands.nativeCommandsAutoEnabled === "boolean"
|
||||
? { nativeCommandsAutoEnabled: channel.commands.nativeCommandsAutoEnabled }
|
||||
: {}),
|
||||
...(typeof channel.commands.nativeSkillsAutoEnabled === "boolean"
|
||||
? { nativeSkillsAutoEnabled: channel.commands.nativeSkillsAutoEnabled }
|
||||
: {}),
|
||||
}
|
||||
: undefined;
|
||||
return {
|
||||
...structuredClone(channel),
|
||||
id,
|
||||
...(label ? { label } : {}),
|
||||
...(blurb ? { blurb } : {}),
|
||||
...(preferOver ? { preferOver } : {}),
|
||||
...(commands ? { commands } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -240,7 +206,9 @@ export function buildInstalledPluginIndexRecords(params: {
|
||||
const packageJsonPath = resolvePackageJsonPath(candidate);
|
||||
const installRecord = params.installRecords[record.id];
|
||||
const packageInstall = describePackageInstallSource(candidate);
|
||||
const packageChannel = normalizePackageChannel(candidate?.packageManifest?.channel);
|
||||
const packageChannel = normalizePackageChannel(
|
||||
record.packageChannel ?? candidate?.packageManifest?.channel,
|
||||
);
|
||||
const manifestHash = resolveManifestHash({ record, diagnostics: params.diagnostics });
|
||||
const packageJson = resolvePackageJsonRecord({
|
||||
candidate,
|
||||
|
||||
@@ -60,10 +60,7 @@ export type InstalledPluginInstallRecordInfo = Pick<
|
||||
| "marketplacePlugin"
|
||||
>;
|
||||
|
||||
export type InstalledPluginPackageChannelInfo = Pick<
|
||||
PluginPackageChannel,
|
||||
"id" | "label" | "blurb" | "preferOver" | "commands"
|
||||
>;
|
||||
export type InstalledPluginPackageChannelInfo = PluginPackageChannel;
|
||||
|
||||
export type InstalledPluginIndexRecord = {
|
||||
pluginId: string;
|
||||
|
||||
@@ -104,36 +104,36 @@ function resolveFallbackPluginSource(record: InstalledPluginIndexRecord): string
|
||||
function resolveInstalledPackageManifest(
|
||||
record: InstalledPluginIndexRecord,
|
||||
): OpenClawPackageManifest | undefined {
|
||||
if (!record.packageChannel) {
|
||||
return undefined;
|
||||
}
|
||||
if (record.packageChannel.commands) {
|
||||
return { channel: record.packageChannel };
|
||||
}
|
||||
const rootDir = resolveInstalledPluginRootDir(record);
|
||||
const packageJsonPath = record.packageJson?.path
|
||||
? path.resolve(rootDir, record.packageJson.path)
|
||||
: undefined;
|
||||
if (!packageJsonPath) {
|
||||
return { channel: record.packageChannel };
|
||||
return record.packageChannel ? { channel: record.packageChannel } : undefined;
|
||||
}
|
||||
const relative = path.relative(rootDir, packageJsonPath);
|
||||
if (relative.startsWith("..") || path.isAbsolute(relative)) {
|
||||
return { channel: record.packageChannel };
|
||||
return record.packageChannel ? { channel: record.packageChannel } : undefined;
|
||||
}
|
||||
try {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as PackageManifest;
|
||||
const packageManifest = getPackageManifestMetadata(packageJson);
|
||||
if (!packageManifest) {
|
||||
return record.packageChannel ? { channel: record.packageChannel } : undefined;
|
||||
}
|
||||
const channel =
|
||||
record.packageChannel || packageManifest.channel
|
||||
? {
|
||||
...record.packageChannel,
|
||||
...packageManifest.channel,
|
||||
}
|
||||
: undefined;
|
||||
return {
|
||||
channel: {
|
||||
...record.packageChannel,
|
||||
...(packageManifest?.channel?.commands
|
||||
? { commands: packageManifest.channel.commands }
|
||||
: {}),
|
||||
},
|
||||
...packageManifest,
|
||||
...(channel ? { channel } : {}),
|
||||
};
|
||||
} catch {
|
||||
return { channel: record.packageChannel };
|
||||
return record.packageChannel ? { channel: record.packageChannel } : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ import {
|
||||
type PluginManifestProviderRequest,
|
||||
type PluginManifestQaRunner,
|
||||
type PluginManifestSetup,
|
||||
type PluginPackageChannel,
|
||||
type PluginPackageInstall,
|
||||
} from "./manifest.js";
|
||||
import { checkMinHostVersion } from "./min-host-version.js";
|
||||
import { isPathInside, safeRealpathSync } from "./path-safety.js";
|
||||
@@ -96,6 +98,9 @@ export type PluginManifestRecord = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
version?: string;
|
||||
packageName?: string;
|
||||
packageVersion?: string;
|
||||
packageDescription?: string;
|
||||
enabledByDefault?: boolean;
|
||||
autoEnableWhenConfiguredProviders?: string[];
|
||||
legacyPluginIds?: string[];
|
||||
@@ -122,6 +127,9 @@ export type PluginManifestRecord = {
|
||||
providerAuthChoices?: PluginManifest["providerAuthChoices"];
|
||||
activation?: PluginManifestActivation;
|
||||
setup?: PluginManifestSetup;
|
||||
packageManifest?: OpenClawPackageManifest;
|
||||
packageChannel?: PluginPackageChannel;
|
||||
packageInstall?: PluginPackageInstall;
|
||||
qaRunners?: PluginManifestQaRunner[];
|
||||
skills: string[];
|
||||
settingsFiles?: string[];
|
||||
@@ -269,6 +277,9 @@ function buildRecord(params: {
|
||||
description:
|
||||
normalizeOptionalString(params.manifest.description) ?? params.candidate.packageDescription,
|
||||
version: normalizeOptionalString(params.manifest.version) ?? params.candidate.packageVersion,
|
||||
packageName: params.candidate.packageName,
|
||||
packageVersion: params.candidate.packageVersion,
|
||||
packageDescription: params.candidate.packageDescription,
|
||||
enabledByDefault: params.manifest.enabledByDefault === true ? true : undefined,
|
||||
autoEnableWhenConfiguredProviders: params.manifest.autoEnableWhenConfiguredProviders,
|
||||
legacyPluginIds: params.manifest.legacyPluginIds,
|
||||
@@ -298,6 +309,9 @@ function buildRecord(params: {
|
||||
providerAuthChoices: params.manifest.providerAuthChoices,
|
||||
activation: params.manifest.activation,
|
||||
setup: params.manifest.setup,
|
||||
packageManifest: params.candidate.packageManifest,
|
||||
packageChannel: params.candidate.packageManifest?.channel,
|
||||
packageInstall: params.candidate.packageManifest?.install,
|
||||
qaRunners: params.manifest.qaRunners,
|
||||
skills: params.manifest.skills ?? [],
|
||||
settingsFiles: [],
|
||||
@@ -357,6 +371,12 @@ function buildBundleRecord(params: {
|
||||
name: normalizeOptionalString(params.manifest.name) ?? params.candidate.idHint,
|
||||
description: normalizeOptionalString(params.manifest.description),
|
||||
version: normalizeOptionalString(params.manifest.version),
|
||||
packageName: params.candidate.packageName,
|
||||
packageVersion: params.candidate.packageVersion,
|
||||
packageDescription: params.candidate.packageDescription,
|
||||
packageManifest: params.candidate.packageManifest,
|
||||
packageChannel: params.candidate.packageManifest?.channel,
|
||||
packageInstall: params.candidate.packageManifest?.install,
|
||||
format: "bundle",
|
||||
bundleFormat: params.candidate.bundleFormat,
|
||||
bundleCapabilities: params.manifest.capabilities,
|
||||
|
||||
Reference in New Issue
Block a user