Add /clear command
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4423148552
commit
4c7d67cabf
8 files changed
+60
-2
No files matched your search
@@ -1128,6 +1128,13 @@ pub(crate) fn send_prompt(app: &AppState, session: crate::types::Session, text:
|
||||
tokio::spawn(do_compact(app.clone(), id.clone()));
|
||||
return Ok(json!({"ok": true, "command": "compact"}));
|
||||
}
|
||||
if text == "/clear" {
|
||||
if app.is_busy(&id) {
|
||||
return Err(ApiError(StatusCode::CONFLICT, "session is busy".into()));
|
||||
}
|
||||
runner::clear_context(app, &id).map_err(|e| ApiError(StatusCode::INTERNAL_SERVER_ERROR, format!("{e:#}")))?;
|
||||
return Ok(json!({"ok": true, "command": "clear"}));
|
||||
}
|
||||
let cwd = PathBuf::from(&session.cwd);
|
||||
let root = app.root_for(session.owner_id.as_deref(), &cwd);
|
||||
let mut prompt_text = text.clone();
|
||||
|
||||
@@ -1723,10 +1723,43 @@ async fn compact_inner(app: Arc<App>, session_id: &str, cancel: CancellationToke
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Text of the marker `/clear` puts into the history (the chat view recognises it).
|
||||
pub const CLEARED_MARKER: &str = "[Context cleared by the user. Earlier messages are no longer part of this conversation – start fresh and do not refer to them.]";
|
||||
|
||||
/// `/clear`: the model forgets the conversation so far (the chat keeps showing it). Like a
|
||||
/// compaction without a summary: history before the marker is not sent any more.
|
||||
pub fn clear_context(app: &App, session_id: &str) -> Result<()> {
|
||||
save_user_parts(app, session_id, vec![Part::Summary { text: CLEARED_MARKER.into(), auto: false, tokens: None }])?;
|
||||
app.update_session(session_id, |s| {
|
||||
s.last_usage = None;
|
||||
s.todos = Value::Null;
|
||||
})?;
|
||||
app.with_runtime(session_id, |r| r.read_files.clear());
|
||||
// Claude Code continues its own conversation: start a new one.
|
||||
if let Some(owner) = app.session(session_id).ok().and_then(|s| s.owner_id) {
|
||||
let _ = app.store.set_user_setting(&owner, &format!("claude_sid:{session_id}"), &Option::<String>::None);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn cleared_marker_starts_a_new_history() {
|
||||
let mk = |role, parts| Message::new(role, parts);
|
||||
let all = vec![
|
||||
mk(Role::User, vec![Part::Text { text: "old question".into() }]),
|
||||
mk(Role::Assistant, vec![Part::Text { text: "old answer".into() }]),
|
||||
mk(Role::User, vec![Part::Summary { text: CLEARED_MARKER.into(), auto: false, tokens: None }]),
|
||||
mk(Role::User, vec![Part::Text { text: "new question".into() }]),
|
||||
];
|
||||
let active = active_history(all);
|
||||
assert_eq!(active.len(), 2);
|
||||
assert!(!active.iter().any(|m| message_text(m).contains("old")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fit_keeps_head_and_tail_within_budget() {
|
||||
let text = format!("{}{}{}", "A".repeat(5000), "M".repeat(5000), "Z".repeat(5000));
|
||||
|
||||
Reference in new issue
Block a user