'use client'; // ExternalConnectModal — shown once after creating a runtime="external" // workspace. Surfaces the workspace_auth_token + ready-to-paste snippets // so the operator can hand them to whoever runs their off-host agent // without piecing together the register payload from docs. // // Security posture: // - The auth_token is visible once. After the modal closes, the value // is unrecoverable (the /workspaces/:id read endpoints never echo it). // UI warns the operator before they dismiss. // - A "copy to clipboard" button uses the navigator.clipboard API which // is same-origin and requires user gesture — no cross-origin leak. // - Snippets use placeholders for the operator's own public URL // ($AGENT_URL). They ARE NOT filled in server-side because the // server doesn't know where the operator's agent will live. import { useCallback, useState } from "react"; import * as Dialog from "@radix-ui/react-dialog"; export interface ExternalConnectionInfo { workspace_id: string; platform_url: string; auth_token: string; registry_endpoint: string; heartbeat_endpoint: string; curl_register_template: string; python_snippet: string; // Claude Code channel plugin snippet — for operators whose external // agent IS a Claude Code session. Polling-based; no tunnel required. // Optional in the type for backward compat with platforms that // haven't shipped molecule-core PR #2304 yet (older response payload // omits the field; tab is hidden if empty). claude_code_channel_snippet?: string; // Universal MCP snippet — runtime-agnostic outbound tool path via // the `molecule-mcp` console script in the // molecule-ai-workspace-runtime PyPI wheel. Works with any MCP-aware // agent runtime (Claude Code, hermes, codex, third-party). Outbound- // only: pair with claude_code_channel or python tabs for heartbeat // + inbound. Optional for backward compat with platforms that // haven't shipped PR #2413 yet. universal_mcp_snippet?: string; } interface Props { info: ExternalConnectionInfo | null; onClose: () => void; } type Tab = "python" | "curl" | "claude" | "mcp" | "fields"; export function ExternalConnectModal({ info, onClose }: Props) { // Default to Claude Code when the platform offers it — that's the // newest + simplest path (no tunnel needed). Falls back to Python // for older platform builds that don't ship the snippet. const initialTab: Tab = info?.claude_code_channel_snippet ? "claude" : "python"; const [tab, setTab] = useState(initialTab); const [copiedKey, setCopiedKey] = useState(null); const copy = useCallback(async (value: string, key: string) => { try { await navigator.clipboard.writeText(value); setCopiedKey(key); // Auto-clear the "Copied!" label after 1.5s so a second copy // attempt feels responsive — without the reset, the second // click appears as a no-op. window.setTimeout(() => setCopiedKey(null), 1500); } catch { // Fallback for browsers that refuse clipboard access (http:// // over insecure origin, Safari private mode, etc.). We surface // a minimal textarea so the operator can manually copy. const el = document.getElementById(`fallback-${key}`) as HTMLTextAreaElement | null; if (el) { el.select(); } } }, []); if (!info) return null; // Python snippet is stamped server-side with workspace_id + // platform_url but leaves AUTH_TOKEN as a "" placeholder // (that's what we're showing in the modal). Fill in the real // token here so the snippet the operator copies is truly ready-to-run. const filledPython = info.python_snippet.replace( 'AUTH_TOKEN = ""', `AUTH_TOKEN = "${info.auth_token}"`, ); const filledCurl = info.curl_register_template.replace( 'WORKSPACE_AUTH_TOKEN=""', `WORKSPACE_AUTH_TOKEN="${info.auth_token}"`, ); // The channel snippet asks the operator to paste the auth_token into // the .env file's MOLECULE_WORKSPACE_TOKENS field. Stamp it server-side // here so the copy-paste-block is truly ready-to-run. const filledChannel = info.claude_code_channel_snippet?.replace( 'MOLECULE_WORKSPACE_TOKENS=', `MOLECULE_WORKSPACE_TOKENS=${info.auth_token}`, ); // Universal MCP snippet uses MOLECULE_WORKSPACE_TOKEN as the env-var // name passed through to molecule-mcp via `claude mcp add ... -- env // MOLECULE_WORKSPACE_TOKEN=...`. The placeholder must match the // template's literal — pre-2026-04-30 polish this looked for // WORKSPACE_AUTH_TOKEN (carryover from the curl tab), which silently // skipped the substitution and left "" // visible in the operator's clipboard. const filledUniversalMcp = info.universal_mcp_snippet?.replace( 'MOLECULE_WORKSPACE_TOKEN=""', `MOLECULE_WORKSPACE_TOKEN="${info.auth_token}"`, ); return ( !o && onClose()}> Connect your external agent Paste the snippet below into your agent's deployment. The auth token is shown only once {" "}— save it somewhere safe before closing this dialog. {/* Tabs */}
{(() => { // Build the tab order dynamically. Claude Code first // (when offered) since it's the simplest setup; Python // SDK second (full register+heartbeat+inbound); Universal // MCP third (any MCP-aware runtime, outbound-only); curl // for one-shot register; Fields for raw values. const tabs: Tab[] = []; if (filledChannel) tabs.push("claude"); tabs.push("python"); if (filledUniversalMcp) tabs.push("mcp"); tabs.push("curl", "fields"); return tabs; })().map((t) => ( ))}
{/* Snippet area */}
{tab === "claude" && filledChannel && ( copy(filledChannel, "claude")} /> )} {tab === "python" && ( copy(filledPython, "python")} /> )} {tab === "curl" && ( copy(filledCurl, "curl")} /> )} {tab === "mcp" && filledUniversalMcp && ( copy(filledUniversalMcp, "mcp")} /> )} {tab === "fields" && (
copy(info.workspace_id, "wsid")} copied={copiedKey === "wsid"} /> copy(info.platform_url, "url")} copied={copiedKey === "url"} /> copy(info.auth_token, "tok")} copied={copiedKey === "tok"} mono /> copy(info.registry_endpoint, "reg")} copied={copiedKey === "reg"} /> copy(info.heartbeat_endpoint, "hb")} copied={copiedKey === "hb"} />
)}
); } function SnippetBlock({ value, label, copied, onCopy, }: { value: string; label: string; copyKey: string; copied: boolean; onCopy: () => void; }) { return (
{label}
        {value}
      
); } function Field({ label, value, onCopy, copied, mono, }: { label: string; value: string; onCopy: () => void; copied: boolean; mono?: boolean; }) { return (
{label} {value || "(missing)"}
); }