feat: 添加会话管理命令,支持列出和加载会话功能
This commit is contained in:
parent
5eb9a26843
commit
e005d06a9b
@ -12,6 +12,8 @@ pub enum InputCommand {
|
||||
Exit,
|
||||
New(Option<String>),
|
||||
Save(Option<String>),
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,6 +149,63 @@ pub async fn run(gateway_url: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
pub mod save_session;
|
||||
pub mod session;
|
||||
pub mod session_query;
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
|
||||
@ -18,6 +18,10 @@ pub enum Command {
|
||||
filepath: Option<String>,
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user