diff --git a/apps/web/src/pages/workers-page.tsx b/apps/web/src/pages/workers-page.tsx index 636fbf87a..ba1c3d709 100644 --- a/apps/web/src/pages/workers-page.tsx +++ b/apps/web/src/pages/workers-page.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { Button, RichEmptyState, Skeleton } from "@corbits/react-ui"; +import { Menu, MenuContent, MenuItem, MenuTrigger } from "@corbits/react-ui/ui/menu"; import { toast } from "@corbits/react-ui/ui/toast"; import { Plus, Robot } from "@/lib/icons"; import { useMutation, useQueryClient } from "@tanstack/react-query"; @@ -13,12 +14,15 @@ import { isAgentNotRunning, type ChatAgent } from "@/chat/threads-api"; import { WorkbenchAvatar } from "@/chat/avatar"; import { describeRestartFailure, redeployWorkbenchAgent } from "../workbench-create"; import { useBench } from "../bench-context"; +import type { HubTenant } from "../needs-converge"; +import { useSidebarSections } from "../shell/sidebar-sections"; import { useBenchWorkers, type BenchWorker } from "../worker-benches"; -import { Link } from "../navigation"; +import { Link, useNavigate } from "../navigation"; import { PageLayout } from "../shell/page-layout"; import { StageTopBar } from "../shell/stage-top-bar"; import { CreateAgentPanel } from "./create-agent-panel"; import { WORKERS_PATH_PREFIX } from "../path-ids"; +import { NEW_WORKBENCH_PATH } from "../routes"; import { useWorkerRole } from "../worker-role-query"; export type WorkerTone = "working" | "ready" | "idle"; @@ -209,8 +213,11 @@ export function WorkersRosterList({ workers }: { readonly workers: readonly Benc export function WorkersRoute() { const { selectedTenantId } = useBench(); const { workers, loading, error } = useBenchWorkers(); + const sections = useSidebarSections(selectedTenantId); + const benches = sections.kind === "ready" ? sections.workbenches : []; const queryClient = useQueryClient(); - const [createOpen, setCreateOpen] = useState(false); + const navigate = useNavigate(); + const [createBench, setCreateBench] = useState(null); return (
@@ -220,10 +227,30 @@ export function WorkersRoute() { title="Workers" subtitle="Each worker picks a name, keeps its memory, and asks before it writes anywhere." actions={ - selectedTenantId === null ? undefined : ( - + ) : benches.length === 1 ? ( + + ) : ( + + + + + + {benches.map((bench) => ( + setCreateBench(bench)}> + {bench.name} + + ))} + + ) } > @@ -244,16 +271,21 @@ export function WorkersRoute() { ) : ( )} - { - void queryClient.invalidateQueries({ - queryKey: tenantKeys.agents(selectedTenantId), - }); - }} - /> + {createBench === null ? null : ( + { + if (!open) setCreateBench(null); + }} + tenantId={createBench.id} + onCreated={() => { + // Prefix key: the roster and the sidebar's agent reads. + void queryClient.invalidateQueries({ + queryKey: ["tenant", createBench.id, "agents"], + }); + }} + /> + )} )} diff --git a/e2e/browser/first-run.test.ts b/e2e/browser/first-run.test.ts index 62c2d895c..b6e41a9b9 100644 --- a/e2e/browser/first-run.test.ts +++ b/e2e/browser/first-run.test.ts @@ -96,6 +96,20 @@ async function runFlow(page: Page, origin: string): Promise { expect(benches).toHaveLength(3); for (const name of benches) expect(name.length).toBeLessThanOrEqual(40); + // (c2) with several benches, "New worker" asks which one. + await page.goto(`${origin}/workers`, { waitUntil: "networkidle0" }); + // Radix opens its menu on pointerdown, which `click()` does not send. + await page.waitForFunction( + `Array.from(document.querySelectorAll("button[aria-haspopup=menu]")).some((e) => e.textContent.includes("New worker"))`, + { timeout: STEP_TIMEOUT }, + ); + await page.evaluate( + `Array.from(document.querySelectorAll("button[aria-haspopup=menu]")).find((e) => e.textContent.includes("New worker")).dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, button: 0, pointerType: "mouse" }))`, + ); + await page.waitForFunction(`document.querySelectorAll("[role=menuitem]").length === 3`, { + timeout: STEP_TIMEOUT, + }); + // (d) no bench shows another's opening message. for (const [i, id] of ids.entries()) { await page.goto(`${origin}/w/${id}`, { waitUntil: "networkidle0" });