본문으로 건너뛰기
cccskills
무료GitHub에 공개

create-tool-renderer

Custom chat display for an MCP or agent tool's result, DB-loaded from tool_ui by default. Use when adding or customizing a tool visualization, making a tool result look good, rendering MCP tool output, or authoring a renderer for a tool that shows the generic view.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md14.3 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Create Tool Renderer

Make a tool call render as a first-class, beautiful surface instead of the generic JSON view. Two paths — default to the DB path.

PathWhenWhere the code lives
A — DB renderer (default, ~90%)Any tool. Author once, renders everywhere, no repo deploy.tool_ui table (runtime-compiled)
B — Hardcoded renderer (escape hatch, ~10%)A genuinely interactive/heavy widget that needs full repo imports.../aidream/apps/shared/chat/src/tool-call-visualization/renderers/ + registry

Resolution order: in-code registry → DB renderer → generic. A DB renderer only renders for a tool with NO in-code registry entry. Architecture: ../aidream/apps/shared/chat/src/tool-call-visualization/FEATURE.md.

Cardinal rule: HIDE NOTHING

Every field in the result surfaces somewhere. Inline = the key info at a glance (truncation OK with a "view all"); overlay = every field, nested object, source, metadata (long text collapses with "Show more", never omitted).

Step 0 — classify the data, THEN pick the form

Before writing any UI, classify the tool's result. This decision is the design:

  1. Known + pretty (the shape is documented/stable AND the content is human-meaningful — a file listing, a skill, a document, questions & answers, a search hit list) → use the canonical card: build on ToolResultCard (renderers/_shared-entity/ToolResultCard.tsx) and register with chrome: "card". The shell then renders your card DIRECTLY — no folded glyph line, no duplicate icon/label. The card gives you the ratified grammar for free: full-width header (tinted icon · title · quiet sub), click-anywhere toggle, seamless attached body, bordered rounded-xl Open dropdown (your tool-specific items + the canonical Window Panel · Tool Admin pair). Thread the shell props through:

    const { expanded, onToggleExpanded, onOpenWindowPanel, onOpenOverlay } = props;
    <ToolResultCard icon={LibraryBig} iconClassName="text-violet-600 dark:text-violet-400"
      title="Found 2 skills" sub={`“${query}”`}
      expanded={expanded} onToggleExpanded={onToggleExpanded}
      onOpenWindowPanel={onOpenWindowPanel && (() => onOpenWindowPanel())}
      onOpenOverlay={onOpenOverlay && (() => onOpenOverlay())}>
      {/* body rows */}
    </ToolResultCard>
    

    Never hand-roll this header. Reference implementations: renderers/skill/SkillInline.tsx, renderers/fs/FsInline.tsx. Artifact-producing tools (working document, note) use ArtifactResultBar instead — same grammar, kind-registry driven (registry/toolArtifact.ts).

  2. Unknown or ugly (freeform blobs, DB rows, arbitrary JSON) → NO card, NO frame. Render a FLUSH body (no border, no background, no padding box) — the shell's folded line is the only chrome. Lean on the result-field library (ResultValue and friends). Reference: renderers/sql/SqlInline.tsx.

Density follows intent. A lookup/search shows WHAT WAS FOUND — one line per hit, no descriptions. An adopt/get/read shows the detail. Never turn a one-line item into five lines.

Never render (full payload always stays in Tool Admin / Raw):

  • UUIDs or UUID lists — ResultValue collapses them (IdListChip / ShortId); never list them out.
  • Raw enum reprs (SklSkillType.REFERENCE) — humanize (humanizeEnumValue, result-fields/shape.ts).
  • Epoch timestamps, true/false chips, byte sizes on directories — humanize or drop.
  • Empty fields — drop the row; no "No result returned" placeholders.
  • The shell line's info again — no repeated intent line, count, or icon in the body.
  • Icon backgrounds — a colored icon changes TEXT color only, never a tile/chip behind it.

Consecutive uniform runs consolidate. N back-to-back calls of the same known-pretty tool = ONE card (header + one row per call, rows expand in place), not a batch line over N cards. Wire it in components/ToolCallBatch.tsx (see the fs_list → FsBatchCard branch).

The contract (BOTH paths)

A renderer is a React component taking ToolRendererProps (from @ai-matrx/chat/tool-call-visualization/types):

interface ToolRendererProps {
  entry: ToolLifecycleEntry;
  events?: ToolEventPayload[];
  onOpenOverlay?: (initialTab?: string) => void;
  onOpenWindowPanel?: (initialTab?: string) => void;
  toolGroupId?: string;
  isPersisted?: boolean;
  conversationId?: string;
}

entry is the data (from @ai-matrx/chat/agents/types/request.types): toolName (registry key), status (started|progress|step|result_preview|completed|error), arguments (input), result (final output — object, JSON string, or null), latestMessage, errorMessage, events. Terminal = status === "completed" || "error". Drive UI from entry.status, never from array shape — a running tool with no result yet is valid.

The shell owns the collapsed row (label from tool_ui.display_name / registry, subtitle, chevron, overlay/window buttons). Your component renders only the expanded body — no duplicate title row, no green-check/red-X status icons (state is conveyed by tense + shimmer on the row).


Path A — DB renderer (default)

Agent-authored component code stored in tool_ui, fetched by tool_name + surface, compiled at runtime by @ai-matrx/code-runtime (db-renderer/ → compileStoredComponent in lib/code-runtime/compile-stored.ts). This is how most tools should be customized: no repo deploy, works for agent- and user-authored renderers, scales to every platform.

The component

A single self-contained default export. Import lines are stripped — identifiers come from the sandbox scope, so you can't import helpers from the repo:

import { Folder, FileText } from "lucide-react"; // (stripped; icons come from scope)

export default function FsListRenderer({ entry }) {
  // Inline your OWN helpers — `_shared`, `@/lib/*`, etc. are NOT in scope.
  function asObj(x) {
    if (x && typeof x === "object") return x;
    if (typeof x === "string") { try { return JSON.parse(x); } catch (e) { return {}; } }
    return {};
  }
  const out = asObj(entry && entry.result);          // result may be a JSON string
  const entries = Array.isArray(out.entries) ? out.entries : [];
  if (entry && entry.status !== "completed" && entries.length === 0)
    return <div className="type-body text-muted-foreground">Listing…</div>; // streaming state
  if (!entries.length) return <div className="type-body text-muted-foreground">Empty.</div>;
  return (
    <div className="overflow-hidden rounded-md border border-border">
      {entries.map((e, i) => (
        <div key={(e && e.path) || i} className="flex items-center gap-2 px-2.5 py-1.5">
          {e.is_dir ? <Folder className="h-4 w-4 text-primary" /> : <FileText className="h-4 w-4 text-muted-foreground" />}
          <span className="truncate type-body text-foreground">{e.name}</span>
        </div>
      ))}
    </div>
  );
}

Sandbox scope (what you CAN use)

Set by allowed_imports. The scope registry is the package's listScopeEntries() (@ai-matrx/code-runtime/scope); the app's own modules are in lib/code-runtime/stored-scope.ts. New code should prefer @ai-matrx/design-system/controls; @/components/ui/* are legacy aliases kept while stored code uses them. Do not assume anything outside the registry:

  • react — always: React, useState, useEffect, useMemo, useCallback, useRef.
  • lucide-react — always when listed: ALL icons by name (a missing icon name renders a safe placeholder, never a crash).
  • Optional UI when listed: @/components/ui/{button,input,textarea,card,label,select,slider,switch,tabs}, @/components/MarkdownStream (as MarkdownStream/Markdown).
  • Forbidden: arbitrary npm, fetch/network, the repo's _shared helpers, @/lib/*. Inline what you need. Plain JS only (the sandbox runs JS — TS types are fine in source but carry no runtime guarantee).

Default allowed_imports: ["react", "lucide-react"]. Add a UI path only if you use it.

The tool_ui row

ColumnRequiredMeaning
tool_name✓Exact backend tool name (entry.toolName).
surface_name✓MUST be matrx-default/default (WEB_TOOL_UI_SURFACE, db-renderer/surface.ts) — the only surface the web app reads.
display_name✓Collapsed-line label (e.g. "Weather", not "Travel Get Weather").
inline_code✓The component above.
header_subtitle_codeexport default function(entry, events) { return string } — collapsed-line subtitle. Omit and the shell shows the most informative arg (path/command/city/key/query/…).
overlay_codeOptional richer full-screen variant; defaults to inline.
results_labelNoun for the result tab (e.g. "entries").
allowed_imports✓Default ["react","lucide-react"].
contract_version✓2.
is_active✓true.

A DB renderer is fully self-describing: display_name → label, header_subtitle_code → subtitle, inline_code → body.

Three ways to author (pick one)

  1. Admin UI — /administration/mcp-tools/[toolId]/ui: Generate tab (AI generator, prompt at features/tool-call-visualization/admin/tool-ui-generator-prompt.ts), Edit Code tab (manual + live compile errors), Preview tab (renders against tool_test_sample fixtures). Saves via POST /api/admin/tool-ui-components (defaults to the correct surface).
  2. The renderer-author agent — the AI Matrx agent specialized for this (invoke via the agent MCP / agent_run). Hand it the tool name + a sample result; it writes the row.
  3. A seed migration — idempotent INSERT … ON CONFLICT (tool_name, surface_name) DO UPDATE into tool_ui (the reference set in migrations/tool_ui_db_renderer_examples*.sql is the template). Apply it with pnpm db:apply migrations/<name>.sql (the ONE path; it records the ledger row itself).

Verify (DB path)

  • Gallery — add a fixture to app/(dev)/demos/tool-viz/result-fields/page.dev.tsx (DB_RENDERER_ENTRIES) and load /demos/tool-viz/result-fields → "DB-loaded renderers". This is the real path (fetch → compile → render).
  • Real chat — run the tool; it renders inline. Reload to confirm the persisted path.
  • If it falls back to generic: check the tool has NO in-code registry entry (registry wins), surface_name = matrx-default/default, is_active = true, and the code compiles (admin Edit Code tab shows compile errors).

Path B — Hardcoded renderer (escape hatch)

Only when the tool needs full repo imports or heavy interactivity. Full power, but ships in the bundle and needs a deploy.

../aidream/apps/shared/chat/src/tool-call-visualization/renderers/<kebab-tool-name>/
├── <Tool>Inline.tsx    # required — "use client"; ToolRendererProps
├── <Tool>Overlay.tsx   # optional — defaults to Inline
└── index.ts            # barrel

Here you CAN import the shared helpers from @ai-matrx/chat/tool-call-visualization/renderers/_shared (resultAsObject, collectMessages, filterStepEvents, getArg, isTerminal, isSuccess) and anything else in the repo. Register in ../aidream/apps/shared/chat/src/tool-call-visualization/registry/registry.tsx:

import { ToolInline, ToolOverlay } from "../renderers/<tool-name>";
// inside toolRendererRegistry:
"<exact_tool_name>": {
  toolName: "<exact_tool_name>",
  chrome: "card",                       // known-pretty tools ONLY — shell renders your ToolResultCard directly
  displayName: "Human Readable Name",
  phaseLabels: { running: "Doing the thing", complete: "Did the thing", errorPrefix: "Failed to do the thing" },
  resultsLabel: "Results",
  InlineComponent: ToolInline,
  OverlayComponent: ToolOverlay,        // optional
  keepExpandedOnStream: true,           // optional — open expanded while streaming
  getHeaderSubtitle: (entry) => getArg<string>(entry, "query") ?? null,
},

The overlay renders inside the Results tab only — never render your own header; use getHeaderSubtitle / getHeaderExtras.

Reference examples (read the registry entry for the closest shape first): renderers/skill/ + renderers/fs/ (known-pretty ToolResultCard + chrome: "card"), renderers/sql/ (unknown data, flush body), renderers/search/ (live conveyor → persistent view), renderers/news-api/ (clean result).


Styling (BOTH paths)

Semantic Tailwind tokens only — no hex/HSL, no blue-500. Lucide icons only, no emoji.

PurposeClass
Primary / links / activetext-primary, bg-primary, border-primary
On-primary texttext-primary-foreground
Body / secondarytext-foreground / text-muted-foreground
Card / page / subtle bgbg-card / bg-background / bg-muted
Bordersborder-border
Success / warn / errortext-success / text-warning / text-destructive

Cards: bg-card rounded-md border border-border. Opacity modifiers OK (bg-primary/10). External links: target="_blank" rel="noopener noreferrer" + Lucide ExternalLink.

Final checklist

  • Classified the data (Step 0): known-pretty → ToolResultCard + chrome: "card"; unknown/ugly → flush body. No hand-rolled headers.
  • No UUIDs, raw enums, epochs, boolean chips, empty-field rows, or shell-line repetition in the body.
  • Drives UI from entry.status; handles the streaming (not-yet-complete) state.
  • Parses entry.result defensively (string-or-object); result / arguments / errorMessage surfaced where relevant.
  • No duplicate title row / status icon on the slim line (the shell owns it).
  • DB path: only sandbox-scope identifiers used; helpers inlined; row on surface_name = matrx-default/default, contract_version = 2, is_active = true; display_name set; verified in the gallery.
  • Hardcoded path: barrel + registry entry under the exact tool name; getHeaderSubtitle/getHeaderExtras instead of a custom header.
  • Semantic tokens; dark mode implicit; empty + error states render; lints clean.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Admin debug system wiring for the floating AdminIndicator's live debug data and Copy Full Context. Use when adding debug visibility to a route or feature, wiring useDebugContext or the debug panel, capturing console errors, or enabling the copy-context workflow for a page.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

armanisadeghi/ai-matrx32026년 10월 19일 업데이트

Compact two-icon Copy / Copy-for-AI controls (components/agent-copy). Use when adding copy buttons to a row, card, list, or record; merging duplicate Copy/JSON/AI controls; continuing the copy rollout; or writing a Copy-for-AI payload. NOT for markdown content actions (use rich-document-actions).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

armanisadeghi/ai-matrx32026년 10월 19일 업데이트

Disclosing a surface's existing fixed AI jobs in the shell's top Agents menu. Use when a page, panel, overlay, or window already runs a mandate behind a button, assist, automatic action, or mode; when the agent-disclosure guard names a file; or during a surface check.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

armanisadeghi/ai-matrx32026년 10월 19일 업데이트

The watch-fix-rerun method for making an existing platform agent or automated process efficient and correct: baseline its ledger, run one unit yourself, fix the class behind every wasted call, rerun, record. Use when asked to improve, optimize, watch, or 'make efficient' an agent, a sandbox session, a sync job, or any recurring automated process.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

armanisadeghi/ai-matrx32026년 10월 19일 업데이트

Redux state for agent execution and firing agent shortcuts. Use when editing features/agents/redux/, building agent UI, creating a conversation, touching assembleRequest or NDJSON stream state, wiring agent-state selectors, adding a per-conversation capability, or triggering a shortcut from a button, menu, or code (useShortcutTrigger, launchAgentExecution).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

armanisadeghi/ai-matrx32026년 10월 19일 업데이트

The provision for a call site: the exhaustive menu of values that place in the code can realistically produce. Use before creating or fixing a mandate or agent that reasons about more than its own input, when deciding whether an agent can answer at all with what it is sent, or when its output 'looks right' but nobody checked it could be. NOT for building the agent (use create-agent).

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

armanisadeghi/ai-matrx32026년 10월 19일 업데이트

armanisadeghi의 스킬 모두 보기

이 스킬의 문제 신고