feat: add manual compact command

This commit is contained in:
2026-05-24 08:59:44 +09:00
parent 95d05628e7
commit cabc556b2c
6 changed files with 348 additions and 2 deletions
+84
View File
@@ -138,6 +138,15 @@ impl CommandRegistry {
can_execute: always_available,
executor: noop_command,
});
registry.register(CommandSpec {
name: "compact",
aliases: &[],
usage: "compact",
description: "Request immediate Pod context compaction.",
argument_parser: compact_args,
can_execute: compact_available,
executor: compact_command,
});
registry
}
@@ -266,6 +275,34 @@ fn help_args(raw: &str) -> Result<CommandArgs, CommandDiagnostic> {
}
}
fn compact_args(raw: &str) -> Result<CommandArgs, CommandDiagnostic> {
let args = CommandArgs::parse_whitespace(raw);
if args.argv().is_empty() {
Ok(args)
} else {
Err(CommandDiagnostic::new("Invalid arguments. Usage: compact"))
}
}
fn compact_available(environment: &CommandEnvironment) -> Result<(), CommandDiagnostic> {
if !environment.connected {
return Err(CommandDiagnostic::new(
"Cannot compact: not connected to a Pod.",
));
}
if environment.running {
return Err(CommandDiagnostic::new(
"Cannot compact while the Pod is running.",
));
}
if environment.paused {
return Err(CommandDiagnostic::new(
"Cannot compact while the Pod is paused; resume or start a fresh turn first.",
));
}
Ok(())
}
fn help_command(invocation: CommandInvocation<'_>) -> CommandExecution {
if let Some(name) = invocation.args.argv().first() {
let Some(command) = invocation.registry.find(name) else {
@@ -301,6 +338,18 @@ fn noop_command(invocation: CommandInvocation<'_>) -> CommandExecution {
CommandExecution::notice("noop: no action")
}
fn compact_command(invocation: CommandInvocation<'_>) -> CommandExecution {
let _ = invocation.command;
let _ = invocation.environment;
let _ = invocation.args.raw();
CommandExecution {
method: Some(Method::Compact),
diagnostics: vec![CommandDiagnostic::new("compact requested")],
exit_command_mode: true,
clear_input: true,
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -337,4 +386,39 @@ mod tests {
assert!(!result.exit_command_mode);
assert!(result.diagnostics[0].message.contains("Invalid arguments"));
}
#[test]
fn compact_command_returns_compact_method_not_run() {
let registry = CommandRegistry::builtins();
let result = registry.dispatch("compact", &env());
assert!(matches!(result.method, Some(Method::Compact)));
assert!(result.exit_command_mode);
assert!(result.clear_input);
assert!(result.diagnostics[0].message.contains("compact requested"));
}
#[test]
fn compact_invalid_arguments_are_local_diagnostic() {
let registry = CommandRegistry::builtins();
let result = registry.dispatch("compact now", &env());
assert!(result.method.is_none());
assert!(!result.exit_command_mode);
assert!(result.diagnostics[0].message.contains("Invalid arguments"));
}
#[test]
fn compact_rejects_running_and_paused_locally() {
let registry = CommandRegistry::builtins();
let mut running = env();
running.running = true;
let result = registry.dispatch("compact", &running);
assert!(result.method.is_none());
assert!(result.diagnostics[0].message.contains("running"));
let mut paused = env();
paused.paused = true;
let result = registry.dispatch("compact", &paused);
assert!(result.method.is_none());
assert!(result.diagnostics[0].message.contains("paused"));
}
}
+32
View File
@@ -1234,6 +1234,38 @@ mod tests {
}));
}
#[test]
fn compact_command_sends_compact_method_without_run() {
let mut app = App::new("agent".to_string());
app.connected = true;
assert!(
handle_key(
&mut app,
KeyEvent::new(KeyCode::Char(':'), KeyModifiers::NONE)
)
.is_none()
);
for c in "compact".chars() {
assert!(
handle_key(
&mut app,
KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)
)
.is_none()
);
}
let method = handle_key(&mut app, KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert!(matches!(method, Some(protocol::Method::Compact)));
assert!(!app.is_command_mode());
assert_eq!(input_text(&app), "");
assert_eq!(app.queued_input_count(), 0);
assert!(app.blocks.iter().any(|block| match block {
crate::block::Block::Alert { message, .. } => message.contains("compact requested"),
_ => false,
}));
}
#[test]
fn command_registry_suggestions_are_available() {
let mut app = App::new("agent".to_string());