Files
hermes-agent/apps/desktop/src/lib/tool-render-class.ts
T
Brooklyn Nicholson 6cd4793081 feat(desktop): render setup_mcp as an interactive consent card in the transcript
The card follows the approval bar's consent vocabulary (primary-tinted
action + ghost decline, ⌘⏎/Esc with clarify's focus-stand-down rule) on
clarify's widget shell. Install prefers the reviewed catalog entry (env
prompts inline, background installs polled to completion) and falls back
to the desktop suggestion directory via the validated add-server POST +
OAuth; success reloads live MCP tools before unblocking the agent so it
resumes with the tools it was just promised. Esc stays live mid-flight
as cancel — the abandoned flow aborts at its next poll and a post-write
cancel rolls the config entry back. Typing while the card is pending
declines it and sends normally (skipClarifyRequest's pattern), and the
request/tool.start rows merge on the server arg so reconnects can't
double-render the card.
2026-08-13 01:06:51 -05:00

47 lines
2.1 KiB
TypeScript

/**
* Which surface a tool call renders as.
*
* Two consumers have to agree on this and they sit on opposite sides of the
* app: the transcript decides what to draw, and the DOM render budget decides
* how much of the transcript to mount. Pricing a turn correctly means pricing
* what the grouping actually renders, so the classification lives on its own
* rather than inside either one.
*/
const FILE_EDIT_TOOL_NAMES = new Set(['edit_file', 'patch', 'write_file'])
/** Renders a diff — the deliverable of the turn, and the one card whose cost scales. */
export function isFileEditTool(toolName: string): boolean {
return FILE_EDIT_TOOL_NAMES.has(toolName)
}
// Tools that draw their own surface and must never be folded into a run's
// summary. Two kinds, for the same reason — the thing on screen IS the point:
//
// - File edits are the deliverable, not scaffolding. The diff is what the
// user reviews, so it stays visible at its place in the turn, live and
// settled, the way a PR shows its changes.
// - `clarify`, `image_generate` and `delegate_task` bypass ToolEntry to
// render their own markup: a question the user has to answer, an image
// they asked for, the several agents a fan-out is running.
// - `setup_mcp` is the same kind: an inline consent card the user has to
// act on. Folding it into a "Using 2 tools" summary hides the buttons.
//
// Everything else is ephemeral activity — reads, searches, commands — which is
// what a run summarizes and what the live ticker cycles through.
const CARD_TOOL_NAMES = new Set(['clarify', 'delegate_task', 'image_generate', 'setup_mcp'])
export function isCardTool(toolName: string): boolean {
return CARD_TOOL_NAMES.has(toolName) || isFileEditTool(toolName)
}
// Activity tools that render nothing at all: `todo` parts are hoisted to a
// dedicated panel above the message content, and a reaction's UI is the emoji
// landing on the bubble. Both still render when they FAIL, which is a bounded
// error row either way.
const SILENT_TOOL_NAMES = new Set(['react_to_message', 'todo'])
export function isSilentTool(toolName: string): boolean {
return SILENT_TOOL_NAMES.has(toolName)
}