use crossterm::style::Color;
use crate::context::ContextFiles;
use crate::provider::AnyAgent;
use crate::ui::run_handlers::{AgentBuildDeps, RunCtx};
fn anchor_summary_with_intent(intent: &str, summary: &str) -> String {
if intent.is_empty() || summary.is_empty() {
return summary.to_string();
}
format!("Original request: {intent}\n\n{summary}")
}
pub(crate) fn handle_checkpoint_refresh(session: &crate::session::Session, summary: &str) {
if summary.is_empty() {
return;
}
let cwd = std::env::current_dir().unwrap_or_else(|_| ".".into());
let paths = crate::extras::dirge_paths::ProjectPaths::new(&cwd);
if let Ok(db) = crate::extras::session_db::SessionDb::open(&paths.session_db_path()) {
let origin = session.effective_origin().to_string();
let mut intent = session.first_user_prompt().unwrap_or("").to_string();
if let Some(cp) = db.get_checkpoint(&origin).ok().flatten()
&& !cp.intent.is_empty()
{
intent = cp.intent;
}
db.checkpoint_after_fold(&origin, &intent, summary);
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn handle_context_compacted(
ctx: &mut RunCtx<'_>,
deps: &AgentBuildDeps<'_>,
agent: &mut AnyAgent,
context: &mut ContextFiles,
new_session_id: &str,
tokens_before: u64,
tokens_after: u64,
summary: &str,
first_kept_index: usize,
) -> anyhow::Result<()> {
let client = deps.client;
let permission = deps.permission;
let ask_tx = deps.ask_tx;
let question_tx = deps.question_tx;
let plan_tx = deps.plan_tx;
let bg_store = deps.bg_store;
let sandbox = deps.sandbox;
#[cfg(feature = "mcp")]
let mcp_manager = deps.mcp_manager;
#[cfg(feature = "semantic")]
let semantic_manager = deps.semantic_manager;
#[cfg(feature = "lsp")]
let lsp_manager = deps.lsp_manager;
let cwd = std::env::current_dir().unwrap_or_else(|_| ".".into());
let paths = crate::extras::dirge_paths::ProjectPaths::new(&cwd);
let origin = ctx.session.effective_origin().to_string();
let mut intent = ctx.session.first_user_prompt().unwrap_or("").to_string();
if let Ok(db) = crate::extras::session_db::SessionDb::open(&paths.session_db_path()) {
let old_sid = format!("dirge-{}", crate::text::short_id(ctx.session.id.as_str()));
let _ = db.end_session(&old_sid, "compression");
let now = chrono::Utc::now().to_rfc3339();
let _ = db.insert_session(
new_session_id,
"cli",
&ctx.session.model,
&ctx.session.provider,
&now,
);
let _ = db.set_parent_session(new_session_id, &old_sid);
if let Some(cp) = db.get_checkpoint(&origin).ok().flatten()
&& !cp.intent.is_empty()
{
intent = cp.intent;
}
db.checkpoint_after_fold(&origin, &intent, summary);
}
let token_savings = tokens_before.saturating_sub(tokens_after);
if !summary.is_empty() {
let anchored = anchor_summary_with_intent(&intent, summary);
ctx.session
.compress_reporting(anchored, first_kept_index, token_savings);
}
let parent_id = ctx.session.id.to_string();
let origin = ctx.session.effective_origin().to_string();
ctx.session.origin_id = Some(compact_str::CompactString::new(origin));
ctx.session.id = compact_str::CompactString::new(new_session_id);
if let Err(e) = crate::session::storage::save_session(ctx.session) {
tracing::warn!(
target: "dirge::ui",
error = %e,
"could not persist rotated session after compaction",
);
}
let model = client.completion_model(ctx.session.model.to_string());
*agent = crate::provider::build_agent(
model,
ctx.cli,
ctx.cfg,
context,
permission.clone(),
ask_tx.clone(),
question_tx.clone(),
plan_tx.clone(),
bg_store.clone(),
#[cfg(feature = "lsp")]
lsp_manager.cloned(),
sandbox.clone(),
#[cfg(feature = "mcp")]
mcp_manager,
#[cfg(feature = "semantic")]
semantic_manager,
Some(ctx.session.id.to_string()),
)
.await;
crate::agent::review::maybe_fire_session_switch(
&*agent,
new_session_id,
&parent_id,
false,
);
ctx.renderer.write_line(
&format!(" context compacted: {tokens_before} → {tokens_after} tokens (session {new_session_id})"),
Color::DarkGrey,
)?;
if !summary.is_empty() {
let base = crate::agent::review::build_transcript(ctx.session);
let transcript =
crate::agent::session_digest::review_transcript(ctx.session, Some(&paths.root), base);
crate::agent::post_session::spawn_post_session(agent.clone(), paths, transcript);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn anchor_prepends_verbatim_intent() {
assert_eq!(
anchor_summary_with_intent("fix the resume bug", "## Goal\nresume works"),
"Original request: fix the resume bug\n\n## Goal\nresume works"
);
}
#[test]
fn anchor_is_a_noop_when_intent_or_summary_empty() {
assert_eq!(anchor_summary_with_intent("", "the summary"), "the summary");
assert_eq!(anchor_summary_with_intent("intent", ""), "");
}
}