mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix(plugins): follow beta channel for plugin updates
This commit is contained in:
@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Changes
|
||||
|
||||
- Plugins/CLI: include package dependency install state in `openclaw plugins list --json` so scripts can spot missing plugin dependencies without runtime-loading plugins.
|
||||
- Plugins/update: on the beta OpenClaw update channel, default-line npm and ClawHub plugin updates try `@beta` first and fall back to default/latest when no plugin beta release exists.
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -322,6 +322,10 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
|
||||
|
||||
Passing the npm package name without a version or tag also resolves back to the tracked plugin record. Use this when a plugin was pinned to an exact version and you want to move it back to the registry's default release line.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Beta channel updates">
|
||||
`openclaw plugins update` reuses the tracked plugin spec unless you pass a new spec. `openclaw update` additionally knows the active OpenClaw update channel: on the beta channel, default-line npm and ClawHub plugin records try `@beta` first, then fall back to the recorded default/latest spec if no plugin beta release exists. Exact versions and explicit tags stay pinned to that selector.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Version checks and integrity drift">
|
||||
Before a live npm update, OpenClaw checks the installed package version against the npm registry metadata. If the installed version and recorded artifact identity already match the resolved target, the update is skipped without downloading, reinstalling, or rewriting `openclaw.json`.
|
||||
|
||||
@@ -144,10 +144,15 @@ it manually.
|
||||
`openclaw doctor` runs as the final safe-update check.
|
||||
</Step>
|
||||
<Step title="Sync plugins">
|
||||
Syncs plugins to the active channel. Dev uses bundled plugins; stable and beta use npm. Updates npm-installed plugins.
|
||||
Syncs plugins to the active channel. Dev uses bundled plugins; stable and beta use npm. Updates tracked plugin installs.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
On the beta update channel, tracked npm and ClawHub plugin installs that follow
|
||||
the default/latest line try a plugin `@beta` release first. If the plugin has no
|
||||
beta release, OpenClaw falls back to the recorded default/latest spec. Exact
|
||||
versions and explicit tags are not rewritten.
|
||||
|
||||
<Warning>
|
||||
If an exact pinned npm plugin update resolves to an artifact whose integrity differs from the stored install record, `openclaw update` aborts that plugin artifact update instead of installing it. Reinstall or update the plugin explicitly only after verifying that you trust the new artifact.
|
||||
</Warning>
|
||||
|
||||
@@ -89,6 +89,11 @@ openclaw plugins update @scope/openclaw-plugin
|
||||
The second command moves a plugin back to the registry's default release line
|
||||
when it was previously pinned to an exact version or tag.
|
||||
|
||||
When `openclaw update` runs on the beta channel, default-line npm and ClawHub
|
||||
plugin records try the matching plugin `@beta` release first. If that beta
|
||||
release does not exist, OpenClaw falls back to the recorded default/latest spec.
|
||||
Exact versions and explicit tags such as `@rc` or `@beta` are preserved.
|
||||
|
||||
## Uninstall plugins
|
||||
|
||||
```bash
|
||||
|
||||
@@ -529,6 +529,9 @@ Passing the package name without a version moves an exact pinned install back to
|
||||
the registry's default release line. If the installed npm plugin already matches
|
||||
the resolved version and recorded artifact identity, OpenClaw skips the update
|
||||
without downloading, reinstalling, or rewriting config.
|
||||
When `openclaw update` runs on the beta channel, default-line npm and ClawHub
|
||||
plugin records try `@beta` first and fall back to default/latest when no plugin
|
||||
beta release exists. Exact versions and explicit tags stay pinned.
|
||||
|
||||
`--pin` is npm-only. It is not supported with `--marketplace`, because
|
||||
marketplace installs persist marketplace source metadata instead of an npm spec.
|
||||
|
||||
@@ -848,6 +848,7 @@ async function updatePluginsAfterCoreUpdate(params: {
|
||||
const npmResult = await updateNpmInstalledPlugins({
|
||||
config: pluginConfig,
|
||||
timeoutMs: params.timeoutMs,
|
||||
updateChannel: params.channel,
|
||||
skipIds: new Set(syncResult.summary.switchedToNpm),
|
||||
skipDisabledPlugins: true,
|
||||
logger: pluginLogger,
|
||||
|
||||
@@ -170,13 +170,14 @@ function createClawHubInstallConfig(params: {
|
||||
clawhubPackage: string;
|
||||
clawhubFamily: "bundle-plugin" | "code-plugin";
|
||||
clawhubChannel: "community" | "official" | "private";
|
||||
spec?: string;
|
||||
}): OpenClawConfig {
|
||||
return {
|
||||
plugins: {
|
||||
installs: {
|
||||
[params.pluginId]: {
|
||||
source: "clawhub" as const,
|
||||
spec: `clawhub:${params.clawhubPackage}`,
|
||||
spec: params.spec ?? `clawhub:${params.clawhubPackage}`,
|
||||
installPath: params.installPath,
|
||||
clawhubUrl: params.clawhubUrl,
|
||||
clawhubPackage: params.clawhubPackage,
|
||||
@@ -1029,6 +1030,115 @@ describe("updateNpmInstalledPlugins", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("tries npm beta for default npm specs on beta channel without persisting the beta tag", async () => {
|
||||
installPluginFromNpmSpecMock.mockResolvedValue(
|
||||
createSuccessfulNpmUpdateResult({
|
||||
pluginId: "openclaw-codex-app-server",
|
||||
targetDir: "/tmp/openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
npmResolution: {
|
||||
name: "openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
resolvedSpec: "openclaw-codex-app-server@0.2.0-beta.4",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: createCodexAppServerInstallConfig({
|
||||
spec: "openclaw-codex-app-server",
|
||||
}),
|
||||
pluginIds: ["openclaw-codex-app-server"],
|
||||
updateChannel: "beta",
|
||||
});
|
||||
|
||||
expectNpmUpdateCall({
|
||||
spec: "openclaw-codex-app-server@beta",
|
||||
expectedPluginId: "openclaw-codex-app-server",
|
||||
});
|
||||
expectCodexAppServerInstallState({
|
||||
result,
|
||||
spec: "openclaw-codex-app-server",
|
||||
version: "0.2.0-beta.4",
|
||||
resolvedSpec: "openclaw-codex-app-server@0.2.0-beta.4",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the default npm spec when a beta tag is unavailable", async () => {
|
||||
installPluginFromNpmSpecMock
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
error:
|
||||
"npm ERR! code ETARGET\nnpm ERR! No matching version found for openclaw-codex-app-server@beta.",
|
||||
})
|
||||
.mockResolvedValueOnce(
|
||||
createSuccessfulNpmUpdateResult({
|
||||
pluginId: "openclaw-codex-app-server",
|
||||
targetDir: "/tmp/openclaw-codex-app-server",
|
||||
version: "0.2.6",
|
||||
npmResolution: {
|
||||
name: "openclaw-codex-app-server",
|
||||
version: "0.2.6",
|
||||
resolvedSpec: "openclaw-codex-app-server@0.2.6",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const warnMessages: string[] = [];
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: createCodexAppServerInstallConfig({
|
||||
spec: "openclaw-codex-app-server",
|
||||
}),
|
||||
pluginIds: ["openclaw-codex-app-server"],
|
||||
updateChannel: "beta",
|
||||
logger: { warn: (msg) => warnMessages.push(msg) },
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpecMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
spec: "openclaw-codex-app-server@beta",
|
||||
}),
|
||||
);
|
||||
expect(installPluginFromNpmSpecMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
spec: "openclaw-codex-app-server",
|
||||
}),
|
||||
);
|
||||
expect(warnMessages).toEqual([expect.stringContaining("has no beta npm release")]);
|
||||
expectCodexAppServerInstallState({
|
||||
result,
|
||||
spec: "openclaw-codex-app-server",
|
||||
version: "0.2.6",
|
||||
resolvedSpec: "openclaw-codex-app-server@0.2.6",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves explicit npm tags when updating on the beta channel", async () => {
|
||||
installPluginFromNpmSpecMock.mockResolvedValue(
|
||||
createSuccessfulNpmUpdateResult({
|
||||
pluginId: "openclaw-codex-app-server",
|
||||
targetDir: "/tmp/openclaw-codex-app-server",
|
||||
version: "0.2.0-rc.1",
|
||||
}),
|
||||
);
|
||||
|
||||
await updateNpmInstalledPlugins({
|
||||
config: createCodexAppServerInstallConfig({
|
||||
spec: "openclaw-codex-app-server@rc",
|
||||
}),
|
||||
pluginIds: ["openclaw-codex-app-server"],
|
||||
updateChannel: "beta",
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expectNpmUpdateCall({
|
||||
spec: "openclaw-codex-app-server@rc",
|
||||
expectedPluginId: "openclaw-codex-app-server",
|
||||
});
|
||||
});
|
||||
|
||||
it("updates ClawHub-installed plugins via recorded package metadata", async () => {
|
||||
installPluginFromClawHubMock.mockResolvedValue({
|
||||
ok: true,
|
||||
@@ -1098,6 +1208,130 @@ describe("updateNpmInstalledPlugins", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("tries ClawHub beta for default ClawHub specs on beta channel without persisting the beta tag", async () => {
|
||||
installPluginFromClawHubMock.mockResolvedValue(
|
||||
createSuccessfulClawHubUpdateResult({
|
||||
pluginId: "demo",
|
||||
targetDir: "/tmp/demo",
|
||||
version: "1.3.0-beta.1",
|
||||
clawhubPackage: "demo",
|
||||
}),
|
||||
);
|
||||
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: createClawHubInstallConfig({
|
||||
pluginId: "demo",
|
||||
installPath: "/tmp/demo",
|
||||
clawhubUrl: "https://clawhub.ai",
|
||||
clawhubPackage: "demo",
|
||||
clawhubFamily: "code-plugin",
|
||||
clawhubChannel: "official",
|
||||
}),
|
||||
pluginIds: ["demo"],
|
||||
updateChannel: "beta",
|
||||
});
|
||||
|
||||
expect(installPluginFromClawHubMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "clawhub:demo@beta",
|
||||
baseUrl: "https://clawhub.ai",
|
||||
expectedPluginId: "demo",
|
||||
}),
|
||||
);
|
||||
expect(result.config.plugins?.installs?.demo).toMatchObject({
|
||||
source: "clawhub",
|
||||
spec: "clawhub:demo",
|
||||
installPath: "/tmp/demo",
|
||||
version: "1.3.0-beta.1",
|
||||
clawhubPackage: "demo",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to the default ClawHub spec when a beta release is unavailable", async () => {
|
||||
installPluginFromClawHubMock
|
||||
.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
code: "version_not_found",
|
||||
error: "version not found: beta",
|
||||
})
|
||||
.mockResolvedValueOnce(
|
||||
createSuccessfulClawHubUpdateResult({
|
||||
pluginId: "demo",
|
||||
targetDir: "/tmp/demo",
|
||||
version: "1.2.4",
|
||||
clawhubPackage: "demo",
|
||||
}),
|
||||
);
|
||||
|
||||
const warnMessages: string[] = [];
|
||||
const result = await updateNpmInstalledPlugins({
|
||||
config: createClawHubInstallConfig({
|
||||
pluginId: "demo",
|
||||
installPath: "/tmp/demo",
|
||||
clawhubUrl: "https://clawhub.ai",
|
||||
clawhubPackage: "demo",
|
||||
clawhubFamily: "code-plugin",
|
||||
clawhubChannel: "official",
|
||||
}),
|
||||
pluginIds: ["demo"],
|
||||
updateChannel: "beta",
|
||||
logger: { warn: (msg) => warnMessages.push(msg) },
|
||||
});
|
||||
|
||||
expect(installPluginFromClawHubMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
spec: "clawhub:demo@beta",
|
||||
}),
|
||||
);
|
||||
expect(installPluginFromClawHubMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
spec: "clawhub:demo",
|
||||
}),
|
||||
);
|
||||
expect(warnMessages).toEqual([expect.stringContaining("has no beta ClawHub release")]);
|
||||
expect(result.config.plugins?.installs?.demo).toMatchObject({
|
||||
source: "clawhub",
|
||||
spec: "clawhub:demo",
|
||||
installPath: "/tmp/demo",
|
||||
version: "1.2.4",
|
||||
clawhubPackage: "demo",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves explicit ClawHub tags when updating on the beta channel", async () => {
|
||||
installPluginFromClawHubMock.mockResolvedValue(
|
||||
createSuccessfulClawHubUpdateResult({
|
||||
pluginId: "demo",
|
||||
targetDir: "/tmp/demo",
|
||||
version: "1.3.0-rc.1",
|
||||
clawhubPackage: "demo",
|
||||
}),
|
||||
);
|
||||
|
||||
await updateNpmInstalledPlugins({
|
||||
config: createClawHubInstallConfig({
|
||||
pluginId: "demo",
|
||||
installPath: "/tmp/demo",
|
||||
clawhubUrl: "https://clawhub.ai",
|
||||
clawhubPackage: "demo",
|
||||
clawhubFamily: "code-plugin",
|
||||
clawhubChannel: "official",
|
||||
spec: "clawhub:demo@rc",
|
||||
}),
|
||||
pluginIds: ["demo"],
|
||||
updateChannel: "beta",
|
||||
dryRun: true,
|
||||
});
|
||||
|
||||
expect(installPluginFromClawHubMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "clawhub:demo@rc",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("skips ClawHub plugin update when bundled version is newer", async () => {
|
||||
resolveBundledPluginSourcesMock.mockReturnValue(
|
||||
new Map([
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||
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 {
|
||||
expectedIntegrityForUpdate,
|
||||
readInstalledPackageVersion,
|
||||
@@ -395,6 +397,113 @@ function shouldFallbackClawHubBridgeToNpm(result: { ok: false; code?: string }):
|
||||
);
|
||||
}
|
||||
|
||||
function shouldFallbackBetaClawHubUpdate(result: { ok: false; code?: string }): boolean {
|
||||
return shouldFallbackClawHubBridgeToNpm(result);
|
||||
}
|
||||
|
||||
function shouldFallbackBetaNpmUpdate(result: { ok: false; code?: string; error: string }): boolean {
|
||||
if (result.code === PLUGIN_INSTALL_ERROR_CODE.NPM_PACKAGE_NOT_FOUND) {
|
||||
return true;
|
||||
}
|
||||
return /\b(ETARGET|notarget)\b|No matching version found|dist-tag|tag .*not found/i.test(
|
||||
result.error,
|
||||
);
|
||||
}
|
||||
|
||||
function isDefaultNpmSpecForBetaUpdate(spec: string): { name: string } | null {
|
||||
const parsed = parseRegistryNpmSpec(spec);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
if (parsed.selectorKind === "none") {
|
||||
return { name: parsed.name };
|
||||
}
|
||||
if (parsed.selectorKind === "tag" && parsed.selector?.toLowerCase() === "latest") {
|
||||
return { name: parsed.name };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveNpmUpdateSpecs(params: {
|
||||
record: PluginInstallRecord;
|
||||
specOverride?: string;
|
||||
updateChannel?: UpdateChannel;
|
||||
}): {
|
||||
installSpec?: string;
|
||||
recordSpec?: string;
|
||||
fallbackSpec?: string;
|
||||
fallbackLabel?: string;
|
||||
} {
|
||||
const recordSpec = params.specOverride ?? params.record.spec;
|
||||
if (!recordSpec) {
|
||||
return {};
|
||||
}
|
||||
if (params.specOverride || params.updateChannel !== "beta") {
|
||||
return {
|
||||
installSpec: recordSpec,
|
||||
recordSpec,
|
||||
};
|
||||
}
|
||||
const betaTarget = isDefaultNpmSpecForBetaUpdate(recordSpec);
|
||||
if (!betaTarget) {
|
||||
return {
|
||||
installSpec: recordSpec,
|
||||
recordSpec,
|
||||
};
|
||||
}
|
||||
return {
|
||||
installSpec: `${betaTarget.name}@beta`,
|
||||
recordSpec,
|
||||
fallbackSpec: recordSpec,
|
||||
fallbackLabel: `${betaTarget.name}@beta`,
|
||||
};
|
||||
}
|
||||
|
||||
function isDefaultClawHubSpecForBetaUpdate(spec: string): { name: string } | null {
|
||||
const parsed = parseClawHubPluginSpec(spec);
|
||||
if (!parsed) {
|
||||
return null;
|
||||
}
|
||||
if (!parsed.version || parsed.version.toLowerCase() === "latest") {
|
||||
return { name: parsed.name };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveClawHubUpdateSpecs(params: {
|
||||
record: PluginInstallRecord;
|
||||
updateChannel?: UpdateChannel;
|
||||
}): {
|
||||
installSpec?: string;
|
||||
recordSpec?: string;
|
||||
fallbackSpec?: string;
|
||||
fallbackLabel?: string;
|
||||
} {
|
||||
if (!params.record.clawhubPackage) {
|
||||
return {};
|
||||
}
|
||||
const recordSpec = params.record.spec ?? `clawhub:${params.record.clawhubPackage}`;
|
||||
if (params.updateChannel !== "beta") {
|
||||
return {
|
||||
installSpec: recordSpec,
|
||||
recordSpec,
|
||||
};
|
||||
}
|
||||
const betaTarget = isDefaultClawHubSpecForBetaUpdate(recordSpec);
|
||||
if (!betaTarget) {
|
||||
return {
|
||||
installSpec: recordSpec,
|
||||
recordSpec,
|
||||
};
|
||||
}
|
||||
return {
|
||||
installSpec: `clawhub:${betaTarget.name}@beta`,
|
||||
recordSpec,
|
||||
fallbackSpec: recordSpec,
|
||||
fallbackLabel: `clawhub:${betaTarget.name}@beta`,
|
||||
};
|
||||
}
|
||||
|
||||
function isBridgeAlreadyInstalledFromPreferredSource(params: {
|
||||
bridge: ExternalizedBundledPluginBridge;
|
||||
record: PluginInstallRecord;
|
||||
@@ -516,6 +625,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
skipDisabledPlugins?: boolean;
|
||||
timeoutMs?: number;
|
||||
dryRun?: boolean;
|
||||
updateChannel?: UpdateChannel;
|
||||
dangerouslyForceUnsafeInstall?: boolean;
|
||||
specOverrides?: Record<string, string>;
|
||||
onIntegrityDrift?: (params: PluginUpdateIntegrityDriftParams) => boolean | Promise<boolean>;
|
||||
@@ -582,12 +692,41 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
continue;
|
||||
}
|
||||
|
||||
const npmSpecs =
|
||||
record.source === "npm"
|
||||
? resolveNpmUpdateSpecs({
|
||||
record,
|
||||
specOverride: params.specOverrides?.[pluginId],
|
||||
updateChannel: params.updateChannel,
|
||||
})
|
||||
: undefined;
|
||||
const clawhubSpecs =
|
||||
record.source === "clawhub"
|
||||
? resolveClawHubUpdateSpecs({
|
||||
record,
|
||||
updateChannel: params.updateChannel,
|
||||
})
|
||||
: undefined;
|
||||
const effectiveSpec =
|
||||
record.source === "npm" ? (params.specOverrides?.[pluginId] ?? record.spec) : record.spec;
|
||||
record.source === "npm"
|
||||
? npmSpecs?.installSpec
|
||||
: record.source === "clawhub"
|
||||
? clawhubSpecs?.installSpec
|
||||
: record.spec;
|
||||
const recordSpec =
|
||||
record.source === "npm"
|
||||
? npmSpecs?.recordSpec
|
||||
: record.source === "clawhub"
|
||||
? clawhubSpecs?.recordSpec
|
||||
: record.spec;
|
||||
const expectedIntegrity =
|
||||
record.source === "npm" && effectiveSpec === record.spec
|
||||
? expectedIntegrityForUpdate(record.spec, record.integrity)
|
||||
: undefined;
|
||||
const fallbackExpectedIntegrity =
|
||||
record.source === "npm" && npmSpecs?.fallbackSpec === record.spec
|
||||
? expectedIntegrityForUpdate(record.spec, record.integrity)
|
||||
: undefined;
|
||||
|
||||
if (record.source === "npm" && !effectiveSpec) {
|
||||
outcomes.push({
|
||||
@@ -764,6 +903,54 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!probe.ok &&
|
||||
record.source === "npm" &&
|
||||
npmSpecs?.fallbackSpec &&
|
||||
shouldFallbackBetaNpmUpdate(probe)
|
||||
) {
|
||||
logger.warn?.(
|
||||
`Plugin "${pluginId}" has no beta npm release for ${npmSpecs.fallbackLabel ?? effectiveSpec}; falling back to ${npmSpecs.fallbackSpec}.`,
|
||||
);
|
||||
probe = await installPluginFromNpmSpec({
|
||||
spec: npmSpecs.fallbackSpec,
|
||||
mode: "update",
|
||||
extensionsDir,
|
||||
timeoutMs: params.timeoutMs,
|
||||
dryRun: true,
|
||||
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
|
||||
expectedPluginId: pluginId,
|
||||
expectedIntegrity: fallbackExpectedIntegrity,
|
||||
onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
|
||||
pluginId,
|
||||
dryRun: true,
|
||||
logger,
|
||||
onIntegrityDrift: params.onIntegrityDrift,
|
||||
}),
|
||||
logger,
|
||||
});
|
||||
}
|
||||
if (
|
||||
!probe.ok &&
|
||||
record.source === "clawhub" &&
|
||||
clawhubSpecs?.fallbackSpec &&
|
||||
shouldFallbackBetaClawHubUpdate(probe)
|
||||
) {
|
||||
logger.warn?.(
|
||||
`Plugin "${pluginId}" has no beta ClawHub release for ${clawhubSpecs.fallbackLabel ?? effectiveSpec}; falling back to ${clawhubSpecs.fallbackSpec}.`,
|
||||
);
|
||||
probe = await installPluginFromClawHub({
|
||||
spec: clawhubSpecs.fallbackSpec,
|
||||
baseUrl: record.clawhubUrl,
|
||||
mode: "update",
|
||||
extensionsDir,
|
||||
timeoutMs: params.timeoutMs,
|
||||
dryRun: true,
|
||||
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
|
||||
expectedPluginId: pluginId,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
if (!probe.ok) {
|
||||
outcomes.push({
|
||||
pluginId,
|
||||
@@ -895,6 +1082,52 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
!result.ok &&
|
||||
record.source === "npm" &&
|
||||
npmSpecs?.fallbackSpec &&
|
||||
shouldFallbackBetaNpmUpdate(result)
|
||||
) {
|
||||
logger.warn?.(
|
||||
`Plugin "${pluginId}" has no beta npm release for ${npmSpecs.fallbackLabel ?? effectiveSpec}; falling back to ${npmSpecs.fallbackSpec}.`,
|
||||
);
|
||||
result = await installPluginFromNpmSpec({
|
||||
spec: npmSpecs.fallbackSpec,
|
||||
mode: "update",
|
||||
extensionsDir,
|
||||
timeoutMs: params.timeoutMs,
|
||||
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
|
||||
expectedPluginId: pluginId,
|
||||
expectedIntegrity: fallbackExpectedIntegrity,
|
||||
onIntegrityDrift: createPluginUpdateIntegrityDriftHandler({
|
||||
pluginId,
|
||||
dryRun: false,
|
||||
logger,
|
||||
onIntegrityDrift: params.onIntegrityDrift,
|
||||
}),
|
||||
logger,
|
||||
});
|
||||
}
|
||||
if (
|
||||
!result.ok &&
|
||||
record.source === "clawhub" &&
|
||||
clawhubSpecs?.fallbackSpec &&
|
||||
shouldFallbackBetaClawHubUpdate(result)
|
||||
) {
|
||||
logger.warn?.(
|
||||
`Plugin "${pluginId}" has no beta ClawHub release for ${clawhubSpecs.fallbackLabel ?? effectiveSpec}; falling back to ${clawhubSpecs.fallbackSpec}.`,
|
||||
);
|
||||
result = await installPluginFromClawHub({
|
||||
spec: clawhubSpecs.fallbackSpec,
|
||||
baseUrl: record.clawhubUrl,
|
||||
mode: "update",
|
||||
extensionsDir,
|
||||
timeoutMs: params.timeoutMs,
|
||||
dangerouslyForceUnsafeInstall: params.dangerouslyForceUnsafeInstall,
|
||||
expectedPluginId: pluginId,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
if (!result.ok) {
|
||||
outcomes.push({
|
||||
pluginId,
|
||||
@@ -942,7 +1175,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
next = recordPluginInstall(next, {
|
||||
pluginId: resolvedPluginId,
|
||||
source: "npm",
|
||||
spec: effectiveSpec,
|
||||
spec: recordSpec,
|
||||
installPath: result.targetDir,
|
||||
version: nextVersion,
|
||||
...buildNpmResolutionInstallFields(result.npmResolution),
|
||||
@@ -955,7 +1188,7 @@ export async function updateNpmInstalledPlugins(params: {
|
||||
next = recordPluginInstall(next, {
|
||||
pluginId: resolvedPluginId,
|
||||
...buildClawHubPluginInstallRecordFields(clawhubResult.clawhub),
|
||||
spec: effectiveSpec ?? record.spec ?? `clawhub:${record.clawhubPackage!}`,
|
||||
spec: recordSpec ?? record.spec ?? `clawhub:${record.clawhubPackage!}`,
|
||||
installPath: result.targetDir,
|
||||
version: nextVersion,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user