Feature: watch the agent's shell commands run in a read-only terminal tab - #66
Merged
Conversation
The agent's commands were invisible while they ran: a long build was a single truncated status line for its whole duration, then a wall of finished output. Give each agent a read-only tab in the terminal panel, fed by the engine's new command output sink. Deliberately NOT a shared pseudo-terminal: the agent keeps running commands through captured pipes, which is what keeps stdout and stderr separate, keeps the output free of escape sequences, and leaves a real exit code to report. Routing the agent through a PTY to make it visible would trade all three away for cursor control the model has no use for, plus a second writer racing the user for one stdin. This is a fan-out of lines that already existed, so it costs the agent nothing. AgentCommandLog buffers per agent from launch, because the terminal panel is built lazily — without it, an agent that built before the panel was ever opened would show an empty tab, which is the case the feature exists for. It holds more than the model's 5000-character copy, so the tail of a long build stays visible. Trimming lands on a line boundary: cutting mid-escape would leave half an SGR code to colour everything after it. AgentCommandFormat renders the lifecycle and strips control characters from command output, so a tool that emits VT when it isn't talking to a terminal can't clear the log someone is reading. Tabs never steal focus — an agent building while you type in a shell must not yank the panel away — so the title accents instead. Closing a tab clears that agent's buffer, so it isn't replayed on the next command.
The output tab was undiscoverable. It lives inside a panel that is closed by default, so the only way to learn it exists was to open the terminal for an unrelated reason and notice a tab you didn't create. Badge the rail's terminal button when an agent has produced output the user hasn't seen. The rail is always visible, so the cue arrives where they are already looking, at the moment it becomes true. It is driven by command activity rather than by panel state on purpose: the panel isn't built until first opened, and the first command a user's agent runs is precisely the one that has to advertise a tab they don't know about. The dot pulses only while a command is in flight and goes still once it finishes, so motion means 'work is happening now' and a still dot means 'output is waiting' — two facts one badge was otherwise conflating. Unhurried on purpose: a fast blink reads as an alarm, and this is an invitation. The storyboard is stopped rather than hidden when idle, and FlatMotion themes get the still dot in both states rather than a pulse they've opted out of. Track in-flight commands with a counter, not a flag: a plan step can have one command running while another closes out, and a flag would stop the pulse with work still going. Clamped at zero so an unbalanced finish can't wedge the rail pulsing forever. Also fixes the landing tab. The panel creates a starter shell when it opens and agent tabs are attached after that, so following the badge dropped the user on an empty prompt with the tab they came for hidden behind it. Opening with unread output now brings that tab to the front. Gated on unread rather than on a tab existing, so reaching for Ctrl+` to get a shell still gets one. Wired into both the first open and every reopen, since Ready fires once.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The terminal panel gains a read-only tab per agent that shows the shell commands that agent runs, live, as they run. A ninety-second build is no longer ninety seconds of silence with a single truncated status line.
Why this matters
When an agent runs a build, a test suite, or a git command today, the user sees one status line, truncated to about eighty characters, until the command finishes. Then the finished output appears in the conversation. There is no way to watch a long-running command, no way to see how far a build got before it stalled, and no way to tell a slow command from a stuck one.
Everything needed to show that work already existed inside the command runner and was thrown away as it was produced. This puts it on screen.
What is new
What deliberately did not change
The agent still runs commands the way it always has, through captured pipes rather than a real terminal. That is what keeps normal output and error output separate, keeps the text free of terminal control codes, and leaves a genuine exit code to report.
Running the agent through a real terminal to make it visible was considered and rejected: it would have degraded the output the model reads, replaced a reliable exit code with prompt-parsing guesswork, broken the existing timeout and kill behavior, and put the user and the agent in a race to type into the same session. This change adds a second reader of information that already existed, so it costs the agent's reliability nothing.
The direct consequence, worth stating plainly: the tab is read-only and cannot be typed into. A command that stops to ask a question still cannot be answered, and still times out as it does today. Making that answerable is a separate piece of work with a very different risk profile.
Scope and risk
Medium, and concentrated in the terminal panel rather than in the agent.
Verification
Desktop builds clean (0 warnings, 0 errors) and the full suite passes — 345 of 345, including 15 new tests covering how a command's lifecycle is rendered, that errors and successes are distinguishable, that output cannot drive the display, that history is bounded and keeps the newest output, that trimming never leaves a broken color code behind, and that the running state driving the pulse survives overlapping commands and cannot be wedged on by an unbalanced call.
An earlier revision of this branch was exercised by hand and confirmed working: live output arriving in its own tab, errors and exit codes rendering distinctly, the idle-timeout kill path, per-agent tabs, read-only input, closing a tab starting clean, and output recorded before the panel was first opened. The rail badge and landing-tab behavior described above were added after that pass and need their own.
Not covered, and needing a manual pass: the panel and the rail are XAML plus a WebView and are not reachable from tests. A reviewer should confirm the badge appears when an agent runs a command with the terminal closed; that it pulses while the command runs and goes still when it ends; that following it opens the panel directly on the agent's tab and clears the dot; that opening the panel with no dot still lands on a shell; and that the badge stays lit if the panel is opened onto a shell tab, since having the panel open is not the same as having read the output.
Dependency
Merge DevMando/MandoCode#95 first — this depends on the command output sink added there. The submodule pin in this branch points at that PR's branch commit so the build works now; once #95 merges, the pin needs re-pointing at the resulting commit on the engine's main before this is merged.