fix(plugins): fallback bundled channel specs when npm install returns 404 (#12849)

* plugins: add bundled source resolver

* plugins: add bundled source resolver tests

* cli: fallback npm 404 plugin installs to bundled sources

* plugins: use bundled source resolver during updates

* protocol: regenerate macos gateway swift models

* protocol: regenerate shared swift models

* Revert "protocol: regenerate shared swift models"

This reverts commit 6a2b08c47d2636610efbf16fc210d4114b05b4b4.

* Revert "protocol: regenerate macos gateway swift models"

This reverts commit 27c03010c6b9da07b404c93cdb0a1c2a3db671f5.
This commit is contained in:
Vincent Koc
2026-02-26 08:06:54 -05:00
committed by GitHub
parent 7b5153f214
commit cf311978ea
4 changed files with 214 additions and 44 deletions

View File

@@ -6,6 +6,7 @@ import type { OpenClawConfig } from "../config/config.js";
import { loadConfig, writeConfigFile } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js";
import { resolveArchiveKind } from "../infra/archive.js";
import { findBundledPluginByNpmSpec } from "../plugins/bundled-sources.js";
import { enablePluginInConfig } from "../plugins/enable.js";
import { installPluginFromNpmSpec, installPluginFromPath } from "../plugins/install.js";
import { recordPluginInstall } from "../plugins/installs.js";
@@ -147,6 +148,16 @@ function logSlotWarnings(warnings: string[]) {
}
}
function isPackageNotFoundInstallError(message: string): boolean {
const lower = message.toLowerCase();
return (
lower.includes("npm pack failed:") &&
(lower.includes("e404") ||
lower.includes("404 not found") ||
lower.includes("could not be found"))
);
}
export function registerPluginsCli(program: Command) {
const plugins = program
.command("plugins")
@@ -614,8 +625,52 @@ export function registerPluginsCli(program: Command) {
logger: createPluginInstallLogger(),
});
if (!result.ok) {
defaultRuntime.error(result.error);
process.exit(1);
const bundledFallback = isPackageNotFoundInstallError(result.error)
? findBundledPluginByNpmSpec({ spec: raw })
: undefined;
if (!bundledFallback) {
defaultRuntime.error(result.error);
process.exit(1);
}
const existing = cfg.plugins?.load?.paths ?? [];
const mergedPaths = Array.from(new Set([...existing, bundledFallback.localPath]));
let next: OpenClawConfig = {
...cfg,
plugins: {
...cfg.plugins,
load: {
...cfg.plugins?.load,
paths: mergedPaths,
},
entries: {
...cfg.plugins?.entries,
[bundledFallback.pluginId]: {
...(cfg.plugins?.entries?.[bundledFallback.pluginId] as object | undefined),
enabled: true,
},
},
},
};
next = recordPluginInstall(next, {
pluginId: bundledFallback.pluginId,
source: "path",
spec: raw,
sourcePath: bundledFallback.localPath,
installPath: bundledFallback.localPath,
});
const slotResult = applySlotSelectionForPlugin(next, bundledFallback.pluginId);
next = slotResult.config;
await writeConfigFile(next);
logSlotWarnings(slotResult.warnings);
defaultRuntime.log(
theme.warn(
`npm package unavailable for ${raw}; using bundled plugin at ${shortenHomePath(bundledFallback.localPath)}.`,
),
);
defaultRuntime.log(`Installed plugin: ${bundledFallback.pluginId}`);
defaultRuntime.log(`Restart the gateway to load plugins.`);
return;
}
// Ensure config validation sees newly installed plugin(s) even if the cache was warmed at startup.
clearPluginManifestRegistryCache();