cliui

package
v0.17.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package cliui hosts the CLI terminal display helpers extracted from cmd/. These files provide the terminal subscriber, tool display, subagent display, and turn statistics functionality used by the agent command modes.

All .go files in this package (other than this doc.go) carry a //go:build !js build constraint and are excluded from the JS/WASM build — the WebUI uses a different rendering path that lives outside this package.

Index

Constants

View Source
const EditDiffMaxLines = 8

EditDiffMaxLines is the default number of diff lines to show in non-verbose mode. Keep it tight — the user wants a glance, not a wall.

View Source
const MaxArgsTrail = 3

MaxArgsTrail caps the per-arg preview list shown in the collapsed line. The earliest entries get dropped — the user usually cares about the most recent few calls in a run.

View Source
const SecurityCautionLabel = "⚠️  SECURITY CAUTION"

SecurityCautionLabel is the bracketed label rendered in security caution messages. CLI-B-2 extraction.

View Source
const SecurityLoopLabel = "🛑 SECURITY LOOP"

SecurityLoopLabel is the bracketed label rendered in security loop messages. CLI-B-2 extraction.

View Source
const VerbosePreviewWidth = 200

VerbosePreviewWidth is the argument-preview truncation width in verbose mode. In verbose mode the width is bumped so power users see more of the path or command.

Variables

View Source
var TurnFirstTokenAt int64

TurnFirstTokenAt is set (atomically) to the Unix nano time of the first non-empty stream chunk in the current turn. Read by PrintPerTurnSummary to compute time-to-first-token, then reset to 0 at the start of each turn. Package-level so the streaming callback in SetupAgentEvents (no agent-state to hang it on) can flip it.

Functions

func AbbreviatePath

func AbbreviatePath(p string, maxLen int) string

AbbreviatePath shortens a path while preserving the filename. A path like "webui/src/components/settings/ProviderSettingsTab.tsx" that exceeds maxLen renders as "…/ProviderSettingsTab.tsx" — the user almost always cares about the file at the tail more than the directory chain.

When the path has a separator we always prefer "…/basename" even if the basename itself is still over maxLen: the alternative (tail- truncating the basename) drops the suffix that usually identifies the file type, which is worse than overshooting maxLen by a few chars on a pathological filename. The only path with no separator falls back to a plain tail-truncate.

func BuildPromptPrefix

func BuildPromptPrefix(model string) string

BuildPromptPrefix returns the interactive REPL prompt for the given model. SP-048-5d. Format: "<model> ▸ " when a model name is available, "sprout> " as the legacy fallback when it isn't.

func CompactCost

func CompactCost(c float64) string

CompactCost formats a cost value compactly.

func CompactDuration

func CompactDuration(d time.Duration) string

CompactDuration formats a time.Duration compactly.

func CompactTokens

func CompactTokens(n int) string

CompactTokens formats token counts compactly.

func ComputeDiffStat

func ComputeDiffStat(toolName, arguments string) string

ComputeDiffStat produces a dim "+N -M" diffstat suffix for file-editing tools. For edit_file it counts lines in old_str vs new_str; for write_file it counts all lines as added (new file or full overwrite). Returns "" for non-file tools or when no useful diff can be computed. CLI-UX-3.

func ComputeEditDiff

func ComputeEditDiff(oldStr, newStr string, maxLines int) string

ComputeEditDiff generates a compact unified diff from the old and new strings for display in the terminal after an edit_file operation.

Shows removed lines in red (-) and added lines in green (+), with up to one context line before and after the changed block. Truncates to maxLines when > 0; pass 0 for unlimited (verbose mode).

func ComputeWriteFileDiff

func ComputeWriteFileDiff(content string, maxLines int) string

ComputeWriteFileDiff generates a preview of new file content written via write_file. Shows the first few lines with green (+) markers, truncated per maxLines (0 = unlimited, for verbose mode).

func CostPrefix

func CostPrefix(cost string) string

CostPrefix returns " · " when cost is non-empty, "" when empty, so the turn-summary line omits the cost segment cleanly for models without pricing.

func ExtractSubagentTask

func ExtractSubagentTask(argsJSON string) (taskDesc, persona string)

ExtractSubagentTask parses run_subagent tool arguments and returns (taskDescription, persona). The task description is the first line of the prompt, truncated to 60 chars so the spawn line stays scannable. Returns ("", "") when the args don't contain a usable prompt/persona.

func FormatCompactDiffLine

func FormatCompactDiffLine(toolName, arguments, diffStat string) string

FormatCompactDiffLine renders the minimal one-liner shown in compact mode for file edits: "edit_file (path.go) +12 -3". Extracts the path from args for context so the user knows which file changed.

func FormatResultSize

func FormatResultSize(length int) string

FormatResultSize renders a human-readable size string for the number of characters in a tool result. Used by verbose mode to append a dim "· 1.2KB" or "· 450 chars" suffix to tool-end lines. Returns "" for zero-length results so we don't clutter the line with "· 0 chars".

Threshold: >=1000 chars switches to kilobytes (base-1024) with one decimal place; below that we show the raw char count.

func FormatRunParallelSubagentsPreview

func FormatRunParallelSubagentsPreview(arguments string) string

FormatRunParallelSubagentsPreview shows the task count so the user knows how many subagents fanned out. No per-task persona since the parallel form doesn't accept per-task persona overrides today; users see the count and infer fan-out from the line.

func FormatRunSubagentPreview

func FormatRunSubagentPreview(chatAgent *agent.Agent, arguments string) string

FormatRunSubagentPreview extracts the persona from args and looks up its effective provider/model via the agent's persona resolver. Format:

(coder · anthropic/claude-haiku-4-5)

Falls back to just persona name (or empty) when the lookup fails.

func FormatSpawnLine

func FormatSpawnLine(chatAgent *agent.Agent, depth int, persona string, maxCtx int, taskDesc string) string

FormatSpawnLine renders the one-shot "↳ persona spawned (provider · model · 128k ctx)" line emitted the first time the CLI sees a new (depth, persona) pair in a turn. Indent matches the corresponding tool-line depth so it visually nests under the parent that spawned it. The `maxCtx` argument carries the subagent's model context budget (from monitorProgress's initial emit); 0 means "unknown" and the ctx suffix is dropped — the line degrades to the original "(provider · model)" form.

CLI-UX-11: when taskDesc is non-empty, it's appended after the persona badge so the user sees what the subagent is doing: "↳ coder: refactor auth.go" instead of just "↳ coder".

func FormatSubagentCtxSuffix

func FormatSubagentCtxSuffix(snap SubagentProgressSnapshot) string

FormatSubagentCtxSuffix renders the trailing "· 12.3k/128k ctx" hint appended to depth>0 tool-start lines. Returns "" when no useful numbers are available so the line stays clean during the first tick before any tokens have accumulated.

func FormatSubagentDoneLine

func FormatSubagentDoneLine(persona, status, reason string, tokens int, cost, elapsedSec float64) string

FormatSubagentDoneLine renders the per-subagent completion summary — the closing bracket for the spawn line. Format:

↳ [persona] done · 12,345 tok · $0.0234 · 4.2s
↳ [persona] cancelled (budget_exceeded) · 8,901 tok · $0.0102 · 2.1s

Indents at depth 1 to nest visually under the parent's run_subagent row. Numeric fields are omitted when zero so a no-cost / no-token cancellation stays terse rather than printing "0 tok · $0.0000".

func FormatThousands

func FormatThousands(n int) string

FormatThousands renders an integer with comma separators (e.g. 1234567 → "1,234,567"). Negative numbers keep the sign.

func FormatTodoListBlock

func FormatTodoListBlock(todosRaw []interface{}) string

FormatTodoListBlock renders the multi-line todo block printed in the scroll region in response to EventTypeTodoUpdate. The header is a one-line summary (counts by status); the body is one row per item with a status-coded glyph (✓ done, → active, · pending, ⏹ cancelled). Truncates long lists to keep the terminal scannable.

func FormatTodoListPanel

func FormatTodoListPanel(todosRaw []interface{}) string

FormatTodoListPanel renders the todo list inside a box-drawing panel for stronger visual structure (CLI-UX-9). The panel header includes the status counts; the body is the same per-row content as formatTodoListBlock but wrapped in light-vertical borders.

func FormatTodoWritePreview

func FormatTodoWritePreview(arguments string) string

FormatTodoWritePreview produces the compact tail for the todo_write tool's spinner / collapse line — "(5 tasks · 1 active · 3 done)" — so the user sees the shape of the list at a glance without waiting for the full TodoUpdate block to land. Returns "" when the args are unparseable or empty, matching the contract of the other per-tool preview helpers.

func FormatTokensShort

func FormatTokensShort(n int) string

FormatTokensShort formats a token count compactly: "1234" → "1.2k", "1234567" → "1.2M". Used inside tool/spawn lines where horizontal space is at a premium — the full comma-separated form lives in the "↳ done" line at the end of the subagent run.

func FormatToolArgPreview

func FormatToolArgPreview(toolName, arguments string, maxArgLen int) string

FormatToolArgPreview produces a short, single-line preview of a tool's arguments for the activity indicator. The arguments string is the raw JSON the model emitted; we extract whichever field is most informative for the tool at hand. Returns an empty string (no parens) when nothing useful is available. Best-effort — invalid JSON yields no preview.

maxArgLen overrides the per-tool truncation widths when > 0 (used by verbose mode to show longer paths/commands). Pass 0 to use the built-in per-tool defaults documented below.

Per-tool max widths and truncation strategies (when maxArgLen == 0):

  • File paths use AbbreviatePath so the filename always survives even when the directory prefix has to be dropped — "…/last/two/seg.go" reads better than "webui/src/components/sett…" where the actual file is lost.
  • shell_command / exec preserve more context (80 chars) because the suffix of a command is often the meaningful part (pipes, args).
  • Everything else gets the conservative 70-char tail truncation.

func FormatToolEndLine

func FormatToolEndLine(depth int, persona, icon, toolName, preview string, durationSec float64) string

FormatToolEndLine builds the activity-indicator replacement line for a ToolEnd event. Same depth/badge logic as FormatToolStartLine.

func FormatToolPreview

func FormatToolPreview(chatAgent *agent.Agent, toolName, arguments string, maxArgLen int) string

FormatToolPreview produces a short, single-line preview of a tool call for the activity-indicator timeline. For subagent tools (run_subagent, run_parallel_subagents) it surfaces the persona and the resolved provider/model so users can see which subagent — and which underlying model, often a cheaper/faster one than the parent's — is doing the work. For everything else it falls through to FormatToolArgPreview.

maxArgLen overrides the per-tool truncation width when > 0 (verbose mode passes a higher value so power users see more of the path/command). Pass 0 to use the built-in per-tool defaults.

func FormatToolRunLine

func FormatToolRunLine(depth int, persona, icon, toolName string, count int, argsTrail []string, totalSec float64) string

FormatToolRunLine renders a collapsed line for repeated calls of the same tool. Replaces N stacked "✓ read_file (foo.go) · 0.1s" entries with a single "✓ read_file × N (foo.go, bar.go, baz.go) · 0.3s" line updated in place via ActivityIndicator.ReplaceLastN.

argsTrail holds the most recent up-to-3 arg previews so the user can still see what was touched without scrolling through identical entries. totalSec is the cumulative duration across all N calls so the line still surfaces "this batch took a moment" even when each individual call was quick.

func FormatToolStartLine

func FormatToolStartLine(depth int, persona, toolName, preview string) string

FormatToolStartLine builds the activity-indicator line for a ToolStart event. At depth 0 it's byte-identical to the pre-SP-051 format (" tool_name(preview)") so primary-agent tool calls render unchanged. At depth >= 1 it adds a depth indent and a colored "[persona]" badge.

func FormatTurnStatsLine

func FormatTurnStatsLine(promptDelta, completionDelta int, costDelta float64, elapsed, ttft time.Duration) string

FormatTurnStatsLine builds the dim single-line turn-summary string. When color is disabled (NO_COLOR), ANSI dim codes are stripped. SP-048-5a.

ttft (time to first token) is rendered as a separate segment when non-zero. Threshold coloring (yellow >2s, red >5s) makes slow provider connections visible at a glance — they're the most common cause of "is sprout stuck?" perception even when the actual model run is fast once it starts streaming.

func NoteFirstStreamChunk

func NoteFirstStreamChunk()

NoteFirstStreamChunk is invoked once per turn from the streaming callback. CompareAndSwap ensures only the very first non-empty chunk updates the timestamp — later chunks are no-ops.

func NotifyTurnCompletion

func NotifyTurnCompletion(chatAgent *agent.Agent, turnStart time.Time, skipPrompt bool)

NotifyTurnCompletion emits a terminal bell and/or OS notification when a turn completes after exceeding the configured minimum duration. Suppressed in non-interactive sessions, when --skip-prompt is set, or for fast turns. SP-070-2.

func PrintAssistantHeader

func PrintAssistantHeader(model string)

PrintAssistantHeader writes the dim "▌ assistant · <model>" header that marks the start of an assistant turn. Honors NO_COLOR via the existing color preference resolver. The brand cyan `▌` aligns visually with the glyph vocabulary in pkg/console; the model name sits in dim grey so the eye is drawn to the bar, not the metadata.

func PrintPerTurnSummary

func PrintPerTurnSummary(chatAgent *agent.Agent, start time.Time, promptBefore, completionBefore int)

PrintPerTurnSummary emits a dim single-line summary of what just happened in the LLM round-trip: input/output tokens consumed, $ spent, elapsed wall time, plus ttft when available. Silent when no tokens were used (e.g. the turn was a slash command or zsh fast path). Only shown when stderr is a TTY (respects NO_COLOR for ANSI codes). SP-048-5a.

func ReadEventDepth

func ReadEventDepth(data map[string]interface{}) int

ReadEventDepth reads the subagent_depth from an event payload. Returns 0 for missing or malformed values — matches today's "primary agent" rendering when older events that pre-date SP-051 metadata land in the bus.

func ReadEventInt

func ReadEventInt(data map[string]interface{}, key string) int

ReadEventInt extracts an int from an event payload, tolerating the numeric types the event bus may marshal through (int / int64 / float64 round-trip via JSON).

func ReadEventInt64

func ReadEventInt64(data map[string]interface{}, key string) int64

ReadEventInt64 extracts an int64 from an event payload.

func ReadEventPersona

func ReadEventPersona(data map[string]interface{}) string

ReadEventPersona reads the active_persona from an event payload, trimmed. Returns "" when absent — which suppresses the persona badge.

func ResetTurnFirstToken

func ResetTurnFirstToken()

ResetTurnFirstToken clears the ttft tracker. Called by the REPL just before submitting a turn so each turn's measurement is independent.

func SanitizeArgForPreview

func SanitizeArgForPreview(s string) string

SanitizeArgForPreview collapses whitespace and strips control characters so the preview always renders on one line inside parentheses.

func ShortModelName

func ShortModelName(model string) string

ShortModelName strips the lab/org prefix from a model ID for display. "deepseek-ai/DeepSeek-V4-Flash" → "DeepSeek-V4-Flash" "meta-llama/Llama-3.3-70B-Instruct" → "Llama-3.3-70B-Instruct" "glm-4.6" → "glm-4.6" (no slash, returned as-is)

func ShouldShowTurnStats

func ShouldShowTurnStats() bool

ShouldShowTurnStats returns true when stderr is connected to a TTY. The turn-summary line is written to os.Stderr, so we must check stderr (not stdout) to determine whether it will render cleanly. This matters in piping scenarios like `sprout agent "query" > output.txt` where stdout is piped but stderr is still the terminal. SP-048-5a.

func StartTerminalToolSubscriber

func StartTerminalToolSubscriber(ctx context.Context, chatAgent *agent.Agent, eventBus *events.EventBus, indicator *console.ActivityIndicator, footer *console.StatusFooter) func()

StartTerminalToolSubscriber subscribes a goroutine to the event bus that translates PublishToolStart / PublishToolEnd events into terminal spinner updates and ✓/✗ result lines. Runs until ctx is cancelled.

Tools whose ToolConfig declares Interactive=true (e.g. ask_user) bypass the spinner entirely so their own prompt rendering isn't clobbered.

Also stops the spinner on any prompt-request event (security approval, security prompt, ask_user) so prompts routed through the event bus get clean rendering with no spinner frames overwriting the prompt text. When footer is non-nil, it is refreshed on each ToolEnd so cost / context stay current as tools consume tokens.

The chatAgent reference is used to resolve subagent personas to their effective provider/model so `run_subagent` lines can show which model will actually run the delegated task (subagents often use cheaper or faster models than the parent, and visibility into that matters).

func TodoBlockRowCount

func TodoBlockRowCount(todosRaw []interface{}) int

TodoBlockRowCount returns the number of terminal rows that fmt.Fprintln(os.Stdout, formatTodoListBlock(todosRaw)) will consume. The block string has a header row plus one row per item (each item prefixed by \n). fmt.Fprintln adds a final \n. So the visible rows = strings.Count(block, "\n") + 1.

Types

type SubagentProgressSnapshot

type SubagentProgressSnapshot struct {
	TokensUsed  int
	CtxUsed     int
	CtxMax      int
	Iteration   int
	LastUpdated time.Time
}

SubagentProgressSnapshot is the most-recent live snapshot of a running subagent's token / context usage, refreshed by the runner's monitorProgress ticker (~every 2s). The CLI subscriber appends a compact "· 12.3k/128k ctx" suffix to subsequent tool-start lines fired by the same persona so users see the budget burn in real time, instead of only learning the final numbers in the "completed" line after the subagent has already exited.

type TerminalSubscriberState

type TerminalSubscriberState struct {
	// contains filtered or unexported fields
}

TerminalSubscriberState holds all mutable state for the terminal tool subscriber goroutine. Extracted from the closure variables of startTerminalToolSubscriber so the event loop can be broken into focused handler methods.

func NewTerminalSubscriberState

func NewTerminalSubscriberState(configMgr *configuration.Manager, chatAgent *agent.Agent) *TerminalSubscriberState

NewTerminalSubscriberState initializes a fresh subscriber state with pre-allocated maps and the config manager for live verbosity reads.

func (*TerminalSubscriberState) HandleAgentMessageEvent

func (s *TerminalSubscriberState) HandleAgentMessageEvent(data map[string]interface{}, indicator *console.ActivityIndicator, footer *console.StatusFooter)

HandleAgentMessageEvent formats and prints an agent message (security caution, security loop, tool error, warning, or generic info) via console.PrintExternal. Breaks the collapse run and refreshes the footer.

func (*TerminalSubscriberState) HandleQueryCompletedEvent

func (s *TerminalSubscriberState) HandleQueryCompletedEvent(data map[string]interface{}, indicator *console.ActivityIndicator)

HandleQueryCompletedEvent processes a QueryCompleted event (CLI-UX-7).

Prints a dim one-line turn summary so the user sees how long the turn took and how much it cost, without the clutter of a full metrics dump. Format:

✓ turn complete · 12.3s · $0.04

Suppressed entirely in compact mode.

func (*TerminalSubscriberState) HandleQueryStartedEvent

func (s *TerminalSubscriberState) HandleQueryStartedEvent(indicator *console.ActivityIndicator)

HandleQueryStartedEvent processes a QueryStarted event (CLI-UX-5).

When the LLM begins "thinking" — the gap between query submission and the first tool or streamed token — we show a contextual "thinking…" spinner so the terminal never looks frozen. The spinner is only started when no tool spinner is already active (the tool line is more informative) and suppressed entirely in compact mode.

The spinner stops naturally when either:

  • A StreamChunk with content_type arrives (assistant prose starts) → HandleStreamChunkEvent clears it.
  • A ToolStart fires → HandleToolStartEvent clears it and starts the tool spinner.

func (*TerminalSubscriberState) HandleSecurityPromptEvent

func (s *TerminalSubscriberState) HandleSecurityPromptEvent(indicator *console.ActivityIndicator)

HandleSecurityPromptEvent stops the spinner and breaks the collapse run when a prompt is about to render (security approval, security prompt, or ask_user). Subsequent activity re-starts the spinner naturally.

func (*TerminalSubscriberState) HandleStreamChunkEvent

func (s *TerminalSubscriberState) HandleStreamChunkEvent(data map[string]interface{}, indicator *console.ActivityIndicator)

HandleStreamChunkEvent processes a StreamChunk event.

If the chunk carries a content_type (assistant text), it breaks any pending tool-collapse run so the next ToolEnd prints a fresh row.

func (*TerminalSubscriberState) HandleSubagentActivityEvent

func (s *TerminalSubscriberState) HandleSubagentActivityEvent(data map[string]interface{}, indicator *console.ActivityIndicator, footer *console.StatusFooter)

HandleSubagentActivityEvent processes a SubagentActivity event.

"progress" status: cache the snapshot keyed by persona and refresh the footer so fleet-cost stays current. "completed"/"cancelled": emit a done summary line, clear progress cache, break the collapse run, and refresh the footer.

func (*TerminalSubscriberState) HandleTodoUpdateEvent

func (s *TerminalSubscriberState) HandleTodoUpdateEvent(data map[string]interface{}, indicator *console.ActivityIndicator, footer *console.StatusFooter)

HandleTodoUpdateEvent renders the agent's todo list as a styled block in the scroll region. Breaks the collapse run and refreshes the footer.

func (*TerminalSubscriberState) HandleToolEndEvent

func (s *TerminalSubscriberState) HandleToolEndEvent(data map[string]interface{}, chatAgent *agent.Agent, indicator *console.ActivityIndicator, footer *console.StatusFooter)

HandleToolEndEvent processes a ToolEnd event.

Interactive tools are skipped. For other tools: recover args from the ToolStart cache, collapse consecutive identical calls into a single in-place row (Phase 3), or emit a fresh end line. Refreshes the footer.

func (*TerminalSubscriberState) HandleToolStartEvent

func (s *TerminalSubscriberState) HandleToolStartEvent(data map[string]interface{}, chatAgent *agent.Agent, indicator *console.ActivityIndicator)

HandleToolStartEvent processes a ToolStart event.

Interactive tools bypass the spinner entirely. For all other tools: resolve any active reasoning fold, cache args for the matching ToolEnd, announce subagent spawns once per (depth, persona) per turn, and start the activity indicator with a context suffix when progress is available.

In "compact" verbosity mode, the spinner and spawn announcements are suppressed — only error results break the silence.

func (*TerminalSubscriberState) IsCompact

func (s *TerminalSubscriberState) IsCompact() bool

IsCompact reports whether the subscriber should suppress tool chrome (spinner, result lines, todo blocks, subagent announcements). Read live from the config manager on each call so a mid-session /settings change takes effect immediately instead of requiring a restart. Falls back to false (non-compact) when the manager is nil (non-agent callers, tests).

func (*TerminalSubscriberState) IsVerbose

func (s *TerminalSubscriberState) IsVerbose() bool

IsVerbose reports whether the subscriber should show extended detail (full tool arguments, result size suffixes). Read live from the config manager — mirrors IsCompact() — so a mid-session /settings change to "verbose" takes effect immediately without a restart. Falls back to false when the manager is nil (non-agent callers, tests).

func (*TerminalSubscriberState) MaybeDisplayEditDiff

func (s *TerminalSubscriberState) MaybeDisplayEditDiff(toolName, argsJSON string)

MaybeDisplayEditDiff shows a compact diff for file-editing tools (edit_file, write_file). In verbose mode the full diff is shown; in default mode it's truncated to EditDiffMaxLines. Compact mode reaches this only for errors — successes return early with just the diffstat.

func (*TerminalSubscriberState) ResetSpawnTurn

func (s *TerminalSubscriberState) ResetSpawnTurn()

ResetSpawnTurn clears the per-turn spawn dedupe map so the next batch of subagents gets fresh announcements. Called by the REPL loop at the start of each user turn.

func (*TerminalSubscriberState) VerboseMaxArgLen

func (s *TerminalSubscriberState) VerboseMaxArgLen() int

VerboseMaxArgLen returns the argument-preview truncation width to pass to FormatToolPreview/FormatToolArgPreview. In verbose mode the width is bumped so power users see more of the path or command. In default or compact mode it returns 0, which tells the preview functions to use their built-in per-tool defaults.

type TodoEntry

type TodoEntry struct {
	Content string
	Status  string
}

TodoEntry mirrors the internal struct used by both the inline block and the panel renderer so they stay in sync.

func CollectTodos

func CollectTodos(todosRaw []interface{}) ([]TodoEntry, map[string]int)

CollectTodos parses the raw todo event payload into typed items and counts by status. Shared by formatTodoListBlock and formatTodoListPanel.

type ToolRunState

type ToolRunState struct {
	Name      string
	Depth     int
	Persona   string
	Count     int
	ArgsTrail []string // most recent up to MaxArgsTrail entries
	TotalMs   int64
	LastIcon  string
	LastEnd   time.Time
}

ToolRunState tracks a sequence of consecutive identical tool calls so the subscriber can collapse them into a single in-place row (Phase 3 of CLI ergonomics). A run is broken — set to nil — whenever any non-tool event would invalidate the row math: streaming assistant text, a different tool, or a user-prompt boundary.

func (*ToolRunState) AppendArg

func (r *ToolRunState) AppendArg(preview string)

AppendArg adds an argument preview to the args trail, capping it at MaxArgsTrail.

func (*ToolRunState) Matches

func (r *ToolRunState) Matches(name string, depth int, persona string) bool

Matches reports whether the run matches the given tool call parameters.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL