mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-02 22:52:58 +00:00
fix(app): hold the skills switch while setup applies its choices
The Paseo tools and CLI switches were disabled while Continue or Finish applied them, but the skills switch only locked while a skills operation ran. With skills needing no change, it stayed editable during a pending tools update and the new value was silently dropped.
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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({
|
||||
>
|
||||
<View style={styles.controls}>
|
||||
{needsUpdate ? (
|
||||
<Button variant="outline" onPress={handleUpdate} disabled={isWorking}>
|
||||
<Button variant="outline" onPress={handleUpdate} disabled={disabled || isWorking}>
|
||||
{isWorking
|
||||
? t("settings.host.skills.actions.working")
|
||||
: t("settings.host.skills.actions.update")}
|
||||
@@ -160,14 +162,16 @@ export function AgentSkillsRow({
|
||||
variant="ghost"
|
||||
leftIcon={chooseIcon}
|
||||
onPress={handleOpen}
|
||||
disabled={status === null || isWorking}
|
||||
disabled={disabled || status === null || isWorking}
|
||||
accessibilityLabel={t("settings.host.skills.choose")}
|
||||
/>
|
||||
<Switch
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
// Nothing picked means nothing to install; the gear is the way forward.
|
||||
disabled={status === null || isWorking || (!value && !hasSelectedSkills(status))}
|
||||
disabled={
|
||||
disabled || status === null || isWorking || (!value && !hasSelectedSkills(status))
|
||||
}
|
||||
accessibilityLabel={t("settings.host.skills.title")}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -15,7 +15,12 @@ export function OrchestrationStep({ setup }: { setup: OrchestrationSetupControll
|
||||
<SettingsCard>
|
||||
<PaseoToolsRow value={choices.tools} onValueChange={setTools} disabled={isApplying} />
|
||||
{offered.skills ? (
|
||||
<AgentSkillsRow skills={setup.skills} value={choices.skills} onValueChange={setSkills} />
|
||||
<AgentSkillsRow
|
||||
skills={setup.skills}
|
||||
value={choices.skills}
|
||||
onValueChange={setSkills}
|
||||
disabled={isApplying}
|
||||
/>
|
||||
) : null}
|
||||
{offered.cli ? (
|
||||
<CommandLineRow value={choices.cli} onValueChange={setCli} disabled={isApplying} />
|
||||
|
||||
Reference in New Issue
Block a user