zeph_commands/handlers/
conv.rs1use std::future::Future;
12use std::pin::Pin;
13
14use crate::context::CommandContext;
15use crate::{CommandError, CommandHandler, CommandOutput, SlashCategory};
16
17pub struct ConvCommand;
25
26impl CommandHandler<CommandContext<'_>> for ConvCommand {
27 fn name(&self) -> &'static str {
28 "/conv"
29 }
30
31 fn description(&self) -> &'static str {
32 "List, inspect, resume, or fork durable conversation-sessions"
33 }
34
35 fn args_hint(&self) -> &'static str {
36 "[list | show <id> | resume <id> | fork <id>]"
37 }
38
39 fn category(&self) -> SlashCategory {
40 SlashCategory::Session
41 }
42
43 fn feature_gate(&self) -> Option<&'static str> {
44 Some("session")
45 }
46
47 fn requires_auth(&self) -> bool {
48 true
49 }
50
51 fn handle<'a>(
52 &'a self,
53 ctx: &'a mut CommandContext<'_>,
54 args: &'a str,
55 ) -> Pin<Box<dyn Future<Output = Result<CommandOutput, CommandError>> + Send + 'a>> {
56 use tracing::Instrument as _;
57 let span = tracing::info_span!("commands.conv.handle");
58 Box::pin(
59 async move {
60 let result = ctx.agent.handle_conv(args).await?;
61 Ok(CommandOutput::Message(result))
62 }
63 .instrument(span),
64 )
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::CommandRegistry;
72 use crate::handlers::test_helpers::{MockDebug, MockMessages, MockSession, make_ctx};
73 use crate::sink::NullSink;
74
75 #[test]
76 fn conv_name_and_description() {
77 assert_eq!(ConvCommand.name(), "/conv");
78 assert!(!ConvCommand.description().is_empty());
79 }
80
81 #[tokio::test]
82 async fn conv_not_supported_returns_ok_message() {
83 let mut sink = NullSink;
84 let mut debug = MockDebug;
85 let mut messages = MockMessages;
86 let session = MockSession;
87 let mut agent = crate::NullAgent;
88 let mut ctx = make_ctx(&mut sink, &mut debug, &mut messages, &session, &mut agent);
89 let result = ConvCommand.handle(&mut ctx, "").await;
90 assert!(result.is_ok());
91 if let Ok(CommandOutput::Message(msg)) = result {
92 assert!(!msg.is_empty());
93 }
94 }
95
96 #[tokio::test]
97 async fn conv_dispatch_allowed_when_trusted() {
98 let mut sink = NullSink;
99 let mut debug = MockDebug;
100 let mut messages = MockMessages;
101 let session = MockSession;
102 let mut agent = crate::NullAgent;
103 let mut ctx = make_ctx(&mut sink, &mut debug, &mut messages, &session, &mut agent);
104
105 let mut reg: CommandRegistry<CommandContext<'_>> = CommandRegistry::new();
106 reg.register(ConvCommand);
107
108 let result = reg.dispatch(&mut ctx, "/conv list", true).await;
109 assert!(result.unwrap().is_ok());
110 }
111
112 #[tokio::test]
113 async fn conv_dispatch_rejected_when_untrusted() {
114 let mut sink = NullSink;
115 let mut debug = MockDebug;
116 let mut messages = MockMessages;
117 let session = MockSession;
118 let mut agent = crate::NullAgent;
119 let mut ctx = make_ctx(&mut sink, &mut debug, &mut messages, &session, &mut agent);
120
121 let mut reg: CommandRegistry<CommandContext<'_>> = CommandRegistry::new();
122 reg.register(ConvCommand);
123
124 let result = reg.dispatch(&mut ctx, "/conv list", false).await;
125 let err = result.unwrap().unwrap_err();
126 assert!(err.0.contains("trusted"));
127 }
128}