mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 09:20:43 +00:00
fix(onboarding): limit ClawHub npm fallback
This commit is contained in:
@@ -23,6 +23,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/doctor: keep missing-plugin repair from overriding official catalog metadata with runtime fallbacks, so ACPX repairs preserve the beta npm spec during the externalization rollout. Thanks @vincentkoc.
|
||||
- Plugins/catalog: preserve ClawHub install specs when generating the packaged channel catalog so future storepack-first channel plugins keep their remote source instead of becoming npm-only. Thanks @vincentkoc.
|
||||
- Plugins/update: treat catalog-matched official npm updates and OpenClaw-authored externalized-bundled npm bridges as trusted official installs so launch-code plugins can update or migrate out of the bundled tree without scanner false positives. Thanks @vincentkoc.
|
||||
- Plugins/onboarding: fall back from ClawHub to npm only for missing package/version errors, keeping integrity and verification failures fail-closed during storepack rollout. Thanks @vincentkoc.
|
||||
- Control UI/Talk: fix Talk (OpenAI Realtime WebRTC) CORS failure by stripping server-side-only attribution headers (`originator`, `version`, `User-Agent`) from browser offer headers; `api.openai.com/v1/realtime/calls` only allows `authorization` and `content-type` in its CORS preflight, so forwarding these headers caused the browser SDP exchange to fail. Fixes #76435. Thanks @hclsys.
|
||||
- CLI/logs: auto-reconnect `openclaw logs --follow` on transient gateway disconnects (WebSocket close, timeout, connection drop) with bounded exponential backoff (up to 8 retries, capped at 30 s) and stderr retry warnings, while still exiting immediately on non-recoverable auth or configuration errors. Fixes #74782. (#75059) Thanks @shashank-poola.
|
||||
- Plugins/onboarding: trust optional official plugin and web-search installs selected from the official catalog so npm security scanning treats them like other source-linked official install paths. Thanks @vincentkoc.
|
||||
|
||||
@@ -33,6 +33,10 @@ vi.mock("../plugins/install.js", () => ({
|
||||
|
||||
const installPluginFromClawHub = vi.hoisted(() => vi.fn());
|
||||
vi.mock("../plugins/clawhub.js", () => ({
|
||||
CLAWHUB_INSTALL_ERROR_CODE: {
|
||||
PACKAGE_NOT_FOUND: "package_not_found",
|
||||
VERSION_NOT_FOUND: "version_not_found",
|
||||
},
|
||||
installPluginFromClawHub,
|
||||
}));
|
||||
|
||||
@@ -391,6 +395,98 @@ describe("ensureOnboardingPluginInstalled", () => {
|
||||
expect(captured?.initialValue).toBe("clawhub");
|
||||
});
|
||||
|
||||
it("falls back from ClawHub to npm when the ClawHub package is unavailable", async () => {
|
||||
installPluginFromClawHub.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
code: "package_not_found",
|
||||
error: "Package not found on ClawHub.",
|
||||
});
|
||||
installPluginFromNpmSpec.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
pluginId: "demo-plugin",
|
||||
targetDir: "/tmp/demo-plugin",
|
||||
version: "2026.5.2",
|
||||
npmResolution: {
|
||||
name: "@openclaw/demo-plugin",
|
||||
version: "2026.5.2",
|
||||
resolvedSpec: "@openclaw/demo-plugin@2026.5.2",
|
||||
resolvedAt: "2026-05-01T00:00:00.000Z",
|
||||
},
|
||||
});
|
||||
|
||||
const result = await ensureOnboardingPluginInstalled({
|
||||
cfg: {},
|
||||
entry: {
|
||||
pluginId: "demo-plugin",
|
||||
label: "Demo Plugin",
|
||||
install: {
|
||||
clawhubSpec: "clawhub:demo-plugin@2026.5.2",
|
||||
npmSpec: "@openclaw/demo-plugin@2026.5.2",
|
||||
defaultChoice: "clawhub",
|
||||
},
|
||||
},
|
||||
prompter: {
|
||||
select: vi.fn(async () => "clawhub"),
|
||||
confirm: vi.fn(async () => true),
|
||||
note: vi.fn(async () => {}),
|
||||
progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
|
||||
} as never,
|
||||
runtime: {} as never,
|
||||
promptInstall: false,
|
||||
});
|
||||
|
||||
expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "@openclaw/demo-plugin@2026.5.2",
|
||||
expectedPluginId: "demo-plugin",
|
||||
}),
|
||||
);
|
||||
expect(result.installed).toBe(true);
|
||||
});
|
||||
|
||||
it("does not fall back from ClawHub to npm when ClawHub verification fails", async () => {
|
||||
const confirm = vi.fn(async () => true);
|
||||
const runtimeError = vi.fn();
|
||||
installPluginFromClawHub.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
code: "archive_integrity_mismatch",
|
||||
error: "ClawHub ClawPack integrity mismatch.",
|
||||
});
|
||||
|
||||
const result = await ensureOnboardingPluginInstalled({
|
||||
cfg: {},
|
||||
entry: {
|
||||
pluginId: "demo-plugin",
|
||||
label: "Demo Plugin",
|
||||
install: {
|
||||
clawhubSpec: "clawhub:demo-plugin@2026.5.2",
|
||||
npmSpec: "@openclaw/demo-plugin@2026.5.2",
|
||||
defaultChoice: "clawhub",
|
||||
},
|
||||
},
|
||||
prompter: {
|
||||
select: vi.fn(async () => "clawhub"),
|
||||
confirm,
|
||||
note: vi.fn(async () => {}),
|
||||
progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
|
||||
} as never,
|
||||
runtime: { error: runtimeError } as never,
|
||||
promptInstall: false,
|
||||
});
|
||||
|
||||
expect(confirm).not.toHaveBeenCalled();
|
||||
expect(installPluginFromNpmSpec).not.toHaveBeenCalled();
|
||||
expect(runtimeError).toHaveBeenCalledWith(
|
||||
"Plugin install failed: ClawHub ClawPack integrity mismatch.",
|
||||
);
|
||||
expect(result).toEqual({
|
||||
cfg: {},
|
||||
installed: false,
|
||||
pluginId: "demo-plugin",
|
||||
status: "failed",
|
||||
});
|
||||
});
|
||||
|
||||
it("does not offer local installs when the workspace only has a spoofed .git marker", async () => {
|
||||
await withTempDir({ prefix: "openclaw-onboarding-install-spoofed-git-" }, async (temp) => {
|
||||
const workspaceDir = path.join(temp, "workspace");
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
resolveBundledPluginSources,
|
||||
} from "../plugins/bundled-sources.js";
|
||||
import { buildClawHubPluginInstallRecordFields } from "../plugins/clawhub-install-records.js";
|
||||
import { CLAWHUB_INSTALL_ERROR_CODE } from "../plugins/clawhub.js";
|
||||
import { enablePluginInConfig, type PluginEnableResult } from "../plugins/enable.js";
|
||||
import { resolveDefaultPluginExtensionsDir } from "../plugins/install-paths.js";
|
||||
import { installPluginFromNpmSpec } from "../plugins/install.js";
|
||||
@@ -42,6 +43,13 @@ export type OnboardingPluginInstallResult = {
|
||||
status: OnboardingPluginInstallStatus;
|
||||
};
|
||||
|
||||
function shouldFallbackClawHubToNpm(result: { ok: false; code?: string }): boolean {
|
||||
return (
|
||||
result.code === CLAWHUB_INSTALL_ERROR_CODE.PACKAGE_NOT_FOUND ||
|
||||
result.code === CLAWHUB_INSTALL_ERROR_CODE.VERSION_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
function resolveRealDirectory(dir: string): string | null {
|
||||
try {
|
||||
const resolved = fs.realpathSync(dir);
|
||||
@@ -852,7 +860,7 @@ export async function ensureOnboardingPluginInstalled(params: {
|
||||
"Plugin install",
|
||||
);
|
||||
|
||||
if (!npmSpec) {
|
||||
if (!npmSpec || !shouldFallbackClawHubToNpm(result)) {
|
||||
runtime.error?.(`Plugin install failed: ${sanitizeTerminalText(result.error)}`);
|
||||
return {
|
||||
cfg: next,
|
||||
|
||||
Reference in New Issue
Block a user