mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 13:10:43 +00:00
feat: reuse current plugin metadata for provider discovery
This commit is contained in:
114
src/plugins/current-plugin-metadata-snapshot.test.ts
Normal file
114
src/plugins/current-plugin-metadata-snapshot.test.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
clearCurrentPluginMetadataSnapshot,
|
||||
getCurrentPluginMetadataSnapshot,
|
||||
setCurrentPluginMetadataSnapshot,
|
||||
} from "./current-plugin-metadata-snapshot.js";
|
||||
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
|
||||
import { writePersistedInstalledPluginIndexSync } from "./installed-plugin-index-store.js";
|
||||
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
|
||||
function createSnapshot(
|
||||
params: {
|
||||
config?: Parameters<typeof resolveInstalledPluginIndexPolicyHash>[0];
|
||||
workspaceDir?: string;
|
||||
} = {},
|
||||
): PluginMetadataSnapshot {
|
||||
return {
|
||||
policyHash: resolveInstalledPluginIndexPolicyHash(params.config),
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
index: {
|
||||
version: 1,
|
||||
hostContractVersion: "test",
|
||||
compatRegistryVersion: "test",
|
||||
migrationVersion: 1,
|
||||
policyHash: resolveInstalledPluginIndexPolicyHash(params.config),
|
||||
generatedAtMs: 1,
|
||||
installRecords: {},
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
},
|
||||
registryDiagnostics: [],
|
||||
manifestRegistry: { plugins: [], diagnostics: [] },
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
byPluginId: new Map(),
|
||||
normalizePluginId: (pluginId) => pluginId,
|
||||
owners: {
|
||||
channels: new Map(),
|
||||
channelConfigs: new Map(),
|
||||
providers: new Map(),
|
||||
modelCatalogProviders: new Map(),
|
||||
cliBackends: new Map(),
|
||||
setupProviders: new Map(),
|
||||
commandAliases: new Map(),
|
||||
contracts: new Map(),
|
||||
},
|
||||
metrics: {
|
||||
registrySnapshotMs: 0,
|
||||
manifestRegistryMs: 0,
|
||||
ownerMapsMs: 0,
|
||||
totalMs: 0,
|
||||
indexPluginCount: 0,
|
||||
manifestPluginCount: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("current plugin metadata snapshot", () => {
|
||||
it("returns the current snapshot only for matching config policy and workspace", () => {
|
||||
const config = { plugins: { allow: ["demo"] } };
|
||||
const snapshot = createSnapshot({ config, workspaceDir: "/workspace/a" });
|
||||
setCurrentPluginMetadataSnapshot(snapshot, { config });
|
||||
|
||||
expect(getCurrentPluginMetadataSnapshot({ config, workspaceDir: "/workspace/a" })).toBe(
|
||||
snapshot,
|
||||
);
|
||||
expect(getCurrentPluginMetadataSnapshot({ config })).toBe(snapshot);
|
||||
expect(
|
||||
getCurrentPluginMetadataSnapshot({
|
||||
config: { plugins: { allow: ["other"] } },
|
||||
workspaceDir: "/workspace/a",
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
getCurrentPluginMetadataSnapshot({ config, workspaceDir: "/workspace/b" }),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects a current snapshot when plugin load paths change", () => {
|
||||
const config = { plugins: { load: { paths: ["/plugins/one"] } } };
|
||||
const snapshot = createSnapshot({ config });
|
||||
setCurrentPluginMetadataSnapshot(snapshot, { config });
|
||||
|
||||
expect(getCurrentPluginMetadataSnapshot({ config })).toBe(snapshot);
|
||||
expect(
|
||||
getCurrentPluginMetadataSnapshot({
|
||||
config: { plugins: { load: { paths: ["/plugins/two"] } } },
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("clears the current snapshot", () => {
|
||||
setCurrentPluginMetadataSnapshot(createSnapshot());
|
||||
clearCurrentPluginMetadataSnapshot();
|
||||
|
||||
expect(getCurrentPluginMetadataSnapshot()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("clears the current snapshot when the persisted installed index changes", () => {
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-plugin-metadata-"));
|
||||
try {
|
||||
setCurrentPluginMetadataSnapshot(createSnapshot());
|
||||
|
||||
writePersistedInstalledPluginIndexSync(createSnapshot().index, { stateDir: tempDir });
|
||||
|
||||
expect(getCurrentPluginMetadataSnapshot()).toBeUndefined();
|
||||
} finally {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
73
src/plugins/current-plugin-metadata-snapshot.ts
Normal file
73
src/plugins/current-plugin-metadata-snapshot.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
|
||||
import type { PluginMetadataSnapshot } from "./plugin-metadata-snapshot.js";
|
||||
|
||||
let currentPluginMetadataSnapshot: PluginMetadataSnapshot | undefined;
|
||||
let currentPluginMetadataSnapshotConfigFingerprint: string | undefined;
|
||||
|
||||
function normalizeLoadPaths(config: OpenClawConfig | undefined): readonly string[] {
|
||||
const paths = config?.plugins?.load?.paths;
|
||||
if (!Array.isArray(paths)) {
|
||||
return [];
|
||||
}
|
||||
return paths.filter((entry) => typeof entry === "string");
|
||||
}
|
||||
|
||||
export function resolvePluginMetadataSnapshotConfigFingerprint(
|
||||
config: OpenClawConfig | undefined,
|
||||
): string {
|
||||
return JSON.stringify({
|
||||
policyHash: resolveInstalledPluginIndexPolicyHash(config),
|
||||
pluginLoadPaths: normalizeLoadPaths(config),
|
||||
});
|
||||
}
|
||||
|
||||
// Single-slot Gateway-owned handoff. Replace or clear it at lifecycle boundaries;
|
||||
// never accumulate historical metadata snapshots here.
|
||||
export function setCurrentPluginMetadataSnapshot(
|
||||
snapshot: PluginMetadataSnapshot | undefined,
|
||||
options: { config?: OpenClawConfig } = {},
|
||||
): void {
|
||||
currentPluginMetadataSnapshot = snapshot;
|
||||
currentPluginMetadataSnapshotConfigFingerprint = snapshot
|
||||
? resolvePluginMetadataSnapshotConfigFingerprint(options.config)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export function clearCurrentPluginMetadataSnapshot(): void {
|
||||
currentPluginMetadataSnapshot = undefined;
|
||||
currentPluginMetadataSnapshotConfigFingerprint = undefined;
|
||||
}
|
||||
|
||||
export function getCurrentPluginMetadataSnapshot(
|
||||
params: {
|
||||
config?: OpenClawConfig;
|
||||
workspaceDir?: string;
|
||||
} = {},
|
||||
): PluginMetadataSnapshot | undefined {
|
||||
const snapshot = currentPluginMetadataSnapshot;
|
||||
if (!snapshot) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
params.config &&
|
||||
snapshot.policyHash !== resolveInstalledPluginIndexPolicyHash(params.config)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
params.config &&
|
||||
currentPluginMetadataSnapshotConfigFingerprint &&
|
||||
currentPluginMetadataSnapshotConfigFingerprint !==
|
||||
resolvePluginMetadataSnapshotConfigFingerprint(params.config)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
if (
|
||||
params.workspaceDir !== undefined &&
|
||||
(snapshot.workspaceDir ?? "") !== (params.workspaceDir ?? "")
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
return snapshot;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { saveJsonFile } from "../infra/json-file.js";
|
||||
import { readJsonFile, readJsonFileSync, writeJsonAtomic } from "../infra/json-files.js";
|
||||
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
|
||||
import { safeParseWithSchema } from "../utils/zod-parse.js";
|
||||
import { clearCurrentPluginMetadataSnapshot } from "./current-plugin-metadata-snapshot.js";
|
||||
import {
|
||||
resolveInstalledPluginIndexStorePath,
|
||||
type InstalledPluginIndexStoreOptions,
|
||||
@@ -171,6 +172,7 @@ export async function writePersistedInstalledPluginIndex(
|
||||
mode: 0o600,
|
||||
},
|
||||
);
|
||||
clearCurrentPluginMetadataSnapshot();
|
||||
return filePath;
|
||||
}
|
||||
|
||||
@@ -180,6 +182,7 @@ export function writePersistedInstalledPluginIndexSync(
|
||||
): string {
|
||||
const filePath = resolveInstalledPluginIndexStorePath(options);
|
||||
saveJsonFile(filePath, { ...index, warning: INSTALLED_PLUGIN_INDEX_WARNING });
|
||||
clearCurrentPluginMetadataSnapshot();
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user