opentui(v6): bash tool renderer — command + full output

This commit is contained in:
alt-glitch
2026-06-10 16:18:08 +05:30
parent 60cbc4c68b
commit 0f92a3cf63
5 changed files with 257 additions and 9 deletions
+74
View File
@@ -0,0 +1,74 @@
/**
* BashTool — renderer for the shell-ish tools `terminal`, `execute_code` and
* `process` (Epic 2.4). Collapsed: the COMMAND BEING INVOKED, verbatim, on one
* line (the shell truncates to width; expanding reveals the rest). Expanded:
* the full command (`$ `-prefixed, multi-line safe) and the FULL output, kept
* to the EXPANDED_MAX cap with the honest omitted / "+N more lines" notes from
* `logic/toolOutput.ts` (via the shared ToolOutputBlock).
*
* Arg keys verified against the Python tool schemas:
* terminal → `command` (tools/terminal_tool.py TERMINAL_SCHEMA)
* execute_code → `code` (tools/code_execution_tool.py)
* process → `action` (+ `session_id`) (tools/process_registry.py PROCESS_SCHEMA)
* Falls back to the gateway's one-line argsPreview when args weren't captured.
*/
import { createMemo, For, Show } from 'solid-js'
import type { ToolPartState } from '../../logic/store.ts'
import { truncate } from '../../logic/toolOutput.ts'
import { useTheme } from '../theme.tsx'
import { defaultSubtitle, resultLines, structuredArgs, ToolOutputBlock } from './defaultTool.tsx'
import type { ToolBodyProps, ToolRenderer } from './registry.tsx'
/** The verbatim invocation: terminal `command` / execute_code `code` /
* process `action [session_id]`; else the gateway's argsPreview. */
export function commandOf(part: ToolPartState): string {
const args = structuredArgs(part)
if (args) {
const cmd = args['command'] ?? args['code']
if (typeof cmd === 'string' && cmd.trim()) return cmd
const action = args['action'] // process: the verb is the invocation
if (typeof action === 'string' && action) {
const sid = args['session_id']
const sidText = typeof sid === 'string' || typeof sid === 'number' ? String(sid) : ''
return sidText ? `${action} ${sidText}` : action
}
}
return part.argsPreview ?? ''
}
/** Expanded body: the full `$ command`, then the full (capped) output. */
export function BashToolBody(props: ToolBodyProps) {
const theme = useTheme()
const command = createMemo(() => commandOf(props.part).replace(/\s+$/, ''))
return (
<box style={{ flexDirection: 'column', flexGrow: 1, minWidth: 0 }}>
<Show when={command()}>
<For each={command().split('\n')}>
{(line, i) => (
<box style={{ flexDirection: 'row', flexShrink: 0 }}>
{/* `$ ` prompt glyph (continuation lines indent under it) — chrome */}
<text selectable={false}>
<span style={{ fg: theme().color.accent }}>{i() === 0 ? '$ ' : ' '}</span>
</text>
{/* the command itself is copyable content */}
<text selectionBg={theme().color.selectionBg}>
<span style={{ fg: theme().color.text }}>{truncate(line, Math.max(1, props.width - 2))}</span>
</text>
</box>
)}
</For>
</Show>
<ToolOutputBlock part={props.part} width={props.width} label={Boolean(command())} />
</box>
)
}
export const bashRenderer: ToolRenderer = {
Body: BashToolBody,
// Collapsed never shows output (the header shows the command), so ANY output
// is hidden content worth expanding — as is a multi-line command.
expandable: part => resultLines(part).length > 0 || commandOf(part).includes('\n'),
// The command, verbatim, flattened to one line (the shell truncates to width).
subtitle: part => commandOf(part).replace(/\s+/g, ' ').trim() || defaultSubtitle(part)
}
+6 -1
View File
@@ -17,6 +17,7 @@
import type { Component } from 'solid-js'
import type { ToolPartState } from '../../logic/store.ts'
import { bashRenderer } from './bashTool.tsx'
import { defaultRenderer } from './defaultTool.tsx'
/** Props every tool Body receives: the part + usable content columns. */
@@ -40,7 +41,11 @@ export interface ToolRenderer {
const TOOLS: Record<string, ToolRenderer> = {
// delegate_task: default labeled fields + the Ink-parity monitor hint
// (ui-tui/src/components/thinking.tsx — "(/agents to monitor)").
delegate_task: { ...defaultRenderer, hint: () => '(/agents to monitor)' }
delegate_task: { ...defaultRenderer, hint: () => '(/agents to monitor)' },
// shell-ish tools (Epic 2.4): collapsed = the command verbatim; expanded = full output.
execute_code: bashRenderer,
process: bashRenderer,
terminal: bashRenderer
}
/** Resolve the renderer for a tool name (default = labeled-fields fallback). */