useSubagentCreation.tsx2.6 KBView on GitHub

Introduced 1 production defect in 180 days, median 11 days to fix.

'use client';

import { useCallback, useRef, useState } from 'react';
import { useNavigate } from 'react-router';
import type { Editor } from '@tiptap/core';
import { SubagentCreateDialog, type CreatedSubagent } from './SubagentCreateDialog';
import { insertSubagentRef, scopeAtCursor } from './playbookExtensions';
import { brainDocumentPath } from '@/modules/brain/brain-routes';

interface UseSubagentCreationOptions {
  /** User AOP id (user scope, and to resolve the org link for org scope). */
  aopId?: string;
  /** Org AOP id when known (composite passes the loaded orgAopId). */
  orgAopId?: string;
  /** Scope used when the cursor isn't inside a scope card (single-scope editors). */
  baseScope?: 'user' | 'org';
  /** The playbook's owner, forwarded to the create mutation. */
  ownerUserId?: string | null;
  /** Persist the current playbook before navigating away (so the inserted ref survives). */
  save?: () => Promise<unknown>;
}

/**
 * Wires the playbook editor's "Subagent" `/` and `#` option to the create flow.
 * `onCreateSubagent` captures the editor + resolves the target scope (the org/user
 * card the cursor is in, else `baseScope`) and opens the name dialog. On submit the
 * dialog creates the subagent doc in the correct folder, then we insert a `<ref>`
 * (auto-wrapped in a trigger when needed), persist the playbook via `save`, and
 * navigate to the new doc in the same tab.
 */
export function useSubagentCreation({
  aopId,
  orgAopId,
  baseScope = 'user',
  ownerUserId,
  save,
}: UseSubagentCreationOptions) {
  const navigate = useNavigate();
  const [open, setOpen] = useState(false);
  const [scope, setScope] = useState<'user' | 'org'>(baseScope);
  const editorRef = useRef<Editor | null>(null);

  const onCreateSubagent = useCallback(
    (editor: Editor) => {
      editorRef.current = editor;
      setScope(scopeAtCursor(editor) ?? baseScope);
      setOpen(true);
    },
    [baseScope],
  );

  const onCreated = useCallback(
    async (result: CreatedSubagent) => {
      if (editorRef.current) insertSubagentRef(editorRef.current, result.documentId);
      try {
        await save?.();
      } catch {
        // Best-effort — still navigate to the new subagent.
      }
      setOpen(false);
      navigate(brainDocumentPath(result.documentId, ownerUserId ?? undefined));
    },
    [navigate, save, ownerUserId],
  );

  const subagentDialog = (
    <SubagentCreateDialog
      open={open}
      onOpenChange={setOpen}
      scope={scope}
      aopId={aopId}
      orgAopId={orgAopId}
      targetUserId={ownerUserId ?? undefined}
      onCreated={onCreated}
    />
  );

  return { onCreateSubagent, subagentDialog };
}