From 82e7accf539d2e8ea2eb385726e3b7654b7492de Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sat, 2 May 2026 18:53:36 -0700 Subject: [PATCH] fix(onboarding): default dual-source installs to npm --- .../onboarding-plugin-install.test.ts | 34 +++++++++++++++++-- src/commands/onboarding-plugin-install.ts | 23 +++++++------ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/commands/onboarding-plugin-install.test.ts b/src/commands/onboarding-plugin-install.test.ts index 76bbafa2b68..b592416e151 100644 --- a/src/commands/onboarding-plugin-install.test.ts +++ b/src/commands/onboarding-plugin-install.test.ts @@ -317,7 +317,7 @@ describe("ensureOnboardingPluginInstalled", () => { expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); }); - it("offers ClawHub as the default remote source when package metadata provides it", async () => { + it("defaults dual-source remote installs to npm unless ClawHub is explicit", async () => { let captured: | { options: Array<{ @@ -353,11 +353,41 @@ describe("ensureOnboardingPluginInstalled", () => { { value: "npm", label: "Download from npm (@openclaw/demo-plugin@2026.5.2)" }, { value: "skip", label: "Skip for now" }, ]); - expect(captured?.initialValue).toBe("clawhub"); + expect(captured?.initialValue).toBe("npm"); expect(installPluginFromClawHub).not.toHaveBeenCalled(); expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); }); + it("honors explicit ClawHub defaults for dual-source remote installs", async () => { + let captured: + | { + initialValue: "clawhub" | "npm" | "local" | "skip"; + } + | undefined; + + await ensureOnboardingPluginInstalled({ + cfg: { update: { channel: "stable" } }, + 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 (input) => { + captured = input; + return "skip"; + }), + } as never, + runtime: {} as never, + }); + + expect(captured?.initialValue).toBe("clawhub"); + }); + 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"); diff --git a/src/commands/onboarding-plugin-install.ts b/src/commands/onboarding-plugin-install.ts index 4b28b9ea7d0..24aa9aad073 100644 --- a/src/commands/onboarding-plugin-install.ts +++ b/src/commands/onboarding-plugin-install.ts @@ -273,11 +273,21 @@ function resolveInstallDefaultChoice(params: { }): InstallChoice { const { cfg, entry, localPath, bundledLocalPath, hasClawHubSpec, hasNpmSpec } = params; const hasRemoteSpec = hasClawHubSpec || hasNpmSpec; + const entryDefault = entry.install.defaultChoice; + const remoteDefault = (): InstallChoice => { + if (entryDefault === "clawhub" && hasClawHubSpec) { + return "clawhub"; + } + if (entryDefault === "npm" && hasNpmSpec) { + return "npm"; + } + return hasNpmSpec ? "npm" : "clawhub"; + }; if (!hasRemoteSpec) { return localPath ? "local" : "skip"; } if (!localPath) { - return hasClawHubSpec ? "clawhub" : "npm"; + return remoteDefault(); } if (bundledLocalPath) { return "local"; @@ -287,19 +297,12 @@ function resolveInstallDefaultChoice(params: { return "local"; } if (updateChannel === "stable" || updateChannel === "beta") { - return hasClawHubSpec ? "clawhub" : "npm"; - } - const entryDefault = entry.install.defaultChoice; - if (entryDefault === "clawhub" && hasClawHubSpec) { - return "clawhub"; + return remoteDefault(); } if (entryDefault === "local") { return "local"; } - if (entryDefault === "npm") { - return "npm"; - } - return hasClawHubSpec ? "clawhub" : "local"; + return remoteDefault(); } async function promptInstallChoice(params: {