diff --git a/packages/app/e2e/browser/onboarding.spec.ts b/packages/app/e2e/browser/onboarding.spec.ts index df5ad71c05..ebe088b7f7 100644 --- a/packages/app/e2e/browser/onboarding.spec.ts +++ b/packages/app/e2e/browser/onboarding.spec.ts @@ -23,6 +23,7 @@ import { skipSetup, startSetupHost, } from "../support/helpers/onboarding"; +import { installDaemonWebSocketGate } from "../support/helpers/daemon-websocket-gate"; import { enableRelayAndExpectOffer } from "../support/helpers/pair-device"; import { seedWorkspace } from "../support/helpers/seed-client"; import { openSettingsHostSection } from "../support/helpers/settings"; @@ -238,6 +239,7 @@ test("setup choices carry over to Settings and the phone step pairs over relay", 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"); + const gate = await installDaemonWebSocketGate(page, { port: host.port }); // The compact sheet is the only surface on screen, and Orchestration is its last step. const step = (title: string) => expect(page.getByRole("heading", { name: title, exact: true })).toBeVisible({ @@ -258,8 +260,19 @@ test("in the compact layout, Finish on Orchestration applies its switches", asyn expect(await hostGivesAgentsPaseoTools(host)).toBe(false); await step("Let agents use Paseo"); await expect(paseoToolsSwitch(page)).toBeChecked(); + // Skills are not installed, so leaving them off has nothing to apply. + const skillsSwitch = page.getByRole("switch", { name: "Orchestration skills", exact: true }); + await skillsSwitch.click(); + await expect(skillsSwitch).not.toBeChecked(); + gate.holdNextClientRequest("set_daemon_config_request"); await primary("Finish").click(); + // While the choices apply, none of them can change. + await gate.waitForHeldClientRequest(); + await expect(paseoToolsSwitch(page)).toBeDisabled(); + await expect(skillsSwitch).toBeDisabled(); + gate.releaseHeldClientRequest(); + await expect.poll(() => hostGivesAgentsPaseoTools(host), { timeout: 30_000 }).toBe(true); await expect(page.getByRole("heading", { name: "Let agents use Paseo" })).toHaveCount(0); } finally { diff --git a/packages/app/e2e/support/helpers/daemon-websocket-gate.ts b/packages/app/e2e/support/helpers/daemon-websocket-gate.ts index bff52fc3f7..c33976879f 100644 --- a/packages/app/e2e/support/helpers/daemon-websocket-gate.ts +++ b/packages/app/e2e/support/helpers/daemon-websocket-gate.ts @@ -1,5 +1,5 @@ import type { Page, WebSocketRoute } from "@playwright/test"; -import { daemonWsRoutePattern } from "./daemon-port"; +import { daemonWsRoutePattern, wsRoutePatternForPort } from "./daemon-port"; export interface DirectoryBootstrapCounts { agents: number; @@ -294,7 +294,8 @@ function recordClientRequest( directoryStarts.total[directory] += 1; } -export async function installDaemonWebSocketGate(page: Page) { +/** Gates the shared E2E daemon, or the daemon on `port` for a spec that runs its own. */ +export async function installDaemonWebSocketGate(page: Page, options: { port?: number } = {}) { let acceptingConnections = true; let reconnectWithFreshClient = false; let suppressAgentStream = false; @@ -445,7 +446,11 @@ export async function installDaemonWebSocketGate(page: Page) { return true; }; - await page.routeWebSocket(daemonWsRoutePattern(), (ws) => { + const routePattern = + options.port === undefined + ? daemonWsRoutePattern() + : wsRoutePatternForPort(String(options.port)); + await page.routeWebSocket(routePattern, (ws) => { if (!acceptingConnections) { blockedConnectionCount += 1; for (const resolve of blockedConnectionWaiters) resolve(); diff --git a/packages/app/src/agent-skills/index.tsx b/packages/app/src/agent-skills/index.tsx index 6cc27f55cf..dfc98649b6 100644 --- a/packages/app/src/agent-skills/index.tsx +++ b/packages/app/src/agent-skills/index.tsx @@ -107,10 +107,12 @@ export function AgentSkillsRow({ skills, value, onValueChange, + disabled = false, }: { skills: AgentSkillsController; value: boolean; onValueChange: (next: boolean) => void; + disabled?: boolean; }) { const { t } = useTranslation(); const { status, isWorking, reconcile, saveSelection } = skills; @@ -150,7 +152,7 @@ export function AgentSkillsRow({ > {needsUpdate ? ( -