Skip to content

Conversation streaming for the TUI#13057

Merged
harryalbert merged 2 commits into
masterfrom
harry/conversation-streaming-for-tui
Jun 29, 2026
Merged

Conversation streaming for the TUI#13057
harryalbert merged 2 commits into
masterfrom
harry/conversation-streaming-for-tui

Conversation

@harryalbert

@harryalbert harryalbert commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

This PR changes it so that the main cluster of conversation models (i.e. the BlocklistAIContextModel, BlocklistAIInputModel, BlocklistAIController, and BlocklistAIHistoryModel) necessary for conversation streaming and basic conversation management can be re-used by the TUI w/ no TUI or GUI specific code within said models (i.e. the models are not concerned w/ whether they are associated with a TUI or GUI surface).

Luckily enough, there's actually not much that needs to be changed within these models to get them working for the TUI and GUI in a surface-agnostic way. The major things that change are:

  • the models stop referencing a terminal_view_id: EntityId, replaced with terminal_surface_id: EntityId. This is where the majority of the LOC changed come from in this PR, and it should be a behavior no-op. Just want to make sure we're clear on our naming here.
  • the shared models no longer hold an AgentViewController, as this controller is GUI specific. What makes this a little tricky is that the AgentViewController was doing several jobs: it was telling the models whether an agent view was open or not, telling the models what conversation was currently selected, and emitting presentation lifecycle events consumed by the shared models. To handle these jobs, this PR adds an object-safe ConversationSelection abstraction that each surface implements and passes to the shared models behind a ConversationSelectionHandle. For GUI surfaces, AgentViewConversationSelection delegates presentation operations to AgentViewController and translates Agent View lifecycle events into events consumed by shared models. For TUI surfaces, TuiConversationSelection owns the pending conversation selection directly.

The history model also now distinguishes between transferring a conversation to a new terminal surface and simply marking an already-owned conversation as the active streaming target. This ensures automatic follow-ups don't accidentally transfer conversations between surfaces, while explicit transfers still clean up the previous renderer and selection.

Finally, this PR adds a one-shot --prompt runtime path to warp_tui, which streams a response directly back as output. I would just delete this as "testing code", but I actually think it's worthwhile to keep given our long-term plan of replacing the portion of the Oz CLI responsible for handling prompt submission with the TUI. The reusable conversation coordination lives in TuiConversationModel, while the one-shot stdout behavior lives in PromptStreamSurface.

This PR also adds a --conversation-id flag so that you can continue an existing conversation. This flag accepts a server conversation token, allowing the TUI to restore the corresponding conversation and test that follow-ups are WAI.

The TUI now uses the normal backing terminal server. To prevent this from recursively starting more TUI frontends, worker invocations are dispatched before TUI frontend arguments are parsed.

Reviewing this PR

A lot of this PR is no-op field name changes or simple call structure changes that aren't worth reading. The files worth digging into are:

  1. The tech spec for an over-arching tech implementation guide
    specs/conversation-streaming-for-tui/TECH.md

  2. The new ConversationSelection abstraction and its GUI/TUI implementations
    app/src/ai/blocklist/conversation_selection.rs
    app/src/ai/blocklist/agent_view/conversation_selection.rs
    crates/warp_tui/src/conversation_selection.rs

  3. The existing models migrated onto that abstraction. These are skim-able IMO but worth looking at just to see how the abstractions introduced in this PR are used in action.
    app/src/ai/blocklist/context_model.rs
    app/src/ai/blocklist/input_model.rs
    app/src/ai/blocklist/controller.rs

  4. The history-model behavioral portions (again, skim-able)
    app/src/ai/blocklist/history_model.rs
    ◦ Do not read all of the changed lines sequentially; most are renames. Focus on:
    set_active_conversation_id
    mark_active_conversation_id
    restore_conversations
    ▪ clear/remove/delete behavior
    terminal_surface_id_for_conversation
    ConversationTransferredBetweenTerminalSurfaces
    BlocklistAIHistoryEvent::terminal_surface_id
    ◦ The important behavior is conversation transfer between surfaces and ensuring the previous renderer/selection is cleaned up.

  5. The TUI conversation model
    crates/warp_tui/src/conversation_model.rs

  6. The TUI presentation streaming adapter
    crates/warp_tui/src/prompt_stream.rs
    ◦ Focus on:
    ▪ model cluster construction
    ▪ retained surface/manager lifetimes
    ▪ stdout snapshot behavior
    ▪ tool-action failure behavior
    ▪ final-status and error termination

  7. The bug fix around preventing a fork bomb while allowing us to spin up a backing terminal server
    app/src/lib.rs
    app/src/tui.rs
    crates/warp_tui/src/lib.rs
    crates/warp_cli/src/lib.rsis_worker_invocation
    crates/warp_tui/tests/worker_dispatch.rs

Testing

  • I have manually tested my changes locally with ./script/run

Screenshots / Videos

https://www.loom.com/share/2ecdc7fa6ee54bcab0b4183a237d12af

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

@cla-bot cla-bot Bot added the cla-signed label Jun 25, 2026

harryalbert commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@harryalbert harryalbert force-pushed the harry/conversation-streaming-for-tui branch 4 times, most recently from 5953228 to d9b26c6 Compare June 26, 2026 14:46
@harryalbert harryalbert marked this pull request as ready for review June 26, 2026 14:46
@harryalbert harryalbert requested a review from kevinyang372 June 26, 2026 14:46
@oz-for-oss

oz-for-oss Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@harryalbert

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@harryalbert harryalbert changed the title add tech spec Conversation streaming for the TUI Jun 26, 2026

@oz-for-oss oz-for-oss Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR refactors the Blocklist AI conversation models around terminal-surface-scoped selection/history, adds a TUI conversation coordinator, and introduces one-shot warp_tui --prompt streaming with worker-dispatch support.

Concerns

  • The TUI prompt stream treats every status other than InProgress as terminal success, which can prematurely stop transient recovery, waiting, blocked, error, and cancellation states while returning a successful CLI exit.
  • This is a user-facing TUI behavior change, but the PR description does not include screenshots or a screen recording. For this user-facing change, please include screenshots or a screen recording demonstrating it working end to end.

Verdict

Found: 0 critical, 2 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread crates/warp_tui/src/prompt_stream.rs Outdated
update: ConversationStatusUpdate::Changed { .. },
} => {
self.print_stream_snapshot(*conversation_id, ctx);
if !status.is_in_progress() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [IMPORTANT] !status.is_in_progress() treats TransientError, WaitingForEvents, and Blocked as final and terminates with None, so retries/waits/tool-blocked/error states can be cut off while the CLI exits successfully; gate completion on terminal statuses and surface non-success statuses as errors.

Comment thread crates/warp_tui/src/conversation_model.rs
Comment thread app/src/ai/blocklist/conversation_selection_model.rs Outdated
Comment thread app/src/ai/blocklist/conversation_selection.rs Outdated
Comment thread app/src/ai/blocklist/conversation_selection.rs Outdated
Comment thread app/src/ai/blocklist/conversation_selection.rs Outdated
Comment thread app/src/ai/blocklist/agent_view/conversation_selection.rs Outdated
Comment thread app/src/ai/blocklist/conversation_selection.rs Outdated
Comment thread crates/warp_tui/src/conversation_selection.rs Outdated
@harryalbert harryalbert force-pushed the harry/conversation-streaming-for-tui branch 2 times, most recently from 5519561 to 976ddc0 Compare June 29, 2026 00:19
harryalbert and others added 2 commits June 28, 2026 20:19
Squashed implementation of conversation streaming in the headless warp_tui crate.

Co-Authored-By: Oz <oz-agent@warp.dev>
@harryalbert harryalbert force-pushed the harry/conversation-streaming-for-tui branch from 976ddc0 to 5aa2c63 Compare June 29, 2026 00:21
@harryalbert harryalbert merged commit 148c811 into master Jun 29, 2026
26 checks passed
@harryalbert harryalbert deleted the harry/conversation-streaming-for-tui branch June 29, 2026 14:09
dagmfactory pushed a commit that referenced this pull request Jun 30, 2026
## Description
<!-- Please remember to add your design buddy onto the PR for review, if
it contains any UI changes! -->

This PR changes it so that the main cluster of conversation models (i.e.
the `BlocklistAIContextModel`, `BlocklistAIInputModel`,
`BlocklistAIController`, and `BlocklistAIHistoryModel`) necessary for
conversation streaming and basic conversation management can be re-used
by the `TUI` w/ no `TUI` or `GUI` specific code within said models (i.e.
the models are not concerned w/ whether they are associated with a `TUI`
or `GUI` surface).

Luckily enough, there's actually not much that needs to be changed
within these models to get them working for the TUI and GUI in a
surface-agnostic way. The major things that change are:
* the models stop referencing a `terminal_view_id: EntityId`, replaced
with `terminal_surface_id: EntityId`. This is where the majority of the
LOC changed come from in this PR, and it should be a behavior no-op.
Just want to make sure we're clear on our naming here.
* the shared models no longer hold an `AgentViewController`, as this
controller is GUI specific. What makes this a little tricky is that the
`AgentViewController` was doing several jobs: it was telling the models
whether an agent view was open or not, telling the models what
conversation was currently selected, and emitting presentation lifecycle
events consumed by the shared models. To handle these jobs, this PR adds
an object-safe `ConversationSelection` abstraction that each surface
implements and passes to the shared models behind a
`ConversationSelectionHandle`. For `GUI` surfaces,
`AgentViewConversationSelection` delegates presentation operations to
`AgentViewController` and translates Agent View lifecycle events into
events consumed by shared models. For `TUI` surfaces,
`TuiConversationSelection` owns the pending conversation selection
directly.

The history model also now distinguishes between transferring a
conversation to a new terminal surface and simply marking an
already-owned conversation as the active streaming target. This ensures
automatic follow-ups don't accidentally transfer conversations between
surfaces, while explicit transfers still clean up the previous renderer
and selection.

Finally, this PR adds a one-shot `--prompt` runtime path to `warp_tui`,
which streams a response directly back as output. I would just delete
this as "testing code", but I actually think it's worthwhile to keep
given our long-term plan of replacing the portion of the Oz CLI
responsible for handling prompt submission with the TUI. The reusable
conversation coordination lives in `TuiConversationModel`, while the
one-shot stdout behavior lives in `PromptStreamSurface`.

This PR also adds a `--conversation-id` flag so that you can continue an
existing conversation. This flag accepts a server conversation token,
allowing the TUI to restore the corresponding conversation and test that
follow-ups are WAI.

The TUI now uses the normal backing terminal server. To prevent this
from recursively starting more TUI frontends, worker invocations are
dispatched before TUI frontend arguments are parsed.

## Reviewing this PR
A lot of this PR is no-op field name changes or simple call structure
changes that aren't worth reading. The files worth digging into are:

1. The tech spec for an over-arching tech implementation guide
◦ `specs/conversation-streaming-for-tui/TECH.md`

2. The new `ConversationSelection` abstraction and its GUI/TUI
implementations
◦ `app/src/ai/blocklist/conversation_selection.rs`
◦ `app/src/ai/blocklist/agent_view/conversation_selection.rs`
◦ `crates/warp_tui/src/conversation_selection.rs`

3. The existing models migrated onto that abstraction. These are
skim-able IMO but worth looking at just to see how the abstractions
introduced in this PR are used in action.
◦ `app/src/ai/blocklist/context_model.rs`
◦ `app/src/ai/blocklist/input_model.rs`
◦ `app/src/ai/blocklist/controller.rs`

4. The history-model behavioral portions (again, skim-able)
◦ `app/src/ai/blocklist/history_model.rs`
◦ Do not read all of the changed lines sequentially; most are renames.
Focus on:
   ▪ `set_active_conversation_id`
   ▪ `mark_active_conversation_id`
   ▪ `restore_conversations`
   ▪ clear/remove/delete behavior
   ▪ `terminal_surface_id_for_conversation`
   ▪ `ConversationTransferredBetweenTerminalSurfaces`
   ▪ `BlocklistAIHistoryEvent::terminal_surface_id`
◦ The important behavior is conversation transfer between surfaces and
ensuring the previous renderer/selection is cleaned up.

5. The TUI conversation model
◦ `crates/warp_tui/src/conversation_model.rs`

6. The TUI presentation streaming adapter
◦ `crates/warp_tui/src/prompt_stream.rs`
◦ Focus on:
▪ model cluster construction
▪ retained surface/manager lifetimes
▪ stdout snapshot behavior
▪ tool-action failure behavior
▪ final-status and error termination

7. The bug fix around preventing a fork bomb while allowing us to spin
up a backing terminal server
◦ `app/src/lib.rs`
◦ `app/src/tui.rs`
◦ `crates/warp_tui/src/lib.rs`
◦ `crates/warp_cli/src/lib.rs` — `is_worker_invocation`
◦ `crates/warp_tui/tests/worker_dispatch.rs`

## Testing
<!--
How did you test this change? What automated tests did you add? If you
didn't add any new tests, what's your justification for not adding any?

Manual testing is required for changes that can be manually tested, and
almost all changes can be manually tested. If your change can be
manually tested, please include screenshots or a screen recording that
show it working end to end.

You can run the app locally using `./script/run` - see AGENTS.md for
more details on how to get set up.
-->

- [x] I have manually tested my changes locally with `./script/run`

### Screenshots / Videos
<!-- Attach screenshots or a short video demonstrating the change, where
appropriate. Remove this section if it is not relevant to your PR. -->

https://www.loom.com/share/2ecdc7fa6ee54bcab0b4183a237d12af

## Agent Mode
- [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode

---------

Co-authored-by: Oz <oz-agent@warp.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants