diff --git a/src/cli/input.rs b/src/cli/input.rs index f6b0417..3e4bb18 100644 --- a/src/cli/input.rs +++ b/src/cli/input.rs @@ -12,6 +12,8 @@ pub enum InputCommand { Exit, New(Option), Save(Option), + Sessions, + Use(String), } pub struct InputHandler { @@ -70,6 +72,8 @@ impl InputHandler { "/quit" | "/exit" | "/q" => Some(InputCommand::Exit), "/new" => Some(InputCommand::New(arg.map(ToOwned::to_owned))), "/save" => Some(InputCommand::Save(arg.map(ToOwned::to_owned))), + "/sessions" | "/list" => Some(InputCommand::Sessions), + "/use" => arg.map(|value| InputCommand::Use(value.to_string())), _ => None, } } @@ -124,6 +128,19 @@ mod tests { handler.handle_special_commands("/save ./debug/session.md"), Some(InputCommand::Save(Some("./debug/session.md".to_string()))) ); + assert_eq!( + handler.handle_special_commands("/list"), + Some(InputCommand::Sessions) + ); + assert_eq!( + handler.handle_special_commands("/sessions"), + Some(InputCommand::Sessions) + ); + assert_eq!( + handler.handle_special_commands("/use abc123"), + Some(InputCommand::Use("abc123".to_string())) + ); assert_eq!(handler.handle_special_commands("/unknown"), None); + assert_eq!(handler.handle_special_commands("/use"), None); } } diff --git a/src/client/mod.rs b/src/client/mod.rs index 3cf9f62..8ae48af 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -149,6 +149,63 @@ pub async fn run(gateway_url: &str) -> Result<(), Box> { } continue; } + InputEvent::Command(InputCommand::Sessions) => { + // 使用 CliInputAdapter 构建 Command + let adapter = CliInputAdapter::new(); + let ctx = AdapterContext::new("cli") + .with_session_id(current_session_id.as_deref().unwrap_or("")); + + // 解析为 Command + match adapter.try_parse("/list", ctx) { + Ok(Some(command)) => { + // 序列化为 JSON + let json = serde_json::to_string(&command).unwrap_or_default(); + // 通过 Command 消息发送 + let inbound = WsInbound::Command { payload: json }; + if let Ok(text) = serialize_inbound(&inbound) { + let _ = sender.send(Message::Text(text.into())).await; + } + } + Ok(None) => { + tracing::warn!("Failed to parse /list command"); + } + Err(e) => { + tracing::error!(error = %e, "Error parsing /list command"); + } + } + continue; + } + InputEvent::Command(InputCommand::Use(session_id)) => { + // 使用 CliInputAdapter 构建 Command + let adapter = CliInputAdapter::new(); + let ctx = AdapterContext::new("cli") + .with_session_id(current_session_id.as_deref().unwrap_or("")); + + // 构建输入字符串 + let input_str = format!("/use {}", session_id); + + // 解析为 Command + match adapter.try_parse(&input_str, ctx) { + Ok(Some(command)) => { + // 序列化为 JSON + let json = serde_json::to_string(&command).unwrap_or_default(); + // 通过 Command 消息发送 + let inbound = WsInbound::Command { payload: json }; + if let Ok(text) = serialize_inbound(&inbound) { + let _ = sender.send(Message::Text(text.into())).await; + } + // 更新当前会话 ID + current_session_id = Some(session_id.clone()); + } + Ok(None) => { + tracing::warn!("Failed to parse /use command"); + } + Err(e) => { + tracing::error!(error = %e, "Error parsing /use command"); + } + } + continue; + } InputEvent::Message(msg) => { let inbound = WsInbound::Message { content: msg.content, diff --git a/src/command/adapters/channel.rs b/src/command/adapters/channel.rs index ce43ab0..cd9e55b 100644 --- a/src/command/adapters/channel.rs +++ b/src/command/adapters/channel.rs @@ -65,6 +65,27 @@ impl InputAdapter for ChannelInputAdapter { return Ok(Some(Command::SaveSession { filepath, include_all })); } + // 解析 /list 命令 + if trimmed == "/list" { + return Ok(Some(Command::ListSessions { + include_archived: false, + })); + } + + if trimmed == "/list all" { + return Ok(Some(Command::ListSessions { + include_archived: true, + })); + } + + // 解析 /use 命令 + if let Some(session_id) = trimmed.strip_prefix("/use ") { + let session_id = session_id.trim(); + return Ok(Some(Command::LoadSession { + session_id: session_id.to_string(), + })); + } + // 不是命令,返回 None Ok(None) } diff --git a/src/command/adapters/cli.rs b/src/command/adapters/cli.rs index 1f2c723..809286b 100644 --- a/src/command/adapters/cli.rs +++ b/src/command/adapters/cli.rs @@ -66,6 +66,27 @@ impl InputAdapter for CliInputAdapter { return Ok(Some(Command::SaveSession { filepath, include_all })); } + // 解析 /list 命令 + if trimmed == "/list" { + return Ok(Some(Command::ListSessions { + include_archived: false, + })); + } + + if trimmed == "/list all" { + return Ok(Some(Command::ListSessions { + include_archived: true, + })); + } + + // 解析 /use 命令 + if let Some(session_id) = trimmed.strip_prefix("/use ") { + let session_id = session_id.trim(); + return Ok(Some(Command::LoadSession { + session_id: session_id.to_string(), + })); + } + // 不是命令,返回 None Ok(None) } diff --git a/src/command/handlers/mod.rs b/src/command/handlers/mod.rs index 0df37c6..3f4f14f 100644 --- a/src/command/handlers/mod.rs +++ b/src/command/handlers/mod.rs @@ -1,2 +1,3 @@ pub mod save_session; pub mod session; +pub mod session_query; diff --git a/src/command/handlers/session.rs b/src/command/handlers/session.rs index 7367578..a4840e9 100644 --- a/src/command/handlers/session.rs +++ b/src/command/handlers/session.rs @@ -36,6 +36,7 @@ impl CommandHandler for SessionCommandHandler { match cmd { Command::CreateSession { title } => handle_create_session(self, title, ctx).await, Command::SaveSession { .. } => unreachable!("SaveSession should be handled by SaveSessionCommandHandler"), + _ => unreachable!("Other commands should be handled by other handlers"), } } } diff --git a/src/command/handlers/session_query.rs b/src/command/handlers/session_query.rs index ab428bb..3b05410 100644 --- a/src/command/handlers/session_query.rs +++ b/src/command/handlers/session_query.rs @@ -117,7 +117,7 @@ mod tests { async fn test_list_sessions_empty() { let service = create_test_service(); let handler = SessionQueryCommandHandler::new(service); - let ctx = CommandContext::new("test"); + let ctx = CommandContext::new("test", "test"); let cmd = Command::ListSessions { include_archived: false, }; @@ -138,7 +138,7 @@ mod tests { // 创建一些会话 service.create(Some("test session")).unwrap(); - let ctx = CommandContext::new("test"); + let ctx = CommandContext::new("test", "test"); let cmd = Command::ListSessions { include_archived: false, }; @@ -155,7 +155,7 @@ mod tests { async fn test_load_session_not_found() { let service = create_test_service(); let handler = SessionQueryCommandHandler::new(service); - let ctx = CommandContext::new("test"); + let ctx = CommandContext::new("test", "test"); let cmd = Command::LoadSession { session_id: "nonexistent".to_string(), }; @@ -173,7 +173,7 @@ mod tests { // 创建会话 let record = service.create(Some("test session")).unwrap(); - let ctx = CommandContext::new("test"); + let ctx = CommandContext::new("test", "test"); let cmd = Command::LoadSession { session_id: record.id.clone(), }; diff --git a/src/command/mod.rs b/src/command/mod.rs index 81e344f..935abc5 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -18,6 +18,10 @@ pub enum Command { filepath: Option, include_all: bool, }, + /// 列出会话 + ListSessions { include_archived: bool }, + /// 加载指定会话 + LoadSession { session_id: String }, } impl Command { @@ -26,6 +30,8 @@ impl Command { match self { Command::CreateSession { .. } => "create_session", Command::SaveSession { .. } => "save_session", + Command::ListSessions { .. } => "list_sessions", + Command::LoadSession { .. } => "load_session", } } } diff --git a/src/gateway/processor.rs b/src/gateway/processor.rs index 610d13b..92ff1b9 100644 --- a/src/gateway/processor.rs +++ b/src/gateway/processor.rs @@ -9,6 +9,7 @@ use crate::command::adapters::channel::ChannelInputAdapter; use crate::command::handler::CommandRouter; use crate::command::handlers::save_session::SaveSessionCommandHandler; use crate::command::handlers::session::SessionCommandHandler; +use crate::command::handlers::session_query::SessionQueryCommandHandler; use crate::config::LLMProviderConfig; use crate::gateway::agent_prompt_provider::AgentPromptProvider; use crate::skills::SkillPromptProvider; @@ -36,7 +37,10 @@ impl InboundProcessor { // 注册 Session 处理器 let cli_sessions = session_manager.cli_sessions(); - command_router.register(Box::new(SessionCommandHandler::new(cli_sessions))); + command_router.register(Box::new(SessionCommandHandler::new(cli_sessions.clone()))); + + // 注册 session_query 处理器 + command_router.register(Box::new(SessionQueryCommandHandler::new(cli_sessions))); // 注册 save_session 处理器 let store = session_manager.store(); diff --git a/src/gateway/ws.rs b/src/gateway/ws.rs index 0045066..a193a7e 100644 --- a/src/gateway/ws.rs +++ b/src/gateway/ws.rs @@ -7,6 +7,7 @@ use crate::command::context::CommandContext; use crate::command::handler::CommandRouter; use crate::command::handlers::save_session::SaveSessionCommandHandler; use crate::command::handlers::session::SessionCommandHandler; +use crate::command::handlers::session_query::SessionQueryCommandHandler; use crate::gateway::agent_prompt_provider::AgentPromptProvider; use crate::protocol::{WsInbound, WsOutbound, parse_inbound, serialize_outbound}; use crate::skills::SkillPromptProvider; @@ -221,6 +222,7 @@ async fn handle_inbound( let mut router = CommandRouter::new(); router.register(Box::new(SessionCommandHandler::new(cli_sessions.clone()))); + router.register(Box::new(SessionQueryCommandHandler::new(cli_sessions))); router.register(Box::new(SaveSessionCommandHandler::new( store, system_prompt_provider,