15 KiB
APP-4080: Use Latest User Prompt as Conversation Title in Tab Names
Problem
Vertical tabs currently resolve agent terminal row text in several places, and the Oz and plugin-backed CLI agent paths use different semantics.
Oz rows call through TerminalView::selected_conversation_display_title, which ultimately uses AIConversation::title(): generated task description, then an initial prompt-style fallback, then explicit fallback title. Plugin-backed CLI agent rows call CLIAgentSessionContext::display_title(), which currently prefers the latest plugin query over summary. The new setting needs to make this choice consistent:
- setting off/default: prefer conversation title/title-like metadata
- setting on: prefer latest user prompt
The technical work is to add the setting, expose it in AI settings, and route all vertical-tabs conversation-text call sites through shared resolution logic so row rendering, compact subtitles, detail sidecar, and search stay in sync.
Relevant code
specs/APP-4080/PRODUCT.md— source of truth for desired behavior.app/src/settings/ai.rs:551—AISettingsgroup where persisted AI settings are declared.app/src/settings/ai.rs:1246— existing user-visible AI setting with global sync policy, useful as the sync/default pattern.app/src/settings_view/ai_page.rs:1368— AI settings page widget registration order.app/src/settings_view/ai_page.rs:1870—AISettingsPageAction, where the toggle action should be added.app/src/settings_view/ai_page.rs:2461— settings action handling for persisted toggle settings.app/src/settings_view/ai_page.rs:4910—OtherAIWidget, currently home for conversation/display-adjacent AI settings such as conversation history and thinking display.app/src/terminal/view/pane_impl.rs:945— Oz conversation selection/title helpers for user-facing chrome.app/src/ai/agent/conversation.rs:1053—AIConversation::title(), the generated-title-first path.app/src/ai/agent/conversation.rs:1121—AIConversation::initial_query()andinitial_user_query()helpers.app/src/ai/agent/mod.rs:2478—AIAgentInput::user_query(), the canonical display text for user prompt-like inputs.app/src/terminal/cli_agent_sessions/mod.rs:39—CLIAgentSessionContextstores plugin metadata:summary,query, etc.app/src/terminal/cli_agent_sessions/mod.rs:93—CLIAgentSessionContext::display_title(), which currently prefersqueryoversummary.app/src/terminal/cli_agent_sessions/event/mod.rs:27andevent/v1.rs:53— CLI plugin payload parsing forqueryandsummary.app/src/workspace/view/vertical_tabs.rs:2217— vertical-tabs search text for terminal rows.app/src/workspace/view/vertical_tabs.rs:2237—terminal_primary_line_data()fallback chain.app/src/workspace/view/vertical_tabs.rs:2679— main terminal row command/conversation text rendering.app/src/workspace/view/vertical_tabs.rs:4079— detail sidecar terminal conversation text.app/src/workspace/view/vertical_tabs.rs:4613— compact subtitle command/conversation text.app/src/workspace/view/vertical_tabs_tests.rs:331— existing terminal primary-line precedence tests.app/src/terminal/cli_agent_sessions/mod_tests.rs:186— existing CLI session metadata tests.
Current state
vertical_tabs.rs repeats the same terminal-agent extraction pattern in at least four call sites:
terminal_pane_search_text_fragmentsrender_terminal_primary_line_for_viewrender_terminal_detail_section- the
VerticalTabsCompactSubtitle::Commandbranch inrender_compact_pane_row
Each call site:
- reads
CLIAgentSessionsModel::session(terminal_view.id()) - suppresses agent metadata for non-plugin-backed CLI sessions
- calls
terminal_view.selected_conversation_display_title(app)for Oz - calls
session.session_context.display_title()for plugin-backed CLI agents - passes both values into
terminal_primary_line_data()
terminal_primary_line_data() then prefers CLI agent text over Oz conversation text, and only falls through to terminal title, last completed command, and New session when no agent text is available.
This works for the current UI but makes APP-4080 easy to implement inconsistently because the new setting would otherwise need to be read at every duplicated extraction site.
Proposed changes
Add the setting
Add a new boolean setting to AISettings in app/src/settings/ai.rs.
Suggested setting:
- field:
use_latest_user_prompt_as_conversation_title_in_tab_names - generated setting type:
UseLatestUserPromptAsConversationTitleInTabNames - default:
false - supported platforms:
SupportedPlatforms::ALL - sync:
SyncToCloud::Globally(RespectUserSyncSetting::Yes) - private:
false - TOML path:
agents.display.use_latest_user_prompt_as_conversation_title_in_tab_names - description:
Whether agent tab names use the latest user prompt instead of the generated conversation title.
The sync policy should match similar user-visible display/AI behavior preferences. The setting affects the user's preferred navigation chrome rather than machine-local state, so global sync is preferable.
Add the settings UI
Add UseLatestUserPromptAsConversationTitleInTabNames to the imports in app/src/settings_view/ai_page.rs.
Add an action:
AISettingsPageAction::ToggleUseLatestUserPromptAsConversationTitleInTabNames
Handle the action by toggling AISettings::use_latest_user_prompt_as_conversation_title_in_tab_names with toggle_and_save_value(ctx) and notifying the view.
Render the toggle in OtherAIWidget, near Show conversation history in tools panel and Agent thinking display, because it is a conversation display/navigation preference rather than an input-routing behavior. Suggested label:
Use latest user prompt as conversation title in tab names
Suggested description:
Show the latest user prompt instead of the generated conversation title for Oz and third-party agent sessions in vertical tabs.
Because the setting also affects plugin-backed third-party CLI agent sessions, do not make editability depend exclusively on AISettings::is_any_ai_enabled(app). The row can still use standard AI settings styling, but the toggle should remain usable for users who keep Warp AI disabled while using third-party coding agents.
Centralize agent tab text resolution
Introduce a small private resolver in app/src/workspace/view/vertical_tabs.rs so every vertical-tabs call site uses the same setting semantics.
Suggested private types in vertical_tabs.rs:
AgentTabTextPreferencewith variantsConversationTitleandLatestUserPromptTerminalAgentTextcontaining:conversation_display_title: Option<String>cli_agent_title: Option<String>is_oz_agent: boolcli_agent: Option<CLIAgent>
Suggested helpers:
agent_tab_text_preference(app: &AppContext) -> AgentTabTextPreferenceterminal_agent_text(terminal_view: &TerminalView, app: &AppContext) -> TerminalAgentText
terminal_agent_text() should own the plugin-backed suppression rule:
- if a CLI agent session exists and
listener.is_none(), do not use CLI or Oz conversation metadata for the row; preserve current terminal fallback behavior - otherwise resolve Oz and CLI text according to the setting
terminal_primary_line_data() can remain as the pure fallback combiner once the agent text has been chosen. This avoids coupling it directly to settings or AppContext, and keeps existing unit tests easy to extend.
Oz conversation prompt support
Add a helper on AIConversation:
latest_user_prompt_for_tab_name(&self) -> Option<String>
Implementation should iterate root_task_exchanges() in reverse order and find the latest non-empty AIAgentInput::user_query() value. This uses the existing display-oriented prompt helper, which already accounts for slash-command-like inputs such as plan/orchestrate, create project, clone repository, code review, invoke skill, and accepted suggested prompts.
Keep AIConversation::title() unchanged. The new helper is only for the explicit latest-prompt tab-name setting.
Update TerminalView in app/src/terminal/view/pane_impl.rs with either:
- a new method such as
selected_conversation_tab_name_text(use_latest_user_prompt, ctx), or - a new private helper used by vertical tabs to access the selected
AIConversation
Prefer not to change existing pane-header behavior. selected_conversation_display_title() should keep serving pane headers and other existing chrome with title semantics.
CLI agent title/prompt support
Extend CLIAgentSessionContext with explicit methods instead of overloading display_title():
latest_user_prompt(&self) -> Option<String>returns trimmed non-emptyquerytitle_like_text(&self) -> Option<String>returns trimmed non-empty title-like metadata
Keep the preference application in vertical_tabs.rs rather than making terminal::cli_agent_sessions depend on a workspace/view-specific enum.
For the current protocol, summary is the only available title-like field. Use it as title_like_text() for APP-4080, but keep the method boundary explicit so a future plugin protocol can add a dedicated title field without touching vertical tabs again.
Do not use CLI agent text for non-plugin-backed detections. Those sessions should continue to fall through to terminal title/last command.
Update vertical tabs call sites
Replace duplicated local extraction with terminal_agent_text() in:
terminal_pane_search_text_fragmentsrender_terminal_primary_line_for_viewrender_terminal_detail_sectionrender_compact_pane_rowwhenVerticalTabsCompactSubtitle::Command
Search should include the visibly rendered setting-driven text. If adding the non-rendered counterpart is cheap after centralization, include both title-like text and latest prompt in terminal_search_text_fragments() for eligible agent rows, while keeping primary_text as the visible setting-driven text.
Keep generated schemas/settings in sync
Adding an AISettings field may require updating generated or checked-in settings schema artifacts if this repository expects those to change. Follow the existing settings workflow in the repo after implementation; do not hand-edit generated schema files unless that is the established pattern for this codebase.
End-to-end flow
- User opens Settings > AI and sees
Use latest user prompt as conversation title in tab namesdisabled by default. - Vertical tabs render a terminal row.
- The row asks
terminal_agent_text()for Oz/CLI conversation text. terminal_agent_text()readsAISettings::use_latest_user_prompt_as_conversation_title_in_tab_names.- For Oz:
- disabled: use
AIConversation::title(), with existing empty-conversation default title handling - enabled: use
AIConversation::latest_user_prompt_for_tab_name(), falling back to title/default text
- disabled: use
- For plugin-backed CLI agents:
- disabled: use
CLIAgentSessionContext::title_like_text(), falling back to latest prompt - enabled: use
CLIAgentSessionContext::latest_user_prompt(), falling back to title-like text
- disabled: use
terminal_primary_line_data()receives already-selected agent text and preserves the existing terminal fallback chain.- Row rendering, compact subtitles, detail sidecar, and search use the same selected text.
Diagram
flowchart TD
Settings[AISettings: use_latest_user_prompt_as_conversation_title_in_tab_names]
VTabs[vertical_tabs.rs terminal_agent_text]
Oz[TerminalView selected AIConversation]
CLI[CLIAgentSessionsModel session]
Primary[terminal_primary_line_data]
Surfaces[Row text / Compact subtitle / Detail sidecar / Search]
Settings --> VTabs
Oz --> VTabs
CLI --> VTabs
VTabs --> Primary
Primary --> Surfaces
Risks and mitigations
- Duplicated resolution stays inconsistent: centralize the setting read in one helper and make all four vertical-tabs call sites use it.
- Pane headers accidentally change: keep
TerminalView::selected_conversation_display_title()title-first for existing chrome; add a new tab-specific helper rather than changing the existing method globally. - Third-party
summarymay not be a durable title: hide this behindCLIAgentSessionContext::title_like_text()and treat a dedicated plugintitlepayload field as a follow-up if needed. - Global AI disabled blocks a third-party setting: make the UI toggle usable even when Warp AI is disabled, since the setting also affects plugin-backed third-party CLI sessions.
- Blank or whitespace text: trim and filter every title/prompt candidate before choosing it.
- Search mismatch: test that search includes the visible text after toggling. If both title and prompt are indexed, make sure rendered text still follows the setting.
Testing and validation
Add or update unit tests:
app/src/ai/agent/conversation.rstests forlatest_user_prompt_for_tab_name():- returns the latest non-empty user prompt across root-task exchanges
- ignores exchanges without user prompt text
- returns
Nonefor empty conversations
app/src/terminal/cli_agent_sessions/mod_tests.rstests forCLIAgentSessionContext:- default/title preference returns
summarybeforequery - latest-prompt preference returns
querybeforesummary - empty/whitespace values fall through
- default/title preference returns
app/src/workspace/view/vertical_tabs_tests.rstests for any pure helper added invertical_tabs.rs:- setting off: CLI title-like text wins over latest prompt
- setting on: latest prompt wins over title-like text
- missing preferred text falls back to the other agent text
- no plugin-backed metadata preserves terminal title/last-command fallback
app/src/settings/ai_tests.rsor an adjacent settings test:- new setting defaults to
false - setting persists through the normal settings machinery
- new setting defaults to
Manual validation:
- Default-off Oz conversation shows generated title in vertical tabs.
- Toggle on and send an Oz follow-up prompt; row, compact command subtitle, detail sidecar, and search reflect the latest prompt.
- Plugin-backed CLI agent with both summary and query shows summary/title-like text by default and query when toggled on.
- Non-plugin CLI detection and plain terminal rows keep existing fallback behavior.
Suggested targeted commands after implementation:
cargo test -p warp workspace::view::vertical_tabs_testscargo test -p warp terminal::cli_agent_sessions::mod_testscargo test -p warp settings::ai_tests
Do not run cargo fmt --all or file-specific cargo fmt; follow the repository formatting guidance when implementation changes are made.
Follow-ups
- Add an explicit
titlefield to the CLI agent plugin protocol ifsummaryis not sufficiently stable for tab names. - Consider telemetry for the setting toggle only if product wants adoption data; the implementation does not need telemetry to satisfy APP-4080.
- If vertical-tabs search indexes both rendered and non-rendered counterpart text, document that behavior in a small helper test so future changes do not accidentally regress discoverability.