// Tool registry — full list of every first-party tool exposed to the LLM.
// Mirrors agents/src/brain/tools/index.ts :: FIRST_PARTY_TOOLS.
function ToolsPage() {
  const shell = useShell();
  const tocItems = [
    { id: 'overview', label: 'Overview' },
    { id: 'gating', label: 'Role gating' },
    { id: 'file-io', label: 'File I/O' },
    { id: 'web', label: 'Web' },
    { id: 'shell', label: 'Shell' },
    { id: 'memory', label: 'Memory & search' },
    { id: 'media', label: 'Media' },
    { id: 'channels', label: 'Channels' },
    { id: 'sessions', label: 'Sessions' },
    { id: 'multi-agent', label: 'Multi-agent' },
    { id: 'ops', label: 'Ops' },
    { id: 'skills', label: 'Skills' },
    { id: 'plugins', label: 'Plugin tools' },
  ];

  const T = ({ name, both, admin, desc }) => (
    <tr>
      <td><code style={{fontSize:'0.9em', whiteSpace:'nowrap'}}>{name}</code></td>
      <td style={{textAlign:'center'}}>{both || admin ? 'Yes' : '—'}</td>
      <td style={{textAlign:'center'}}>{both ? 'Yes' : '—'}</td>
      <td>{desc}</td>
    </tr>
  );

  const TableHead = () => (
    <thead><tr>
      <th style={{width:'22%'}}>Tool</th>
      <th style={{width:'10%', textAlign:'center'}}>Admin</th>
      <th style={{width:'10%', textAlign:'center'}}>CS</th>
      <th>Description</th>
    </tr></thead>
  );

  return (
    <div className="app">
      <Topbar section="docs" theme={shell.theme} setTheme={shell.setTheme}
              onSearch={() => shell.setSearchOpen(true)}
              onMenuToggle={() => shell.setMobileMenuOpen(true)} />
      <div className="main">
        <Sidebar activeId="tools"
                 mobileOpen={shell.mobileMenuOpen}
                 onMobileClose={() => shell.setMobileMenuOpen(false)} />
        <article className="content">
          <div className="crumbs">
            <a href="index.html">Docs</a>
            <span className="sep">/</span>
            <a href="#">Concepts</a>
            <span className="sep">/</span>
            <span>Tool registry</span>
          </div>

          <div className="eyebrow">Concepts · ~28 tools</div>
          <h1 className="h1">Tool <em>registry.</em></h1>
          <p className="lede">
            Every first-party tool the LLM can invoke, grouped by domain. Each tool declares an
            agent allowlist — the admin agent sees the full set; the customer-facing (CS) agent
            sees a read-only + message subset. Plugin tools layer on top per tenant.
          </p>

          <h2 id="overview" className="h2">Overview</h2>
          <p>
            The registry lives at <code>agents/src/brain/tools/</code>, one file per tool. The
            aggregator at <code>index.ts</code> exports <code>FIRST_PARTY_TOOLS</code> and produces
            the two shapes the brain runner needs: tool schemas for the LLM, and a dispatch map
            of handlers. Adding a tool is a single-file change — export a <code>Tool</code> object,
            add it to the array. No brain changes.
          </p>

          <h2 id="gating" className="h2">Role gating</h2>
          <p>
            A thin helper <code>adminOnly(tool)</code> sets <code>agents: ["admin"]</code> on any
            tool definition. The runner filters the registry by the current agent before sending
            schemas to the LLM, so a customer talking to the CS agent never even sees the
            privileged capabilities:
          </p>
          <ul>
            <li><strong>Admin-only:</strong> <code>write</code>, <code>edit</code>, <code>apply_patch</code>, <code>bash</code>, <code>exec</code>, <code>update_plan</code>, <code>image_generate</code>, <code>sessions_spawn</code>, <code>sessions_send</code>, <code>subagents</code>, <code>cron</code>, <code>process</code></li>
            <li><strong>Both agents:</strong> read-only file ops, web, memory/search, <code>pdf</code>, <code>message</code>, session listings, <code>agents_list</code>, <code>load_skill</code>, <code>sessions_yield</code></li>
          </ul>
          <Callout type="note" title="Why gate at the schema level">
            CS restrictions are enforced by omission — the LLM can't call a tool it doesn't know
            exists. This is more robust than string-matching tool names at handler time and keeps
            the LLM's attention focused on the subset relevant to its role.
          </Callout>

          {/* FILE I/O */}
          <h2 id="file-io" className="h2">File I/O</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="read" both desc="Read a file from the tenant workspace. Returns line-numbered content (up to 2000 lines by default; use offset / limit for large files)." />
              <T name="ls" both desc="List directory contents inside the tenant workspace. Sorted, with trailing '/' for directories." />
              <T name="find" both desc="Find files by glob (e.g. '*.md', '**/*.ts', 'skills/**/SKILL.md'). Skips node_modules and .git." />
              <T name="grep" both desc="Search file contents by regex (or literal). Returns 'path:line: content' matches with optional context lines and glob filters." />
              <T name="write" admin desc="Create or overwrite a file in the tenant workspace. Creates parent directories as needed." />
              <T name="edit" admin desc="Edit a file by exact-string replacement. Fails if old_string is not unique — add context or pass replace_all: true." />
              <T name="apply_patch" admin desc="Apply a multi-hunk patch in '*** Begin Patch' format. Supports Add File, Delete File, Update File, and Update File + Move to." />
            </tbody>
          </table>
          </div>

          {/* WEB */}
          <h2 id="web" className="h2">Web</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="web_search" both desc="Search the public web. Returns ranked results (title, URL, snippet). Provider is per-tenant: Tavily, Brave, or Gemini." />
              <T name="web_fetch" both desc="Fetch a URL and return its body as readable text. HTML is stripped to visible text (links kept inline); response capped." />
            </tbody>
          </table>
          </div>

          {/* SHELL */}
          <h2 id="shell" className="h2">Shell</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="bash" admin desc="Run a host command via the tunder dispatcher (tunder <noun> <verb> [--arg=value]). Used for tenant-level operations without shell injection risk." />
              <T name="exec" admin desc="Run a shell command in an ephemeral sandbox container. Tenant filesystem at /workspace, network disabled by default. Returns {exitCode, stdout, stderr, durationMs}." />
            </tbody>
          </table>
          </div>

          {/* MEMORY */}
          <h2 id="memory" className="h2">Memory & search</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="memory_query" both desc="Hybrid semantic + keyword search over the tenant's memories. Returns {id, key, value, category, score, source}." />
              <T name="faq_search" both desc="Hybrid search over the tenant's FAQ corpus. Returns {id, question, answer, category, score, source}. For support-style questions." />
              <T name="session_status" both desc="Return the current session's metadata — message count, last updated, role breakdown. Useful to decide whether to summarize." />
              <T name="update_plan" admin desc="Write or replace the session's plan — ordered steps with status (pending / in_progress / done / skipped). Persisted to the workspace." />
            </tbody>
          </table>
          </div>

          {/* MEDIA */}
          <h2 id="media" className="h2">Media</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="pdf" both desc="Extract text from a PDF file in the tenant workspace. Uses pdftotext via the sandbox. Returns plain text up to max_chars (default 50000)." />
              <T name="image_generate" admin desc="Generate an image from a text prompt via the configured provider (Azure DALL-E). Returns the saved file path(s)." />
            </tbody>
          </table>
          </div>

          {/* CHANNELS */}
          <h2 id="channels" className="h2">Channels</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="message" both desc="Send a text message to a contact on a channel (today: telegram, whatsapp_web). Returns {success, message_id?}." />
            </tbody>
          </table>
          </div>

          {/* SESSIONS */}
          <h2 id="sessions" className="h2">Sessions</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="sessions_list" both desc="List the tenant's sessions (most-recent-first). Optional filter by agent (admin / cs)." />
              <T name="sessions_history" both desc="Read a session's messages as a compact transcript. Tool calls and results are summarized for catching up on long sessions." />
              <T name="sessions_spawn" admin desc="Hand a task off to another agent. Returns {session_id, output} from the spawned agent's first turn." />
              <T name="sessions_send" admin desc="Send a follow-up message to an existing agent session. Returns {session_id, output} from that agent's response turn." />
              <T name="sessions_yield" both desc="End the current agent turn with a final output. Use when completing a delegated task or signaling 'here is my answer, done'." />
              <T name="subagents" admin desc="Run N subagents in parallel (max 5), one per task. Each task is {agent, message}. Returns [{agent, output}]." />
            </tbody>
          </table>
          </div>

          {/* MULTI-AGENT */}
          <h2 id="multi-agent" className="h2">Multi-agent</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="agents_list" both desc="List the agents this tenant has configured. Returns [{id, description, tools?}]." />
            </tbody>
          </table>
          </div>

          {/* OPS */}
          <h2 id="ops" className="h2">Ops</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="cron" admin desc="Manage scheduled tasks. Actions: list, add (name + schedule + command), remove, pause, resume. Standard cron expressions." />
              <T name="process" admin desc="Manage long-running sandbox processes. Actions: start (command), list (session_id or all), kill (session_id)." />
            </tbody>
          </table>
          </div>

          {/* SKILLS */}
          <h2 id="skills" className="h2">Skills</h2>
          <div style={{overflow:'auto', margin:'12px 0 28px'}}>
          <table className="doc-table"><TableHead />
            <tbody>
              <T name="load_skill" both desc="Load the full SKILL.md body of a specific skill by name. Progressive disclosure — skill names + one-line descriptions are always in the system prompt; full bodies only load on demand." />
            </tbody>
          </table>
          </div>

          <h2 id="plugins" className="h2">Plugin tools</h2>
          <p>
            Per-tenant plugins extend the registry via{' '}
            <code>api.registerTool(accountId, tool)</code>. A plugin tool declares the same{' '}
            <code>Tool</code> shape as a first-party tool (name, description, input schema,
            handler) and can also set an <code>agents</code> allowlist. First-party tools win on
            name collisions — a plugin cannot silently shadow a core tool.
          </p>
          <Callout type="info" title="Surfaced on the next turn">
            Registering a plugin tool for an accountId is an in-memory map insert. The next time
            that tenant's agent assembles its tool schemas, the new tool appears. No process
            restart.
          </Callout>

          <Feedback />
          <PageFoot
            prev={{ label: 'Architecture', href: 'guide.html' }}
            next={{ label: 'Message flow', href: 'flow.html' }}
          />
        </article>
        <TOC items={tocItems} />
      </div>
      <SearchOverlay open={shell.searchOpen} onClose={() => shell.setSearchOpen(false)} />
      <TweaksPanel visible={shell.tweaksVisible} theme={shell.theme} setTheme={shell.setTheme} />
    </div>
  );
}
ReactDOM.createRoot(document.getElementById('root')).render(<ToolsPage />);
