zeph_commands/handlers/
compaction.rs1use std::future::Future;
7use std::pin::Pin;
8
9use crate::context::CommandContext;
10use crate::{CommandError, CommandHandler, CommandOutput, SlashCategory};
11
12pub struct CompactCommand;
17
18impl CommandHandler<CommandContext<'_>> for CompactCommand {
19 fn name(&self) -> &'static str {
20 "/compact"
21 }
22
23 fn description(&self) -> &'static str {
24 "Compact the context window by summarizing older messages"
25 }
26
27 fn args_hint(&self) -> &'static str {
28 ""
29 }
30
31 fn category(&self) -> SlashCategory {
32 SlashCategory::Session
33 }
34
35 fn handle<'a>(
36 &'a self,
37 ctx: &'a mut CommandContext<'_>,
38 _args: &'a str,
39 ) -> Pin<Box<dyn Future<Output = Result<CommandOutput, CommandError>> + Send + 'a>> {
40 Box::pin(async move {
41 let result = ctx.agent.compact_context().await?;
42 Ok(CommandOutput::Message(result))
43 })
44 }
45}
46
47pub struct NewConversationCommand;
53
54impl CommandHandler<CommandContext<'_>> for NewConversationCommand {
55 fn name(&self) -> &'static str {
56 "/new"
57 }
58
59 fn description(&self) -> &'static str {
60 "Start a new conversation (reset context, preserve memory and MCP)"
61 }
62
63 fn args_hint(&self) -> &'static str {
64 "[--no-digest] [--keep-plan]"
65 }
66
67 fn category(&self) -> SlashCategory {
68 SlashCategory::Session
69 }
70
71 fn handle<'a>(
72 &'a self,
73 ctx: &'a mut CommandContext<'_>,
74 args: &'a str,
75 ) -> Pin<Box<dyn Future<Output = Result<CommandOutput, CommandError>> + Send + 'a>> {
76 Box::pin(async move {
77 let (keep_plan, no_digest) = parse_new_flags(args);
78 let result = ctx.agent.reset_conversation(keep_plan, no_digest).await?;
79 Ok(CommandOutput::Message(result))
80 })
81 }
82}
83
84pub struct RecapCommand;
90
91impl CommandHandler<CommandContext<'_>> for RecapCommand {
92 fn name(&self) -> &'static str {
93 "/recap"
94 }
95
96 fn description(&self) -> &'static str {
97 "Show a recap of the current or previous session"
98 }
99
100 fn args_hint(&self) -> &'static str {
101 ""
102 }
103
104 fn category(&self) -> SlashCategory {
105 SlashCategory::Session
106 }
107
108 fn handle<'a>(
109 &'a self,
110 ctx: &'a mut CommandContext<'_>,
111 _args: &'a str,
112 ) -> Pin<Box<dyn Future<Output = Result<CommandOutput, CommandError>> + Send + 'a>> {
113 Box::pin(async move {
114 let text = ctx.agent.session_recap().await?;
115 Ok(CommandOutput::Message(text))
116 })
117 }
118}
119
120fn parse_new_flags(args: &str) -> (bool, bool) {
122 let keep_plan = args.split_whitespace().any(|a| a == "--keep-plan");
123 let no_digest = args.split_whitespace().any(|a| a == "--no-digest");
124 (keep_plan, no_digest)
125}
126
127#[cfg(test)]
128mod tests {
129 use super::parse_new_flags;
130
131 #[test]
132 fn no_flags_both_false() {
133 assert_eq!(parse_new_flags(""), (false, false));
134 assert_eq!(parse_new_flags(" "), (false, false));
135 }
136
137 #[test]
138 fn keep_plan_flag_detected() {
139 assert_eq!(parse_new_flags("--keep-plan"), (true, false));
140 assert_eq!(parse_new_flags("--keep-plan --no-digest"), (true, true));
141 }
142
143 #[test]
144 fn no_digest_flag_detected() {
145 assert_eq!(parse_new_flags("--no-digest"), (false, true));
146 }
147
148 #[test]
149 fn both_flags_order_independent() {
150 assert_eq!(parse_new_flags("--no-digest --keep-plan"), (true, true));
151 assert_eq!(parse_new_flags("--keep-plan --no-digest"), (true, true));
152 }
153
154 #[test]
155 fn partial_flag_name_not_matched() {
156 assert_eq!(parse_new_flags("--keep"), (false, false));
157 assert_eq!(parse_new_flags("--no"), (false, false));
158 assert_eq!(parse_new_flags("keep-plan"), (false, false));
159 }
160}