import { useMemo, useState } from "react"; import { Link } from "@tanstack/react-router"; import { useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { effectivePolicyFromSorted } from "@executor-js/sdk"; import { policiesOptimisticAtom, toolsAtom } from "../api/atoms"; import { useScope } from "../hooks/use-scope"; import { usePolicyActions } from "../hooks/use-policy-actions"; import { ToolTree, type ToolSummary } from "../components/tool-tree"; import { ToolDetail, ToolDetailEmpty } from "../components/tool-detail"; import { Button } from "../components/button"; import { Skeleton } from "../components/skeleton"; export function ToolsPage() { const scopeId = useScope(); const tools = useAtomValue(toolsAtom(scopeId)); const policies = useAtomValue(policiesOptimisticAtom(scopeId)); const policyActions = usePolicyActions(scopeId); const [selectedToolId, setSelectedToolId] = useState(null); const policyList = useMemo( () => (AsyncResult.isSuccess(policies) ? policies.value : []), [policies], ); const sortedPolicies = useMemo( () => [...policyList].sort((a, b) => { if (a.position < b.position) return -1; if (a.position > b.position) return 1; return a.id < b.id ? -1 : a.id > b.id ? 1 : 0; }), [policyList], ); const summaries: ToolSummary[] = useMemo(() => { if (!AsyncResult.isSuccess(tools)) return []; return tools.value.map( (t: { readonly id: string; readonly name: string; readonly pluginId: string; readonly description?: string; readonly requiresApproval?: boolean; }) => ({ id: t.id, // Tree path + saved pattern must be the canonical tool id // (`stripe_api.account.getAccount`), not the short `t.name` // which strips the source prefix and would never match at // resolution time. name: t.id, pluginKey: t.pluginId, description: t.description, policy: effectivePolicyFromSorted(t.id, sortedPolicies, t.requiresApproval), }), ); }, [tools, sortedPolicies]); const selectedTool = useMemo( () => summaries.find((t) => t.id === selectedToolId) ?? null, [summaries, selectedToolId], ); return (
{/* Header bar */}

Tools

{AsyncResult.isSuccess(tools) && ( {summaries.length} {summaries.length === 1 ? "tool" : "tools"} )}
{AsyncResult.match(tools, { onInitial: () => , onFailure: () =>
Failed to load tools
, onSuccess: () => summaries.length === 0 ? (

No tools registered

Add a source to start discovering tools.

) : (
void policyActions.set(pattern, action)} onClearPolicy={(pattern) => void policyActions.clear(pattern)} policies={sortedPolicies} />
{selectedTool ? ( void policyActions.set(pattern, action)} onClearPolicy={(pattern) => void policyActions.clear(pattern)} /> ) : ( 0} /> )}
), })}
); } function ToolsPageSkeleton() { return (
{Array.from({ length: 8 }).map((_, i) => (
))}
); }