commit 315af1c4839489d37ea426625f4365cf1c879e68
parent 69f29f1194054b3aa010b359f01af84b0c4c33e1
Author: William Casarin <jb55@jb55.com>
Date: Mon, 9 Feb 2026 10:21:23 -0800
dave: add AiMode enum for Chat vs Agentic modes
Introduce AiMode enum to differentiate between simple chat (OpenAI) and
full agentic IDE (Claude) experiences. In Chat mode: skip directory picker,
hide CWD/status bars, disable agentic keybindings. Mode is determined by
backend type and stored per-session for future mixed-mode support.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
6 files changed, 198 insertions(+), 104 deletions(-)
diff --git a/crates/notedeck_dave/src/config.rs b/crates/notedeck_dave/src/config.rs
@@ -1,6 +1,15 @@
use crate::backend::BackendType;
use async_openai::config::OpenAIConfig;
+/// AI interaction mode - determines UI complexity and feature set
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum AiMode {
+ /// Simple chat interface (OpenAI-style) - no permissions, no CWD, no scene view
+ Chat,
+ /// Full IDE with permissions, sessions, scene view, etc. (Claude backend)
+ Agentic,
+}
+
/// Available AI providers for Dave
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum AiProvider {
@@ -208,6 +217,13 @@ impl Default for ModelConfig {
}
impl ModelConfig {
+ pub fn ai_mode(&self) -> AiMode {
+ match self.backend {
+ BackendType::Claude => AiMode::Agentic,
+ BackendType::OpenAI => AiMode::Chat,
+ }
+ }
+
pub fn model(&self) -> &str {
&self.model
}
diff --git a/crates/notedeck_dave/src/lib.rs b/crates/notedeck_dave/src/lib.rs
@@ -30,7 +30,7 @@ use std::sync::Arc;
use std::time::Instant;
pub use avatar::DaveAvatar;
-pub use config::{AiProvider, DaveSettings, ModelConfig};
+pub use config::{AiMode, AiProvider, DaveSettings, ModelConfig};
pub use messages::{
AskUserQuestionInput, DaveApiResponse, Message, PermissionResponse, PermissionResponseType,
QuestionAnswer, SessionInfo, SubagentInfo, SubagentStatus, ToolResult,
@@ -60,6 +60,8 @@ pub enum DaveOverlay {
}
pub struct Dave {
+ /// AI interaction mode (Chat vs Agentic)
+ ai_mode: AiMode,
/// Manages multiple chat sessions
session_manager: SessionManager,
/// A 3d representation of dave.
@@ -144,6 +146,9 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
let model_config = ModelConfig::default();
//let model_config = ModelConfig::ollama();
+ // Determine AI mode from backend type
+ let ai_mode = model_config.ai_mode();
+
// Create backend based on configuration
let backend: Box<dyn AiBackend> = match model_config.backend {
BackendType::OpenAI => {
@@ -173,10 +178,23 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
// Create IPC listener for external spawn-agent commands
let ipc_listener = ipc::create_listener(ctx);
+ // In Chat mode, create a default session immediately and skip directory picker
+ // In Agentic mode, show directory picker on startup
+ let (session_manager, active_overlay) = match ai_mode {
+ AiMode::Chat => {
+ let mut manager = SessionManager::new();
+ // Create a default session with current directory
+ manager.new_session(std::env::current_dir().unwrap_or_default(), ai_mode);
+ (manager, DaveOverlay::None)
+ }
+ AiMode::Agentic => (SessionManager::new(), DaveOverlay::DirectoryPicker),
+ };
+
Dave {
+ ai_mode,
backend,
avatar,
- session_manager: SessionManager::new(),
+ session_manager,
tools: Arc::new(tools),
model_config,
show_session_list: false,
@@ -190,8 +208,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
home_session: None,
directory_picker,
session_picker: SessionPicker::new(),
- // Auto-show directory picker on startup since there are no sessions
- active_overlay: DaveOverlay::DirectoryPicker,
+ active_overlay,
ipc_listener,
}
}
@@ -581,6 +598,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
&session.chat,
&mut session.input,
&mut session.focus_requested,
+ session.ai_mode,
)
.compact(true)
.is_working(is_working)
@@ -664,21 +682,23 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
.fill(ui.visuals().faint_bg_color)
.inner_margin(egui::Margin::symmetric(8, 12))
.show(ui, |ui| {
- // Add scene view toggle button
- ui.horizontal(|ui| {
- if ui
- .button("Scene View")
- .on_hover_text("Ctrl+L to toggle views")
- .clicked()
- {
- self.show_scene = true;
- }
- if ctrl_held {
- ui::keybind_hint(ui, "L");
- }
- });
- ui.separator();
- SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held)
+ // Add scene view toggle button - only in Agentic mode
+ if self.ai_mode == AiMode::Agentic {
+ ui.horizontal(|ui| {
+ if ui
+ .button("Scene View")
+ .on_hover_text("Ctrl+L to toggle views")
+ .clicked()
+ {
+ self.show_scene = true;
+ }
+ if ctrl_held {
+ ui::keybind_hint(ui, "L");
+ }
+ });
+ ui.separator();
+ }
+ SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held, self.ai_mode)
.ui(ui)
})
.inner
@@ -699,6 +719,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
&session.chat,
&mut session.input,
&mut session.focus_requested,
+ session.ai_mode,
)
.is_working(is_working)
.interrupt_pending(interrupt_pending)
@@ -741,7 +762,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
.fill(ui.visuals().faint_bg_color)
.inner_margin(egui::Margin::symmetric(8, 12))
.show(ui, |ui| {
- SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held).ui(ui)
+ SessionListUi::new(&self.session_manager, &self.focus_queue, ctrl_held, self.ai_mode).ui(ui)
})
.inner;
if let Some(action) = session_action {
@@ -773,6 +794,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
&session.chat,
&mut session.input,
&mut session.focus_requested,
+ session.ai_mode,
)
.is_working(is_working)
.interrupt_pending(interrupt_pending)
@@ -800,7 +822,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
// Add to recent directories
self.directory_picker.add_recent(cwd.clone());
- let id = self.session_manager.new_session(cwd);
+ let id = self.session_manager.new_session(cwd, self.ai_mode);
// Request focus on the new session's input
if let Some(session) = self.session_manager.get_mut(id) {
session.focus_requested = true;
@@ -824,7 +846,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
let id = self
.session_manager
- .new_resumed_session(cwd, resume_session_id, title);
+ .new_resumed_session(cwd, resume_session_id, title, self.ai_mode);
// Request focus on the new session's input
if let Some(session) = self.session_manager.get_mut(id) {
session.focus_requested = true;
@@ -852,7 +874,7 @@ You are an AI agent for the nostr protocol called Dave, created by Damus. nostr
// Drain all pending connections (non-blocking)
while let Some(mut pending) = listener.try_recv() {
// Create the session and get its ID
- let id = self.session_manager.new_session(pending.cwd.clone());
+ let id = self.session_manager.new_session(pending.cwd.clone(), self.ai_mode);
self.directory_picker.add_recent(pending.cwd);
// Focus on new session
@@ -1599,6 +1621,7 @@ impl notedeck::App for Dave {
has_pending_permission,
has_pending_question,
in_tentative_state,
+ self.ai_mode,
) {
match key_action {
KeyAction::AcceptPermission => {
diff --git a/crates/notedeck_dave/src/session.rs b/crates/notedeck_dave/src/session.rs
@@ -3,6 +3,7 @@ use std::path::PathBuf;
use std::sync::mpsc::Receiver;
use crate::agent_status::AgentStatus;
+use crate::config::AiMode;
use crate::messages::{
CompactionInfo, PermissionResponse, QuestionAnswer, SessionInfo, SubagentStatus,
};
@@ -63,6 +64,8 @@ pub struct ChatSession {
/// Claude session ID to resume (UUID from Claude CLI's session storage)
/// When set, the backend will use --resume to continue this session
pub resume_session_id: Option<String>,
+ /// AI interaction mode for this session (Chat vs Agentic)
+ pub ai_mode: AiMode,
}
impl Drop for ChatSession {
@@ -74,7 +77,7 @@ impl Drop for ChatSession {
}
impl ChatSession {
- pub fn new(id: SessionId, cwd: PathBuf) -> Self {
+ pub fn new(id: SessionId, cwd: PathBuf, ai_mode: AiMode) -> Self {
// Arrange sessions in a grid pattern
let col = (id as i32 - 1) % 4;
let row = (id as i32 - 1) / 4;
@@ -102,6 +105,7 @@ impl ChatSession {
is_compacting: false,
last_compaction: None,
resume_session_id: None,
+ ai_mode,
}
}
@@ -111,8 +115,9 @@ impl ChatSession {
cwd: PathBuf,
resume_session_id: String,
title: String,
+ ai_mode: AiMode,
) -> Self {
- let mut session = Self::new(id, cwd);
+ let mut session = Self::new(id, cwd, ai_mode);
session.resume_session_id = Some(resume_session_id);
session.title = title;
session
@@ -229,11 +234,11 @@ impl SessionManager {
}
/// Create a new session with the given cwd and make it active
- pub fn new_session(&mut self, cwd: PathBuf) -> SessionId {
+ pub fn new_session(&mut self, cwd: PathBuf, ai_mode: AiMode) -> SessionId {
let id = self.next_id;
self.next_id += 1;
- let session = ChatSession::new(id, cwd);
+ let session = ChatSession::new(id, cwd, ai_mode);
self.sessions.insert(id, session);
self.order.insert(0, id); // Most recent first
self.active = Some(id);
@@ -247,11 +252,12 @@ impl SessionManager {
cwd: PathBuf,
resume_session_id: String,
title: String,
+ ai_mode: AiMode,
) -> SessionId {
let id = self.next_id;
self.next_id += 1;
- let session = ChatSession::new_resumed(id, cwd, resume_session_id, title);
+ let session = ChatSession::new_resumed(id, cwd, resume_session_id, title, ai_mode);
self.sessions.insert(id, session);
self.order.insert(0, id); // Most recent first
self.active = Some(id);
diff --git a/crates/notedeck_dave/src/ui/dave.rs b/crates/notedeck_dave/src/ui/dave.rs
@@ -3,7 +3,7 @@ use super::diff;
use super::query_ui::query_call_ui;
use super::top_buttons::top_buttons_ui;
use crate::{
- config::DaveSettings,
+ config::{AiMode, DaveSettings},
file_update::FileUpdate,
messages::{
AskUserQuestionInput, CompactionInfo, Message, PermissionRequest, PermissionResponse,
@@ -40,6 +40,8 @@ pub struct DaveUi<'a> {
is_compacting: bool,
/// Whether auto-steal focus mode is active
auto_steal_focus: bool,
+ /// AI interaction mode (Chat vs Agentic)
+ ai_mode: AiMode,
}
/// The response the app generates. The response contains an optional
@@ -121,6 +123,7 @@ impl<'a> DaveUi<'a> {
chat: &'a [Message],
input: &'a mut String,
focus_requested: &'a mut bool,
+ ai_mode: AiMode,
) -> Self {
DaveUi {
trial,
@@ -137,6 +140,7 @@ impl<'a> DaveUi<'a> {
question_index: None,
is_compacting: false,
auto_steal_focus: false,
+ ai_mode,
}
}
@@ -275,6 +279,8 @@ impl<'a> DaveUi<'a> {
/// Render a chat message (user, assistant, tool call/response, etc)
fn render_chat(&mut self, ctx: &mut AppContext, ui: &mut egui::Ui) -> DaveResponse {
let mut response = DaveResponse::default();
+ let is_agentic = self.ai_mode == AiMode::Agentic;
+
for message in self.chat {
match message {
Message::Error(err) => {
@@ -299,24 +305,36 @@ impl<'a> DaveUi<'a> {
}
}
Message::PermissionRequest(request) => {
- if let Some(action) = self.permission_request_ui(request, ui) {
- response = DaveResponse::new(action);
+ // Permission requests only in Agentic mode
+ if is_agentic {
+ if let Some(action) = self.permission_request_ui(request, ui) {
+ response = DaveResponse::new(action);
+ }
}
}
Message::ToolResult(result) => {
- Self::tool_result_ui(result, ui);
+ // Tool results only in Agentic mode
+ if is_agentic {
+ Self::tool_result_ui(result, ui);
+ }
}
Message::CompactionComplete(info) => {
- Self::compaction_complete_ui(info, ui);
+ // Compaction only in Agentic mode
+ if is_agentic {
+ Self::compaction_complete_ui(info, ui);
+ }
}
Message::Subagent(info) => {
- Self::subagent_ui(info, ui);
+ // Subagents only in Agentic mode
+ if is_agentic {
+ Self::subagent_ui(info, ui);
+ }
}
};
}
// Show status line at the bottom of chat when working or compacting
- let status_text = if self.is_compacting {
+ let status_text = if is_agentic && self.is_compacting {
Some("compacting...")
} else if self.is_working {
Some("computing...")
@@ -971,34 +989,38 @@ impl<'a> DaveUi<'a> {
dave_response = DaveResponse::send();
}
- // Show plan mode indicator with optional keybind hint when Ctrl is held
- let ctrl_held = ui.input(|i| i.modifiers.ctrl);
- let mut plan_badge =
- super::badge::StatusBadge::new("PLAN").variant(if self.plan_mode_active {
- super::badge::BadgeVariant::Info
- } else {
- super::badge::BadgeVariant::Default
- });
- if ctrl_held {
- plan_badge = plan_badge.keybind("M");
- }
- plan_badge
- .show(ui)
- .on_hover_text("Ctrl+M to toggle plan mode");
-
- // Show auto-steal focus indicator
- let mut auto_badge =
- super::badge::StatusBadge::new("AUTO").variant(if self.auto_steal_focus {
- super::badge::BadgeVariant::Info
- } else {
- super::badge::BadgeVariant::Default
- });
- if ctrl_held {
- auto_badge = auto_badge.keybind("⎵");
+ // Show plan mode and auto-steal indicators only in Agentic mode
+ if self.ai_mode == AiMode::Agentic {
+ let ctrl_held = ui.input(|i| i.modifiers.ctrl);
+
+ // Plan mode indicator with optional keybind hint when Ctrl is held
+ let mut plan_badge =
+ super::badge::StatusBadge::new("PLAN").variant(if self.plan_mode_active {
+ super::badge::BadgeVariant::Info
+ } else {
+ super::badge::BadgeVariant::Default
+ });
+ if ctrl_held {
+ plan_badge = plan_badge.keybind("M");
+ }
+ plan_badge
+ .show(ui)
+ .on_hover_text("Ctrl+M to toggle plan mode");
+
+ // Auto-steal focus indicator
+ let mut auto_badge =
+ super::badge::StatusBadge::new("AUTO").variant(if self.auto_steal_focus {
+ super::badge::BadgeVariant::Info
+ } else {
+ super::badge::BadgeVariant::Default
+ });
+ if ctrl_held {
+ auto_badge = auto_badge.keybind("⎵");
+ }
+ auto_badge
+ .show(ui)
+ .on_hover_text("Ctrl+Space to toggle auto-focus mode");
}
- auto_badge
- .show(ui)
- .on_hover_text("Ctrl+Space to toggle auto-focus mode");
let r = ui.add(
egui::TextEdit::multiline(self.input)
diff --git a/crates/notedeck_dave/src/ui/keybindings.rs b/crates/notedeck_dave/src/ui/keybindings.rs
@@ -1,3 +1,4 @@
+use crate::config::AiMode;
use egui::Key;
/// Keybinding actions that can be triggered globally
@@ -46,14 +47,18 @@ pub enum KeyAction {
/// Check for keybinding actions.
/// Most keybindings use Ctrl modifier to avoid conflicts with text input.
/// Exception: 1/2 for permission responses work without Ctrl but only when no text input has focus.
+/// In Chat mode, agentic-specific keybindings (scene view, plan mode, focus queue) are disabled.
pub fn check_keybindings(
ctx: &egui::Context,
has_pending_permission: bool,
has_pending_question: bool,
in_tentative_state: bool,
+ ai_mode: AiMode,
) -> Option<KeyAction> {
- // Escape in tentative state cancels the tentative mode
- if in_tentative_state && ctx.input(|i| i.key_pressed(Key::Escape)) {
+ let is_agentic = ai_mode == AiMode::Agentic;
+
+ // Escape in tentative state cancels the tentative mode (agentic only)
+ if is_agentic && in_tentative_state && ctx.input(|i| i.key_pressed(Key::Escape)) {
return Some(KeyAction::CancelTentative);
}
@@ -65,7 +70,7 @@ pub fn check_keybindings(
let ctrl = egui::Modifiers::CTRL;
let ctrl_shift = egui::Modifiers::CTRL | egui::Modifiers::SHIFT;
- // Ctrl+Tab / Ctrl+Shift+Tab for cycling through agents
+ // Ctrl+Tab / Ctrl+Shift+Tab for cycling through agents/chats
// Works even with text input focus since Ctrl modifier makes it unambiguous
// IMPORTANT: Check Ctrl+Shift+Tab first because consume_key uses matches_logically
// which ignores extra Shift, so Ctrl+Tab would consume Ctrl+Shift+Tab otherwise
@@ -81,28 +86,31 @@ pub fn check_keybindings(
return Some(action);
}
- // Ctrl+N for higher priority (toward NeedsInput)
- if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::N)) {
- return Some(KeyAction::FocusQueueNext);
- }
+ // Focus queue navigation - agentic only
+ if is_agentic {
+ // Ctrl+N for higher priority (toward NeedsInput)
+ if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::N)) {
+ return Some(KeyAction::FocusQueueNext);
+ }
- // Ctrl+P for lower priority (toward Done)
- if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::P)) {
- return Some(KeyAction::FocusQueuePrev);
+ // Ctrl+P for lower priority (toward Done)
+ if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::P)) {
+ return Some(KeyAction::FocusQueuePrev);
+ }
}
- // Ctrl+Shift+T to clone the active agent (check before Ctrl+T)
- if ctx.input(|i| i.modifiers.matches_exact(ctrl_shift) && i.key_pressed(Key::T)) {
+ // Ctrl+Shift+T to clone the active agent (check before Ctrl+T) - agentic only
+ if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl_shift) && i.key_pressed(Key::T)) {
return Some(KeyAction::CloneAgent);
}
- // Ctrl+T to spawn a new agent
+ // Ctrl+T to spawn a new agent/chat
if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::T)) {
return Some(KeyAction::NewAgent);
}
- // Ctrl+L to toggle between scene view and list view
- if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::L)) {
+ // Ctrl+L to toggle between scene view and list view - agentic only
+ if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::L)) {
return Some(KeyAction::ToggleView);
}
@@ -111,18 +119,18 @@ pub fn check_keybindings(
return Some(KeyAction::OpenExternalEditor);
}
- // Ctrl+M to toggle plan mode
- if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::M)) {
+ // Ctrl+M to toggle plan mode - agentic only
+ if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::M)) {
return Some(KeyAction::TogglePlanMode);
}
- // Ctrl+D to toggle Done status for current focus queue item
- if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::D)) {
+ // Ctrl+D to toggle Done status for current focus queue item - agentic only
+ if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::D)) {
return Some(KeyAction::FocusQueueToggleDone);
}
- // Ctrl+Space to toggle auto-steal focus mode
- if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::Space)) {
+ // Ctrl+Space to toggle auto-steal focus mode - agentic only
+ if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::Space)) {
return Some(KeyAction::ToggleAutoSteal);
}
@@ -131,7 +139,7 @@ pub fn check_keybindings(
return Some(KeyAction::DeleteActiveSession);
}
- // Ctrl+1-9 for switching agents (works even with text input focus)
+ // Ctrl+1-9 for switching agents/chats (works even with text input focus)
// Check this BEFORE permission bindings so Ctrl+number always switches agents
if let Some(action) = ctx.input(|i| {
if !i.modifiers.matches_exact(ctrl) {
@@ -162,6 +170,7 @@ pub fn check_keybindings(
return Some(action);
}
+ // Permission keybindings - agentic only
// When there's a pending permission (but NOT an AskUserQuestion):
// - 1 = accept, 2 = deny (no modifiers)
// - Shift+1 = tentative accept, Shift+2 = tentative deny (for adding message)
@@ -169,7 +178,7 @@ pub fn check_keybindings(
// IMPORTANT: Only handle these when no text input has focus, to avoid
// capturing keypresses when user is typing a message in tentative state
// AskUserQuestion uses number keys for option selection, so we skip these bindings
- if has_pending_permission && !has_pending_question && !ctx.wants_keyboard_input() {
+ if is_agentic && has_pending_permission && !has_pending_question && !ctx.wants_keyboard_input() {
// Shift+1 = tentative accept, Shift+2 = tentative deny
// Note: egui may report shifted keys as their symbol (e.g., Shift+1 as Exclamationmark)
// We check for both the symbol key and Shift+Num key to handle different behaviors
diff --git a/crates/notedeck_dave/src/ui/session_list.rs b/crates/notedeck_dave/src/ui/session_list.rs
@@ -4,6 +4,7 @@ use egui::{Align, Color32, Layout, Sense};
use notedeck_ui::app_images;
use crate::agent_status::AgentStatus;
+use crate::config::AiMode;
use crate::focus_queue::{FocusPriority, FocusQueue};
use crate::session::{SessionId, SessionManager};
use crate::ui::keybind_hint::paint_keybind_hint;
@@ -21,6 +22,7 @@ pub struct SessionListUi<'a> {
session_manager: &'a SessionManager,
focus_queue: &'a FocusQueue,
ctrl_held: bool,
+ ai_mode: AiMode,
}
impl<'a> SessionListUi<'a> {
@@ -28,11 +30,13 @@ impl<'a> SessionListUi<'a> {
session_manager: &'a SessionManager,
focus_queue: &'a FocusQueue,
ctrl_held: bool,
+ ai_mode: AiMode,
) -> Self {
SessionListUi {
session_manager,
focus_queue,
ctrl_held,
+ ai_mode,
}
}
@@ -62,9 +66,15 @@ impl<'a> SessionListUi<'a> {
fn header_ui(&self, ui: &mut egui::Ui) -> Option<SessionListAction> {
let mut action = None;
+ // Header text and tooltip depend on mode
+ let (header_text, new_tooltip) = match self.ai_mode {
+ AiMode::Chat => ("Chats", "New Chat"),
+ AiMode::Agentic => ("Agents", "New Agent"),
+ };
+
ui.horizontal(|ui| {
ui.add_space(4.0);
- ui.label(egui::RichText::new("Agents").size(18.0).strong());
+ ui.label(egui::RichText::new(header_text).size(18.0).strong());
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
let icon = app_images::new_message_image()
@@ -74,7 +84,7 @@ impl<'a> SessionListUi<'a> {
if ui
.add(icon)
.on_hover_cursor(egui::CursorIcon::PointingHand)
- .on_hover_text("New Agent")
+ .on_hover_text(new_tooltip)
.clicked()
{
action = Some(SessionListAction::NewSession);
@@ -138,8 +148,12 @@ impl<'a> SessionListUi<'a> {
status: AgentStatus,
queue_priority: Option<FocusPriority>,
) -> egui::Response {
- // Always use taller height since cwd is always present
- let item_height = 48.0;
+ // In Chat mode: shorter height (no CWD), no status bar
+ // In Agentic mode: taller height with CWD and status bar
+ let show_cwd = self.ai_mode == AiMode::Agentic;
+ let show_status_bar = self.ai_mode == AiMode::Agentic;
+
+ let item_height = if show_cwd { 48.0 } else { 32.0 };
let desired_size = egui::vec2(ui.available_width(), item_height);
let (rect, response) = ui.allocate_exact_size(desired_size, Sense::click());
let hover_text = format!("Ctrl+{} to switch", shortcut_hint.unwrap_or(0));
@@ -159,16 +173,18 @@ impl<'a> SessionListUi<'a> {
let corner_radius = 8.0;
ui.painter().rect_filled(rect, corner_radius, fill);
- // Status color indicator (left edge vertical bar)
- let status_color = status.color();
- let status_bar_rect = egui::Rect::from_min_size(
- rect.left_top() + egui::vec2(2.0, 4.0),
- egui::vec2(3.0, rect.height() - 8.0),
- );
- ui.painter().rect_filled(status_bar_rect, 1.5, status_color);
-
- // Left padding (room for status bar)
- let text_start_x = 12.0;
+ // Status color indicator (left edge vertical bar) - only in Agentic mode
+ let text_start_x = if show_status_bar {
+ let status_color = status.color();
+ let status_bar_rect = egui::Rect::from_min_size(
+ rect.left_top() + egui::vec2(2.0, 4.0),
+ egui::vec2(3.0, rect.height() - 8.0),
+ );
+ ui.painter().rect_filled(status_bar_rect, 1.5, status_color);
+ 12.0 // Left padding (room for status bar)
+ } else {
+ 8.0 // Smaller padding in Chat mode (no status bar)
+ };
// Draw shortcut hint at the far right
let mut right_offset = 8.0; // Start with normal right padding
@@ -192,8 +208,8 @@ impl<'a> SessionListUi<'a> {
right_offset
};
- // Calculate text position - offset title upward since cwd is always present
- let title_y_offset = -7.0;
+ // Calculate text position - offset title upward only if showing CWD
+ let title_y_offset = if show_cwd { -7.0 } else { 0.0 };
let text_pos = rect.left_center() + egui::vec2(text_start_x, title_y_offset);
let max_text_width = rect.width() - text_start_x - text_end_x;
@@ -225,9 +241,11 @@ impl<'a> SessionListUi<'a> {
);
}
- // Draw cwd below title
- let cwd_pos = rect.left_center() + egui::vec2(text_start_x, 7.0);
- cwd_ui(ui, cwd, cwd_pos, max_text_width);
+ // Draw cwd below title - only in Agentic mode
+ if show_cwd {
+ let cwd_pos = rect.left_center() + egui::vec2(text_start_x, 7.0);
+ cwd_ui(ui, cwd, cwd_pos, max_text_width);
+ }
response
}