From 5551ef40e3155f119fa2f292d7e3a61356d9ffac Mon Sep 17 00:00:00 2001 From: claude-ceo-assistant Date: Sat, 23 May 2026 12:48:50 -0700 Subject: [PATCH] feat(display): add desktop workspace creation flow --- .../src/components/CreateWorkspaceDialog.tsx | 94 ++++++++++++++ .../__tests__/CreateWorkspaceDialog.test.tsx | 40 ++++++ canvas/src/components/tabs/DisplayTab.tsx | 118 +++++++++++++++++- .../tabs/__tests__/DisplayTab.test.tsx | 55 ++++++++ .../internal/handlers/workspace_compute.go | 55 +++++++- .../handlers/workspace_compute_test.go | 106 +++++++++++++++- .../internal/handlers/workspace_provision.go | 26 ++-- .../internal/provisioner/cp_provisioner.go | 18 +-- .../provisioner/cp_provisioner_test.go | 7 ++ .../internal/provisioner/provisioner.go | 14 ++- 10 files changed, 502 insertions(+), 31 deletions(-) diff --git a/canvas/src/components/CreateWorkspaceDialog.tsx b/canvas/src/components/CreateWorkspaceDialog.tsx index 265c48824..c5849a534 100644 --- a/canvas/src/components/CreateWorkspaceDialog.tsx +++ b/canvas/src/components/CreateWorkspaceDialog.tsx @@ -33,6 +33,8 @@ interface HermesProvider { models: string[]; } +const DEFAULT_CREATE_MODEL = "anthropic:claude-opus-4-7"; + // All providers supported by Hermes runtime via providers.resolve_provider(). // `defaultModel` is the slug injected into the workspace provision request // when the user picks this provider โ€” template-hermes's derive-provider.sh @@ -68,6 +70,10 @@ export function CreateWorkspaceButton() { const [creating, setCreating] = useState(false); const [error, setError] = useState(null); const [workspaces, setWorkspaces] = useState([]); + const [displayEnabled, setDisplayEnabled] = useState(false); + const [displayInstanceType, setDisplayInstanceType] = useState("t3.xlarge"); + const [displayRootGB, setDisplayRootGB] = useState("80"); + const [displayResolution, setDisplayResolution] = useState("1920x1080"); // Templates fetched from /api/templates โ€” drives the dynamic provider // filter below. Same data source ConfigTab uses (PR #2454). When the // selected template declares `runtime_config.providers` in its @@ -223,6 +229,10 @@ export function CreateWorkspaceButton() { setParentId(""); setBudgetLimit(""); setError(null); + setDisplayEnabled(false); + setDisplayInstanceType("t3.xlarge"); + setDisplayRootGB("80"); + setDisplayResolution("1920x1080"); setHermesProvider("anthropic"); setExternalRuntime("external"); setHermesApiKey(""); @@ -264,6 +274,8 @@ export function CreateWorkspaceButton() { const parsedBudget = budgetLimit.trim() ? parseFloat(budgetLimit) : null; + const [displayWidth, displayHeight] = displayResolution.split("x").map((v) => parseInt(v, 10)); + const parsedRootGB = parseInt(displayRootGB, 10); const createResp = await api.post<{ id: string; @@ -280,6 +292,21 @@ export function CreateWorkspaceButton() { tier, parent_id: parentId || undefined, budget_limit: parsedBudget, + ...(!isExternal && !isHermes ? { model: DEFAULT_CREATE_MODEL } : {}), + ...(displayEnabled + ? { + compute: { + instance_type: displayInstanceType, + volume: { root_gb: Number.isFinite(parsedRootGB) ? parsedRootGB : 80 }, + display: { + mode: "desktop-control", + protocol: "novnc", + width: Number.isFinite(displayWidth) ? displayWidth : 1920, + height: Number.isFinite(displayHeight) ? displayHeight : 1080, + }, + }, + } + : {}), canvas: { x: Math.random() * 400 + 100, y: Math.random() * 300 + 100 }, // Runtime=external flips the backend into awaiting-agent mode: // no container provisioning, token minted, connection payload @@ -447,6 +474,73 @@ export function CreateWorkspaceButton() { + {!isExternal && ( +
+
+ Container Config +
+ + {displayEnabled && ( +
+
+ + +
+
+ + setDisplayRootGB(e.target.value)} + className="w-full bg-surface-card/60 border border-line/50 rounded-lg px-2 py-2 text-xs text-ink focus:outline-none focus:border-accent/60 focus:ring-1 focus:ring-accent/20 transition-colors" + /> +
+
+ + +
+
+ )} +
+ )} +