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