zeph_commands/handlers/
trajectory.rs1use std::future::Future;
7use std::pin::Pin;
8
9use crate::context::CommandContext;
10use crate::{CommandError, CommandHandler, CommandOutput, SlashCategory};
11
12pub struct TrajectoryCommand;
16
17impl CommandHandler<CommandContext<'_>> for TrajectoryCommand {
18 fn name(&self) -> &'static str {
19 "/trajectory"
20 }
21
22 fn description(&self) -> &'static str {
23 "Show trajectory risk sentinel status or reset it"
24 }
25
26 fn args_hint(&self) -> &'static str {
27 "[status|reset]"
28 }
29
30 fn category(&self) -> SlashCategory {
31 SlashCategory::Advanced
32 }
33
34 fn requires_auth(&self) -> bool {
35 true
36 }
37
38 fn handle<'a>(
39 &'a self,
40 ctx: &'a mut CommandContext<'_>,
41 args: &'a str,
42 ) -> Pin<Box<dyn Future<Output = Result<CommandOutput, CommandError>> + Send + 'a>> {
43 use tracing::Instrument as _;
44 let span = tracing::info_span!("commands.trajectory.handle");
45 Box::pin(
46 async move {
47 let result = ctx.agent.handle_trajectory(args);
48 Ok(CommandOutput::Message(result))
49 }
50 .instrument(span),
51 )
52 }
53}
54
55pub struct ScopeCommand;
59
60impl CommandHandler<CommandContext<'_>> for ScopeCommand {
61 fn name(&self) -> &'static str {
62 "/scope"
63 }
64
65 fn description(&self) -> &'static str {
66 "List configured capability scopes (spec 050)"
67 }
68
69 fn args_hint(&self) -> &'static str {
70 "[list [task_type]]"
71 }
72
73 fn category(&self) -> SlashCategory {
74 SlashCategory::Advanced
75 }
76
77 fn requires_auth(&self) -> bool {
78 true
79 }
80
81 fn handle<'a>(
82 &'a self,
83 ctx: &'a mut CommandContext<'_>,
84 args: &'a str,
85 ) -> Pin<Box<dyn Future<Output = Result<CommandOutput, CommandError>> + Send + 'a>> {
86 use tracing::Instrument as _;
87 let span = tracing::info_span!("commands.scope.handle");
88 Box::pin(
89 async move {
90 let result = ctx.agent.handle_scope(args);
91 Ok(CommandOutput::Message(result))
92 }
93 .instrument(span),
94 )
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::handlers::test_helpers::{MockDebug, MockMessages, MockSession, make_ctx};
102 use crate::sink::NullSink;
103 use std::assert_matches;
104
105 #[test]
106 fn trajectory_name_and_description() {
107 assert_eq!(TrajectoryCommand.name(), "/trajectory");
108 assert!(!TrajectoryCommand.description().is_empty());
109 }
110
111 #[test]
112 fn scope_name_and_description() {
113 assert_eq!(ScopeCommand.name(), "/scope");
114 assert!(!ScopeCommand.description().is_empty());
115 }
116
117 #[tokio::test]
118 async fn trajectory_returns_message() {
119 let mut sink = NullSink;
120 let mut debug = MockDebug;
121 let mut messages = MockMessages;
122 let session = MockSession;
123 let mut agent = crate::NullAgent;
124 let mut ctx = make_ctx(&mut sink, &mut debug, &mut messages, &session, &mut agent);
125 let out = TrajectoryCommand.handle(&mut ctx, "status").await.unwrap();
126 assert_matches!(out, CommandOutput::Message(_));
127 }
128
129 #[tokio::test]
130 async fn scope_returns_message() {
131 let mut sink = NullSink;
132 let mut debug = MockDebug;
133 let mut messages = MockMessages;
134 let session = MockSession;
135 let mut agent = crate::NullAgent;
136 let mut ctx = make_ctx(&mut sink, &mut debug, &mut messages, &session, &mut agent);
137 let out = ScopeCommand.handle(&mut ctx, "list").await.unwrap();
138 assert_matches!(out, CommandOutput::Message(_));
139 }
140}