  // Browser-only agent adapter. The native command engine stays upstream-owned.
  const studioAgentRef = useRef({ snapshot: mcpSceneSnapshot, undo, redo, sketchActive, invalidateCadModifierSession, appendHistorySnapshot, syncProjectShapes });
  studioAgentRef.current = { snapshot: mcpSceneSnapshot, undo, redo, sketchActive, invalidateCadModifierSession, appendHistorySnapshot, syncProjectShapes };
  useEffect(() => {
    let disposed = false;
    let disconnect: (() => void) | undefined;
    const moduleUrl = "/studio/agent/endpoint.mjs";
    void import(/* webpackIgnore: true */ moduleUrl).then(({ connectEndpoint, revision }) => {
      if (disposed) return;
      disconnect = connectEndpoint({
        workshop: "3d",
        isBusy: () => projectInteractionActiveRef.current || studioAgentRef.current.sketchActive,
        beginBatch: () => {
          if (openGroupsRef.current.length || historyViewStateRef.current) throw new Error("Close group editing and history view before a build.");
          return {
            projectId: projectInfoRef.current.projectId,
            entry: structuredClone(editorHistoryEntry(shapesRef.current, selectedIdsRef.current, notesRef.current, placementWorkplaneRef.current)),
            history: historyRef.current.slice(), index: historyIndexRef.current,
          };
        },
        endBatch: (token: { projectId: unknown; entry: EditorHistoryEntry; history: EditorHistoryEntry[]; index: number }, success: boolean) => {
          if (token.projectId !== projectInfoRef.current.projectId) throw new Error("Project changed during build; refusing to overwrite another project.");
          if (!success) {
            studioAgentRef.current.invalidateCadModifierSession();
            shapesRef.current = token.entry.shapes;
            selectedIdsRef.current = token.entry.selectedIds;
            notesRef.current = normalizeNotes(token.entry.notes);
            setShapes(token.entry.shapes);setSelectedIds(token.entry.selectedIds);setNotes(notesRef.current);
          }
          historyRef.current = token.history;historyIndexRef.current = token.index;
          setHistory(token.history);setHistoryIndex(token.index);
          if (success) studioAgentRef.current.appendHistorySnapshot(shapesRef.current, selectedIdsRef.current);
          studioAgentRef.current.syncProjectShapes(shapesRef.current, true);
          const previousIds = new Set(token.entry.shapes.map(shape => shape.id));
          return { createdIds: shapesRef.current.filter(shape => !previousIds.has(shape.id)).map(shape => shape.id), selectedIds: selectedIdsRef.current, undoSteps: success && token.entry.fingerprint !== editorHistoryEntry(shapesRef.current, selectedIdsRef.current, notesRef.current, placementWorkplaneRef.current).fingerprint ? 1 : 0 };
        },
        getContext: async () => {
          const scene = studioAgentRef.current.snapshot();
          const { notice: _transientNotice, ...revisionScene } = scene;
          return { ok: true, workshop: "3d", ...scene, revision: await revision({
            scene: revisionScene, fingerprint: projectShapesFingerprint(shapesRef.current),
            history: historyIndexRef.current, historyView: historyViewStateRef.current,
          }) };
        },
        execute: async (action: string, params: Record<string, unknown>) => {
          if (action === "undo" || action === "redo") {
            if (historyViewStateRef.current) throw new Error("Close history view first.");
            studioAgentRef.current[action]();
            return { applied: action };
          }
          if (!executeMcpCommandRef.current) throw new Error("Editor is still loading");
          return executeMcpCommandRef.current({ id: crypto.randomUUID(), action: action as LayerlingMcpCommand["action"], params, createdAt: Date.now() });
        },
        settle: async () => {
          // React effects and undo restoration can settle after the next paint.
          let previous = "";
          for (let i = 0; i < 10; i += 1) {
            await new Promise<void>(resolve => setTimeout(resolve, 50));
            const now = JSON.stringify([projectShapesFingerprint(shapesRef.current), selectedIdsRef.current, historyIndexRef.current]);
            if (now === previous) break;
            previous = now;
          }
        },
      });
    }).catch(error => console.warn("SkaperStudio agent adapter unavailable", error));
    return () => { disposed = true; disconnect?.(); };
  }, []);

