fix(plugins): clean pinned externalized load paths

This commit is contained in:
Vincent Koc
2026-05-03 17:25:16 -07:00
parent b13e9f1864
commit 9799e412f8
3 changed files with 106 additions and 3 deletions

View File

@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
- Gateway/performance: keep raw channel-config schema parsing from discovering bundled plugin runtime metadata, and add `pnpm gateway:watch --benchmark-no-force` for profiling startup without the default port cleanup.
- Plugins/onboarding: let Manual setup install optional official plugins, including ClawHub-backed diagnostics with npm fallback, and expose the external Codex plugin as a selectable provider setup choice. Thanks @vincentkoc.
- Plugins/update: treat official externalized bundled npm migrations and ClawHub-to-npm fallbacks as trusted source-linked installs, so prerelease-only official plugin packages can migrate from bundled builds without being rejected as unsafe prerelease resolutions. Thanks @vincentkoc.
- Plugins/update: clean stale bundled load paths for already-externalized pinned npm and ClawHub plugin installs, so release-channel sync does not leave removed bundled paths ahead of the installed external package. Thanks @vincentkoc.
- Plugins/CLI: include package dependency install state in `openclaw plugins list --json` so scripts can spot missing plugin dependencies without runtime-loading plugins.
- Discord/status: add degraded Discord transport and gateway event-loop starvation signals to `openclaw channels status`, `openclaw status --deep`, and fetch-timeout logs so intermittent socket resets do not look like a healthy running channel. (#76327) Thanks @joshavant.
- Providers/OpenRouter: add opt-in response caching params that send OpenRouter's `X-OpenRouter-Cache`, `X-OpenRouter-Cache-TTL`, and cache-clear headers only on verified OpenRouter routes. Thanks @vincentkoc.

View File

@@ -2619,4 +2619,93 @@ describe("syncPluginsForUpdateChannel", () => {
spec: "@openclaw/legacy-chat",
});
});
it("removes stale bundled load paths for already-externalized pinned npm installs", async () => {
resolveBundledPluginSourcesMock.mockReturnValue(new Map());
const result = await syncPluginsForUpdateChannel({
channel: "stable",
externalizedBundledPluginBridges: [
{
bundledPluginId: "legacy-chat",
npmSpec: "@openclaw/legacy-chat",
channelIds: ["legacy-chat"],
},
],
config: {
channels: {
"legacy-chat": {
enabled: true,
},
},
plugins: {
load: {
paths: [appBundledPluginRoot("legacy-chat"), "/workspace/plugins/other"],
},
installs: {
"legacy-chat": {
source: "npm",
spec: "@openclaw/legacy-chat@1.2.3",
resolvedSpec: "@openclaw/legacy-chat@1.2.3",
installPath: "/tmp/openclaw-plugins/legacy-chat",
},
},
},
},
});
expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
expect(result.changed).toBe(true);
expect(result.config.plugins?.load?.paths).toEqual(["/workspace/plugins/other"]);
expect(result.config.plugins?.installs?.["legacy-chat"]).toMatchObject({
source: "npm",
spec: "@openclaw/legacy-chat@1.2.3",
});
});
it("removes stale bundled load paths for already-externalized pinned ClawHub installs", async () => {
resolveBundledPluginSourcesMock.mockReturnValue(new Map());
const result = await syncPluginsForUpdateChannel({
channel: "stable",
externalizedBundledPluginBridges: [
{
bundledPluginId: "legacy-chat",
preferredSource: "clawhub",
clawhubSpec: "clawhub:legacy-chat",
npmSpec: "@openclaw/legacy-chat",
channelIds: ["legacy-chat"],
},
],
config: {
channels: {
"legacy-chat": {
enabled: true,
},
},
plugins: {
load: {
paths: [appBundledPluginRoot("legacy-chat"), "/workspace/plugins/other"],
},
installs: {
"legacy-chat": {
source: "clawhub",
spec: "clawhub:legacy-chat@2026.5.1",
clawhubPackage: "legacy-chat",
installPath: "/tmp/openclaw-plugins/legacy-chat",
},
},
},
},
});
expect(installPluginFromClawHubMock).not.toHaveBeenCalled();
expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
expect(result.changed).toBe(true);
expect(result.config.plugins?.load?.paths).toEqual(["/workspace/plugins/other"]);
expect(result.config.plugins?.installs?.["legacy-chat"]).toMatchObject({
source: "clawhub",
spec: "clawhub:legacy-chat@2026.5.1",
});
});
});

View File

@@ -577,12 +577,25 @@ function isBridgeAlreadyInstalledFromPreferredSource(params: {
record: PluginInstallRecord;
}): boolean {
const npmSpec = getExternalizedBundledPluginNpmSpec(params.bridge);
if (npmSpec && params.record.source === "npm" && params.record.spec === npmSpec) {
return true;
if (npmSpec && params.record.source === "npm") {
const bridgePackageName = resolveNpmSpecPackageName(npmSpec);
const recordPackageName =
resolveNpmSpecPackageName(params.record.spec) ??
resolveNpmSpecPackageName(params.record.resolvedSpec);
if (bridgePackageName && recordPackageName === bridgePackageName) {
return true;
}
}
const clawhubSpec = getExternalizedBundledPluginClawHubSpec(params.bridge);
const bridgeClawHubPackage = clawhubSpec ? parseClawHubPluginSpec(clawhubSpec)?.name : undefined;
const recordClawHubPackage =
params.record.source === "clawhub"
? (params.record.clawhubPackage ?? parseClawHubPluginSpec(params.record.spec ?? "")?.name)
: undefined;
return Boolean(
clawhubSpec && params.record.source === "clawhub" && params.record.spec === clawhubSpec,
bridgeClawHubPackage &&
params.record.source === "clawhub" &&
recordClawHubPackage === bridgeClawHubPackage,
);
}