diff --git a/src/commands/onboard-types.ts b/src/commands/onboard-types.ts index f090fb34a9d..b28d9fbcac0 100644 --- a/src/commands/onboard-types.ts +++ b/src/commands/onboard-types.ts @@ -4,6 +4,13 @@ import type { GatewayDaemonRuntime } from "./daemon-runtime.js"; export type { ToolProfileId } from "../config/types.tools.js"; +export const VALID_TOOLS_PROFILES = new Set([ + "minimal", + "coding", + "messaging", + "full", +]); + export type OnboardMode = "local" | "remote"; export type AuthChoice = // Legacy alias for `setup-token` (kept for backwards CLI compatibility). diff --git a/src/commands/onboard.ts b/src/commands/onboard.ts index 7b4d839f1f1..bf589bf000b 100644 --- a/src/commands/onboard.ts +++ b/src/commands/onboard.ts @@ -8,10 +8,9 @@ import { isDeprecatedAuthChoice, normalizeLegacyOnboardAuthChoice } from "./auth import { DEFAULT_WORKSPACE, handleReset } from "./onboard-helpers.js"; import { runInteractiveOnboarding } from "./onboard-interactive.js"; import { runNonInteractiveOnboarding } from "./onboard-non-interactive.js"; -import type { OnboardOptions, ResetScope } from "./onboard-types.js"; +import { VALID_TOOLS_PROFILES, type OnboardOptions, type ResetScope } from "./onboard-types.js"; const VALID_RESET_SCOPES = new Set(["config", "config+creds+sessions", "full"]); -const VALID_TOOLS_PROFILES = new Set(["minimal", "coding", "messaging", "full"]); export async function onboardCommand(opts: OnboardOptions, runtime: RuntimeEnv = defaultRuntime) { assertSupportedRuntime(runtime); diff --git a/src/wizard/onboarding.test.ts b/src/wizard/onboarding.test.ts index 98930fb8d04..24c33985ade 100644 --- a/src/wizard/onboarding.test.ts +++ b/src/wizard/onboarding.test.ts @@ -275,7 +275,7 @@ describe("runOnboardingWizard", () => { expect(prompter.outro).toHaveBeenCalled(); }); - it("skips prompts and setup steps when flags are set", async () => { + it("skips channel/skill/health setup but still prompts for tool profile", async () => { const select = vi.fn(async (params: WizardSelectParams) => { if (params.message === "Tool access profile") { return "messaging"; @@ -599,4 +599,40 @@ describe("runOnboardingWizard", () => { }), ); }); + + it("ignores invalid toolsProfile option and still prompts for a valid profile", async () => { + writeConfigFile.mockClear(); + const select = vi.fn(async (params: WizardSelectParams) => { + if (params.message === "Tool access profile") { + return "messaging"; + } + return "quickstart"; + }) as unknown as WizardPrompter["select"]; + const prompter = buildWizardPrompter({ select }); + + await runOnboardingWizard( + { + acceptRisk: true, + flow: "quickstart", + mode: "local", + toolsProfile: "invalid" as never, + authChoice: "skip", + installDaemon: false, + skipProviders: true, + skipSkills: true, + skipHealth: true, + skipUi: true, + }, + createRuntime(), + prompter, + ); + + expect(select).toHaveBeenCalledWith( + expect.objectContaining({ + message: "Tool access profile", + }), + ); + const firstWrite = writeConfigFile.mock.calls[0]?.[0] as { tools?: { profile?: string } }; + expect(firstWrite?.tools?.profile).toBe("messaging"); + }); }); diff --git a/src/wizard/onboarding.ts b/src/wizard/onboarding.ts index 3d48aee8960..99ec4794746 100644 --- a/src/wizard/onboarding.ts +++ b/src/wizard/onboarding.ts @@ -1,10 +1,11 @@ import { formatCliCommand } from "../cli/command-format.js"; -import type { - GatewayAuthChoice, - OnboardMode, - OnboardOptions, - ResetScope, - ToolProfileId, +import { + VALID_TOOLS_PROFILES, + type GatewayAuthChoice, + type OnboardMode, + type OnboardOptions, + type ResetScope, + type ToolProfileId, } from "../commands/onboard-types.js"; import type { OpenClawConfig } from "../config/config.js"; import { @@ -71,8 +72,6 @@ async function requireRiskAcknowledgement(params: { } } -const VALID_TOOLS_PROFILES = new Set(["minimal", "coding", "messaging", "full"]); - const TOOL_PROFILE_CHOICES: Array<{ value: ToolProfileId; label: string; hint: string }> = [ { value: "messaging", @@ -438,8 +437,12 @@ export async function runOnboardingWizard( ? existingToolsProfile : undefined : undefined; + const explicitToolsProfile = + typeof opts.toolsProfile === "string" && VALID_TOOLS_PROFILES.has(opts.toolsProfile) + ? opts.toolsProfile + : undefined; const toolsProfile = - opts.toolsProfile ?? + explicitToolsProfile ?? (await prompter.select({ message: "Tool access profile", options: TOOL_PROFILE_CHOICES,