From 5f656103261da2f77a49a415a31ddd4bffc35263 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Tue, 29 Sep 2026 22:34:27 -0700 Subject: [PATCH] feat(web): /new is one composer that starts a workbench from a first message (CL-9580) --- apps/web/src/pages/new-workbench-picker.tsx | 336 +++----------------- apps/web/src/pages/new-workbench.css | 67 ++++ 2 files changed, 114 insertions(+), 289 deletions(-) create mode 100644 apps/web/src/pages/new-workbench.css diff --git a/apps/web/src/pages/new-workbench-picker.tsx b/apps/web/src/pages/new-workbench-picker.tsx index 5a70f3e4f..62e4dc8a5 100644 --- a/apps/web/src/pages/new-workbench-picker.tsx +++ b/apps/web/src/pages/new-workbench-picker.tsx @@ -1,14 +1,11 @@ -// A new workbench is a child tenant plus the agents you put in it. This -// screen is the prompt box: say what you want and hit Enter, or open an -// empty workbench and add people and agents as you go. +// A new workbench starts from one message: creating it deploys its agent and +// posts the message so the worker starts. import { Button, toast } from "@corbits/react-ui"; -import { useDismissablePopover } from "@corbits/react-ui/hooks/use-dismissable-popover"; -import { Check, PaperPlaneRight, X } from "@/lib/icons"; +import { PaperPlaneRight } from "@/lib/icons"; import { CHAT_STRINGS, WorkbenchLoadingState } from "@/chat"; -import { isMyraAgent, listChatAgents } from "@/chat/threads-api"; -import { useMemo, useRef, useState } from "react"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import { reportError } from "@corbits/error-sink"; import { useBench } from "../bench-context"; @@ -17,7 +14,7 @@ import { createWorkbench, WorkbenchCreateError } from "../workbench-create"; import { useNavigate } from "../navigation"; import { StageTopBar } from "../shell/stage-top-bar"; import { workbenchPath } from "../workbench-path"; -import { CreateAgentPanel } from "./create-agent-panel"; +import "./new-workbench.css"; const GENERIC_CREATE_FAILURE = "Something went wrong creating this workbench. Try again."; @@ -34,11 +31,13 @@ export function describeWorkbenchCreateFailure(cause: unknown, refId?: string): return refId === undefined ? message : `${message} Reference: ${refId}`; } -const PROMPT_PLACEHOLDER = "What do you want your Workbench to do?"; -const AGENT_LISTBOX_ID = "new-workbench-agent-listbox"; +const SUGGESTIONS = [ + "Review every pull request on a repository", + "Vet a vendor before we sign", + "Summarize what shipped each Friday", +]; -/** A workbench's name is its opening ask, trimmed — an empty workbench is just - * "Workbench" until it is renamed. */ +/** A workbench's name is its opening ask, trimmed. */ function workbenchName(prompt: string): string { const trimmed = prompt.trim(); return trimmed === "" ? "Workbench" : trimmed.slice(0, 60); @@ -49,57 +48,6 @@ export function NewWorkbenchPickerRoute() { const queryClient = useQueryClient(); const { selectedTenantId } = useBench(); const [prompt, setPrompt] = useState(""); - const [selectedAgentIds, setSelectedAgentIds] = useState([]); - const [agentPickerOpen, setAgentPickerOpen] = useState(false); - const [createAgentOpen, setCreateAgentOpen] = useState(false); - const [agentQuery, setAgentQuery] = useState(""); - const [activeAgentIndex, setActiveAgentIndex] = useState(0); - const promptRef = useRef(null); - - const { - rootRef: agentPickerRootRef, - triggerRef: agentPickerTriggerRef, - close: dismissAgentPicker, - } = useDismissablePopover({ - closeOnEscape: false, - open: agentPickerOpen, - onOpenChange: setAgentPickerOpen, - }); - - const agentsQuery = useQuery({ - queryKey: ["bench-agents", selectedTenantId], - queryFn: () => listChatAgents(selectedTenantId!), - enabled: selectedTenantId !== null, - }); - const pickableAgents = useMemo( - () => (agentsQuery.data ?? []).filter((agent) => !isMyraAgent(agent)), - [agentsQuery.data], - ); - const filteredAgents = useMemo(() => { - const query = agentQuery.trim().toLocaleLowerCase(); - return query === "" - ? pickableAgents - : pickableAgents.filter((agent) => agent.name.toLocaleLowerCase().includes(query)); - }, [pickableAgents, agentQuery]); - const selectedAgents = pickableAgents.filter((agent) => selectedAgentIds.includes(agent.id)); - - function toggleAgent(id: string) { - setSelectedAgentIds((current) => - current.includes(id) ? current.filter((existing) => existing !== id) : [...current, id], - ); - } - - function closeAgentPicker() { - setAgentQuery(""); - setActiveAgentIndex(0); - dismissAgentPicker(); - } - - function openAgentPicker() { - setAgentPickerOpen(true); - setAgentQuery(""); - setActiveAgentIndex(0); - } const create = useMutation({ mutationFn: ({ @@ -107,15 +55,12 @@ export function NewWorkbenchPickerRoute() { openingMessage, }: { readonly benchTenantId: string; - readonly openingMessage: string | undefined; + readonly openingMessage: string; }) => createWorkbench({ benchTenantId, - name: workbenchName(openingMessage ?? ""), - ...(openingMessage !== undefined ? { openingMessage } : {}), - pickedAgents: pickableAgents - .filter((agent) => selectedAgentIds.includes(agent.id)) - .map((agent) => ({ id: agent.id, name: agent.name, assetName: agent.assetName })), + name: workbenchName(openingMessage), + openingMessage, }), onSuccess: (tenantId, variables) => { void queryClient.invalidateQueries({ @@ -140,214 +85,45 @@ export function NewWorkbenchPickerRoute() { }, }); - function startCreate(openingMessage?: string) { - if (selectedTenantId === null || create.isPending) return; - create.mutate({ - benchTenantId: selectedTenantId, - ...(openingMessage !== undefined ? { openingMessage } : { openingMessage: undefined }), - }); - } - - function handlePromptSubmit() { + function submit() { const trimmed = prompt.trim(); - if (trimmed === "") return; - startCreate(trimmed); + if (trimmed === "" || selectedTenantId === null || create.isPending) return; + create.mutate({ benchTenantId: selectedTenantId, openingMessage: trimmed }); } return (
- navigate("/")}> - Cancel - - } - /> -
+ +
{create.isPending ? ( - // `delayMs={0}`: this is a genuine wait the instant the person - // hits Enter, so the default hold-back only bought a blank pane. + // `delayMs={0}`: a genuine wait the instant the person sends. ) : ( <> -

What do you want your Workbench to do?

-

- Tell it what you're trying to get done. Takes about ten seconds. -

- +

What should this workbench do?

{ event.preventDefault(); - handlePromptSubmit(); + submit(); }} > -
-