mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix(plugins): update trusted prerelease installs
This commit is contained in:
@@ -37,6 +37,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Channels/WhatsApp: allow `@whiskeysockets/libsignal-node` in `onlyBuiltDependencies` so pnpm v9+ `blockExoticSubdeps` no longer rejects the baileys git-tarball subdep and silences all inbound agent replies. Fixes #76539. Thanks @ottodeng and @vincentkoc.
|
||||
- Gateway/systemd: preserve operator-added secrets in the Gateway env file across re-stage while clearing OpenClaw-managed keys (such as `OPENCLAW_GATEWAY_TOKEN`) so a fresh staging value is never shadowed by a stale env-file copy; operator secrets are also retained when the state-dir `.env` is empty. Fixes #76860. Thanks @hclsys.
|
||||
- Plugin updates: do not short-circuit trusted official npm updates as unchanged when the default/latest spec still resolves to an already-installed prerelease that the installer should replace with a stable fallback. Thanks @vincentkoc.
|
||||
- Plugin tools: keep auth-unavailable optional tools hidden even when another default tool from the same plugin is available and `tools.alsoAllow` names the optional tool. Thanks @vincentkoc.
|
||||
- Realtime transcription: report socket closes before provider readiness as closed-before-ready failures instead of mislabeling them as connection timeouts for OpenAI, xAI, and Deepgram streaming transcription. Thanks @vincentkoc.
|
||||
- OpenAI/Google Meet: fail realtime voice connection attempts when the socket closes before `session.updated`, avoiding stuck Meet joins waiting on a bridge that never became ready. Thanks @vincentkoc.
|
||||
|
||||
@@ -507,6 +507,59 @@ describe("updateNpmInstalledPlugins", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not skip trusted official default updates when latest resolves to the installed prerelease", async () => {
|
||||
const installPath = createInstalledPackageDir({
|
||||
name: "@openclaw/acpx",
|
||||
version: "2026.5.2-beta.2",
|
||||
});
|
||||
mockNpmViewMetadata({
|
||||
name: "@openclaw/acpx",
|
||||
version: "2026.5.2-beta.2",
|
||||
integrity: "sha512-beta",
|
||||
shasum: "beta",
|
||||
});
|
||||
installPluginFromNpmSpecMock.mockResolvedValue(
|
||||
createSuccessfulNpmUpdateResult({
|
||||
pluginId: "acpx",
|
||||
targetDir: installPath,
|
||||
version: "2026.5.2",
|
||||
npmResolution: {
|
||||
name: "@openclaw/acpx",
|
||||
version: "2026.5.2",
|
||||
resolvedSpec: "@openclaw/acpx@2026.5.2",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: createNpmInstallConfig({
|
||||
pluginId: "acpx",
|
||||
spec: "@openclaw/acpx",
|
||||
installPath,
|
||||
integrity: "sha512-beta",
|
||||
shasum: "beta",
|
||||
resolvedName: "@openclaw/acpx",
|
||||
resolvedSpec: "@openclaw/acpx@2026.5.2-beta.2",
|
||||
resolvedVersion: "2026.5.2-beta.2",
|
||||
}),
|
||||
pluginIds: ["acpx"],
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpecMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "@openclaw/acpx",
|
||||
expectedPluginId: "acpx",
|
||||
trustedSourceLinkedOfficialInstall: true,
|
||||
}),
|
||||
);
|
||||
expect(result.outcomes[0]).toMatchObject({
|
||||
pluginId: "acpx",
|
||||
status: "updated",
|
||||
currentVersion: "2026.5.2-beta.2",
|
||||
nextVersion: "2026.5.2",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not trust official npm updates when the install record package mismatches", async () => {
|
||||
const installPath = createInstalledPackageDir({
|
||||
name: "@vendor/acpx-fork",
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import { parseClawHubPluginSpec } from "../infra/clawhub-spec.js";
|
||||
import type { NpmSpecResolution } from "../infra/install-source-utils.js";
|
||||
import { resolveNpmSpecMetadata } from "../infra/install-source-utils.js";
|
||||
import { parseRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
||||
import { isPrereleaseResolutionAllowed, parseRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
||||
import {
|
||||
expectedIntegrityForUpdate,
|
||||
readInstalledPackageVersion,
|
||||
@@ -179,6 +179,24 @@ function shouldSkipUnchangedNpmInstall(params: {
|
||||
);
|
||||
}
|
||||
|
||||
function shouldBypassTrustedOfficialUnchangedNpmCheck(params: {
|
||||
metadata: NpmSpecResolution;
|
||||
spec: string;
|
||||
trustedSourceLinkedOfficialInstall: boolean;
|
||||
}): boolean {
|
||||
if (!params.trustedSourceLinkedOfficialInstall || !params.metadata.version) {
|
||||
return false;
|
||||
}
|
||||
const parsedSpec = parseRegistryNpmSpec(params.spec);
|
||||
return Boolean(
|
||||
parsedSpec &&
|
||||
!isPrereleaseResolutionAllowed({
|
||||
spec: parsedSpec,
|
||||
resolvedVersion: params.metadata.version,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function isBundledVersionNewer(bundledVersion: string, installedVersion: string): boolean {
|
||||
const bundled = parseComparableSemver(bundledVersion);
|
||||
const installed = parseComparableSemver(installedVersion);
|
||||
@@ -853,6 +871,11 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
});
|
||||
if (metadataResult.ok) {
|
||||
if (
|
||||
!shouldBypassTrustedOfficialUnchangedNpmCheck({
|
||||
metadata: metadataResult.metadata,
|
||||
spec: effectiveSpec!,
|
||||
trustedSourceLinkedOfficialInstall,
|
||||
}) &&
|
||||
shouldSkipUnchangedNpmInstall({
|
||||
currentVersion,
|
||||
record,
|
||||
|
||||
Reference in New Issue
Block a user