From d3317c4cb78b3d75b347c823d9ac87eaa6b0489b Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 2 Nov 2026 18:13:40 +0100 Subject: [PATCH] fix(app): read setup's skills choice as off when no skill is picked Saving an empty selection in the skills picker left setup's skills switch on. Continue then asked the host to install nothing, the host reported skills as not installed, and the step stayed put with no error. Setup now reads skills as off with nothing picked, as Settings does. --- packages/app/e2e/browser/onboarding.spec.ts | 30 +++++++++++++++++++ packages/app/src/agent-skills/index.tsx | 3 +- .../internal/orchestration-setup.ts | 19 +++++++++--- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/packages/app/e2e/browser/onboarding.spec.ts b/packages/app/e2e/browser/onboarding.spec.ts index ebe088b7f7..68d54687a9 100644 --- a/packages/app/e2e/browser/onboarding.spec.ts +++ b/packages/app/e2e/browser/onboarding.spec.ts @@ -23,6 +23,11 @@ import { skipSetup, startSetupHost, } from "../support/helpers/onboarding"; +import { + chooseCustomSkills, + openSkillSelection, + saveSkillSelection, +} from "../support/helpers/agent-skills"; import { installDaemonWebSocketGate } from "../support/helpers/daemon-websocket-gate"; import { enableRelayAndExpectOffer } from "../support/helpers/pair-device"; import { seedWorkspace } from "../support/helpers/seed-client"; @@ -236,6 +241,31 @@ test("setup choices carry over to Settings and the phone step pairs over relay", } }); +test("with no skills picked, skills read as off and Continue moves on", async ({ page }) => { + const host = await startSetupHost("no-skills"); + + try { + await openAppAsNewUser(page, host); + await expectSetupStep(page, "Welcome to Paseo"); + await goToSetupStep(page, "Orchestration"); + await expectSetupStep(page, "Let agents use Paseo"); + + await openSkillSelection(page); + await chooseCustomSkills(page, []); + await saveSkillSelection(page); + const skillsSwitch = setupModal(page).getByRole("switch", { + name: "Orchestration skills", + exact: true, + }); + await expect(skillsSwitch).not.toBeChecked(); + + await continueSetup(page); + await expectSetupStep(page, "Take your agents with you"); + } finally { + await host.close(); + } +}); + test("in the compact layout, Finish on Orchestration applies its switches", async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); const host = await startSetupHost("compact"); diff --git a/packages/app/src/agent-skills/index.tsx b/packages/app/src/agent-skills/index.tsx index dfc98649b6..9be1813dfe 100644 --- a/packages/app/src/agent-skills/index.tsx +++ b/packages/app/src/agent-skills/index.tsx @@ -38,7 +38,8 @@ export function areSkillsInstalled(status: AgentSkillsStatus | null): boolean { return status !== null && status.state !== "not-installed"; } -function hasSelectedSkills(status: AgentSkillsStatus | null): boolean { +/** Whether the saved selection names any skill there is to install. */ +export function hasSelectedSkills(status: AgentSkillsStatus | null): boolean { if (!status) return false; if (status.selection.mode === "all") return status.available.length > 0; return status.selection.skills.some((name) => status.available.includes(name)); diff --git a/packages/app/src/onboarding/internal/orchestration-setup.ts b/packages/app/src/onboarding/internal/orchestration-setup.ts index 9478ad9632..5606379dab 100644 --- a/packages/app/src/onboarding/internal/orchestration-setup.ts +++ b/packages/app/src/onboarding/internal/orchestration-setup.ts @@ -1,6 +1,11 @@ -import { useCallback, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { areSkillsInstalled, useAgentSkills, type AgentSkillsController } from "@/agent-skills"; +import { + areSkillsInstalled, + hasSelectedSkills, + useAgentSkills, + type AgentSkillsController, +} from "@/agent-skills"; import { useToast } from "@/contexts/toast-context"; import { useCliInstall } from "@/desktop/hooks/use-install-status"; import { useDaemonConfig } from "@/hooks/use-daemon-config"; @@ -35,7 +40,13 @@ export function useOrchestrationSetup(serverId: string): OrchestrationSetupContr const skills = useAgentSkills(serverId); const cli = useCliInstall(); const { config, patchConfig } = useDaemonConfig(serverId); - const [choices, setChoices] = useState(ALL_ON); + const [chosen, setChosen] = useState(ALL_ON); + // With nothing picked there is nothing to install, so skills read as off, as they do in Settings. + const skillsSelected = skills.status === null || hasSelectedSkills(skills.status); + const choices = useMemo( + () => ({ ...chosen, skills: chosen.skills && skillsSelected }), + [chosen, skillsSelected], + ); const [isApplying, setIsApplying] = useState(false); const offered: Choices = { tools: true, @@ -44,7 +55,7 @@ export function useOrchestrationSetup(serverId: string): OrchestrationSetupContr }; const setChoice = useCallback((choice: OrchestrationChoice, on: boolean) => { - setChoices((current) => ({ ...current, [choice]: on })); + setChosen((current) => ({ ...current, [choice]: on })); }, []); // What the host and this computer have now, or null while it is still being read. A choice is