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)