The Terminal UI

The default run presentation is a full-screen terminal UI built on tview and tcell. It shows the run as it happens, folds thinking and tool output on a keypress, and can keep a chat bar open to continue after a turn. The same widget model backs the read-only transcript viewer.

Where it lives

internal/tui: the shared widget model and transcript viewer in viewer.go, the live-run wrapper in live.go, the native prompts in prompter.go, and the identity card in splash.go. The seam from the run loop is tcellEvents in run_tui_events.go.

Two goroutines, one writer

A live run has two goroutines. One runs agent.Run and emits Events; the other is the tview event loop that owns the screen. The run goroutine never touches widgets directly. Every cross-goroutine mutation is marshaled onto the tview loop with QueueUpdateDraw, so view state has a single writer. tcellEvents is the only producer, and it always goes through Live.Append.

agent looprun goroutinetcellEventsmaps to Linesviewertview loopterminalalt-screenEventsQueueUpdateDrawPrompter
Events flow down onto the single-writer tview loop; the prompter carries an operator's answer back up to the blocked run goroutine.

Rendering and hot-keys

Each Events callback maps to one or more Line values and appends them. Narration is rendered as markdown, then escaped; everything else is sanitized and wrapped in trusted per-kind color tags, so model text can never open a color or region tag. Tool output starts collapsed. The z and Z keys toggle folding of thinking and tool output as a global view mode, since a flat text view has no per-block cursor. The view tails the run like tail -f, re-arming follow when a scroll reaches the bottom, and ? opens interactive help.

The chat bar

With --chat, an input row is added above the status bar. At each turn boundary the agent calls NextPrompt, which activates the field and blocks the run goroutine until the operator submits or leaves. Enter sends, Alt-Enter inserts a newline, Ctrl-D ends cleanly, and Ctrl-C aborts. Slash commands like /clear and /restart are resolved before the text is sent.

Keeping stdout pipeable

The full-screen UI collapses answers, narration, and traces into one viewport, so it must protect the alt-screen. muzzleStderr redirects os.Stderr into a buffer for the whole run so library logging cannot corrupt the display, then flushes it to the restored terminal on exit. The raw answer, warnings, and rotated-session ids are captured as they arrive and re-printed to real stdout and stderr after teardown, so a piped answer still lands on stdout exactly as the line UI would deliver it.

Load-bearing decision

When a run blocks on an operator decision, the prompter rings the terminal bell and recolors the status bar amber, unless no_bell is set. It rings only on the block transition, not while simply awaiting chat input, so an unattended run is noticed the moment it needs a human.

Next

Continue to Interoperability for serving the same tools to other clients and agents.