mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix(plugins): allow prerelease-only official packages
This commit is contained in:
@@ -32,6 +32,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
- Plugins/install: recover the install ledger from the managed npm root when `plugins/installs.json` is empty or partial, so reinstalling Discord and Codex no longer makes the other installed plugin disappear.
|
||||
|
||||
@@ -935,6 +935,36 @@ describe("installPluginFromNpmSpec", () => {
|
||||
expect(officialFallback.npmResolution?.resolvedSpec).toBe("@openclaw/voice-call@0.0.1");
|
||||
expect(warnings.join("\n")).toContain("falling back to stable @openclaw/voice-call@0.0.1");
|
||||
|
||||
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",
|
||||
});
|
||||
|
||||
const prereleaseOnly = await installPluginFromNpmSpec({
|
||||
spec: "@openclaw/voice-call",
|
||||
npmDir: prereleaseOnlyNpmRoot,
|
||||
expectedPluginId: "voice-call",
|
||||
trustedSourceLinkedOfficialInstall: true,
|
||||
logger: {
|
||||
info: () => {},
|
||||
warn: (msg: string) => prereleaseOnlyWarnings.push(msg),
|
||||
},
|
||||
});
|
||||
expect(prereleaseOnly.ok).toBe(true);
|
||||
if (!prereleaseOnly.ok) {
|
||||
return;
|
||||
}
|
||||
expect(prereleaseOnly.npmResolution?.version).toBe("0.0.2-beta.1");
|
||||
expect(prereleaseOnlyWarnings.join("\n")).toContain("has no stable npm versions yet");
|
||||
|
||||
runCommandWithTimeoutMock.mockReset();
|
||||
const npmRoot = path.join(suiteTempRootTracker.makeTempDir(), "npm");
|
||||
mockNpmViewAndInstall({
|
||||
|
||||
@@ -170,12 +170,16 @@ function compareStableSemver(a: string, b: string): number {
|
||||
return left[0] - right[0] || left[1] - right[1] || left[2] - right[2];
|
||||
}
|
||||
|
||||
async function resolveTrustedOfficialStableNpmResolution(params: {
|
||||
type TrustedOfficialPrereleaseResolution =
|
||||
| { kind: "stable"; resolution: NpmSpecResolution }
|
||||
| { kind: "allow-prerelease-only" };
|
||||
|
||||
async function resolveTrustedOfficialPrereleaseResolution(params: {
|
||||
spec: ParsedRegistryNpmSpec;
|
||||
resolvedPrereleaseVersion: string;
|
||||
timeoutMs: number;
|
||||
logger: PluginInstallLogger;
|
||||
}): Promise<NpmSpecResolution | null> {
|
||||
}): Promise<TrustedOfficialPrereleaseResolution | null> {
|
||||
if (!params.spec.name.startsWith("@openclaw/")) {
|
||||
return null;
|
||||
}
|
||||
@@ -199,12 +203,20 @@ async function resolveTrustedOfficialStableNpmResolution(params: {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const stableVersion = (Array.isArray(parsed) ? parsed : [parsed])
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
.filter((value) => isExactSemverVersion(value) && !isPrereleaseSemverVersion(value))
|
||||
const semverVersions = (Array.isArray(parsed) ? parsed : [parsed]).filter(
|
||||
(value): value is string => typeof value === "string" && isExactSemverVersion(value),
|
||||
);
|
||||
const stableVersion = semverVersions
|
||||
.filter((value) => !isPrereleaseSemverVersion(value))
|
||||
.sort(compareStableSemver)
|
||||
.at(-1);
|
||||
if (!stableVersion) {
|
||||
if (semverVersions.length > 0 && semverVersions.every(isPrereleaseSemverVersion)) {
|
||||
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.`,
|
||||
);
|
||||
return { kind: "allow-prerelease-only" };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -219,7 +231,7 @@ async function resolveTrustedOfficialStableNpmResolution(params: {
|
||||
params.logger.warn?.(
|
||||
`Resolved ${params.spec.raw} to prerelease version ${params.resolvedPrereleaseVersion}; falling back to stable ${stableSpec} for this trusted official OpenClaw install.`,
|
||||
);
|
||||
return metadataResult.metadata;
|
||||
return { kind: "stable", resolution: metadataResult.metadata };
|
||||
}
|
||||
|
||||
function buildFileInstallResult(pluginId: string, targetFile: string): InstallPluginResult {
|
||||
@@ -1245,18 +1257,20 @@ export async function installPluginFromNpmSpec(
|
||||
resolvedVersion: npmResolution.version,
|
||||
})
|
||||
) {
|
||||
const stableResolution = params.trustedSourceLinkedOfficialInstall
|
||||
? await resolveTrustedOfficialStableNpmResolution({
|
||||
const trustedResolution = params.trustedSourceLinkedOfficialInstall
|
||||
? await resolveTrustedOfficialPrereleaseResolution({
|
||||
spec: parsedSpec,
|
||||
resolvedPrereleaseVersion: npmResolution.version,
|
||||
timeoutMs,
|
||||
logger,
|
||||
})
|
||||
: null;
|
||||
if (stableResolution) {
|
||||
Object.assign(npmResolution, stableResolution, {
|
||||
if (trustedResolution?.kind === "stable") {
|
||||
Object.assign(npmResolution, trustedResolution.resolution, {
|
||||
resolvedAt: npmResolution.resolvedAt,
|
||||
});
|
||||
} else if (trustedResolution?.kind === "allow-prerelease-only") {
|
||||
// Keep the original prerelease resolution. The package has no stable line yet.
|
||||
} else {
|
||||
return {
|
||||
ok: false,
|
||||
|
||||
Reference in New Issue
Block a user