mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:40:44 +00:00
fix(plugins): prefer newest official prerelease install
This commit is contained in:
@@ -34,7 +34,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- Plugins/catalog: merge official external catalog descriptors into partial package channel config metadata, so lagging WeCom/Yuanbao manifests keep their own schema while still exposing host-supplied labels and setup text. Thanks @vincentkoc.
|
||||
- Plugins/catalog: supplement lagging official external WeCom and Yuanbao npm manifests with channel config descriptors and declared tool contracts from the OpenClaw catalog, so trusted package sweeps no longer fail because external package metadata trails the host contract. Thanks @vincentkoc.
|
||||
- Plugins/install: let trusted official `@openclaw/*` catalog installs recover when npm `latest` points at a prerelease by falling back to the newest stable version, or by allowing prerelease-only launch packages with a warning instead of making beta/development plugin sweeps fail at install time. Thanks @vincentkoc.
|
||||
- Plugins/install: let trusted official `@openclaw/*` catalog installs recover when npm `latest` points at a prerelease by falling back to the newest stable version, or by selecting the newest exact prerelease for prerelease-only launch packages with a warning instead of making beta/development plugin sweeps fail at install time. Thanks @vincentkoc.
|
||||
- Google Meet: grant Chrome media permissions against the actual Meet tab, start the local realtime audio bridge only after Meet joins, expose realtime transcripts in status/logs, and force explicit audio responses with current OpenAI realtime output-audio events so BlackHole capture does not keep the OpenClaw participant muted or silent.
|
||||
- Google Meet: use the local call-control microphone button instead of disabled remote participant mute buttons, and block realtime speech when the OpenClaw Meet microphone remains muted.
|
||||
- Google Meet: refresh realtime browser state during status and retry delayed speech after Meet finishes joining, so a just-opened in-call tab no longer leaves speech stuck behind stale `not-in-call` health.
|
||||
|
||||
@@ -938,15 +938,24 @@ describe("installPluginFromNpmSpec", () => {
|
||||
runCommandWithTimeoutMock.mockReset();
|
||||
const prereleaseOnlyNpmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm");
|
||||
const prereleaseOnlyWarnings: string[] = [];
|
||||
mockNpmViewAndInstall({
|
||||
spec: "@openclaw/voice-call",
|
||||
packageName: "@openclaw/voice-call",
|
||||
version: "0.0.2-beta.1",
|
||||
pluginId: "voice-call",
|
||||
npmRoot: prereleaseOnlyNpmRoot,
|
||||
versions: ["0.0.2-beta.1"],
|
||||
expectedDependencySpec: "0.0.2-beta.1",
|
||||
});
|
||||
mockNpmViewAndInstallMany([
|
||||
{
|
||||
spec: "@openclaw/voice-call",
|
||||
packageName: "@openclaw/voice-call",
|
||||
version: "0.0.1-beta.1",
|
||||
pluginId: "voice-call",
|
||||
npmRoot: prereleaseOnlyNpmRoot,
|
||||
versions: ["0.0.1-beta.1", "0.0.2-beta.1"],
|
||||
},
|
||||
{
|
||||
spec: "@openclaw/voice-call@0.0.2-beta.1",
|
||||
packageName: "@openclaw/voice-call",
|
||||
version: "0.0.2-beta.1",
|
||||
pluginId: "voice-call",
|
||||
npmRoot: prereleaseOnlyNpmRoot,
|
||||
expectedDependencySpec: "0.0.2-beta.1",
|
||||
},
|
||||
]);
|
||||
|
||||
const prereleaseOnly = await installPluginFromNpmSpec({
|
||||
spec: "@openclaw/voice-call",
|
||||
@@ -963,7 +972,11 @@ describe("installPluginFromNpmSpec", () => {
|
||||
return;
|
||||
}
|
||||
expect(prereleaseOnly.npmResolution?.version).toBe("0.0.2-beta.1");
|
||||
expect(prereleaseOnly.npmResolution?.resolvedSpec).toBe("@openclaw/voice-call@0.0.2-beta.1");
|
||||
expect(prereleaseOnlyWarnings.join("\n")).toContain("has no stable npm versions yet");
|
||||
expect(prereleaseOnlyWarnings.join("\n")).toContain(
|
||||
"using newest prerelease @openclaw/voice-call@0.0.2-beta.1",
|
||||
);
|
||||
|
||||
runCommandWithTimeoutMock.mockReset();
|
||||
const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm");
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
createSafeNpmInstallArgs,
|
||||
createSafeNpmInstallEnv,
|
||||
} from "../infra/safe-package-install.js";
|
||||
import { compareComparableSemver, parseComparableSemver } from "../infra/semver-compare.js";
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
import { createLazyImportLoader } from "../shared/lazy-promise.js";
|
||||
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||
@@ -160,18 +161,13 @@ function isNpmPackageNotFoundMessage(error: string): boolean {
|
||||
return /E404|404 not found|not in this registry/i.test(normalized);
|
||||
}
|
||||
|
||||
function compareStableSemver(a: string, b: string): number {
|
||||
const parse = (value: string): [number, number, number] => {
|
||||
const match = /^v?(\d+)\.(\d+)\.(\d+)/.exec(value.trim());
|
||||
return [Number(match?.[1] ?? 0), Number(match?.[2] ?? 0), Number(match?.[3] ?? 0)];
|
||||
};
|
||||
const left = parse(a);
|
||||
const right = parse(b);
|
||||
return left[0] - right[0] || left[1] - right[1] || left[2] - right[2];
|
||||
function compareNpmSemver(a: string, b: string): number {
|
||||
return compareComparableSemver(parseComparableSemver(a), parseComparableSemver(b)) ?? 0;
|
||||
}
|
||||
|
||||
type TrustedOfficialPrereleaseResolution =
|
||||
| { kind: "stable"; resolution: NpmSpecResolution }
|
||||
| { kind: "prerelease-only"; resolution: NpmSpecResolution }
|
||||
| { kind: "allow-prerelease-only" };
|
||||
|
||||
async function resolveTrustedOfficialPrereleaseResolution(params: {
|
||||
@@ -208,10 +204,28 @@ async function resolveTrustedOfficialPrereleaseResolution(params: {
|
||||
);
|
||||
const stableVersion = semverVersions
|
||||
.filter((value) => !isPrereleaseSemverVersion(value))
|
||||
.toSorted(compareStableSemver)
|
||||
.toSorted(compareNpmSemver)
|
||||
.at(-1);
|
||||
if (!stableVersion) {
|
||||
if (semverVersions.length > 0 && semverVersions.every(isPrereleaseSemverVersion)) {
|
||||
const prereleaseVersion = semverVersions
|
||||
.filter(isPrereleaseSemverVersion)
|
||||
.toSorted(compareNpmSemver)
|
||||
.at(-1);
|
||||
if (prereleaseVersion && semverVersions.every(isPrereleaseSemverVersion)) {
|
||||
if (prereleaseVersion !== params.resolvedPrereleaseVersion) {
|
||||
const prereleaseSpec = `${params.spec.name}@${prereleaseVersion}`;
|
||||
const metadataResult = await resolveNpmSpecMetadata({
|
||||
spec: prereleaseSpec,
|
||||
timeoutMs: params.timeoutMs,
|
||||
});
|
||||
if (!metadataResult.ok) {
|
||||
return null;
|
||||
}
|
||||
params.logger.warn?.(
|
||||
`Resolved ${params.spec.raw} to prerelease version ${params.resolvedPrereleaseVersion}; using newest prerelease ${prereleaseSpec} because this trusted official OpenClaw package has no stable npm versions yet.`,
|
||||
);
|
||||
return { kind: "prerelease-only", resolution: metadataResult.metadata };
|
||||
}
|
||||
params.logger.warn?.(
|
||||
`Resolved ${params.spec.raw} to prerelease version ${params.resolvedPrereleaseVersion}; allowing it because this trusted official OpenClaw package has no stable npm versions yet.`,
|
||||
);
|
||||
@@ -1265,7 +1279,7 @@ export async function installPluginFromNpmSpec(
|
||||
logger,
|
||||
})
|
||||
: null;
|
||||
if (trustedResolution?.kind === "stable") {
|
||||
if (trustedResolution?.kind === "stable" || trustedResolution?.kind === "prerelease-only") {
|
||||
Object.assign(npmResolution, trustedResolution.resolution, {
|
||||
resolvedAt: npmResolution.resolvedAt,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user