Allow naming sessions with /new and /clear (#34605)

## What changed

- Accept an optional session name after `/new` or `/clear`.
- Set the requested name through the app server when starting the new thread and update the attached session with that name.
- Report naming failures in the chat while still attaching the newly created thread.

## Testing

- Cover named `/new` and `/clear` dispatch, app-server name persistence, and the existing unnamed `/clear` behavior.

GitOrigin-RevId: 570886abb4af522123f0693ce05d2415e1c0d902
This commit is contained in:
Eric Traut authored and copyberry committed 2026-07-21 20:13:08 +00:00
1 parent 2497972808
commit 25d2dfcad0
8 files changed
+149 -21

No files matched your search

+12 -2
View File
@@ -171,7 +171,7 @@ impl ChatWidget {
self.request_redraw();
}
SlashCommand::New => {
self.app_event_tx.send(AppEvent::NewSession);
self.app_event_tx.send(AppEvent::NewSession { name: None });
}
SlashCommand::Archive => {
self.bottom_pane.show_selection_view(SelectionViewParams {
@@ -231,7 +231,7 @@ impl ChatWidget {
self.request_redraw();
}
SlashCommand::Clear => {
self.app_event_tx.send(AppEvent::ClearUi);
self.app_event_tx.send(AppEvent::ClearUi { name: None });
}
SlashCommand::Resume => {
self.app_event_tx.send(AppEvent::OpenResumePicker);
@@ -725,6 +725,16 @@ impl ChatWidget {
};
self.app_event_tx.set_thread_name(name);
}
SlashCommand::New if !trimmed.is_empty() => {
self.app_event_tx.send(AppEvent::NewSession {
name: Some(trimmed.to_string()),
});
}
SlashCommand::Clear if !trimmed.is_empty() => {
self.app_event_tx.send(AppEvent::ClearUi {
name: Some(trimmed.to_string()),
});
}
SlashCommand::Plan if !trimmed.is_empty() => {
if !self.apply_plan_slash_command() {
return;
@@ -2104,7 +2104,39 @@ async fn slash_clear_requests_ui_clear_when_idle() {
chat.dispatch_command(SlashCommand::Clear);
assert_matches!(rx.try_recv(), Ok(AppEvent::ClearUi));
assert_matches!(rx.try_recv(), Ok(AppEvent::ClearUi { name: None }));
}
#[tokio::test]
async fn slash_new_with_name_requests_named_session() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.bottom_pane
.set_composer_text("/new Add User ".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert_matches!(
rx.try_recv(),
Ok(AppEvent::NewSession {
name: Some(name)
}) if name == "Add User"
);
}
#[tokio::test]
async fn slash_clear_with_name_requests_named_session() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.bottom_pane
.set_composer_text("/clear Add User ".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert_matches!(
rx.try_recv(),
Ok(AppEvent::ClearUi {
name: Some(name)
}) if name == "Add User"
);
}
#[tokio::test]
@@ -2130,7 +2162,7 @@ async fn slash_clear_after_ctrl_c_keeps_stashed_draft_recallable() {
.set_composer_text("/clear".to_string(), Vec::new(), Vec::new());
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert_matches!(rx.try_recv(), Ok(AppEvent::ClearUi));
assert_matches!(rx.try_recv(), Ok(AppEvent::ClearUi { name: None }));
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
assert_eq!(chat.bottom_pane.composer_text(), stashed_draft);