mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:20:42 +00:00
fix(plugins): restore preferred clawhub installs
This commit is contained in:
@@ -31,6 +31,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: move ClawHub-preferred externalized plugin installs back to ClawHub after an earlier npm fallback once the ClawHub package becomes available. 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.
|
||||
- Google Meet: preserve `realtime.introMessage: ""` so realtime Chrome joins can stay silent instead of restoring the default spoken intro. Thanks @vincentkoc.
|
||||
|
||||
@@ -2330,6 +2330,63 @@ describe("syncPluginsForUpdateChannel", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("moves ClawHub-preferred externalized plugin fallbacks back to ClawHub", async () => {
|
||||
resolveBundledPluginSourcesMock.mockReturnValue(new Map());
|
||||
installPluginFromClawHubMock.mockResolvedValue(
|
||||
createSuccessfulClawHubUpdateResult({
|
||||
pluginId: "legacy-chat",
|
||||
targetDir: "/tmp/openclaw-plugins/legacy-chat",
|
||||
version: "2026.5.1-beta.2",
|
||||
clawhubPackage: "legacy-chat",
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await syncPluginsForUpdateChannel({
|
||||
channel: "stable",
|
||||
externalizedBundledPluginBridges: [
|
||||
{
|
||||
bundledPluginId: "legacy-chat",
|
||||
preferredSource: "clawhub",
|
||||
clawhubSpec: "clawhub:legacy-chat@2026.5.1-beta.2",
|
||||
npmSpec: "@openclaw/legacy-chat",
|
||||
channelIds: ["legacy-chat"],
|
||||
},
|
||||
],
|
||||
config: {
|
||||
channels: {
|
||||
"legacy-chat": {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
installs: {
|
||||
"legacy-chat": {
|
||||
source: "npm",
|
||||
spec: "@openclaw/legacy-chat",
|
||||
installPath: "/tmp/openclaw-plugins/legacy-chat",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(installPluginFromClawHubMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "clawhub:legacy-chat@2026.5.1-beta.2",
|
||||
mode: "update",
|
||||
expectedPluginId: "legacy-chat",
|
||||
}),
|
||||
);
|
||||
expect(installPluginFromNpmSpecMock).not.toHaveBeenCalled();
|
||||
expect(result.changed).toBe(true);
|
||||
expect(result.summary.switchedToClawHub).toEqual(["legacy-chat"]);
|
||||
expect(result.config.plugins?.installs?.["legacy-chat"]).toMatchObject({
|
||||
source: "clawhub",
|
||||
spec: "clawhub:legacy-chat@2026.5.1-beta.2",
|
||||
installPath: "/tmp/openclaw-plugins/legacy-chat",
|
||||
});
|
||||
});
|
||||
|
||||
it("fails closed without npm fallback when ClawHub returns integrity drift", async () => {
|
||||
resolveBundledPluginSourcesMock.mockReturnValue(new Map());
|
||||
installPluginFromClawHubMock.mockResolvedValue({
|
||||
|
||||
@@ -492,6 +492,36 @@ function isTrustedSourceLinkedOfficialBridgeNpmInstall(params: {
|
||||
return Boolean(officialPackageName && requestedPackageName === officialPackageName);
|
||||
}
|
||||
|
||||
function isBridgeNpmInstall(params: {
|
||||
bridge: ExternalizedBundledPluginBridge;
|
||||
record: PluginInstallRecord;
|
||||
}): boolean {
|
||||
const npmSpec = getExternalizedBundledPluginNpmSpec(params.bridge);
|
||||
if (!npmSpec || params.record.source !== "npm") {
|
||||
return false;
|
||||
}
|
||||
const bridgePackageName = resolveNpmSpecPackageName(npmSpec);
|
||||
const recordPackageName =
|
||||
params.record.resolvedName ??
|
||||
resolveNpmSpecPackageName(params.record.spec) ??
|
||||
resolveNpmSpecPackageName(params.record.resolvedSpec);
|
||||
return Boolean(bridgePackageName && recordPackageName === bridgePackageName);
|
||||
}
|
||||
|
||||
function isBridgeClawHubInstall(params: {
|
||||
bridge: ExternalizedBundledPluginBridge;
|
||||
record: PluginInstallRecord;
|
||||
}): boolean {
|
||||
if (params.record.source !== "clawhub") {
|
||||
return false;
|
||||
}
|
||||
const clawhubSpec = getExternalizedBundledPluginClawHubSpec(params.bridge);
|
||||
const bridgeClawHubPackage = clawhubSpec ? parseClawHubPluginSpec(clawhubSpec)?.name : undefined;
|
||||
const recordClawHubPackage =
|
||||
params.record.clawhubPackage ?? parseClawHubPluginSpec(params.record.spec ?? "")?.name;
|
||||
return Boolean(bridgeClawHubPackage && recordClawHubPackage === bridgeClawHubPackage);
|
||||
}
|
||||
|
||||
function resolveNpmUpdateSpecs(params: {
|
||||
record: PluginInstallRecord;
|
||||
specOverride?: string;
|
||||
@@ -576,28 +606,20 @@ function isBridgeAlreadyInstalledFromPreferredSource(params: {
|
||||
bridge: ExternalizedBundledPluginBridge;
|
||||
record: PluginInstallRecord;
|
||||
}): boolean {
|
||||
const npmSpec = getExternalizedBundledPluginNpmSpec(params.bridge);
|
||||
if (npmSpec && params.record.source === "npm") {
|
||||
const bridgePackageName = resolveNpmSpecPackageName(npmSpec);
|
||||
const recordPackageName =
|
||||
params.record.resolvedName ??
|
||||
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(
|
||||
bridgeClawHubPackage &&
|
||||
params.record.source === "clawhub" &&
|
||||
recordClawHubPackage === bridgeClawHubPackage,
|
||||
);
|
||||
const preferredSource = getExternalizedBundledPluginPreferredSource(params.bridge);
|
||||
return preferredSource === "clawhub"
|
||||
? isBridgeClawHubInstall(params)
|
||||
: isBridgeNpmInstall(params);
|
||||
}
|
||||
|
||||
function isBridgeInstalledFromFallbackSource(params: {
|
||||
bridge: ExternalizedBundledPluginBridge;
|
||||
record: PluginInstallRecord;
|
||||
}): boolean {
|
||||
const preferredSource = getExternalizedBundledPluginPreferredSource(params.bridge);
|
||||
return preferredSource === "clawhub"
|
||||
? isBridgeNpmInstall(params)
|
||||
: isBridgeClawHubInstall(params);
|
||||
}
|
||||
|
||||
function replacePluginIdInList(
|
||||
@@ -1448,6 +1470,10 @@ export async function syncPluginsForUpdateChannel(params: {
|
||||
bridge,
|
||||
record: existing.record,
|
||||
env,
|
||||
}) &&
|
||||
!isBridgeInstalledFromFallbackSource({
|
||||
bridge,
|
||||
record: existing.record,
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user