fix(plugins): keep externalized launch installs on npm

This commit is contained in:
Vincent Koc
2026-05-02 12:24:48 -07:00
parent 40b8cb5837
commit 44f3b5ad89
6 changed files with 154 additions and 7 deletions

View File

@@ -0,0 +1,145 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { InstalledPluginIndex } from "../plugins/installed-plugin-index.js";
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
const readPersistedInstalledPluginIndexMock = vi.fn();
const loadPluginManifestRegistryForInstalledIndexMock = vi.fn();
vi.mock("../plugins/installed-plugin-index-store.js", () => ({
readPersistedInstalledPluginIndex: (...args: unknown[]) =>
readPersistedInstalledPluginIndexMock(...args),
}));
vi.mock("../plugins/manifest-registry-installed.js", () => ({
loadPluginManifestRegistryForInstalledIndex: (...args: unknown[]) =>
loadPluginManifestRegistryForInstalledIndexMock(...args),
}));
const { listPersistedBundledPluginLocationBridges } = await import("./plugins-location-bridges.js");
function makeIndex(record: InstalledPluginIndex["plugins"][number]): InstalledPluginIndex {
return {
version: 1,
hostContractVersion: "2026.5.2",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "test",
generatedAtMs: 1,
refreshReason: "test",
installRecords: {},
plugins: [record],
};
}
function makeRegistry(pluginId: string): PluginManifestRegistry {
return {
plugins: [
{
id: pluginId,
name: pluginId,
rootDir: `/app/dist/extensions/${pluginId}`,
source: `/app/dist/extensions/${pluginId}/index.js`,
origin: "bundled",
channels: [pluginId],
providers: [],
cliBackends: [],
syntheticAuthRefs: [],
nonSecretAuthMarkers: [],
skills: [],
settingsFiles: [],
hooks: [],
configContracts: [],
activation: {},
startup: {},
packageInstall: {
clawhubSpec: `clawhub:@openclaw/${pluginId}`,
npmSpec: `@openclaw/${pluginId}`,
defaultChoice: "clawhub",
},
},
],
diagnostics: [],
} as unknown as PluginManifestRegistry;
}
describe("listPersistedBundledPluginLocationBridges", () => {
beforeEach(() => {
readPersistedInstalledPluginIndexMock.mockReset();
loadPluginManifestRegistryForInstalledIndexMock.mockReset();
});
it("keeps persisted bundled relocations npm-first for launch", async () => {
readPersistedInstalledPluginIndexMock.mockResolvedValue(
makeIndex({
pluginId: "diagnostics-otel",
manifestPath: "/app/dist/extensions/diagnostics-otel/openclaw.plugin.json",
manifestHash: "hash",
source: "/app/dist/extensions/diagnostics-otel/index.js",
rootDir: "/app/dist/extensions/diagnostics-otel",
origin: "bundled",
enabled: true,
startup: {},
compat: [],
packageInstall: {
defaultChoice: "clawhub",
clawhub: {
spec: "clawhub:@openclaw/diagnostics-otel",
packageName: "@openclaw/diagnostics-otel",
exactVersion: false,
},
npm: {
spec: "@openclaw/diagnostics-otel",
packageName: "@openclaw/diagnostics-otel",
selectorKind: "none",
exactVersion: false,
pinState: "floating-without-integrity",
},
warnings: [],
},
}),
);
loadPluginManifestRegistryForInstalledIndexMock.mockReturnValue(
makeRegistry("diagnostics-otel"),
);
await expect(listPersistedBundledPluginLocationBridges({})).resolves.toEqual([
{
bundledPluginId: "diagnostics-otel",
pluginId: "diagnostics-otel",
preferredSource: "npm",
npmSpec: "@openclaw/diagnostics-otel",
channelIds: ["diagnostics-otel"],
},
]);
});
it("does not create a relocation bridge without npm metadata", async () => {
readPersistedInstalledPluginIndexMock.mockResolvedValue(
makeIndex({
pluginId: "diagnostics-otel",
manifestPath: "/app/dist/extensions/diagnostics-otel/openclaw.plugin.json",
manifestHash: "hash",
source: "/app/dist/extensions/diagnostics-otel/index.js",
rootDir: "/app/dist/extensions/diagnostics-otel",
origin: "bundled",
enabled: true,
startup: {},
compat: [],
packageInstall: {
defaultChoice: "clawhub",
clawhub: {
spec: "clawhub:@openclaw/diagnostics-otel",
packageName: "@openclaw/diagnostics-otel",
exactVersion: false,
},
warnings: [],
},
}),
);
loadPluginManifestRegistryForInstalledIndexMock.mockReturnValue(
makeRegistry("diagnostics-otel"),
);
await expect(listPersistedBundledPluginLocationBridges({})).resolves.toEqual([]);
});
});