Preserve queued TUI input semantics (#39604)

## Why

Paste placeholders and deferred slash-command parsing can hide the meaning of an input until it is expanded or a session is configured. Draining such input as an ordinary message can accidentally treat pasted text beginning with `!` as a shell command or lose `/plan` behavior.

## What changed

- Track paste-expanded inputs that begin with `!` as literal model input through history, queue editing, retries, and startup queues.
- Defer `/plan` parsing when session or model state is unavailable, then submit it in plan mode with shell escapes disabled.
- Drain queued input only after session configuration, and keep later queued prompts pending when an initial prompt cannot be submitted.
- Suppress queue auto-send before applying a replayed thread session so input from the outgoing thread is not submitted during restoration.

## Testing

Add coverage for direct, recalled, edited, interrupted, rejected, startup-queued, and plan-mode paste flows, including placeholder collisions and rejected initial images.

GitOrigin-RevId: d482220d8df21754f166fea5b54e9945f622a978
This commit is contained in:
Eric Traut authored and copyberry committed 2026-08-20 05:49:49 +00:00
1 parent 8e7f646974
commit 3434c2545b
10 files changed
+519 -36

No files matched your search

@@ -149,7 +149,13 @@ impl ChatWidget {
self.transcript.saw_copy_source_this_turn = false;
self.refresh_skills_for_current_cwd(/*force_reload*/ true);
self.refresh_connector_mentions(/*force_refresh*/ false);
let initial_user_message_pending = self.initial_user_message.is_some();
self.submit_initial_user_message_if_pending();
if self.mcp_startup_status.is_none()
&& (!initial_user_message_pending || self.is_user_turn_pending_or_running())
{
self.maybe_send_next_queued_input();
}
if display == SessionConfiguredDisplay::Normal
&& let Some(forked_from_id) = forked_from_id
{