988 lines · 41.3 KB · TypeScript
RawDownload
"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<string, unknown> = {};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<ProviderDriverKind>(DEFAULT_DRIVER_KIND);  const [label, setLabel] = useState("");  const [accentColor, setAccentColor] = useState<string>("");  const [instanceIdOverride, setInstanceIdOverride] = useState<string | null>(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<Record<string, Record<string, unknown>>>({});  // 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<HermesGatewayEnrollmentResult | null>(    null,  );  const [saveError, setSaveError] = useState<string | null>(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<HermesGatewayConnectionState | null>(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<string, unknown> | 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.`,      });

Lines 1–500 of 988.