import { useEffect, useMemo, useState } from "react"; import { Link, Navigate, useLocation, useNavigate, useParams } from "@/lib/router"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { ExecutionWorkspace, Issue, Project, ProjectWorkspace, RoutineListItem } from "@paperclipai/shared"; import { Copy, ExternalLink, Loader2, Play, Repeat } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardAction } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; import { Tabs } from "@/components/ui/tabs"; import { Textarea } from "@/components/ui/textarea"; import { CopyText } from "../components/CopyText"; import { ExecutionWorkspaceCloseDialog } from "../components/ExecutionWorkspaceCloseDialog"; import { MissingPluginTabPlaceholder } from "../components/MissingPluginTabPlaceholder"; import { agentsApi } from "../api/agents"; import { executionWorkspacesApi } from "../api/execution-workspaces"; import { heartbeatsApi } from "../api/heartbeats"; import { issuesApi } from "../api/issues"; import { projectsApi } from "../api/projects"; import { routinesApi } from "../api/routines"; import { IssuesList } from "../components/IssuesList"; import { PageTabBar } from "../components/PageTabBar"; import { PluginSlotMount, PluginSlotOutlet, usePluginSlots } from "@/plugins/slots"; import { RoutineRunVariablesDialog, type RoutineRunDialogSubmitData, } from "../components/RoutineRunVariablesDialog"; import { buildWorkspaceRuntimeControlSections, WorkspaceRuntimeQuickControls, WorkspaceRuntimeControls, type WorkspaceRuntimeControlRequest, } from "../components/WorkspaceRuntimeControls"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { useCompany } from "../context/CompanyContext"; import { useToastActions } from "../context/ToastContext"; import { collectLiveIssueIds } from "../lib/liveIssueIds"; import { queryKeys } from "../lib/queryKeys"; import { cn, formatDateTime, issueUrl, projectRouteRef, projectWorkspaceUrl } from "../lib/utils"; import { getWorkspaceSpecificRoutineVariableNames, routineHasWorkspaceSpecificVariables, } from "../lib/workspace-routines"; type WorkspaceFormState = { name: string; cwd: string; repoUrl: string; baseRef: string; branchName: string; providerRef: string; provisionCommand: string; teardownCommand: string; cleanupCommand: string; inheritRuntime: boolean; workspaceRuntime: string; }; type ExecutionWorkspaceBaseTab = "services" | "configuration" | "runtime_logs" | "issues" | "routines"; type ExecutionWorkspacePluginTab = `plugin:${string}`; type ExecutionWorkspaceTab = ExecutionWorkspaceBaseTab | ExecutionWorkspacePluginTab; type OrderedExecutionWorkspaceTabItem = { value: ExecutionWorkspaceTab; label: string; order: number; }; const DEFAULT_PLUGIN_DETAIL_TAB_ORDER = 100; const EXECUTION_WORKSPACE_BASE_TAB_ITEMS: OrderedExecutionWorkspaceTabItem[] = [ { value: "issues", label: "Issues", order: 10 }, { value: "services", label: "Services", order: 20 }, { value: "configuration", label: "Configuration", order: 30 }, { value: "runtime_logs", label: "Runtime logs", order: 40 }, { value: "routines", label: "Routines", order: 60 }, ]; function isExecutionWorkspacePluginTab(value: string | null): value is ExecutionWorkspacePluginTab { return typeof value === "string" && value.startsWith("plugin:"); } function orderExecutionWorkspaceTabItems(items: OrderedExecutionWorkspaceTabItem[]) { return items .map((item, index) => ({ item, index })) .sort((left, right) => left.item.order - right.item.order || left.index - right.index) .map(({ item }) => item); } function resolveExecutionWorkspaceTab(pathname: string, workspaceId: string): ExecutionWorkspaceBaseTab | null { const segments = pathname.split("/").filter(Boolean); const executionWorkspacesIndex = segments.indexOf("execution-workspaces"); if (executionWorkspacesIndex === -1 || segments[executionWorkspacesIndex + 1] !== workspaceId) return null; const tab = segments[executionWorkspacesIndex + 2]; if (tab === "services") return "services"; if (tab === "issues") return "issues"; if (tab === "routines") return "routines"; if (tab === "runtime-logs") return "runtime_logs"; if (tab === "configuration") return "configuration"; return null; } function executionWorkspaceTabPath(workspaceId: string, tab: ExecutionWorkspaceBaseTab) { const segment = tab === "runtime_logs" ? "runtime-logs" : tab; return `/execution-workspaces/${workspaceId}/${segment}`; } function LegacyWorkspaceTabRedirect({ workspaceId }: { workspaceId: string }) { useEffect(() => { try { localStorage.removeItem(`paperclip:execution-workspace-tab:${workspaceId}`); } catch {} }, [workspaceId]); return ; } function isSafeExternalUrl(value: string | null | undefined) { if (!value) return false; try { const parsed = new URL(value); return parsed.protocol === "http:" || parsed.protocol === "https:"; } catch { return false; } } function readText(value: string | null | undefined) { return value ?? ""; } function formatJson(value: Record | null | undefined) { if (!value || Object.keys(value).length === 0) return ""; return JSON.stringify(value, null, 2); } function formatOptionalDateTime(value: Date | string | null | undefined) { return value ? formatDateTime(value) : "Never"; } function normalizeText(value: string) { const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function parseWorkspaceRuntimeJson(value: string) { const trimmed = value.trim(); if (!trimmed) return { ok: true as const, value: null as Record | null }; try { const parsed = JSON.parse(trimmed); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { return { ok: false as const, error: "Workspace commands JSON must be a JSON object.", }; } return { ok: true as const, value: parsed as Record }; } catch (error) { return { ok: false as const, error: error instanceof Error ? error.message : "Invalid JSON.", }; } } function formStateFromWorkspace(workspace: ExecutionWorkspace): WorkspaceFormState { return { name: workspace.name, cwd: readText(workspace.cwd), repoUrl: readText(workspace.repoUrl), baseRef: readText(workspace.baseRef), branchName: readText(workspace.branchName), providerRef: readText(workspace.providerRef), provisionCommand: readText(workspace.config?.provisionCommand), teardownCommand: readText(workspace.config?.teardownCommand), cleanupCommand: readText(workspace.config?.cleanupCommand), inheritRuntime: !workspace.config?.workspaceRuntime, workspaceRuntime: formatJson(workspace.config?.workspaceRuntime), }; } function buildWorkspacePatch(initialState: WorkspaceFormState, nextState: WorkspaceFormState) { const patch: Record = {}; const configPatch: Record = {}; const maybeAssign = ( key: keyof Pick, ) => { if (initialState[key] === nextState[key]) return; patch[key] = key === "name" ? (normalizeText(nextState[key]) ?? initialState.name) : normalizeText(nextState[key]); }; maybeAssign("name"); maybeAssign("cwd"); maybeAssign("repoUrl"); maybeAssign("baseRef"); maybeAssign("branchName"); maybeAssign("providerRef"); const maybeAssignConfigText = (key: keyof Pick) => { if (initialState[key] === nextState[key]) return; configPatch[key] = normalizeText(nextState[key]); }; maybeAssignConfigText("provisionCommand"); maybeAssignConfigText("teardownCommand"); maybeAssignConfigText("cleanupCommand"); if (initialState.inheritRuntime !== nextState.inheritRuntime || initialState.workspaceRuntime !== nextState.workspaceRuntime) { const parsed = parseWorkspaceRuntimeJson(nextState.workspaceRuntime); if (!parsed.ok) throw new Error(parsed.error); configPatch.workspaceRuntime = nextState.inheritRuntime ? null : parsed.value; } if (Object.keys(configPatch).length > 0) { patch.config = configPatch; } return patch; } function validateForm(form: WorkspaceFormState) { const repoUrl = normalizeText(form.repoUrl); if (repoUrl) { try { new URL(repoUrl); } catch { return "Repo URL must be a valid URL."; } } if (!form.inheritRuntime) { const runtimeJson = parseWorkspaceRuntimeJson(form.workspaceRuntime); if (!runtimeJson.ok) { return runtimeJson.error; } } return null; } function Field({ label, hint, children, }: { label: string; hint?: string; children: React.ReactNode; }) { return ( ); } function DetailRow({ label, children }: { label: string; children: React.ReactNode }) { return (
{label}
{children}
); } function StatusPill({ children, className }: { children: React.ReactNode; className?: string }) { return (
{children}
); } function MonoValue({ value, copy }: { value: string; copy?: boolean }) { return (
{value} {copy ? ( ) : null}
); } function WorkspaceLink({ project, workspace, }: { project: Project; workspace: ProjectWorkspace; }) { return {workspace.name}; } function ExecutionWorkspaceIssuesList({ companyId, workspace, issues, isLoading, error, project, }: { companyId: string; workspace: ExecutionWorkspace; issues: Issue[]; isLoading: boolean; error: Error | null; project: Project | null; }) { const queryClient = useQueryClient(); const { data: agents } = useQuery({ queryKey: queryKeys.agents.list(companyId), queryFn: () => agentsApi.list(companyId), enabled: !!companyId, }); const { data: liveRuns } = useQuery({ queryKey: queryKeys.liveRuns(companyId), queryFn: () => heartbeatsApi.liveRunsForCompany(companyId), enabled: !!companyId, refetchInterval: 5000, }); const liveIssueIds = useMemo(() => collectLiveIssueIds(liveRuns), [liveRuns]); const updateIssue = useMutation({ mutationFn: ({ id, data }: { id: string; data: Record }) => issuesApi.update(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByExecutionWorkspace(companyId, workspace.id) }); queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(companyId) }); if (project?.id) { queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByProject(companyId, project.id) }); } }, }); const projectOptions = useMemo( () => (project ? [{ id: project.id, name: project.name, workspaces: project.workspaces ?? [] }] : undefined), [project], ); const createIssueDefaults = useMemo( () => ({ projectId: workspace.projectId, ...(workspace.projectWorkspaceId ? { projectWorkspaceId: workspace.projectWorkspaceId } : {}), executionWorkspaceId: workspace.id, executionWorkspaceMode: "reuse_existing", }), [workspace.id, workspace.projectId, workspace.projectWorkspaceId], ); return ( updateIssue.mutate({ id, data })} /> ); } function WorkspaceRoutineRow({ routine, variableNames, runningRoutineId, onRunNow, }: { routine: RoutineListItem; variableNames: string[]; runningRoutineId: string | null; onRunNow: (routine: RoutineListItem) => void; }) { const isArchived = routine.status === "archived"; const isRunning = runningRoutineId === routine.id; return (
{routine.title} {routine.status !== "active" ? ( {routine.status} ) : null}
{routine.assigneeAgentId ? "Default agent set" : "Choose agent when running"} Last run {formatOptionalDateTime(routine.lastRun?.triggeredAt ?? routine.lastTriggeredAt)} {variableNames.map((name) => ( {name} ))}
); } function ExecutionWorkspaceRoutinesList({ workspace, project, }: { workspace: ExecutionWorkspace; project: Project | null; }) { const queryClient = useQueryClient(); const { pushToast } = useToastActions(); const [runDialogRoutine, setRunDialogRoutine] = useState(null); const [runningRoutineId, setRunningRoutineId] = useState(null); const { data: routines, isLoading, error } = useQuery({ queryKey: queryKeys.routines.list(workspace.companyId, { projectId: workspace.projectId }), queryFn: () => routinesApi.list(workspace.companyId, { projectId: workspace.projectId }), }); const { data: agents } = useQuery({ queryKey: queryKeys.agents.list(workspace.companyId), queryFn: () => agentsApi.list(workspace.companyId), }); const workspaceRoutines = useMemo( () => (routines ?? []).filter(routineHasWorkspaceSpecificVariables), [routines], ); const runRoutine = useMutation({ mutationFn: ({ id, data }: { id: string; data?: RoutineRunDialogSubmitData }) => routinesApi.run(id, { ...(data?.variables && Object.keys(data.variables).length > 0 ? { variables: data.variables } : {}), ...(data?.assigneeAgentId !== undefined ? { assigneeAgentId: data.assigneeAgentId } : {}), ...(data?.projectId !== undefined ? { projectId: data.projectId } : {}), ...(data?.executionWorkspaceId !== undefined ? { executionWorkspaceId: data.executionWorkspaceId } : {}), ...(data?.executionWorkspacePreference !== undefined ? { executionWorkspacePreference: data.executionWorkspacePreference } : {}), ...(data?.executionWorkspaceSettings !== undefined ? { executionWorkspaceSettings: data.executionWorkspaceSettings } : {}), }), onMutate: ({ id }) => { setRunningRoutineId(id); }, onSuccess: async (_, { id }) => { setRunDialogRoutine(null); await Promise.all([ queryClient.invalidateQueries({ queryKey: ["routines", workspace.companyId] }), queryClient.invalidateQueries({ queryKey: queryKeys.routines.detail(id) }), queryClient.invalidateQueries({ queryKey: queryKeys.issues.listByExecutionWorkspace(workspace.companyId, workspace.id) }), queryClient.invalidateQueries({ queryKey: queryKeys.issues.list(workspace.companyId) }), ]); pushToast({ title: "Routine started", body: "Paperclip created a run using this execution workspace.", tone: "success", }); }, onSettled: () => { setRunningRoutineId(null); }, onError: (mutationError) => { pushToast({ title: "Routine run failed", body: mutationError instanceof Error ? mutationError.message : "Paperclip could not start the routine run.", tone: "error", }); }, }); return ( <> Workspace routines Routines that use workspace-specific variables can be run against this execution workspace. {isLoading ? (

Loading routines...

) : error ? (

{error instanceof Error ? error.message : "Failed to load routines."}

) : workspaceRoutines.length === 0 ? (

No routines use workspace-specific variables yet.

) : (
{workspaceRoutines.map((routine) => ( ))}
)}
{ if (!next) setRunDialogRoutine(null); }} companyId={workspace.companyId} routineName={runDialogRoutine?.title ?? null} agents={agents ?? []} projects={project ? [project] : []} defaultProjectId={workspace.projectId} defaultAssigneeAgentId={runDialogRoutine?.assigneeAgentId ?? null} defaultExecutionWorkspace={workspace} variables={runDialogRoutine?.variables ?? []} isPending={runRoutine.isPending} onSubmit={(data) => { if (!runDialogRoutine) return; runRoutine.mutate({ id: runDialogRoutine.id, data }); }} /> ); } export function ExecutionWorkspaceDetail() { const { workspaceId } = useParams<{ workspaceId: string }>(); const location = useLocation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { setBreadcrumbs } = useBreadcrumbs(); const { selectedCompanyId, setSelectedCompanyId } = useCompany(); const [form, setForm] = useState(null); const [closeDialogOpen, setCloseDialogOpen] = useState(false); const [errorMessage, setErrorMessage] = useState(null); const [runtimeActionErrorMessage, setRuntimeActionErrorMessage] = useState(null); const [runtimeActionMessage, setRuntimeActionMessage] = useState(null); const activeRouteTab = workspaceId ? resolveExecutionWorkspaceTab(location.pathname, workspaceId) : null; const pluginTabFromSearch = useMemo(() => { const tab = new URLSearchParams(location.search).get("tab"); return isExecutionWorkspacePluginTab(tab) ? tab : null; }, [location.search]); const activeTab: ExecutionWorkspaceTab | null = activeRouteTab ?? pluginTabFromSearch; const workspaceQuery = useQuery({ queryKey: queryKeys.executionWorkspaces.detail(workspaceId!), queryFn: () => executionWorkspacesApi.get(workspaceId!), enabled: Boolean(workspaceId), }); const workspace = workspaceQuery.data ?? null; const projectQuery = useQuery({ queryKey: workspace ? [...queryKeys.projects.detail(workspace.projectId), workspace.companyId] : ["projects", "detail", "__pending__"], queryFn: () => projectsApi.get(workspace!.projectId, workspace!.companyId), enabled: Boolean(workspace?.projectId), }); const project = projectQuery.data ?? null; const sourceIssueQuery = useQuery({ queryKey: workspace?.sourceIssueId ? queryKeys.issues.detail(workspace.sourceIssueId) : ["issues", "detail", "__none__"], queryFn: () => issuesApi.get(workspace!.sourceIssueId!), enabled: Boolean(workspace?.sourceIssueId), }); const sourceIssue = sourceIssueQuery.data ?? null; const derivedWorkspaceQuery = useQuery({ queryKey: workspace?.derivedFromExecutionWorkspaceId ? queryKeys.executionWorkspaces.detail(workspace.derivedFromExecutionWorkspaceId) : ["execution-workspaces", "detail", "__none__"], queryFn: () => executionWorkspacesApi.get(workspace!.derivedFromExecutionWorkspaceId!), enabled: Boolean(workspace?.derivedFromExecutionWorkspaceId), }); const derivedWorkspace = derivedWorkspaceQuery.data ?? null; const linkedIssuesQuery = useQuery({ queryKey: workspace ? queryKeys.issues.listByExecutionWorkspace(workspace.companyId, workspace.id) : ["issues", "__execution-workspace__", "__none__"], queryFn: () => issuesApi.list(workspace!.companyId, { executionWorkspaceId: workspace!.id }), enabled: Boolean(workspace?.companyId), }); const linkedIssues = linkedIssuesQuery.data ?? []; const linkedProjectWorkspace = useMemo( () => project?.workspaces.find((item) => item.id === workspace?.projectWorkspaceId) ?? null, [project, workspace?.projectWorkspaceId], ); const { slots: workspacePluginDetailSlots, isLoading: workspacePluginDetailSlotsLoading, errorMessage: workspacePluginDetailSlotsError, } = usePluginSlots({ slotTypes: ["detailTab"], entityType: "execution_workspace", companyId: workspace?.companyId ?? null, enabled: !!workspace?.companyId, }); const workspacePluginTabItems = useMemo( () => workspacePluginDetailSlots.map((slot) => ({ value: `plugin:${slot.pluginKey}:${slot.id}` as ExecutionWorkspacePluginTab, label: slot.displayName, order: slot.order ?? DEFAULT_PLUGIN_DETAIL_TAB_ORDER, slot, })), [workspacePluginDetailSlots], ); const workspaceTabItems = useMemo( () => orderExecutionWorkspaceTabItems([...EXECUTION_WORKSPACE_BASE_TAB_ITEMS, ...workspacePluginTabItems]), [workspacePluginTabItems], ); const inheritedRuntimeConfig = linkedProjectWorkspace?.runtimeConfig?.workspaceRuntime ?? null; const effectiveRuntimeConfig = workspace?.config?.workspaceRuntime ?? inheritedRuntimeConfig; const runtimeConfigSource = workspace?.config?.workspaceRuntime ? "execution_workspace" : inheritedRuntimeConfig ? "project_workspace" : "none"; const initialState = useMemo(() => (workspace ? formStateFromWorkspace(workspace) : null), [workspace]); const isDirty = Boolean(form && initialState && JSON.stringify(form) !== JSON.stringify(initialState)); const projectRef = project ? projectRouteRef(project) : workspace?.projectId ?? ""; useEffect(() => { if (!workspace?.companyId || workspace.companyId === selectedCompanyId) return; setSelectedCompanyId(workspace.companyId, { source: "route_sync" }); }, [workspace?.companyId, selectedCompanyId, setSelectedCompanyId]); useEffect(() => { if (!workspace) return; setForm(formStateFromWorkspace(workspace)); setErrorMessage(null); setRuntimeActionErrorMessage(null); }, [workspace]); useEffect(() => { if (!workspace) return; const crumbs = [ { label: "Projects", href: "/projects" }, ...(project ? [{ label: project.name, href: `/projects/${projectRef}` }] : []), ...(project ? [{ label: "Workspaces", href: `/projects/${projectRef}/workspaces` }] : []), { label: workspace.name }, ]; setBreadcrumbs(crumbs); }, [setBreadcrumbs, workspace, project, projectRef]); const updateWorkspace = useMutation({ mutationFn: (patch: Record) => executionWorkspacesApi.update(workspace!.id, patch), onSuccess: (nextWorkspace) => { queryClient.setQueryData(queryKeys.executionWorkspaces.detail(nextWorkspace.id), nextWorkspace); queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.closeReadiness(nextWorkspace.id) }); queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.workspaceOperations(nextWorkspace.id) }); if (project) { queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(project.id) }); queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(project.urlKey) }); } if (sourceIssue) { queryClient.invalidateQueries({ queryKey: queryKeys.issues.detail(sourceIssue.id) }); } setErrorMessage(null); }, onError: (error) => { setErrorMessage(error instanceof Error ? error.message : "Failed to save execution workspace."); }, }); const workspaceOperationsQuery = useQuery({ queryKey: queryKeys.executionWorkspaces.workspaceOperations(workspaceId!), queryFn: () => executionWorkspacesApi.listWorkspaceOperations(workspaceId!), enabled: Boolean(workspaceId), }); const controlRuntimeServices = useMutation({ mutationFn: (request: WorkspaceRuntimeControlRequest) => executionWorkspacesApi.controlRuntimeCommands(workspace!.id, request.action, request), onSuccess: (result, request) => { queryClient.setQueryData(queryKeys.executionWorkspaces.detail(result.workspace.id), result.workspace); queryClient.invalidateQueries({ queryKey: queryKeys.executionWorkspaces.workspaceOperations(result.workspace.id) }); queryClient.invalidateQueries({ queryKey: queryKeys.projects.detail(result.workspace.projectId) }); setRuntimeActionErrorMessage(null); setRuntimeActionMessage( request.action === "run" ? "Workspace job completed." : request.action === "stop" ? "Workspace service stopped." : request.action === "restart" ? "Workspace service restarted." : "Workspace service started.", ); }, onError: (error) => { setRuntimeActionMessage(null); setRuntimeActionErrorMessage(error instanceof Error ? error.message : "Failed to control workspace commands."); }, }); if (workspaceQuery.isLoading) return

Loading workspace…

; if (workspaceQuery.error) { return (

{workspaceQuery.error instanceof Error ? workspaceQuery.error.message : "Failed to load workspace"}

); } if (!workspace || !form || !initialState) return null; const canRunWorkspaceCommands = Boolean(workspace.cwd); const canStartRuntimeServices = Boolean(effectiveRuntimeConfig) && canRunWorkspaceCommands; const runtimeControlSections = buildWorkspaceRuntimeControlSections({ runtimeConfig: effectiveRuntimeConfig, runtimeServices: workspace.runtimeServices ?? [], canStartServices: canStartRuntimeServices, canRunJobs: canRunWorkspaceCommands, }); const pendingRuntimeAction = controlRuntimeServices.isPending ? controlRuntimeServices.variables ?? null : null; const pluginSlotContext = { companyId: workspace.companyId, projectId: workspace.projectId, entityId: workspace.id, entityType: "execution_workspace" as const, }; const activePluginTab = workspacePluginTabItems.find((item) => item.value === activeTab) ?? null; if (workspaceId && activeTab === null) { return ; } const handleTabChange = (tab: ExecutionWorkspaceTab) => { if (isExecutionWorkspacePluginTab(tab)) { navigate(`/execution-workspaces/${workspace.id}?tab=${encodeURIComponent(tab)}`); return; } navigate(executionWorkspaceTabPath(workspace.id, tab)); }; const saveChanges = () => { const validationError = validateForm(form); if (validationError) { setErrorMessage(validationError); return; } let patch: Record; try { patch = buildWorkspacePatch(initialState, form); } catch (error) { setErrorMessage(error instanceof Error ? error.message : "Failed to build workspace update."); return; } if (Object.keys(patch).length === 0) return; updateWorkspace.mutate(patch); }; return ( <>
Execution workspace

{workspace.name}

controlRuntimeServices.mutate(request)} />
{runtimeActionErrorMessage ?

{runtimeActionErrorMessage}

: null} {!runtimeActionErrorMessage && runtimeActionMessage ?

{runtimeActionMessage}

: null} handleTabChange(value as ExecutionWorkspaceTab)}> ({ value: item.value, label: item.label }))} align="start" value={activeTab ?? "issues"} onValueChange={(value) => handleTabChange(value as ExecutionWorkspaceTab)} /> {activeTab === "services" ? ( controlRuntimeServices.mutate(request)} /> ) : activeTab === "configuration" ? (
Workspace settings Edit the concrete path, repo, branch, provisioning, teardown, and runtime overrides attached to this execution workspace.
General
setForm((current) => current ? { ...current, name: event.target.value } : current)} placeholder="Execution workspace name" />
Source control
setForm((current) => current ? { ...current, branchName: event.target.value } : current)} placeholder="PAP-946-workspace" /> setForm((current) => current ? { ...current, baseRef: event.target.value } : current)} placeholder="origin/main" />
setForm((current) => current ? { ...current, repoUrl: event.target.value } : current)} placeholder="https://github.com/org/repo" />
Paths
setForm((current) => current ? { ...current, cwd: event.target.value } : current)} placeholder="/absolute/path/to/workspace" /> setForm((current) => current ? { ...current, providerRef: event.target.value } : current)} placeholder="/path/to/worktree or provider ref" />
Lifecycle commands