feat(tui): add Alt+C and /copy to copy last agent response as markdown#11333
feat(tui): add Alt+C and /copy to copy last agent response as markdown#11333fcoury wants to merge 18 commits intoopenai:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds the ability to copy the last agent response as markdown to the clipboard using Alt+C or the /copy slash command. The implementation maintains a parallel timeline of raw markdown alongside the rendered transcript cells, with a bounded history of 256 entries. The feature supports both native clipboard (via arboard) and OSC 52 terminal escape sequences for SSH sessions.
Changes:
- Added clipboard copy functionality with native and OSC 52 support
- Implemented copy state management tracking agent message markdown
- Integrated copy feature with keyboard shortcut (Alt+C) and slash command (/copy)
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| codex-rs/tui/src/clipboard_copy.rs | New module implementing clipboard copy with OSC 52 and arboard, including SSH detection and macOS stderr suppression |
| codex-rs/tui/src/chatwidget.rs | Added copy state fields and logic, event handlers for recording agent markdown, Alt+C binding, and /copy command handler |
| codex-rs/tui/src/app_backtrack.rs | Integrated copy state with rollback, added transcript fallback for recovering copy source after rollback |
| codex-rs/tui/src/history_cell.rs | Added plain_text() method to AgentMessageCell for lossy markdown reconstruction |
| codex-rs/tui/src/slash_command.rs | Added Copy command to slash command enum |
| codex-rs/tui/src/lib.rs | Added clipboard_copy module import |
| codex-rs/tui/src/chatwidget/tests.rs | Comprehensive tests for copy state management, rollback, and edge cases |
| codex-rs/tui/src/app.rs | Tests for rollback integration with copy state, including deep rollback scenarios |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@fcoury, please resolve the merge conflicts. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c1b46516de
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
c1b4651 to
8dcd492
Compare
|
Will this feature resolve this issue? |
2b25898 to
5a1a1d5
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a1a1d5fe5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ategy Add module-level, type-level, and function-level documentation to the copy/clipboard feature so reviewers and future maintainers can understand the design without reading every line of implementation. No runtime behavior changes.
Promote non-empty TurnItem::Plan text to the active copy source so Alt+C and /copy copy the final plan output instead of prior commentary in the same turn. Adds regression coverage for replayed user message + commentary + completed plan item flow.
Rename the per-turn copy-source flag to reflect non-AgentMessage sources, clarify ordinal docs for copy-history entries, and make the backtrack continuation-group test assert real merged output.
Record rendered review output on ExitedReviewMode so Alt+C and /copy use the latest review content, including explanation-only and findings-based review results. Adds regression tests for both paths.
67c2cdf to
d82a2bc
Compare
Yes, you just need to press Alt+C when the plan is output and it will be copied to the clipboard as markdown. |
|
Closing in favor of #12613 |
Problem
Users want to quickly copy the last agent response as markdown — for pasting into docs, issues, or chat. The TUI had no clipboard integration, and over SSH even if it did the clipboard would write to the remote machine.
Mental model
The copy feature maintains a parallel timeline of agent response markdown alongside the existing transcript cells. The transcript's
historystores renderedLine/Spanobjects for display — the original markdown syntax (#headings,**bold**, code fences) is lost after rendering. Copying needs the raw markdown, so we keep it separately rather than modifying the shared display model that the rest of the TUI relies on for rendering, scrolling, and rollback.Alternatives considered:
raw_markdownonAgentMessageCell— couples copy logic to the display model, bloats every cell with anOption<String>even though only the last one matters, and rollback has to walk the history vec instead of a simpleVec::pop.ChatWidgetonly receives events; it has no direct access to the backend transcript. Plumbing an async query path or shared reference would be a bigger architectural change for a single feature.plain_text()on copy — eliminates string duplication but makes every copy lossy (not just the deep-rollback edge case), defeating the purpose of raw markdown.display_lines()— the most principled approach (markdown as single source of truth), but the streaming pipeline currently renders deltas incrementally into pre-renderedLine/Spancells, and a single agent response is split across multipleAgentMessageCellinstances (one per stream chunk). This would require restructuring streaming to accumulate raw markdown, merging cells into one per response, and re-rendering on every draw (or caching, which is back to storing both). Valid long-term direction but too large a refactor for a copy-feature PR.The current approach (parallel bounded vec) is the simplest that gives lossless copy in the common case without coupling to either the display model or the backend.
Two data structures track this:
last_agent_markdown— the single string that Alt+C //copywill send to the clipboard.agent_turn_markdowns: Vec<AgentTurnMarkdown>— a bounded (256-entry) vec with drain-on-overflow of(ordinal, markdown)pairs so that transcript rollbacks can truncate the timeline and recover the correct copy source.The timeline is fed from four event sources (last writer wins within a turn):
AgentMessage— primary source; records commentary or full response text.PlanItemCompleted— overwrites commentary with the completed plan text, so Alt+C copies the plan the user actually sees rather than a brief preamble.ExitedReviewMode— records the rendered review output (explanation and/or findings) so Alt+C copies the review result.TurnComplete.last_agent_message— fallback for providers that don't emitAgentMessage.A
saw_copy_source_this_turnflag preventsTurnCompletefrom recording a duplicate entry when any copy source (AgentMessage, plan item, or review output) was already recorded during the turn.Clipboard writes use a two-tier strategy:
OSC 52 is written to
/dev/ttyto bypass ratatui's alternate-screen buffer on stdout.Non-goals
AgentMessage) is recorded and copyable while the turn is still running; once a plan item or review output completes, it overwrites the commentary.Tradeoffs
plain_text()fallback is lossyLine/Spancells lose original markdown syntax. Acceptable because it only triggers on deep rollbacks past the 256-entry window.SuppressStderrguard around arboardarboard::Clipboard::new()initializesNSPasteboard, which triggersos_log/NSLogoutput on stderr. In raw-mode TUI this corrupts the display. RAII guard redirects fd 2 to/dev/nullaround the call, serialized by a process-wide mutex. No-op on Linux/Windows.Architecture
Copy-state lifecycle:
Copy source priority within a turn:
Observability
tracing::warnon native clipboard failure before OSC 52 fallback.tracing::debugon/dev/ttyopen failure before stdout fallback.Tests
~28 new tests covering:
/copyavailable during running taskManual testing performed on: