"use client"; import { Radio as RadioPrimitive } from "@base-ui/react/radio"; import { CheckIcon, CopyIcon, LoaderIcon, TriangleAlertIcon } from "lucide-react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useAtomValue } from "@effect/atom-react"; import { type HermesGatewayConnectionState, type HermesGatewayEnrollmentResult, ProviderInstanceId, ProviderDriverKind, type ProviderInstanceConfig, } from "@t3tools/contracts"; import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; import { useCopyToClipboard } from "../../hooks/useCopyToClipboard"; import { usePrimarySettings, useUpdatePrimarySettings } from "../../hooks/useSettings"; import { cn, randomUUID } from "../../lib/utils"; import { normalizeProviderAccentColor } from "../../providerInstances"; import { Button } from "../ui/button"; import { ACPRegistryIcon, Gemini, GithubCopilotIcon, PiAgentIcon, type Icon } from "../Icons"; import { Dialog, DialogDescription, DialogFooter, DialogHeader, DialogPopup, DialogTitle, } from "../ui/dialog"; import { Badge } from "../ui/badge"; import { Input } from "../ui/input"; import { RadioGroup } from "../ui/radio-group"; import { toastManager } from "../ui/toast"; import { DRIVER_OPTION_BY_VALUE, DRIVER_OPTIONS } from "./providerDriverMeta"; import { ProviderSettingsForm, deriveProviderSettingsFields } from "./ProviderSettingsForm"; import { AnimatedHeight } from "../AnimatedHeight"; import { ADD_PROVIDER_WIZARD_STEPS, createHermesProviderInstanceId, HERMES_GATEWAY_RESTART_COMMAND, HERMES_PLUGIN_INSTALL_COMMAND, isHermesInstanceRemovedError, isOwnedHermesEnrollmentRetry, resolveHermesWaitingState, resolveWizardNavigation, rollbackProviderInstances, type WizardNavigation, validateProviderInstanceIdForWizard, } from "./AddProviderInstanceDialog.logic"; import { AddProviderInstanceWizardSteps } from "./AddProviderInstanceWizardSteps"; import { usePrimaryEnvironment } from "../../state/environments"; import { primaryServerProvidersAtom, serverEnvironment } from "../../state/server"; import { useAtomCommand } from "../../state/use-atom-command"; import { defaultHermesConnectorUrl, formatHermesLastConnected, hermesGatewayProviderSignal, hermesGatewayStatusGuidance, hermesGatewayStatusLabel, isHermesEnrollmentExpired, messageFromUnknownError, } from "./HermesGatewayInstanceSection.logic"; const PROVIDER_ACCENT_SWATCHES = [ "#2563eb", "#16a34a", "#ea580c", "#dc2626", "#7c3aed", "#0891b2", ] as const; /** * Normalize a user-provided label into a slug suffix for the instance id. * The full id is formed by prefixing the driver slug — e.g. label "Work" on * driver "codex" becomes `codex_work`. Hermes uses a random durable identity * instead because its historical thread bindings must never target a newly * created gateway with the same display name. */ function slugifyLabel(value: string): string { return value .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "_") .replace(/^_+|_+$/g, "") .slice(0, 48); } function deriveInstanceId(driver: ProviderDriverKind, label: string): string { const slug = slugifyLabel(label); return slug ? `${driver}_${slug}` : ""; } const DEFAULT_DRIVER_KIND = ProviderDriverKind.make("codex"); const DEFAULT_DRIVER_OPTION = DRIVER_OPTIONS[0]!; const EMPTY_CONFIG_DRAFT: Record = {}; interface ComingSoonDriverOption { readonly value: ProviderDriverKind; readonly label: string; readonly icon: Icon; } const COMING_SOON_DRIVER_OPTIONS: readonly ComingSoonDriverOption[] = [ { value: ProviderDriverKind.make("githubCopilot"), label: "Github Copilot", icon: GithubCopilotIcon, }, { value: ProviderDriverKind.make("gemini"), label: "Gemini", icon: Gemini, }, { value: ProviderDriverKind.make("acpRegistry"), label: "ACP Registry", icon: ACPRegistryIcon, }, { value: ProviderDriverKind.make("piAgent"), label: "Pi Agent", icon: PiAgentIcon, }, ]; const HERMES_BADGE_VARIANT_BY_STATUS: Record< HermesGatewayConnectionState, "success" | "warning" | "error" | "secondary" > = { connected: "success", connecting: "warning", offline: "secondary", "upgrade-required": "warning", revoked: "error", }; interface AddProviderInstanceDialogProps { open: boolean; onOpenChange: (open: boolean) => void; } export function AddProviderInstanceDialog({ open, onOpenChange }: AddProviderInstanceDialogProps) { const settings = usePrimarySettings(); const updateSettings = useUpdatePrimarySettings(); const environmentId = usePrimaryEnvironment()?.environmentId ?? null; const updateServerSettings = useAtomCommand(serverEnvironment.updateSettings, { reportFailure: false, }); const createHermesEnrollment = useAtomCommand(serverEnvironment.hermesGatewayCreateEnrollment, { reportFailure: false, }); const getHermesStatus = useAtomCommand(serverEnvironment.hermesGatewayGetInstanceStatus, { reportFailure: false, }); const serverProviders = useAtomValue(primaryServerProvidersAtom); const [wizardStep, setWizardStep] = useState(0); const [driver, setDriver] = useState(DEFAULT_DRIVER_KIND); const [label, setLabel] = useState(""); const [accentColor, setAccentColor] = useState(""); const [instanceIdOverride, setInstanceIdOverride] = useState(null); const [hermesIdentityNonce, setHermesIdentityNonce] = useState(randomUUID); // Driver-specific config drafts keyed by driver so toggling between drivers // during the same dialog session does not lose in-progress input. const [configByDriver, setConfigByDriver] = useState>>({}); // Errors are suppressed until the user has tried to submit once. After that // they update live so fixing the problem clears the message in place. const [hasAttemptedSubmit, setHasAttemptedSubmit] = useState(false); const [hermesConnectorUrl, setHermesConnectorUrl] = useState(() => typeof window === "undefined" ? "http://localhost/api/hermes-gateway/ws" : defaultHermesConnectorUrl(window.location.origin), ); const [hermesEnrollment, setHermesEnrollment] = useState( null, ); const [saveError, setSaveError] = useState(null); const [isSaving, setIsSaving] = useState(false); const [createdHermesIdentity, setCreatedHermesIdentity] = useState<{ readonly instanceId: string; readonly nickname: string; /** * False once a failed enrollment rolled the settings write back. The id * stays claimed by this dialog session (so validation keeps exempting it * and no duplicate is ever created), but the next attempt has to write it * again before the server will accept an enrollment for it. */ readonly persisted: boolean; } | null>(null); const [hermesConnectionState, setHermesConnectionState] = useState(null); const [nowMillis, setNowMillis] = useState(() => Date.now()); const { copyToClipboard, isCopied } = useCopyToClipboard({ target: "Hermes enrollment command", onCopy: () => toastManager.add({ type: "success", title: "Hermes command copied", description: "Run it in the terminal where Hermes is installed.", }), }); const { copyToClipboard: copyInstallCommand, isCopied: isInstallCommandCopied } = useCopyToClipboard({ target: "Hermes plugin install command", onCopy: () => toastManager.add({ type: "success", title: "Install command copied", description: "Run it from the T3 Code checkout on the Hermes host.", }), }); const { copyToClipboard: copyRestartCommand, isCopied: isRestartCommandCopied } = useCopyToClipboard({ target: "Hermes gateway restart command", onCopy: () => toastManager.add({ type: "success", title: "Restart command copied", description: "Run it on the Hermes host to activate the pairing.", }), }); // Rollback of a failed Hermes enrollment must survive the dialog closing // mid-failure, so the pending undo lives in a ref that an unmount-safe // effect drains rather than in the `handleSave` closure alone. const pendingRollbackRef = useRef<{ readonly instanceId: string; readonly written: ProviderInstanceConfig; } | null>(null); const latestProviderInstancesRef = useRef(settings.providerInstances); latestProviderInstancesRef.current = settings.providerInstances; const environmentIdRef = useRef(environmentId); environmentIdRef.current = environmentId; const updateServerSettingsRef = useRef(updateServerSettings); updateServerSettingsRef.current = updateServerSettings; const rollbackPendingHermesInstance = useCallback(async () => { const pending = pendingRollbackRef.current; pendingRollbackRef.current = null; const rollbackEnvironmentId = environmentIdRef.current; if (pending === null || rollbackEnvironmentId === null) return; const providerInstances = rollbackProviderInstances({ latest: latestProviderInstancesRef.current ?? {}, instanceId: pending.instanceId, written: pending.written, }); if (providerInstances === null) return; await updateServerSettingsRef.current({ environmentId: rollbackEnvironmentId, input: { patch: { providerInstances } }, }); }, []); // Fires both when the dialog is closed and when it is unmounted outright // (its parent renders it conditionally), so an orphan is never left behind. useEffect(() => { if (open) return; void rollbackPendingHermesInstance(); setWizardStep(0); setDriver(DEFAULT_DRIVER_KIND); setLabel(""); setAccentColor(""); setInstanceIdOverride(null); setHermesIdentityNonce(randomUUID()); setConfigByDriver({}); setHasAttemptedSubmit(false); setHermesConnectorUrl( typeof window === "undefined" ? "http://localhost/api/hermes-gateway/ws" : defaultHermesConnectorUrl(window.location.origin), ); setHermesEnrollment(null); setSaveError(null); setIsSaving(false); setCreatedHermesIdentity(null); setHermesConnectionState(null); }, [open, rollbackPendingHermesInstance]); useEffect( () => () => { void rollbackPendingHermesInstance(); }, [rollbackPendingHermesInstance], ); const enrolledInstanceId = hermesEnrollment?.instanceId ?? null; // The server pushes a fresh `ServerProvider` snapshot over the already-open // `subscribeServerConfig` stream every time the broker publishes a gateway // status change, so this flips the moment Hermes dials in — no polling. const hermesProviderSignal = useMemo( () => enrolledInstanceId === null ? null : hermesGatewayProviderSignal(serverProviders, enrolledInstanceId), [serverProviders, enrolledInstanceId], ); const hermesProviderSignalKey = hermesProviderSignal ? `${hermesProviderSignal.present}:${hermesProviderSignal.connected}:${hermesProviderSignal.revision ?? ""}` : null; // The pushed snapshot only tells us connected/not. Resolve the authoritative // `HermesGatewayConnectionState` once per change so `upgrade-required` and // `revoked` are distinguishable from a plain wait. useEffect(() => { if (enrolledInstanceId === null || environmentId === null) return; if (hermesProviderSignalKey === null) return; let cancelled = false; void (async () => { const result = await getHermesStatus({ environmentId, input: { instanceId: ProviderInstanceId.make(enrolledInstanceId) }, }); if (cancelled) return; if (result._tag === "Success") setHermesConnectionState(result.value.status); })(); return () => { cancelled = true; }; }, [enrolledInstanceId, environmentId, getHermesStatus, hermesProviderSignalKey]); // Re-evaluate the enrollment expiry on a coarse tick so the panel switches // itself to the "mint a new command" affordance without a manual refresh. useEffect(() => { if (hermesEnrollment === null) return; setNowMillis(Date.now()); const interval = window.setInterval(() => setNowMillis(Date.now()), 15_000); return () => window.clearInterval(interval); }, [hermesEnrollment]); const hermesWaitingState = useMemo( () => hermesEnrollment === null ? null : resolveHermesWaitingState({ connectionState: hermesConnectionState, isExpired: isHermesEnrollmentExpired(hermesEnrollment.expiresAt, nowMillis), }), [hermesEnrollment, hermesConnectionState, nowMillis], ); const existingIds = useMemo( () => new Set(Object.keys(settings.providerInstances ?? {})), [settings.providerInstances], ); const driverOption = DRIVER_OPTION_BY_VALUE[driver] ?? DEFAULT_DRIVER_OPTION; const instanceId = driver === "hermes" ? createHermesProviderInstanceId(label, () => hermesIdentityNonce) : (instanceIdOverride ?? deriveInstanceId(driver, label)); const driverSettingsFields = useMemo( () => deriveProviderSettingsFields(driverOption), [driverOption], ); const instanceIdError = driver === "hermes" && label.trim().length === 0 ? "Hermes nickname is required." : validateProviderInstanceIdForWizard({ driver, instanceId, existingIds, createdHermesInstanceId: createdHermesIdentity?.instanceId ?? null, }); const showInstanceIdError = hasAttemptedSubmit && instanceIdError !== null; const previewLabel = label.trim() || `${driverOption.label} Workspace`; const wizardStepSummaries = [driverOption.label, previewLabel, null] as const; const configDraft = configByDriver[driver] ?? EMPTY_CONFIG_DRAFT; const setConfigDraft = (config: Record | undefined) => { setConfigByDriver((existing) => { const next = { ...existing }; if (config === undefined || Object.keys(config).length === 0) { delete next[driver]; } else { next[driver] = config; } return next; }); }; const applyWizardNavigation = (navigation: WizardNavigation) => { if (navigation.kind === "blocked") { setHasAttemptedSubmit(true); } setWizardStep(navigation.step); }; const navigateToStep = (requestedStep: number) => { applyWizardNavigation( resolveWizardNavigation(wizardStep, requestedStep, ADD_PROVIDER_WIZARD_STEPS.length, { instanceIdError, }), ); }; const handleSave = async () => { setHasAttemptedSubmit(true); if (instanceIdError !== null) return; const config = configByDriver[driver] ?? {}; const hasConfig = Object.keys(config).length > 0; const normalizedAccentColor = normalizeProviderAccentColor(accentColor); const nextInstance: ProviderInstanceConfig = { driver, enabled: true, ...(label.trim().length > 0 ? { displayName: label.trim() } : {}), ...(normalizedAccentColor ? { accentColor: normalizedAccentColor } : {}), ...(hasConfig ? { config } : {}), }; // `ProviderInstanceId.make` revalidates the slug; we've already checked // it via `validateProviderInstanceIdForWizard`, but going through the brand constructor // keeps the type boundary honest and guards against any future drift in // the slug rules. const brandedId = ProviderInstanceId.make(instanceId); // Only skip the settings write when the instance this dialog created is // still persisted. After a rolled-back failure the id is still ours, but // it has to be written again before the server will enroll it. const isOwnedHermesRetry = isOwnedHermesEnrollmentRetry({ driver, instanceId, createdHermesInstanceId: createdHermesIdentity?.instanceId ?? null, }) && createdHermesIdentity?.persisted === true; const nextMap = { ...settings.providerInstances, [brandedId]: nextInstance, }; try { setIsSaving(true); setSaveError(null); if (driver === "hermes") { if (environmentId === null) { throw new Error("Connect this browser to a T3 server before pairing Hermes."); } // The server rejects `create-enrollment` unless the instance already // exists in `providerInstances` with the Hermes driver, so the write // genuinely has to come first. Instead of enroll-first, the write is // registered as a pending rollback that the failure path — and the // close/unmount effects — undo, so a failed enrollment never leaves a // visible `instance-not-found` orphan in the model picker. if (!isOwnedHermesRetry) { const settingsResult = await updateServerSettings({ environmentId, input: { patch: { providerInstances: nextMap } }, }); if (settingsResult._tag === "Failure") { throw squashAtomCommandFailure(settingsResult); } pendingRollbackRef.current = { instanceId, written: nextInstance }; setCreatedHermesIdentity({ instanceId, nickname: label.trim(), persisted: true, }); } const enrollmentResult = await createHermesEnrollment({ environmentId, input: { instanceId: brandedId, nickname: createdHermesIdentity?.nickname ?? label.trim(), connectorUrl: hermesConnectorUrl, }, }); if (enrollmentResult._tag === "Failure") { const enrollmentError = squashAtomCommandFailure(enrollmentResult); // The server already deleted the tombstoned instance itself, so // there is nothing left for us to roll back — just rotate the nonce // so the next attempt uses a fresh, untainted id. if (isHermesInstanceRemovedError(enrollmentError)) { pendingRollbackRef.current = null; setCreatedHermesIdentity(null); setHermesIdentityNonce(randomUUID()); } else { await rollbackPendingHermesInstance(); setCreatedHermesIdentity((current) => current ? { ...current, persisted: false } : current, ); } throw enrollmentError; } // Enrollment succeeded: the instance is legitimately persisted now. pendingRollbackRef.current = null; setHermesConnectionState(null); setHermesEnrollment(enrollmentResult.value); toastManager.add({ type: "success", title: "Hermes instance added", description: "Run the one-time command to connect it.", }); setIsSaving(false); return; } updateSettings({ providerInstances: nextMap }); toastManager.add({ type: "success", title: "Provider instance added", description: `${driverOption.label} instance '${instanceId}' was added.`, }); onOpenChange(false); } catch (error) { const message = messageFromUnknownError(error); setSaveError(message); toastManager.add({ type: "error", title: "Could not add provider instance", description: message, }); } finally { setIsSaving(false); } }; /** * Mint a replacement one-time command for the instance already persisted by * this dialog session. The instance itself is untouched, so there is nothing * to roll back here — only the expired/revoked token is replaced. */ const handleRegenerateEnrollment = async () => { const identity = createdHermesIdentity; if (identity === null || environmentId === null) return; try { setIsSaving(true); setSaveError(null); const enrollmentResult = await createHermesEnrollment({ environmentId, input: { instanceId: ProviderInstanceId.make(identity.instanceId), nickname: identity.nickname, connectorUrl: hermesConnectorUrl, }, }); if (enrollmentResult._tag === "Failure") { const enrollmentError = squashAtomCommandFailure(enrollmentResult); if (isHermesInstanceRemovedError(enrollmentError)) { setCreatedHermesIdentity(null); setHermesIdentityNonce(randomUUID()); setHermesEnrollment(null); } throw enrollmentError; } setHermesConnectionState(null); setHermesEnrollment(enrollmentResult.value); setNowMillis(Date.now()); } catch (error) { const message = messageFromUnknownError(error); setSaveError(message); toastManager.add({ type: "error", title: "Could not create a new Hermes command", description: message, }); } finally { setIsSaving(false); } }; return (
Add provider instance Configure an additional provider instance — for example, a second Codex install pointed at a different workspace. {hermesEnrollment === null && createdHermesIdentity === null ? ( ) : null}
{hermesEnrollment && hermesWaitingState ? (

{hermesWaitingState.phase === "connected" ? "Hermes is connected" : "Waiting for Hermes"}

{hermesGatewayStatusGuidance(hermesWaitingState.statusLabelState)}

{hermesWaitingState.isPending ? ( ) : hermesWaitingState.phase === "connected" ? ( ) : null} {hermesGatewayStatusLabel(hermesWaitingState.statusLabelState)}
{hermesWaitingState.phase === "connected" ? (

The gateway dialled in and its persistent credential is stored on the Hermes host. You can close this dialog and start a thread against it.

) : (
  1. 1. Install the plugin on the Hermes host

    Run this from your T3 Code checkout on the machine running Hermes. It symlinks the plugin into ~/.hermes/plugins and enables it. T3 Code cannot do this for you — skip it only if you already installed the plugin there.

    {HERMES_PLUGIN_INSTALL_COMMAND}
  2. 2. Run the one-time connect command

    {hermesWaitingState.phase === "expired" ? (

    This command expired{" "} {formatHermesLastConnected(hermesEnrollment.expiresAt)}. Generate a new one below — the instance itself is unchanged.

    ) : (

    Expires {formatHermesLastConnected(hermesEnrollment.expiresAt)}.

    )}
    {hermesEnrollment.command}
  3. 3. Restart the gateway on that host

    This step is required — the plugin only dials T3 Code after the restart, so nothing is wrong while this panel still says{" "} {hermesGatewayStatusLabel(hermesWaitingState.statusLabelState)}. This page updates itself the moment Hermes connects.

    {HERMES_GATEWAY_RESTART_COMMAND}

    Without an installed service this runs in the foreground and keeps the terminal — that is expected. Use hermes gateway install to run it as a background service instead.

)} {hermesWaitingState.needsNewCommand ? (
) : null} {saveError ?

{saveError}

: null}

The persistent gateway credential is delivered directly to the plugin and is never shown in T3 Code.

) : (
Driver
setDriver(ProviderDriverKind.make(value))} aria-labelledby="add-instance-driver-label" className="grid grid-cols-1 gap-2 sm:grid-cols-2" > {DRIVER_OPTIONS.map((option) => { const IconComponent = option.icon; return ( {option.label} {option.badgeLabel ? ( {option.badgeLabel} ) : null} ); })} {COMING_SOON_DRIVER_OPTIONS.map((option) => { const IconComponent = option.icon; return ( {option.label} Coming Soon ); })}
Accent color
setAccentColor(event.target.value)} aria-label="Provider instance accent color" className="h-8 w-10 cursor-pointer rounded-xl border border-input bg-background p-0.5" />
{PROVIDER_ACCENT_SWATCHES.map((swatch) => { const selected = accentColor.toLowerCase() === swatch; return (
{accentColor ? ( ) : null}
Optional marker shown in the picker.
{driver === "hermes" && wizardStep === 2 ? ( ) : driverSettingsFields.length > 0 ? (
) : wizardStep === 2 ? (

This driver has no required configuration. You can add the instance now.

) : null} {saveError ?

{saveError}

: null} {createdHermesIdentity ? (

{createdHermesIdentity.persisted ? "The Hermes instance was added. Retry enrollment for this exact instance, or close this dialog and finish pairing from its Settings card." : "Nothing was left behind — the half-created instance was removed. Retry to create it again with the same identity."}

) : null}
)}
{hermesEnrollment ? ( ) : ( )} {hermesEnrollment ? null : wizardStep < ADD_PROVIDER_WIZARD_STEPS.length - 1 ? ( ) : ( )}
); }