cliui

package
v0.31.0 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: Apache-2.0 Imports: 19 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. The duration suffix is dimmed so glyph + tool name carry the visual weight. Shell commands render the literal command in place of the tool name.

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. Shell runs render with the shell glyph but keep the args trail so distinct commands stay visible.

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. Shell commands render the literal command in place of the tool name.

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 PrintCommandEcho added in v0.21.10

func PrintCommandEcho(text string)

PrintCommandEcho echoes a submitted slash or shell command as one dim line, lighter than a message since it never reaches the model.

func PrintPerTurnSummary

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

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 PrintUserMessage added in v0.21.10

func PrintUserMessage(text string, queued bool)

PrintUserMessage echoes a submitted message into the conversation under a "▌ you" header that mirrors the assistant's, its text indented beneath. queued marks a message typed mid-turn that is running now.

func ProgressCompleteSummary added in v0.23.0

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

ProgressCompleteSummary renders a progress_complete event as a single line: a verified run carries the final check count when the nested verification has checks, an unverified run carries its reason when one is present. A run that is not verified and carries no reason (verification disabled, the default) renders nothing — the CLI's empty-summary suppression then prints no line, so the default user sees no per-turn "not verified" notice.

func ProgressEventSummary added in v0.23.0

func ProgressEventSummary(eventType string, data map[string]interface{}) string

ProgressEventSummary dispatches a progress event to its deterministic template summary. Returns "" for non-progress event types or when the template has nothing to say.

func ProgressMilestoneSummary added in v0.23.0

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

ProgressMilestoneSummary renders a progress_milestone event as a single line. A coalesced batch (the payload carries a "milestones" array of flat payloads) renders as a count only; a flat milestone renders the scope item's phase. Returns "" when there is nothing to say.

func ProgressQuestionSummary added in v0.23.0

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

ProgressQuestionSummary renders a progress_question event as a single line. The terminal does not print this one — the interactive ask_user prompt already shows the decision (see HandleProgressEvent); the template exists for the web UI and webhooks. Returns "" when the question is empty.

func ProgressVerificationSummary added in v0.23.0

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

ProgressVerificationSummary renders a progress_verification event as a single "Checks: <passed>/<total> passed" line. A skipped check is listed, never counted as passed. Returns "" when nothing ran.

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 SetREPLOwnsTurnSummary added in v0.19.2

func SetREPLOwnsTurnSummary(owned bool)

SetREPLOwnsTurnSummary marks whether the interactive REPL prints the turn-end summary itself.

func ShellCommandLabel added in v0.17.26

func ShellCommandLabel(toolName, preview string) (string, bool)

ShellCommandLabel extracts the literal command from a shell tool's preview so the line reads "$ go test ./..." instead of "shell_command (go test…)". The preview wrapper (" (cmd)") comes from FormatToolArgPreview; trim it. Returns ok=false for non-shell tools or an empty command, leaving the caller on the default rendering.

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 SummarizeProgress added in v0.23.0

func SummarizeProgress(ctx context.Context, client ModelClient, eventTypeName string, data map[string]interface{}) string

SummarizeProgress returns a short summary of a progress event. It prefers the model-written summary when client is non-nil; the deterministic template (ProgressEventSummary) is returned when the client is nil (the summarizer role is not configured), when the event cannot claim success (see progressVerificationPassed), when the model call errors or times out, or when the model output is empty.

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.

func ToolEndGlyph added in v0.17.26

func ToolEndGlyph(toolName, status string) console.Glyph

ToolEndGlyph picks the end-of-call glyph per the CLI display grammar: shell commands close with the shell prompt glyph, other completed tools close with the action arrow, and failures always close with the error cross. GlyphSuccess (✓) is reserved for outcomes — turn complete, todo completed, subagent results — so scanning for ✓ surfaces results, not routine activity.

Types

type ModelClient added in v0.23.0

type ModelClient interface {
	Complete(ctx context.Context, systemPrompt, userPrompt string) (string, error)
}

ModelClient is the minimal model interface the progress summarizer needs: one system+user prompt to one completion. Callers adapt a concrete client (apiModelClient below); a fake keeps the summarizer testable without a real provider.

func NewSummarizerModelClient added in v0.23.0

func NewSummarizerModelClient(cfg *configuration.Config) ModelClient

NewSummarizerModelClient builds the optional model client for progress summaries. It makes the model summary OPTIONAL: it returns nil when the `summarizer` role is not configured or when no client can be created for the resolved (provider, model) — in either case the caller uses the deterministic template.

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 SummaryUsage added in v0.23.0

type SummaryUsage struct {
	PromptTokens     int
	CompletionTokens int
	Cost             float64
}

SummaryUsage is the model usage one metered progress-summary call incurred: the tokens sent and returned and the estimated cost, ready to be booked under the summarizer role. Cost is an estimate from the provider/model pricing catalog because the summary seam does not surface provider-reported billing.

func SummarizeProgressWithUsageFn added in v0.23.0

func SummarizeProgressWithUsageFn(ctx context.Context, cfg *configuration.Config, eventTypeName string, data map[string]interface{}, build func(*configuration.Config) ModelClient) (string, SummaryUsage)

SummarizeProgressWithUsageFn is the metered entry point for the optional progress summary. It resolves the `summarizer` role through the injected client builder (production passes NewSummarizerModelClient), calls the model through the bounded, template-fallback SummarizeProgress path, and returns the summary alongside the model usage the call incurred so the caller can attribute the spend to the summarizer role. A nil/unconfigured summarizer role — or any fallback to the template — returns the deterministic template with zero usage and books nothing, so the capability stays opt-in.

It is the summarizer's role-metering seam: the caller (the turn's progress-event path, with the parent agent's per-role metrics) books the returned usage under the summarizer role, so /cost-style views and the usage ledger attribute the spend to the summary purpose rather than to the main coder loop. The builder is a parameter so the contract is testable with a fake summarizer (no live model).

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) HandleLanguageGuardReplacementEvent added in v0.23.0

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

HandleLanguageGuardReplacementEvent renders a language_guard_replacement event: the guard repaired a reply that was already streamed, so the terminal must show the corrected text in place of the prose it already printed. The replacement lands on stdout as an external notice — the streaming turn's prose is already on the screen above it, so a "replaced" marker keeps the two texts distinguishable. A dim hint line follows, pointing at /original — the held original text stays in the session and the notice alone never carries it.

func (*TerminalSubscriberState) HandleProgressEvent added in v0.23.0

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

HandleProgressEvent renders the deterministic one-line summary of a progress event in the scroll region: milestone, verification, and completion events.

progress_question events are deliberately NOT rendered: the CLI already shows the interactive ask_user prompt for the same decision (the ask_user_request / security-prompt path), so a second "Needs a decision" line would be redundant. The question template still exists for the web UI and webhooks — it just isn't printed in the terminal.

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) SetProgressSummarizerModelClient added in v0.23.0

func (s *TerminalSubscriberState) SetProgressSummarizerModelClient(client ModelClient)

SetProgressSummarizerModelClient overrides the model client the subscriber uses for the model-written progress summary. Tests inject a stub; production leaves it nil so the client is resolved from the `summarizer` role at summary time.

func (*TerminalSubscriberState) SetProgressSummarizerUsage added in v0.23.0

func (s *TerminalSubscriberState) SetProgressSummarizerUsage(record func(SummaryUsage))

SetProgressSummarizerUsage overrides where the subscriber books the summarizer role's model usage. Tests inject a recorder; production leaves it nil so the usage is booked into the chat agent's per-role metrics.

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