mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 14:31:40 +00:00
* fix: stabilize concurrent model and config snapshots * fix: retire obsolete plugin metadata cleanup facade * fix: route Codex prewarm regression exactly once
161 lines
5.4 KiB
TypeScript
161 lines
5.4 KiB
TypeScript
// Verifies model IDs declared by plugin manifests are normalized.
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { captureEnv, deleteTestEnvValue, setTestEnvValue } from "../test-utils/env.js";
|
|
import { writePersistedInstalledPluginIndexSync } from "./installed-plugin-index-store.js";
|
|
import { listOpenClawPluginManifestMetadata } from "./manifest-metadata-scan.js";
|
|
import { normalizeProviderModelIdWithManifest } from "./manifest-model-id-normalization.js";
|
|
import { clearPluginMetadataLifecycleCaches } from "./plugin-metadata-lifecycle.js";
|
|
import { resetPluginRuntimeStateForTest } from "./runtime.js";
|
|
|
|
const tempDirs: string[] = [];
|
|
const testEnvSnapshot = captureEnv([
|
|
"OPENCLAW_STATE_DIR",
|
|
"OPENCLAW_HOME",
|
|
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
|
|
"OPENCLAW_BUNDLED_PLUGINS_DIR",
|
|
]);
|
|
|
|
function restoreEnv(): void {
|
|
testEnvSnapshot.restore();
|
|
}
|
|
|
|
function makeTempDir(): string {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-model-id-normalization-"));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
function writeInstallIndex(params: { stateDir: string; pluginDir: string }): void {
|
|
writePersistedInstalledPluginIndexSync(
|
|
{
|
|
version: 1,
|
|
hostContractVersion: "test",
|
|
compatRegistryVersion: "test",
|
|
migrationVersion: 1,
|
|
policyHash: "test",
|
|
generatedAtMs: 1,
|
|
installRecords: {},
|
|
plugins: [
|
|
{
|
|
pluginId: "normalizer",
|
|
manifestPath: path.join(params.pluginDir, "openclaw.plugin.json"),
|
|
manifestHash: "normalizer-manifest",
|
|
rootDir: params.pluginDir,
|
|
origin: "global",
|
|
enabled: true,
|
|
startup: {
|
|
sidecar: false,
|
|
memory: false,
|
|
deferConfiguredChannelFullLoadUntilAfterListen: false,
|
|
agentHarnesses: [],
|
|
},
|
|
compat: [],
|
|
},
|
|
],
|
|
diagnostics: [],
|
|
},
|
|
{ stateDir: params.stateDir },
|
|
);
|
|
}
|
|
|
|
function writeNormalizerManifest(params: { pluginDir: string; prefix: string }): void {
|
|
fs.mkdirSync(params.pluginDir, { recursive: true });
|
|
fs.writeFileSync(
|
|
path.join(params.pluginDir, "index.ts"),
|
|
"throw new Error('runtime entry should not load while reading manifests');\n",
|
|
"utf-8",
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(params.pluginDir, "openclaw.plugin.json"),
|
|
JSON.stringify({
|
|
id: "normalizer",
|
|
configSchema: { type: "object" },
|
|
providers: ["demo"],
|
|
modelIdNormalization: {
|
|
providers: {
|
|
demo: {
|
|
prefixWhenBare: params.prefix,
|
|
},
|
|
},
|
|
},
|
|
}),
|
|
"utf-8",
|
|
);
|
|
}
|
|
|
|
function normalizeDemoModel(modelId = "demo-model"): string | undefined {
|
|
return normalizeProviderModelIdWithManifest({
|
|
provider: "demo",
|
|
context: { provider: "demo", modelId },
|
|
});
|
|
}
|
|
|
|
describe("manifest model id normalization", () => {
|
|
beforeEach(() => {
|
|
resetPluginRuntimeStateForTest();
|
|
clearPluginMetadataLifecycleCaches();
|
|
});
|
|
|
|
afterEach(() => {
|
|
resetPluginRuntimeStateForTest();
|
|
clearPluginMetadataLifecycleCaches();
|
|
restoreEnv();
|
|
for (const dir of tempDirs.splice(0)) {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("reflects manifest and state-dir changes without a prepared snapshot", () => {
|
|
const stateDirA = makeTempDir();
|
|
const pluginDirA = path.join(stateDirA, "extensions", "normalizer");
|
|
writeInstallIndex({ stateDir: stateDirA, pluginDir: pluginDirA });
|
|
writeNormalizerManifest({ pluginDir: pluginDirA, prefix: "alpha" });
|
|
|
|
setTestEnvValue("OPENCLAW_STATE_DIR", stateDirA);
|
|
deleteTestEnvValue("OPENCLAW_HOME");
|
|
setTestEnvValue("OPENCLAW_DISABLE_BUNDLED_PLUGINS", "1");
|
|
deleteTestEnvValue("OPENCLAW_BUNDLED_PLUGINS_DIR");
|
|
|
|
expect(normalizeDemoModel()).toBe("alpha/demo-model");
|
|
|
|
writeNormalizerManifest({ pluginDir: pluginDirA, prefix: "bravo-local" });
|
|
expect(normalizeDemoModel()).toBe("bravo-local/demo-model");
|
|
|
|
const stateDirB = makeTempDir();
|
|
const pluginDirB = path.join(stateDirB, "extensions", "normalizer");
|
|
writeInstallIndex({ stateDir: stateDirB, pluginDir: pluginDirB });
|
|
writeNormalizerManifest({ pluginDir: pluginDirB, prefix: "charlie" });
|
|
|
|
setTestEnvValue("OPENCLAW_STATE_DIR", stateDirB);
|
|
clearPluginMetadataLifecycleCaches();
|
|
expect(normalizeDemoModel()).toBe("charlie/demo-model");
|
|
});
|
|
|
|
it("reuses manifest metadata while file fingerprints are unchanged", () => {
|
|
const stateDir = makeTempDir();
|
|
const pluginDir = path.join(stateDir, "extensions", "normalizer");
|
|
writeInstallIndex({ stateDir, pluginDir });
|
|
writeNormalizerManifest({ pluginDir, prefix: "alpha" });
|
|
|
|
setTestEnvValue("OPENCLAW_STATE_DIR", stateDir);
|
|
deleteTestEnvValue("OPENCLAW_HOME");
|
|
setTestEnvValue("OPENCLAW_DISABLE_BUNDLED_PLUGINS", "1");
|
|
deleteTestEnvValue("OPENCLAW_BUNDLED_PLUGINS_DIR");
|
|
|
|
// The scan also lists source-checkout extensions/ manifests when tests run
|
|
// from a repo checkout, so only pin the record for the plugin under test.
|
|
const listNormalizerRecords = () =>
|
|
listOpenClawPluginManifestMetadata(process.env).filter(
|
|
(record) => record.pluginDir === pluginDir,
|
|
);
|
|
const firstRecords = listNormalizerRecords();
|
|
const secondRecords = listNormalizerRecords();
|
|
expect(firstRecords).toHaveLength(1);
|
|
expect(secondRecords).toHaveLength(1);
|
|
expect(secondRecords[0]).toBe(firstRecords[0]);
|
|
});
|
|
});
|