diff --git a/ui/src/pages/ExecutionWorkspaceDetail.test.tsx b/ui/src/pages/ExecutionWorkspaceDetail.test.tsx index d4356c0d212..acc75649388 100644 --- a/ui/src/pages/ExecutionWorkspaceDetail.test.tsx +++ b/ui/src/pages/ExecutionWorkspaceDetail.test.tsx @@ -318,6 +318,14 @@ describe("ExecutionWorkspaceDetail plugin slots", () => { expect(container.querySelector('[data-testid="summary-slot-card"]')).not.toBeNull(); }); + it("does not show a workspace access status card", async () => { + await render(); + + expect(container.querySelector('[data-testid="workspace-access-card"]')).toBeNull(); + expect(container.textContent).not.toContain("Workspace is not running"); + expect(container.textContent).not.toContain("Open workspace"); + }); + it("does not mount plugin slots scoped to other entity types", async () => { await render(); diff --git a/ui/src/pages/ExecutionWorkspaceDetail.tsx b/ui/src/pages/ExecutionWorkspaceDetail.tsx index 17506e841f2..62df8bf8a6d 100644 --- a/ui/src/pages/ExecutionWorkspaceDetail.tsx +++ b/ui/src/pages/ExecutionWorkspaceDetail.tsx @@ -13,7 +13,6 @@ import { CopyText } from "../components/CopyText"; import { ExecutionWorkspaceCloseDialog } from "../components/ExecutionWorkspaceCloseDialog"; import { MissingPluginTabPlaceholder } from "../components/MissingPluginTabPlaceholder"; import { agentsApi } from "../api/agents"; -import { ApiError } from "../api/client"; import { executionWorkspacesApi } from "../api/execution-workspaces"; import { heartbeatsApi } from "../api/heartbeats"; import { issuesApi } from "../api/issues"; @@ -36,7 +35,6 @@ import { type WorkspaceRuntimeControlRequest, } from "../components/WorkspaceRuntimeControls"; import { WorkspaceServiceControlBar } from "../components/WorkspaceServiceControlBar"; -import { WorkspaceAccessCard } from "../components/WorkspaceAccessCard"; import { useBreadcrumbs } from "../context/BreadcrumbContext"; import { useCompany } from "../context/CompanyContext"; import { useManagedSandboxOnly } from "../hooks/useManagedSandboxOnly"; @@ -44,10 +42,6 @@ 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 { - resolveWorkspaceAccessState, - type WorkspaceLoginHandoffFailureInfo, -} from "../lib/workspace-access-state"; import { getWorkspaceSpecificRoutineVariableNames, routineHasWorkspaceSpecificVariables, @@ -423,27 +417,6 @@ export function resolveRuntimeProvisionStatus(input: { return configured ? { kind: "deferred" } : { kind: "eager" }; } -/** - * Read the structured refusal the login-handoff endpoint returns. - * - * The server keeps a machine `reason` (and, where it probed, the workspace's own - * readiness) on the error body so the UI can name the cause instead of showing a - * bare HTTP failure. Anything else is a genuine transport error. - */ -export function readWorkspaceHandoffFailure(error: unknown): WorkspaceLoginHandoffFailureInfo | null { - if (!(error instanceof ApiError)) return null; - const body = error.body as - | { reason?: unknown; detail?: unknown; readiness?: unknown } - | null - | undefined; - if (!body || typeof body.reason !== "string") return null; - return { - reason: body.reason, - detail: typeof body.detail === "string" ? body.detail : null, - readiness: (body.readiness as WorkspaceLoginHandoffFailureInfo["readiness"]) ?? null, - }; -} - function DetailRow({ label, children }: { label: string; children: React.ReactNode }) { return (
@@ -812,8 +785,6 @@ export function ExecutionWorkspaceDetail() { const [errorMessage, setErrorMessage] = useState(null); const [runtimeActionErrorMessage, setRuntimeActionErrorMessage] = useState(null); const [runtimeActionMessage, setRuntimeActionMessage] = useState(null); - const [handoffFailure, setHandoffFailure] = useState(null); - const [handoffErrorMessage, setHandoffErrorMessage] = useState(null); const [pendingRuntimeActions, setPendingRuntimeActions] = useState([]); const activeRouteTab = workspaceId ? resolveExecutionWorkspaceTab(location.pathname, workspaceId) : null; const pluginTabFromSearch = useMemo(() => { @@ -1003,67 +974,6 @@ export function ExecutionWorkspaceDetail() { }, }); - /** - * Password-independent workspace entry (PAP-17572). - * - * The server answers with a ticket-bearing URL, and the workspace answers *that* - * with a redirect — which is what keeps the ticket out of session history. - * - * The target tab is opened synchronously on click and only pointed at the URL - * once the ticket arrives. Opening it after the request resolves would be a - * popup the browser did not attribute to the click, and Safari and Firefox - * block exactly that. If the tab could not be opened anyway, fall back to - * navigating this one rather than silently doing nothing. - */ - const openWorkspace = useMutation({ - mutationFn: async () => { - const target = window.open("about:blank", "_blank", "noopener,noreferrer"); - try { - return { ticket: await executionWorkspacesApi.requestLoginHandoff(workspace!.id), target }; - } catch (error) { - target?.close(); - throw error; - } - }, - onSuccess: ({ ticket, target }) => { - setHandoffFailure(null); - setHandoffErrorMessage(null); - if (target && !target.closed) target.location.replace(ticket.url); - else window.location.assign(ticket.url); - }, - onError: async (error) => { - // A structured refusal is rendered as workspace state by the access card, - // so only an unrecognized transport error needs its own message line. - const failure = readWorkspaceHandoffFailure(error); - setHandoffFailure(failure); - setHandoffErrorMessage( - failure ? null : error instanceof Error ? error.message : "Failed to open the workspace.", - ); - // The refusal reason often comes from an operation that has since advanced, - // so refresh the log the access card derives its state from. - await queryClient.invalidateQueries({ - queryKey: queryKeys.executionWorkspaces.workspaceOperations(workspace!.id), - }); - }, - }); - const repairWorkspace = useMutation({ - mutationFn: () => executionWorkspacesApi.repair(workspace!.id), - onSuccess: (result) => { - queryClient.setQueryData(queryKeys.executionWorkspaces.detail(result.workspace.id), result.workspace); - queryClient.invalidateQueries({ - queryKey: queryKeys.executionWorkspaces.workspaceOperations(result.workspace.id), - }); - setHandoffFailure(null); - setHandoffErrorMessage(null); - setRuntimeActionErrorMessage(null); - setRuntimeActionMessage("Workspace database repaired."); - }, - onError: (error) => { - setRuntimeActionMessage(null); - setRuntimeActionErrorMessage(error instanceof Error ? error.message : "Failed to repair the workspace."); - }, - }); - if (workspaceQuery.isLoading) return

Loading workspace…

; if (workspaceQuery.error) { return ( @@ -1088,12 +998,6 @@ export function ExecutionWorkspaceDetail() { runtimeServices: workspace.runtimeServices ?? [], pendingRequests: pendingRuntimeActions, }); - const workspaceAccess = resolveWorkspaceAccessState({ - runtimeServices: workspace.runtimeServices ?? [], - operations: workspaceOperationsQuery.data, - handoffFailure, - }); - const pluginSlotContext = { companyId: workspace.companyId, projectId: workspace.projectId, @@ -1163,20 +1067,6 @@ export function ExecutionWorkspaceDetail() { {runtimeActionErrorMessage ?

{runtimeActionErrorMessage}

: null} {!runtimeActionErrorMessage && runtimeActionMessage ?

{runtimeActionMessage}

: null} - openWorkspace.mutate()} - onStart={() => { - runRuntimeControlRequests( - resolveWorkspaceServiceControlRequests(runtimeControlSections, "start", null), - ); - }} - onRepair={() => repairWorkspace.mutate()} - onViewLogs={() => handleTabChange("runtime_logs")} - errorMessage={handoffErrorMessage} - /> -