import { useMemo, useState } from "react"; import { useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { toolSchemaAtom } from "../api/atoms"; import { ScopeId, ToolId, type EffectivePolicy, type ToolPolicyAction } from "@executor-js/sdk"; import { Badge } from "./badge"; import { Button } from "./button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "./dropdown-menu"; import { Markdown } from "./markdown"; import { SchemaExplorer } from "./schema-explorer"; import { ExpandableCodeBlock } from "./expandable-code-block"; import { CardStack, CardStackHeader, CardStackContent } from "./card-stack"; import { CopyButton } from "./copy-button"; import { ChevronRight, ChevronDownIcon } from "lucide-react"; import { cn } from "../lib/utils"; import { POLICY_ACTION_LABEL, POLICY_ACTIONS_IN_ORDER, POLICY_BADGE_VARIANT, POLICY_STATE_LABEL, } from "../lib/policy-display"; // Render the effective policy as a badge. User policies show the // matched pattern; plugin defaults read "Default: ". Silent for // the always-run plugin default — that's the safe state and the // header would just be noise. const policyBadgeFor = (policy: EffectivePolicy) => { if (policy.source === "plugin-default" && policy.action === "approve") { return null; } if (policy.source === "user") { return { variant: POLICY_BADGE_VARIANT[policy.action], title: `Matched policy: ${policy.pattern}`, text: `${POLICY_STATE_LABEL[policy.action]} · ${policy.pattern}`, className: "font-mono text-[10px]", }; } return { variant: "outline" as const, title: "No matching policy — plugin default applies", text: `Default: ${POLICY_STATE_LABEL[policy.action]}`, className: "text-[10px] text-muted-foreground", }; }; function EmptySection(props: { title: string; message: string }) { return ( {props.title}

{props.message}

); } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- const friendlyName = (name: string): string => { const leaf = name.split(".").pop() ?? name; return leaf .replace(/([a-z])([A-Z])/g, "$1 $2") .replace(/[_.-]/g, " ") .replace(/\b\w/g, (c) => c.toUpperCase()); }; const breadcrumbParts = (name: string): string[] => name.split(".").map((p) => p .replace(/([a-z])([A-Z])/g, "$1 $2") .replace(/[_-]/g, " ") .replace(/\b\w/g, (c) => c.toUpperCase()), ); // --------------------------------------------------------------------------- // ToolDetail // --------------------------------------------------------------------------- export function ToolDetail(props: { toolId: string; toolName: string; toolDescription?: string; scopeId: ScopeId; /** Resolved effective policy — user-authored or plugin-default, * unified into one shape. Surfaces in the header. */ policy?: EffectivePolicy; /** When provided, the policy badge becomes a dropdown trigger that * applies a user rule to this tool's exact id. */ onSetPolicy?: (pattern: string, action: ToolPolicyAction) => void; onClearPolicy?: (pattern: string) => void; }) { const toolContract = useAtomValue(toolSchemaAtom(props.scopeId, props.toolId as ToolId)); const [tab, setTab] = useState<"schema" | "typescript">("schema"); const data = useMemo(() => { if (!AsyncResult.isSuccess(toolContract)) return null; const v = toolContract.value; const definitions = Object.entries(v.typeScriptDefinitions ?? {}).map(([name, body]) => ({ name, code: String(body), })); return { inputSchema: v.inputSchema, outputSchema: v.outputSchema, inputTypeScript: v.inputTypeScript ? `type Input = ${v.inputTypeScript}` : null, outputTypeScript: v.outputTypeScript ? `type Output = ${v.outputTypeScript}` : null, definitions, }; }, [toolContract]); const crumbs = breadcrumbParts(props.toolName); const displayName = friendlyName(props.toolName); return (
{/* Header + tabs */}
{crumbs.length > 1 && (
{crumbs.slice(0, -1).map((part, i) => ( {i > 0 && } {part} ))}
)}

{displayName}

{props.toolDescription && (
{props.toolDescription}
)} {/* Tabs */}
{/* Content */}
{AsyncResult.match(toolContract, { onInitial: () =>
Loading…
, onFailure: () =>
Something went wrong
, onSuccess: () => tab === "schema" ? (
{data?.inputSchema ? ( ) : ( )} {data?.outputSchema ? ( ) : ( )}
) : (
{data?.inputTypeScript ? ( Input ) : ( )} {data?.outputTypeScript ? ( Output ) : ( )}
), })}
); } // --------------------------------------------------------------------------- // PolicyBadgeMenu — clickable header badge that opens the same // Always run / Require approval / Block / Clear menu the tree row uses. // Falls back to a plain Badge when no actions are provided. // --------------------------------------------------------------------------- function PolicyBadgeMenu(props: { toolName: string; policy?: EffectivePolicy; onSetPolicy?: (pattern: string, action: ToolPolicyAction) => void; onClearPolicy?: (pattern: string) => void; }) { const interactive = !!props.onSetPolicy; // The "Clear" affordance only makes sense when there's a user rule // pinned to this exact tool id — clearing a wildcard rule from a // single tool's detail header would silently affect siblings. const hasExactUserRule = props.policy?.source === "user" && props.policy.pattern === props.toolName; const currentAction = hasExactUserRule ? props.policy?.action : undefined; if (!interactive) { if (!props.policy) return null; const badge = policyBadgeFor(props.policy); if (!badge) return null; return ( {badge.text} ); } // Interactive trigger always renders, even when the effective policy // would otherwise be "silent" (auto-approve plugin-default), so the // user can click it to override. const badge = props.policy ? policyBadgeFor(props.policy) : null; const triggerLabel = badge?.text ?? "Set policy"; const triggerVariant = badge?.variant ?? "outline"; const triggerTitle = badge?.title ?? "Set policy"; const triggerClassName = badge?.className ?? "text-[10px] text-muted-foreground"; return ( {props.toolName} {POLICY_ACTIONS_IN_ORDER.map((action) => ( props.onSetPolicy?.(props.toolName, action)} > {POLICY_ACTION_LABEL[action]} {currentAction === action && ( ✓ )} ))} {hasExactUserRule && props.onClearPolicy && ( <> props.onClearPolicy?.(props.toolName)} className="text-muted-foreground" > Clear )} ); } // --------------------------------------------------------------------------- // Empty state // --------------------------------------------------------------------------- export function ToolDetailEmpty(props: { hasTools: boolean }) { return (

{props.hasTools ? "Select a tool" : "No tools available"}

{props.hasTools && (

Choose from the list to see what it does.

)}
); }