

























@@ -33,6 +33,10 @@ vi.mock("../plugins/install.js", () => ({
33333434const installPluginFromClawHub = vi.hoisted(() => vi.fn());
3535vi.mock("../plugins/clawhub.js", () => ({
36+CLAWHUB_INSTALL_ERROR_CODE: {
37+PACKAGE_NOT_FOUND: "package_not_found",
38+VERSION_NOT_FOUND: "version_not_found",
39+},
3640 installPluginFromClawHub,
3741}));
3842@@ -391,6 +395,98 @@ describe("ensureOnboardingPluginInstalled", () => {
391395expect(captured?.initialValue).toBe("clawhub");
392396});
393397398+it("falls back from ClawHub to npm when the ClawHub package is unavailable", async () => {
399+installPluginFromClawHub.mockResolvedValueOnce({
400+ok: false,
401+code: "package_not_found",
402+error: "Package not found on ClawHub.",
403+});
404+installPluginFromNpmSpec.mockResolvedValueOnce({
405+ok: true,
406+pluginId: "demo-plugin",
407+targetDir: "/tmp/demo-plugin",
408+version: "2026.5.2",
409+npmResolution: {
410+name: "@openclaw/demo-plugin",
411+version: "2026.5.2",
412+resolvedSpec: "@openclaw/demo-plugin@2026.5.2",
413+resolvedAt: "2026-05-01T00:00:00.000Z",
414+},
415+});
416+417+const result = await ensureOnboardingPluginInstalled({
418+cfg: {},
419+entry: {
420+pluginId: "demo-plugin",
421+label: "Demo Plugin",
422+install: {
423+clawhubSpec: "clawhub:demo-plugin@2026.5.2",
424+npmSpec: "@openclaw/demo-plugin@2026.5.2",
425+defaultChoice: "clawhub",
426+},
427+},
428+prompter: {
429+select: vi.fn(async () => "clawhub"),
430+confirm: vi.fn(async () => true),
431+note: vi.fn(async () => {}),
432+progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
433+} as never,
434+runtime: {} as never,
435+promptInstall: false,
436+});
437+438+expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
439+expect.objectContaining({
440+spec: "@openclaw/demo-plugin@2026.5.2",
441+expectedPluginId: "demo-plugin",
442+}),
443+);
444+expect(result.installed).toBe(true);
445+});
446+447+it("does not fall back from ClawHub to npm when ClawHub verification fails", async () => {
448+const confirm = vi.fn(async () => true);
449+const runtimeError = vi.fn();
450+installPluginFromClawHub.mockResolvedValueOnce({
451+ok: false,
452+code: "archive_integrity_mismatch",
453+error: "ClawHub ClawPack integrity mismatch.",
454+});
455+456+const result = await ensureOnboardingPluginInstalled({
457+cfg: {},
458+entry: {
459+pluginId: "demo-plugin",
460+label: "Demo Plugin",
461+install: {
462+clawhubSpec: "clawhub:demo-plugin@2026.5.2",
463+npmSpec: "@openclaw/demo-plugin@2026.5.2",
464+defaultChoice: "clawhub",
465+},
466+},
467+prompter: {
468+select: vi.fn(async () => "clawhub"),
469+ confirm,
470+note: vi.fn(async () => {}),
471+progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
472+} as never,
473+runtime: { error: runtimeError } as never,
474+promptInstall: false,
475+});
476+477+expect(confirm).not.toHaveBeenCalled();
478+expect(installPluginFromNpmSpec).not.toHaveBeenCalled();
479+expect(runtimeError).toHaveBeenCalledWith(
480+"Plugin install failed: ClawHub ClawPack integrity mismatch.",
481+);
482+expect(result).toEqual({
483+cfg: {},
484+installed: false,
485+pluginId: "demo-plugin",
486+status: "failed",
487+});
488+});
489+394490it("does not offer local installs when the workspace only has a spoofed .git marker", async () => {
395491await withTempDir({ prefix: "openclaw-onboarding-install-spoofed-git-" }, async (temp) => {
396492const workspaceDir = path.join(temp, "workspace");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。