Skip to content

Tool-call card

In review
Live · web
Large 2.110:24 AM

Lifecycle: running · success (params + output) · error · diff (tab a header, Enter / Space)

file_path
app/src/main/.../net/SseClient.kt
limit
40
Output
// SseClient.kt: the reconnect loop
suspend fun reconnect(attempt: Int) {
delay(nextDelay(attempt))
}
command
./gradlew compileDebugKotlin
Error
e: Backoff.kt:14:18 unresolved reference: Random
> Task :app:compileDebugKotlin FAILED
BUILD FAILED in 3s
file_path
app/src/main/.../net/Backoff.kt
Backoff.kt+2-1
fun nextDelay(attempt: Int): Long =
minOf(15_000, 1_000 shl attempt)
minOf(30_000, 1_000 shl attempt)
.plus(Random.nextLong(250))

Families: the FILLED icon tile tints by tool family (categorical, never the brand cyan)

Copy fired 0 times · tab a card header to expand, tab to its Copy button. The island is live.

// Web: React, consuming @dizyx/nockerl-tokens. (Web is the laggard platform. Spec + live demo,
// not yet exported from @dizyx/nockerl-react; promotion is tracked.)
import { NockerlIcon } from '@dizyx/nockerl-react';
// `icon` is a ReactNode glyph, not a name: pass the inline SVG node so the tile can tint
// it via currentColor. These paths mirror the Compose `toolIcon` mapping.
const IconTerminal = <NockerlIcon path="m5 8 4 4-4 4M12 16h6" />;
const IconFile = <NockerlIcon path="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6M8 13h8M8 17h6" />;
const IconEdit = <NockerlIcon path="M11 4H5a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h13a2 2 0 0 0 2-2v-6M18.5 2.5a2.1 2.1 0 0 1 3 3L12 15l-4 1 1-4z" />;
export function Turn() {
return (
<>
{/* A running shell-fs call: a pulsing dot + elapsed, no body to disclose yet. */}
<ToolCallCard name="$ ./gradlew test" family="shell-fs" status="running" elapsedSeconds={6} icon={IconTerminal} />
{/* A finished Read: collapsible key/value params + a recessed code/text well. */}
<ToolCallCard
name="Read SseClient.kt"
family="shell-fs"
status="success"
elapsedSeconds={0.4}
icon={IconFile}
params={[{ label: 'file_path', value: 'net/SseClient.kt' }, { label: 'limit', value: '40' }]}
output={{ lines: readSnippet }}
/>
{/* An Edit: the output renders as an inline unified diff instead of a code well. */}
<ToolCallCard
name="Edit Backoff.kt"
family="shell-fs"
status="success"
icon={IconEdit}
diff={{ path: 'net/Backoff.kt', lines: editDiff }}
/>
</>
);
}

Harness failure block (a view, no new grammar)

Section titled “Harness failure block (a view, no new grammar)”

A harness failure is the whole agent run dying (a crashed session, a gateway 500, a budget kill), bigger than one tool error. It was settled deliberately as a thin composition of ratified parts, not a component:

  1. The failure banner. The ratified failure grammar: an inset error disc and a whisper-red border (the Banner 22% mix, never a red fill), on a lifted solid surface. Title = what died (“Harness crashed”); message = the one-line why.
  2. The evidence well. Whatever the failure left behind, in the part that already owns it: this card’s error output well for a tool-level trace, a CodeBlock for a stack/log excerpt, a DiffViewer for a bad patch.
  3. Recovery actions. The banner’s own right-aligned quiet action (“Retry”, “Open logs”), or for a decision moment the Approval content CTA row.

Stack them in that order in the transcript column: banner first (the verdict), the evidence collapsed beneath (this card’s collapse idiom). Compose; invent nothing: the failure expression must stay byte-identical with the ChatBubble send-failure and the AgentMessage stream-failure, which bind to the same banner grammar.

PropTypeDefaultDescription
name *stringTool name / one-line summary, in the mono font (e.g. Read SseClient.kt, $ ./gradlew test).
family *'shell-fs' | 'agent' | 'scheduling' | 'planning' | 'external' | 'default'Drives the filled icon-tile accent from the categorical family tokens. The family color rides in the leading tile, never a left rail (Law 6), never the brand cyan.
status *'running' | 'success' | 'error'Run state: running → a neutral pulse dot, success → a check, error → a close. All warm + a glyph, never color-alone.
elapsedSecondsnumberWall-clock time in the status chip: %.1fs under 10s, else Ns.
icon *ReactNodeThe family glyph for the icon tile (tinted via currentColor).
paramsToolParam[]Compact key → value call parameters shown first in the expanded body (muted label + strong mono value).
output{ lines: OutLine[]; error?: boolean }Expanded code/text result rendered in the recessed well. error tints it with the error wash.
diff{ path: string; lines: DiffLine[] }Render the result as an inline unified diff (added/removed washes + a +/- gutter) instead of a code well.
defaultOpenbooleanfalseStart expanded. The header is the disclosure (aria-expanded); a body-less card stays static.
Cross-platform drift (tracked for reconciliation)
  • tool-call-card vs agent-message: the tool-call card is the standalone, reusable tool-invocation panel; agent-message is the whole assistant turn and embeds these cards inline. Document the card here, the turn there.
  • Family taxonomy: Android’s ToolFamily has six families (shell-fs · agent · scheduling · planning · external · default); the inline panel in agent-message only surfaces four. The card is the full taxonomy.
  • Swift: the Swift code + params above document the intended API, derived from the Compose card for a macOS transcript rather than from shipped code.
  • Web: no @dizyx/nockerl-react component is published yet; the TypeScript API above is the canonical target.