fix(tui): /compact <instructions> 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
This commit is contained in:
雷电芽衣 committed 2026-09-13 09:23:39 -04:00
1 parent 4512df79e6
commit 584d0fb77e
4 files changed
+46 -6

No files matched your search

@@ -1431,6 +1431,8 @@ pub enum Effect {
Compact {
agent_id: AgentId,
session_id: acp::SessionId,
/// `/compact <instructions>`, forwarded as `userContext`.
user_context: Option<String>,
},
/// Kill a background task.
KillBgTask {
@@ -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 <instructions>` text from a queued command; a queue edit may leave leading whitespace.
pub(super) fn compact_user_context(text: &str) -> Option<String> {
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<Effect> {
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 => {
@@ -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]
@@ -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(&params)