From 584d0fb77e30e6d5edcdedcde41ee4f254850bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sun, 13 Sep 2026 09:23:39 -0400 Subject: [PATCH] fix(tui): /compact reach the compaction request The queued /compact command drained into Effect::Compact with only the session id, so the instructions typed after the command never reached the shell's compaction prompt (which already accepts userContext). The effect carries the parsed instructions, trimmed so an edited queue row with leading whitespace still counts. Ported from upstream 1.0.22 (grok-build dispatch/queue.rs). Claude-Session: https://claude.ai/code/session_01RUqV8HVGXcAETyYbZ2tPQ4 --- crates/codegen/kigi-tui/src/app/actions.rs | 2 ++ .../kigi-tui/src/app/dispatch/queue.rs | 8 +++++ .../kigi-tui/src/app/dispatch/tests/prompt.rs | 29 +++++++++++++++++-- .../codegen/kigi-tui/src/app/effects/mod.rs | 13 ++++++--- 4 files changed, 46 insertions(+), 6 deletions(-) diff --git a/crates/codegen/kigi-tui/src/app/actions.rs b/crates/codegen/kigi-tui/src/app/actions.rs index 12f27cd..97b3026 100644 --- a/crates/codegen/kigi-tui/src/app/actions.rs +++ b/crates/codegen/kigi-tui/src/app/actions.rs @@ -1431,6 +1431,8 @@ pub enum Effect { Compact { agent_id: AgentId, session_id: acp::SessionId, + /// `/compact `, forwarded as `userContext`. + user_context: Option, }, /// Kill a background task. KillBgTask { diff --git a/crates/codegen/kigi-tui/src/app/dispatch/queue.rs b/crates/codegen/kigi-tui/src/app/dispatch/queue.rs index 4952893..6cc5af5 100644 --- a/crates/codegen/kigi-tui/src/app/dispatch/queue.rs +++ b/crates/codegen/kigi-tui/src/app/dispatch/queue.rs @@ -162,6 +162,12 @@ fn format_cron_prompt(prompt: &str, task_id: &str, human_schedule: &str) -> Stri kigi_tools::reminders::format_scheduled_task_prompt(prompt, task_id, human_schedule) } +/// `/compact ` text from a queued command; a queue edit may leave leading whitespace. +pub(super) fn compact_user_context(text: &str) -> Option { + let inv = crate::slash::parse_invocation(text.trim_start())?; + let args = inv.args.trim(); + (inv.token == "compact" && !args.is_empty()).then(|| args.to_string()) +} /// Try to send the next queued entry (prompt, command, bash, or cron) if the agent is idle. /// /// Called after enqueue operations and task completions to advance the queue. @@ -392,10 +398,12 @@ pub(crate) fn maybe_drain_queue(agent: &mut AgentView) -> Vec { agent.end_work_announced = false; agent.session.start_command(AgentCommand::Compact); agent.turn_started_at = Some(Instant::now()); + let user_context = compact_user_context(&queued.text); vec![Effect::Compact { agent_id, session_id, + user_context, }] } QueueEntryKind::BashCommand => { diff --git a/crates/codegen/kigi-tui/src/app/dispatch/tests/prompt.rs b/crates/codegen/kigi-tui/src/app/dispatch/tests/prompt.rs index 124489a..d40594b 100644 --- a/crates/codegen/kigi-tui/src/app/dispatch/tests/prompt.rs +++ b/crates/codegen/kigi-tui/src/app/dispatch/tests/prompt.rs @@ -1810,7 +1810,13 @@ fn slash_compact_enqueues_command() { let effects = dispatch(Action::SendPrompt("/compact".into()), &mut app); // /compact enqueues as Command and drains immediately (agent was idle). assert_eq!(effects.len(), 1); - assert!(matches!(&effects[0], Effect::Compact { .. })); + assert!(matches!( + &effects[0], + Effect::Compact { + user_context: None, + .. + } + )); // Prompt should be cleared. assert!(app.agents[&id].prompt.text().is_empty()); } @@ -1853,6 +1859,19 @@ fn palette_dispatch_preserves_prompt_draft() { ); } +/// An edited queue row keeps its text verbatim, so leading whitespace must not drop the instructions. +#[test] +fn compact_user_context_survives_leading_whitespace_from_a_queue_edit() { + use crate::app::dispatch::queue::compact_user_context; + assert_eq!( + compact_user_context(" /compact focus on auth").as_deref(), + Some("focus on auth") + ); + assert_eq!(compact_user_context("\n/compact x ").as_deref(), Some("x")); + assert_eq!(compact_user_context("/compact"), None); + assert_eq!(compact_user_context("/other args"), None); +} + #[test] fn slash_compact_with_context_enqueues_command() { let mut app = test_app_with_agent(); @@ -1861,7 +1880,13 @@ fn slash_compact_with_context_enqueues_command() { &mut app, ); assert_eq!(effects.len(), 1); - assert!(matches!(&effects[0], Effect::Compact { .. })); + assert!(matches!( + &effects[0], + Effect::Compact { + user_context: Some(ctx), + .. + } if ctx == "focus on auth" + )); } #[test] diff --git a/crates/codegen/kigi-tui/src/app/effects/mod.rs b/crates/codegen/kigi-tui/src/app/effects/mod.rs index ae71fc0..be84870 100644 --- a/crates/codegen/kigi-tui/src/app/effects/mod.rs +++ b/crates/codegen/kigi-tui/src/app/effects/mod.rs @@ -1388,13 +1388,18 @@ pub(crate) fn execute( } }); } - Effect::Compact { agent_id, session_id } => { + Effect::Compact { + agent_id, + session_id, + user_context, + } => { let tx = acp_tx.clone(); tasks .spawn(async move { - let params = serde_json::json!( - { "sessionId" : session_id.0.to_string(), } - ); + let params = serde_json::json!({ + "sessionId": session_id.0.to_string(), + "userContext": user_context, + }); let req = acp::ExtRequest::new( "kigi/compact_conversation", serde_json::value::to_raw_value(¶ms)