Tool-call card
Lifecycle: running · success (params + output) · error · diff (tab a header, Enter / Space)
- file_path
- app/src/main/.../net/SseClient.kt
- limit
- 40
// SseClient.kt: the reconnect loopsuspend fun reconnect(attempt: Int) {delay(nextDelay(attempt))}
- command
- ./gradlew assembleDebug
> Task :app:compileDebugKotlinBUILD SUCCESSFUL in 18s42 actionable tasks: 11 executed, 31 up-to-date
- command
- ./gradlew compileDebugKotlin
e: Backoff.kt:14:18 unresolved reference: Random> Task :app:compileDebugKotlin FAILEDBUILD FAILED in 3s
- file_path
- app/src/main/.../net/Backoff.kt
Families: the FILLED icon tile tints by tool family (categorical, never the brand cyan)
- file_path
- AGENTS.md
# Nockerl Platform: Global Instructions
- pattern
- familyAccent
- glob
- *.kt
ToolFamily.kt:139:internal fun familyAccent
- url
- https://nockerel.ai/docs
200 OK · 14.2 kB
- cron
- */13 * * * *
Scheduled · next run 13m
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.)
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="terminal" />
{/* 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="file" 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="edit" diff={{ path: 'net/Backoff.kt', lines: editDiff }} /> </> );}// Android: Jetpack Compose (canonical). com.nockerl.app.chat.ui.ToolCallCard// renders ONE ToolCall (chat/domain/ContentBlock.kt). Status is DERIVED from the// model (output == null → running, output present → success, isError → error), and// the family accent comes from toolFamily(name) → familyAccent(…) (ToolFamily.kt).import com.nockerl.app.chat.ui.ToolCallCardimport com.nockerl.app.chat.domain.ToolCallimport kotlinx.serialization.json.buildJsonObjectimport kotlinx.serialization.json.put
// Running: no output yet, so the card shows a neutral pulse + elapsed.ToolCallCard( tool = ToolCall( id = "toolu_01", name = "Bash", input = buildJsonObject { put("command", "./gradlew test") }, elapsedSeconds = 6.0, ),)
// Finished Read (output present → success); the expanded body is a CopyableCodeSurface.ToolCallCard( tool = ToolCall( id = "toolu_02", name = "Read", input = buildJsonObject { put("file_path", "net/SseClient.kt") }, output = readSnippet, elapsedSeconds = 0.4, ),)
// Edit. Compose computes the inline unified diff (computeLineDiff) from old/new_string.ToolCallCard( tool = ToolCall( id = "toolu_03", name = "Edit", input = buildJsonObject { put("file_path", "net/Backoff.kt") put("old_string", oldSrc) put("new_string", newSrc) }, output = "Applied 1 edit", ),)// macOS: Nockerl Voice ships NO tool-call card. Voice is a voice-first surface// (NockerlVoice/UI) with no chat tool-invocation feed, so there is no `.nockerl…`// style or view to document here. The card below is the INTENDED SwiftUI API for// when the transcript lands on macOS, modelled on the canonical Compose ToolCallCard.// Tracked as cross-platform drift; see the Aside below.ToolCallCard( name: "Read SseClient.kt", family: .shellFs, // → familyAccent: cyan · orange · indigo · sky · fuchsia · muted status: .success, // .running · .success · .error (derived from the call) elapsedSeconds: 0.4, params: [.init(label: "file_path", value: "net/SseClient.kt")], output: .code(readSnippet) // .code(_) | .diff(path:lines:) | nil)Harness failure block (a view, no new grammar)
Section titled “Harness failure block (a view, no new grammar)”Per the WS4 verdict (task 2655d), a harness failure is the whole agent run dying (a crashed session, a gateway 500, a budget kill), bigger than one tool error. It is a thin composition of ratified parts, not a component:
- The failure banner. The r5.2-ratified failure grammar (adjudication
2026-07-02, addendum r5.2): an inset error disc, a whisper-red border
(the
Banner22% mix, never a red fill), on a lifted solid surface. Title = what died (“Harness crashed”); message = the one-line why. - 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
CodeBlockfor a stack/log excerpt, aDiffViewerfor a bad patch. - 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.
Parameters
Section titled “Parameters”| Prop | Type | Default | Description |
|---|---|---|---|
name * | string | Tool 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. | |
elapsedSeconds | number | Wall-clock time in the status chip: %.1fs under 10s, else Ns. | |
icon * | ReactNode | The family glyph for the icon tile (tinted via currentColor). | |
params | ToolParam[] | 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. | |
defaultOpen | boolean | false | Start expanded. The header is the disclosure (aria-expanded); a body-less card stays static. |
| Parameter | Type | Default | Description |
|---|---|---|---|
tool * | ToolCall | The call to render: id · name · input: JsonObject? · output: String? · isError · elapsedSeconds. Status is derived from output/isError; the summary + icon + family from name. | |
spawnState | SpawnState? | null | Only for the nockerl_spawn_agent fan-out adapter. Drives the per-child status/transcript block. Ignored by the generic card. |
The card is a single composable over the ToolCall domain model. There is no status,
family, or icon parameter: normalizeName + toolFamily + toolIcon resolve them
from tool.name, and the run state from tool.output / tool.isError. A handful of
gateway tools (nockerl_web_research, ToolSearch, nockerl_spawn_agent) and plan-mode
markers short out to dedicated cards before the generic mold.
Voice ships no tool-call card (it has no chat tool-invocation feed), so there is no shipped Swift API. The table below is the intended surface for when the transcript lands on macOS, modelled on the Compose card.
| Style | Type | Default | Description |
|---|---|---|---|
name * | String | Intended. Tool name / summary in the mono font. | |
family * | ToolFamily | Intended. .shellFs · .agent · .scheduling · .planning · .external · .default → the filled family-accent icon tile (never a left rail). | |
status * | ToolStatus | Intended. .running · .success · .error (the warm status chip). | |
output | ToolOutput? | Intended. .code(_) for the recessed well or .diff(path:lines:) for an inline diff. |