mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:30:42 +00:00
feat: reuse current plugin metadata for provider discovery
This commit is contained in:
@@ -9,6 +9,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Plugins/models: wire manifest `modelCatalog.aliases` and `modelCatalog.suppressions` into model-catalog planning and built-in model suppression, with OpenAI stale Spark suppression now declared in the plugin manifest before runtime fallback. Thanks @shakkernerd.
|
||||
- Channels/Yuanbao: register the Tencent Yuanbao external channel plugin (`openclaw-plugin-yuanbao`) in the official channel catalog, contract suites, and community plugin docs, with a new `docs/channels/yuanbao.md` quick-start guide for WebSocket bot DMs and group chats. (#72756) Thanks @loongfay.
|
||||
- Channels/QQBot: add full group chat support (history tracking, @-mention gating, activation modes, per-group config, FIFO message queue with deliver debounce), C2C `stream_messages` streaming with a `StreamingController` lifecycle manager, unified `sendMedia` with chunked upload for large files, and refactor the engine into pipeline stages, focused outbound submodules, builtin slash-command modules, and explicit DI ports via `createEngineAdapters()`. (#70624) Thanks @cxyhhhhh.
|
||||
- Gateway/runtime: reuse the current plugin metadata snapshot for provider discovery so repeated model-provider discovery avoids rebuilding plugin manifest metadata. Thanks @shakkernerd.
|
||||
- Gateway/startup: pass the plugin metadata snapshot from config validation into plugin bootstrap so startup reuses one manifest product instead of rebuilding plugin metadata. Thanks @shakkernerd.
|
||||
|
||||
## 2026.4.26
|
||||
|
||||
@@ -151,6 +151,8 @@ Gateway startup builds one `PluginMetadataSnapshot` for the current config snaps
|
||||
|
||||
Plugin-aware config validation, startup auto-enable, and Gateway plugin bootstrap consume that snapshot instead of rebuilding manifest/index metadata independently. `PluginLookUpTable` is derived from the same snapshot and adds the startup plugin plan for the current runtime config.
|
||||
|
||||
After startup, Gateway keeps the current metadata snapshot as a replaceable runtime product. Repeated runtime provider discovery can borrow that snapshot instead of reconstructing the installed index and manifest registry for each provider-catalog pass. The snapshot is cleared or replaced on Gateway shutdown, config/plugin inventory changes, and installed index writes; callers fall back to the cold manifest/index path when no compatible current snapshot exists.
|
||||
|
||||
The snapshot and lookup table keep repeated startup decisions on the fast path:
|
||||
|
||||
- channel ownership
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { createConfigRuntimeEnv } from "../config/env-vars.js";
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import { unsetEnv, withTempEnv } from "./models-config.e2e-harness.js";
|
||||
import { resolveProvidersForModelsJsonWithDeps } from "./models-config.plan.js";
|
||||
import {
|
||||
planOpenClawModelsJsonWithDeps,
|
||||
resolveProvidersForModelsJsonWithDeps,
|
||||
} from "./models-config.plan.js";
|
||||
import type { ProviderConfig } from "./models-config.providers.secrets.js";
|
||||
|
||||
const TEST_ENV_VAR = "OPENCLAW_MODELS_CONFIG_TEST_ENV";
|
||||
@@ -72,6 +76,64 @@ async function resolveProvidersAndCaptureDiscoveryEnv(cfg: OpenClawConfig) {
|
||||
}
|
||||
|
||||
describe("models-config", () => {
|
||||
it("threads plugin metadata snapshots into implicit provider discovery", async () => {
|
||||
const pluginMetadataSnapshot = {
|
||||
index: { plugins: [] },
|
||||
manifestRegistry: { plugins: [], diagnostics: [] },
|
||||
owners: { providers: new Map() },
|
||||
} as unknown as Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
let observedSnapshot:
|
||||
| Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">
|
||||
| undefined;
|
||||
|
||||
await resolveProvidersForModelsJsonWithDeps(
|
||||
{
|
||||
cfg: { models: { providers: {} } },
|
||||
agentDir: "/tmp/openclaw-models-config-env-vars-test",
|
||||
env: {},
|
||||
pluginMetadataSnapshot,
|
||||
},
|
||||
{
|
||||
resolveImplicitProviders: async ({ pluginMetadataSnapshot: receivedSnapshot }) => {
|
||||
observedSnapshot = receivedSnapshot;
|
||||
return {};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(observedSnapshot).toBe(pluginMetadataSnapshot);
|
||||
});
|
||||
|
||||
it("threads plugin metadata snapshots through models.json planning", async () => {
|
||||
const pluginMetadataSnapshot = {
|
||||
index: { plugins: [] },
|
||||
manifestRegistry: { plugins: [], diagnostics: [] },
|
||||
owners: { providers: new Map() },
|
||||
} as unknown as Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
let observedSnapshot:
|
||||
| Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">
|
||||
| undefined;
|
||||
|
||||
await planOpenClawModelsJsonWithDeps(
|
||||
{
|
||||
cfg: { models: { providers: {} } },
|
||||
agentDir: "/tmp/openclaw-models-config-env-vars-test",
|
||||
env: {},
|
||||
existingRaw: "",
|
||||
existingParsed: null,
|
||||
pluginMetadataSnapshot,
|
||||
},
|
||||
{
|
||||
resolveImplicitProviders: async ({ pluginMetadataSnapshot: receivedSnapshot }) => {
|
||||
observedSnapshot = receivedSnapshot;
|
||||
return {};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(observedSnapshot).toBe(pluginMetadataSnapshot);
|
||||
});
|
||||
|
||||
it("uses config env.vars entries for implicit provider discovery without mutating process.env", async () => {
|
||||
await withTempEnv(["OPENROUTER_API_KEY", TEST_ENV_VAR], async () => {
|
||||
unsetEnv(["OPENROUTER_API_KEY", TEST_ENV_VAR]);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import {
|
||||
mergeProviders,
|
||||
@@ -19,6 +20,7 @@ export type ResolveImplicitProvidersForModelsJson = (params: {
|
||||
config: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
explicitProviders: Record<string, ProviderConfig>;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
}) => Promise<Record<string, ProviderConfig>>;
|
||||
|
||||
export type ModelsJsonPlan =
|
||||
@@ -38,6 +40,7 @@ export async function resolveProvidersForModelsJsonWithDeps(
|
||||
cfg: OpenClawConfig;
|
||||
agentDir: string;
|
||||
env: NodeJS.ProcessEnv;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
},
|
||||
deps?: {
|
||||
resolveImplicitProviders?: ResolveImplicitProvidersForModelsJson;
|
||||
@@ -51,6 +54,9 @@ export async function resolveProvidersForModelsJsonWithDeps(
|
||||
config: cfg,
|
||||
env,
|
||||
explicitProviders,
|
||||
...(params.pluginMetadataSnapshot
|
||||
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
|
||||
: {}),
|
||||
});
|
||||
return mergeProviders({
|
||||
implicit: implicitProviders,
|
||||
@@ -90,13 +96,24 @@ export async function planOpenClawModelsJsonWithDeps(
|
||||
env: NodeJS.ProcessEnv;
|
||||
existingRaw: string;
|
||||
existingParsed: unknown;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
},
|
||||
deps?: {
|
||||
resolveImplicitProviders?: ResolveImplicitProvidersForModelsJson;
|
||||
},
|
||||
): Promise<ModelsJsonPlan> {
|
||||
const { cfg, agentDir, env } = params;
|
||||
const providers = await resolveProvidersForModelsJsonWithDeps({ cfg, agentDir, env }, deps);
|
||||
const providers = await resolveProvidersForModelsJsonWithDeps(
|
||||
{
|
||||
cfg,
|
||||
agentDir,
|
||||
env,
|
||||
...(params.pluginMetadataSnapshot
|
||||
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
|
||||
: {}),
|
||||
},
|
||||
deps,
|
||||
);
|
||||
|
||||
if (Object.keys(providers).length === 0) {
|
||||
return { action: "skip" };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import {
|
||||
groupPluginDiscoveryProvidersByOrder,
|
||||
normalizePluginDiscoveryResult,
|
||||
@@ -43,6 +44,7 @@ type ImplicitProviderParams = {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
explicitProviders?: Record<string, ProviderConfig> | null;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
};
|
||||
|
||||
type ImplicitProviderContext = ImplicitProviderParams & {
|
||||
@@ -367,7 +369,13 @@ export async function resolveImplicitProviders(
|
||||
config: params.config,
|
||||
workspaceDir: params.workspaceDir,
|
||||
env,
|
||||
resolveOwners: params.pluginMetadataSnapshot
|
||||
? (provider) => params.pluginMetadataSnapshot?.owners.providers.get(provider)
|
||||
: undefined,
|
||||
}),
|
||||
...(params.pluginMetadataSnapshot
|
||||
? { pluginMetadataSnapshot: params.pluginMetadataSnapshot }
|
||||
: {}),
|
||||
});
|
||||
|
||||
for (const order of PLUGIN_DISCOVERY_ORDERS) {
|
||||
|
||||
@@ -7,6 +7,9 @@ import {
|
||||
type OpenClawConfig,
|
||||
} from "../config/config.js";
|
||||
import { createConfigRuntimeEnv } from "../config/env-vars.js";
|
||||
import { getCurrentPluginMetadataSnapshot } from "../plugins/current-plugin-metadata-snapshot.js";
|
||||
import { resolveInstalledManifestRegistryIndexFingerprint } from "../plugins/manifest-registry-installed.js";
|
||||
import type { PluginMetadataSnapshot } from "../plugins/plugin-metadata-snapshot.js";
|
||||
import { resolveOpenClawAgentDir } from "./agent-paths.js";
|
||||
import { MODELS_JSON_STATE } from "./models-config-state.js";
|
||||
import { planOpenClawModelsJson } from "./models-config.plan.js";
|
||||
@@ -41,18 +44,23 @@ async function buildModelsJsonFingerprint(params: {
|
||||
config: OpenClawConfig;
|
||||
sourceConfigForSecrets: OpenClawConfig;
|
||||
agentDir: string;
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index">;
|
||||
}): Promise<string> {
|
||||
const authProfilesMtimeMs = await readFileMtimeMs(
|
||||
path.join(params.agentDir, "auth-profiles.json"),
|
||||
);
|
||||
const modelsFileMtimeMs = await readFileMtimeMs(path.join(params.agentDir, "models.json"));
|
||||
const envShape = createConfigRuntimeEnv(params.config, {});
|
||||
const pluginMetadataSnapshotIndexFingerprint = params.pluginMetadataSnapshot
|
||||
? resolveInstalledManifestRegistryIndexFingerprint(params.pluginMetadataSnapshot.index)
|
||||
: undefined;
|
||||
return stableStringify({
|
||||
config: params.config,
|
||||
sourceConfigForSecrets: params.sourceConfigForSecrets,
|
||||
envShape,
|
||||
authProfilesMtimeMs,
|
||||
modelsFileMtimeMs,
|
||||
pluginMetadataSnapshotIndexFingerprint,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -138,15 +146,21 @@ async function withModelsJsonWriteLock<T>(targetPath: string, run: () => Promise
|
||||
export async function ensureOpenClawModelsJson(
|
||||
config?: OpenClawConfig,
|
||||
agentDirOverride?: string,
|
||||
options: {
|
||||
pluginMetadataSnapshot?: Pick<PluginMetadataSnapshot, "index" | "manifestRegistry" | "owners">;
|
||||
} = {},
|
||||
): Promise<{ agentDir: string; wrote: boolean }> {
|
||||
const resolved = resolveModelsConfigInput(config);
|
||||
const cfg = resolved.config;
|
||||
const pluginMetadataSnapshot =
|
||||
options.pluginMetadataSnapshot ?? getCurrentPluginMetadataSnapshot({ config: cfg });
|
||||
const agentDir = agentDirOverride?.trim() ? agentDirOverride.trim() : resolveOpenClawAgentDir();
|
||||
const targetPath = path.join(agentDir, "models.json");
|
||||
const fingerprint = await buildModelsJsonFingerprint({
|
||||
config: cfg,
|
||||
sourceConfigForSecrets: resolved.sourceConfigForSecrets,
|
||||
agentDir,
|
||||
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
|
||||
});
|
||||
const cached = MODELS_JSON_STATE.readyCache.get(targetPath);
|
||||
if (cached) {
|
||||
@@ -169,6 +183,7 @@ export async function ensureOpenClawModelsJson(
|
||||
env,
|
||||
existingRaw: existingModelsFile.raw,
|
||||
existingParsed: existingModelsFile.parsed,
|
||||
...(pluginMetadataSnapshot ? { pluginMetadataSnapshot } : {}),
|
||||
});
|
||||
|
||||
if (plan.action === "skip") {
|
||||
|
||||
@@ -31,6 +31,10 @@ import type { VoiceWakeRoutingConfig } from "../infra/voicewake-routing.js";
|
||||
import { startDiagnosticHeartbeat, stopDiagnosticHeartbeat } from "../logging/diagnostic.js";
|
||||
import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js";
|
||||
import { getActiveBundledRuntimeDepsInstallCount } from "../plugins/bundled-runtime-deps-activity.js";
|
||||
import {
|
||||
clearCurrentPluginMetadataSnapshot,
|
||||
setCurrentPluginMetadataSnapshot,
|
||||
} from "../plugins/current-plugin-metadata-snapshot.js";
|
||||
import { runGlobalGatewayStopSafely } from "../plugins/hook-runner-global.js";
|
||||
import type { PluginRuntime } from "../plugins/runtime/types.js";
|
||||
import { getTotalQueueSize } from "../process/command-queue.js";
|
||||
@@ -424,6 +428,7 @@ export async function startGatewayServer(
|
||||
pluginLookUpTable,
|
||||
baseMethods,
|
||||
} = pluginBootstrap;
|
||||
setCurrentPluginMetadataSnapshot(pluginLookUpTable, { config: gatewayPluginConfigAtStart });
|
||||
if (pluginLookUpTable) {
|
||||
const metrics = pluginLookUpTable.metrics;
|
||||
startupTrace.detail("plugins.lookup-table", [
|
||||
@@ -640,7 +645,8 @@ export async function startGatewayServer(
|
||||
});
|
||||
deps.cron = runtimeState.cronState.cron;
|
||||
|
||||
const runClosePrelude = async () =>
|
||||
const runClosePrelude = async () => {
|
||||
clearCurrentPluginMetadataSnapshot();
|
||||
await runGatewayClosePrelude({
|
||||
...(diagnosticsEnabled ? { stopDiagnostics: stopDiagnosticHeartbeat } : {}),
|
||||
clearSkillsRefreshTimer: () => {
|
||||
@@ -658,6 +664,7 @@ export async function startGatewayServer(
|
||||
clearSecretsRuntimeSnapshot,
|
||||
closeMcpServer: closeMcpLoopbackServerOnDemand,
|
||||
});
|
||||
};
|
||||
const { getRuntimeSnapshot, startChannels, startChannel, stopChannel, markChannelLoggedOut } =
|
||||
channelManager;
|
||||
const createCloseHandler = () =>
|
||||
|
||||
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