mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:10:44 +00:00
fix(plugins): keep config schema on manifest metadata
This commit is contained in:
@@ -191,6 +191,10 @@ describe("readBestEffortRuntimeConfigSchema", () => {
|
||||
cache: true,
|
||||
}),
|
||||
);
|
||||
expect(mockLoadPluginManifestRegistry.mock.calls[0]?.[0]).not.toHaveProperty("cache", false);
|
||||
expect(mockLoadPluginManifestRegistry.mock.calls[0]?.[0]).not.toHaveProperty(
|
||||
"bundledChannelConfigCollector",
|
||||
);
|
||||
expect(channelProps?.telegram).toBeTruthy();
|
||||
expect(channelProps?.matrix).toBeTruthy();
|
||||
expect(entryProps?.demo).toBeTruthy();
|
||||
@@ -207,6 +211,10 @@ describe("readBestEffortRuntimeConfigSchema", () => {
|
||||
cache: true,
|
||||
}),
|
||||
);
|
||||
expect(mockLoadPluginManifestRegistry.mock.calls[0]?.[0]).not.toHaveProperty("cache", false);
|
||||
expect(mockLoadPluginManifestRegistry.mock.calls[0]?.[0]).not.toHaveProperty(
|
||||
"bundledChannelConfigCollector",
|
||||
);
|
||||
expect(channelProps?.telegram).toBeTruthy();
|
||||
expect(channelProps?.slack).toBeTruthy();
|
||||
expect(entryProps?.demo).toBeUndefined();
|
||||
|
||||
@@ -24,7 +24,7 @@ const SOURCE_CONFIG_SCHEMA_CANDIDATES = [
|
||||
path.join("src", "config-schema.cts"),
|
||||
path.join("src", "config-schema.cjs"),
|
||||
] as const;
|
||||
const PUBLIC_CONFIG_SURFACE_BASENAMES = ["channel-config-api", "runtime-api", "api"] as const;
|
||||
const PUBLIC_CONFIG_SURFACE_BASENAMES = ["channel-config-api"] as const;
|
||||
|
||||
type ChannelConfigSurface = {
|
||||
schema: JsonSchemaObject;
|
||||
|
||||
@@ -614,4 +614,82 @@ describe("bundled plugin metadata", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("does not probe broad runtime public surfaces for channel config metadata", () => {
|
||||
const tempRoot = createGeneratedPluginTempRoot("openclaw-bundled-plugin-dist-config-runtime-");
|
||||
const distRoot = path.join(tempRoot, "dist");
|
||||
const markerPath = path.join(tempRoot, "runtime-api-loaded");
|
||||
|
||||
writeJson(path.join(distRoot, "extensions", "alpha", "package.json"), {
|
||||
name: "@openclaw/alpha",
|
||||
version: "0.0.1",
|
||||
openclaw: {
|
||||
extensions: ["./index.ts"],
|
||||
channel: {
|
||||
id: "alpha",
|
||||
label: "Alpha Root Label",
|
||||
blurb: "Alpha Root Description",
|
||||
},
|
||||
},
|
||||
});
|
||||
writeJson(path.join(distRoot, "extensions", "alpha", "openclaw.plugin.json"), {
|
||||
id: "alpha",
|
||||
configSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
channels: ["alpha"],
|
||||
channelConfigs: {
|
||||
alpha: {
|
||||
schema: { type: "object", properties: { manifest: { type: "boolean" } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
fs.writeFileSync(
|
||||
path.join(distRoot, "extensions", "alpha", "index.js"),
|
||||
"export {};\n",
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(distRoot, "extensions", "alpha", "runtime-api.js"),
|
||||
[
|
||||
"import fs from 'node:fs';",
|
||||
`fs.writeFileSync(${JSON.stringify(markerPath)}, "loaded", "utf8");`,
|
||||
"export const AlphaChannelConfigSchema = {",
|
||||
" schema: { type: 'object', properties: { runtimeApi: { type: 'string' } } },",
|
||||
"};",
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(distRoot, "extensions", "alpha", "api.js"),
|
||||
[
|
||||
"import fs from 'node:fs';",
|
||||
`fs.writeFileSync(${JSON.stringify(markerPath)}, "loaded", "utf8");`,
|
||||
"export const AlphaChannelConfigSchema = {",
|
||||
" schema: { type: 'object', properties: { api: { type: 'string' } } },",
|
||||
"};",
|
||||
"",
|
||||
].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
clearBundledPluginMetadataCache();
|
||||
const entries = listBundledPluginMetadata({ rootDir: distRoot });
|
||||
const channelConfigs = entries[0]?.manifest.channelConfigs as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
expect(channelConfigs?.alpha).toMatchObject({
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
manifest: { type: "boolean" },
|
||||
},
|
||||
},
|
||||
label: "Alpha Root Label",
|
||||
description: "Alpha Root Description",
|
||||
});
|
||||
expect(fs.existsSync(markerPath)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { resolveCompatibilityHostVersion } from "../version.js";
|
||||
import { resolveBundledPluginsDir } from "./bundled-dir.js";
|
||||
import { normalizePluginsConfig } from "./config-state.js";
|
||||
import {
|
||||
inspectPersistedInstalledPluginIndex,
|
||||
readPersistedInstalledPluginIndexSync,
|
||||
@@ -128,10 +129,6 @@ function resolveDerivedSnapshotCacheKey(
|
||||
if (
|
||||
params.cache === false ||
|
||||
params.preferPersisted === false ||
|
||||
params.config ||
|
||||
params.workspaceDir ||
|
||||
params.stateDir ||
|
||||
params.filePath ||
|
||||
params.pluginIndexFilePath ||
|
||||
params.installRecords ||
|
||||
params.candidates ||
|
||||
@@ -141,11 +138,17 @@ function resolveDerivedSnapshotCacheKey(
|
||||
return null;
|
||||
}
|
||||
|
||||
const { roots, loadPaths } = resolvePluginCacheInputs({ env });
|
||||
const normalizedPlugins = normalizePluginsConfig(params.config?.plugins);
|
||||
const { roots, loadPaths } = resolvePluginCacheInputs({
|
||||
workspaceDir: params.workspaceDir,
|
||||
loadPaths: normalizedPlugins.loadPaths,
|
||||
env,
|
||||
});
|
||||
return JSON.stringify({
|
||||
persistedStore: resolveInstalledPluginIndexStorePath({ env }),
|
||||
persistedStore: resolveInstalledPluginIndexStorePath(params),
|
||||
roots,
|
||||
loadPaths,
|
||||
policyHash: resolveInstalledPluginIndexPolicyHash(params.config),
|
||||
hostContractVersion: resolveCompatibilityHostVersion(env),
|
||||
disablePersisted: env[DISABLE_PERSISTED_PLUGIN_REGISTRY_ENV] ?? "",
|
||||
disableBundled: env.OPENCLAW_DISABLE_BUNDLED_PLUGINS ?? "",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { PluginCandidate } from "./discovery.js";
|
||||
import {
|
||||
readPersistedInstalledPluginIndex,
|
||||
@@ -544,6 +544,43 @@ describe("plugin registry facade", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("caches config-scoped derived registries when the persisted registry is missing", () => {
|
||||
const stateDir = makeTempDir();
|
||||
const workspaceDir = makeTempDir();
|
||||
const bundledRoot = makeTempDir();
|
||||
const rootDir = path.join(bundledRoot, "demo");
|
||||
fs.mkdirSync(rootDir, { recursive: true });
|
||||
createCandidate(rootDir);
|
||||
const env = hermeticEnv({ OPENCLAW_BUNDLED_PLUGINS_DIR: bundledRoot });
|
||||
const config = { plugins: { entries: { demo: { enabled: true } } } } as const;
|
||||
const readFileSyncSpy = vi.spyOn(fs, "readFileSync");
|
||||
|
||||
const first = loadPluginRegistrySnapshotWithMetadata({
|
||||
stateDir,
|
||||
workspaceDir,
|
||||
config,
|
||||
env,
|
||||
});
|
||||
const manifestReadsAfterFirst = readFileSyncSpy.mock.calls.filter((call) =>
|
||||
String(call[0]).endsWith("openclaw.plugin.json"),
|
||||
).length;
|
||||
|
||||
const second = loadPluginRegistrySnapshotWithMetadata({
|
||||
stateDir,
|
||||
workspaceDir,
|
||||
config,
|
||||
env,
|
||||
});
|
||||
const manifestReadsAfterSecond = readFileSyncSpy.mock.calls.filter((call) =>
|
||||
String(call[0]).endsWith("openclaw.plugin.json"),
|
||||
).length;
|
||||
|
||||
expect(first.source).toBe("derived");
|
||||
expect(second).toBe(first);
|
||||
expect(manifestReadsAfterFirst).toBeGreaterThan(0);
|
||||
expect(manifestReadsAfterSecond).toBe(manifestReadsAfterFirst);
|
||||
});
|
||||
|
||||
it("falls back to the derived registry when persisted reads are disabled", async () => {
|
||||
const stateDir = makeTempDir();
|
||||
const rootDir = makeTempDir();
|
||||
|
||||
Reference in New Issue
Block a user