assembly code: TUI UX overhaul (modals, streaming transcript, voice mode)#242
Merged
Conversation
…obustness, unique sessions - Approval prompt: replace the Textual Button row with a plain y/a/n keyboard-hint line, so it reads like a CLI prompt rather than chrome. - Voice mode banner: defer the first mic open until after the splash paints (call_after_refresh) — opening PortAudio inline on mount raced Textual's initial render and left the banner blank until a resize/focus repaint. - Mic open: redirect PortAudio's C-level stderr noise (which corrupted the TUI screen) via a safe-by-construction stdio.suppress_native_stderr; and on a mono open failure, reopen at the device's real channel count and downmix to mono, with a clear permission error when the device exposes 0 channels. - Sessions: give each `assembly code` run a unique thread id instead of reusing a fixed "default" thread (which silently resumed prior chats); `--session NAME` still resumes a named one. Gates verified pre-commit: ruff, pyright, mypy, full pytest suite, 100% patch coverage, mutation gate. Full check.sh not run at user request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) ## Summary This PR refactors the coding-agent TUI into smaller, more maintainable modules while keeping the main `CodeAgentApp` class intact. It also renames the `agent-cascade` command to `live` for clarity. The changes improve code organization without altering user-facing behavior. ## Key Changes **TUI Refactoring:** - **Split `tui.py`** into focused modules to stay under the 500-line file-length gate: - `modals.py`: `ApprovalScreen` and `AskScreen` modal dialogs with voice support - `messages.py`: Transcript widget classes (`UserMessage`, `AssistantMessage`, `ToolOutput`, etc.) - `voice_ui.py`: Voice capture/readback mechanics (`_VoiceIO` protocol, `_VoiceLegs` mixin) - `tui_status.py`: Pure text helpers for status line and spinner (`_spinner_text`, `_status_text`, etc.) - `summarize.py`: Tool activity summaries shared by TUI and Rich fallback - `risk.py`: Risk heuristics for tool approval prompts - **Extracted helper modules:** - `agent_cascade/brain.py`: Deepagents graph builder for the live cascade (system prompt, tool guidance, completer) - Moved `approval_from_speech` mapping to `modals.py` for voice-answerable approval **Command Rename:** - Renamed `agent-cascade` command to `live` throughout (command registration, help text, tests, docs, templates) **Test Reorganization:** - Split large test files to stay under the gate: - `test_code_modals.py`: Modal screen tests with voice doubles - `test_code_messages.py`: Transcript widget rendering tests - `test_code_tui_voice.py`: Voice toggle and readback tests (expanded) - `test_code_tui_status.py`: Pure status/spinner text helpers - `test_code_summarize.py`: Tool summarization tests - `test_code_session_stream.py`: Streaming and cancellation tests - `test_code_risk.py`: Risk heuristic tests - `test_agent_cascade_brain.py`: Deepagents graph builder tests **Installer Improvements:** - Enhanced `install.sh` with dev mode support (`--install-method git` / `--dev`) - Added usage help and environment variable overrides - Supports both release (published) and editable (development) installs **Events & Session:** - Added `AssistantDelta` event for per-token streaming (frozen, hashable) - Updated `CodeSession` to handle dual-mode streaming (`values` + `messages`) ## Implementation Details - **Voice modals** speak prompts and listen for spoken replies off the UI thread (daemon threads), marshaling back via `call_from_thread` - **Risk warnings** are pure functions (no Textual imports) so they unit-test cleanly - **Summarizers** clip long tool args/output to keep the transcript scannable (mirroring deepagents-code's collapsible rows) - **Deepagents brain** builds system prompts that advertise only available tools, preventing the agent from narrating actions it can't take - All refactored modules maintain the same public APIs; `CodeAgentApp` remains the single entry point ## Testing - New test files cover the extracted modules with real Textual app headless tests and pure function unit tests - Snapshot tests updated for the `live` command rename - CI workflow enhanced with end-to-end install.sh validation https://claude.ai/code/session_01Ad72JciKrsz4TKG7ZY9GR6 --------- Co-authored-by: Claude <noreply@anthropic.com>
# Conflicts: # tests/test_microphone.py
| devnull_fd: int | None = None | ||
| try: | ||
| saved_fd = os.dup(_STDERR_FD) | ||
| devnull_fd = os.open(os.devnull, os.O_WRONLY) |
| _voice_paused: bool | ||
| _last_reply: str | ||
|
|
||
| def _set_voice_phase(self, phase: str) -> None: ... |
| _last_reply: str | ||
|
|
||
| def _set_voice_phase(self, phase: str) -> None: ... | ||
| def _sync_input_mode(self) -> None: ... |
|
|
||
| def _set_voice_phase(self, phase: str) -> None: ... | ||
| def _sync_input_mode(self) -> None: ... | ||
| def _submit(self, text: str) -> None: ... |
| def _set_voice_phase(self, phase: str) -> None: ... | ||
| def _sync_input_mode(self) -> None: ... | ||
| def _submit(self, text: str) -> None: ... | ||
| def _note(self, text: str) -> None: ... |
The voice legs run on daemon threads that call back onto the UI thread via call_from_thread. If the app stops (a quit, or a test's run_test block exiting) while a leg is mid-call, that callback raises RuntimeError in the daemon thread, which pytest's threadexception plugin escalates to a failure — surfacing as a flaky `tests (windows, py3.12)` run on test_submit_sets_thinking_phase. Route every leg through a guarded body that swallows the callback error once the app is no longer running (the spoken turn is moot then) while still surfacing a genuine failure that happens while the app is live. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ad72JciKrsz4TKG7ZY9GR6
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
Rolls the
assembly codeTextual TUI UX work — landed on thecode-tui-ux-fixesintegration branch via #240 — up tomain. The branch now also mergesorigin/main(resolving a one-filetests/test_microphone.pyoverlap with #238's SIGINT-teardown guard), so this lands cleanly.The work was driven by exercising the live TUI through Textual's SVG screenshot pilot and importing interaction patterns from
langchain-ai/deepagents'codeagent.TUI fixes
ModalScreen { background: transparent }so approval/ask prompts dock at the bottom and leave the transcript visible above them, instead of blanking the screen.Mounted-widget transcript (replaces the append-only
RichLog)VerticalScrollof per-message widgets. The assistant reply streams in place token-by-token (cheap plain-text repaint), then re-renders as Markdown when finalized.→ write_file(app.py)) and risk warnings on mutating/destructive calls.rich.text.Text(no console-markup parsing) so a stray[can't raise or inject styling.Voice mode
set_intervalpulse + state flips) instead of a static listening bar.Also included
assembly codeTUI fix (222c030): CLI-style approval, voice-mode banner, microphone robustness (stereo→mono downmix fallback for devices that won't open as mono), and unique session IDs.messages.py,modals.py,voice_ui.py,tui_status.py,summarize.py,risk.py) to stay under the 500-line gate, each with its own test module.agent-cascade→livecommand rename, which converges with the same rename already onmain(Rename agent-cascade command to live and add deepagents brain #237) — no net change to that command vs.main.Testing
./scripts/check.shpasses end-to-end on the merge commit: 3419 tests, 99.58% branch coverage, 100% patch coverage, mutation gate (97 mutants on changed lines), no new escape hatches, andtwine checkclean.🤖 Generated with Claude Code
Generated by Claude Code