feat: 添加会话管理命令,支持列出和加载会话功能
This commit is contained in:
parent
5eb9a26843
commit
e005d06a9b
@ -12,6 +12,8 @@ pub enum InputCommand {
|
|||||||
Exit,
|
Exit,
|
||||||
New(Option<String>),
|
New(Option<String>),
|
||||||
Save(Option<String>),
|
Save(Option<String>),
|
||||||
|
Sessions,
|
||||||
|
Use(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InputHandler {
|
pub struct InputHandler {
|
||||||
@ -70,6 +72,8 @@ impl InputHandler {
|
|||||||
"/quit" | "/exit" | "/q" => Some(InputCommand::Exit),
|
"/quit" | "/exit" | "/q" => Some(InputCommand::Exit),
|
||||||
"/new" => Some(InputCommand::New(arg.map(ToOwned::to_owned))),
|
"/new" => Some(InputCommand::New(arg.map(ToOwned::to_owned))),
|
||||||
"/save" => Some(InputCommand::Save(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,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,6 +128,19 @@ mod tests {
|
|||||||
handler.handle_special_commands("/save ./debug/session.md"),
|
handler.handle_special_commands("/save ./debug/session.md"),
|
||||||
Some(InputCommand::Save(Some("./debug/session.md".to_string())))
|
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("/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;
|
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) => {
|
InputEvent::Message(msg) => {
|
||||||
let inbound = WsInbound::Message {
|
let inbound = WsInbound::Message {
|
||||||
content: msg.content,
|
content: msg.content,
|
||||||
|
|||||||
@ -65,6 +65,27 @@ impl InputAdapter for ChannelInputAdapter {
|
|||||||
return Ok(Some(Command::SaveSession { filepath, include_all }));
|
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
|
// 不是命令,返回 None
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,27 @@ impl InputAdapter for CliInputAdapter {
|
|||||||
return Ok(Some(Command::SaveSession { filepath, include_all }));
|
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
|
// 不是命令,返回 None
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
pub mod save_session;
|
pub mod save_session;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
pub mod session_query;
|
||||||
|
|||||||
@ -36,6 +36,7 @@ impl CommandHandler for SessionCommandHandler {
|
|||||||
match cmd {
|
match cmd {
|
||||||
Command::CreateSession { title } => handle_create_session(self, title, ctx).await,
|
Command::CreateSession { title } => handle_create_session(self, title, ctx).await,
|
||||||
Command::SaveSession { .. } => unreachable!("SaveSession should be handled by SaveSessionCommandHandler"),
|
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() {
|
async fn test_list_sessions_empty() {
|
||||||
let service = create_test_service();
|
let service = create_test_service();
|
||||||
let handler = SessionQueryCommandHandler::new(service);
|
let handler = SessionQueryCommandHandler::new(service);
|
||||||
let ctx = CommandContext::new("test");
|
let ctx = CommandContext::new("test", "test");
|
||||||
let cmd = Command::ListSessions {
|
let cmd = Command::ListSessions {
|
||||||
include_archived: false,
|
include_archived: false,
|
||||||
};
|
};
|
||||||
@ -138,7 +138,7 @@ mod tests {
|
|||||||
// 创建一些会话
|
// 创建一些会话
|
||||||
service.create(Some("test session")).unwrap();
|
service.create(Some("test session")).unwrap();
|
||||||
|
|
||||||
let ctx = CommandContext::new("test");
|
let ctx = CommandContext::new("test", "test");
|
||||||
let cmd = Command::ListSessions {
|
let cmd = Command::ListSessions {
|
||||||
include_archived: false,
|
include_archived: false,
|
||||||
};
|
};
|
||||||
@ -155,7 +155,7 @@ mod tests {
|
|||||||
async fn test_load_session_not_found() {
|
async fn test_load_session_not_found() {
|
||||||
let service = create_test_service();
|
let service = create_test_service();
|
||||||
let handler = SessionQueryCommandHandler::new(service);
|
let handler = SessionQueryCommandHandler::new(service);
|
||||||
let ctx = CommandContext::new("test");
|
let ctx = CommandContext::new("test", "test");
|
||||||
let cmd = Command::LoadSession {
|
let cmd = Command::LoadSession {
|
||||||
session_id: "nonexistent".to_string(),
|
session_id: "nonexistent".to_string(),
|
||||||
};
|
};
|
||||||
@ -173,7 +173,7 @@ mod tests {
|
|||||||
// 创建会话
|
// 创建会话
|
||||||
let record = service.create(Some("test session")).unwrap();
|
let record = service.create(Some("test session")).unwrap();
|
||||||
|
|
||||||
let ctx = CommandContext::new("test");
|
let ctx = CommandContext::new("test", "test");
|
||||||
let cmd = Command::LoadSession {
|
let cmd = Command::LoadSession {
|
||||||
session_id: record.id.clone(),
|
session_id: record.id.clone(),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,6 +18,10 @@ pub enum Command {
|
|||||||
filepath: Option<String>,
|
filepath: Option<String>,
|
||||||
include_all: bool,
|
include_all: bool,
|
||||||
},
|
},
|
||||||
|
/// 列出会话
|
||||||
|
ListSessions { include_archived: bool },
|
||||||
|
/// 加载指定会话
|
||||||
|
LoadSession { session_id: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
@ -26,6 +30,8 @@ impl Command {
|
|||||||
match self {
|
match self {
|
||||||
Command::CreateSession { .. } => "create_session",
|
Command::CreateSession { .. } => "create_session",
|
||||||
Command::SaveSession { .. } => "save_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::handler::CommandRouter;
|
||||||
use crate::command::handlers::save_session::SaveSessionCommandHandler;
|
use crate::command::handlers::save_session::SaveSessionCommandHandler;
|
||||||
use crate::command::handlers::session::SessionCommandHandler;
|
use crate::command::handlers::session::SessionCommandHandler;
|
||||||
|
use crate::command::handlers::session_query::SessionQueryCommandHandler;
|
||||||
use crate::config::LLMProviderConfig;
|
use crate::config::LLMProviderConfig;
|
||||||
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
||||||
use crate::skills::SkillPromptProvider;
|
use crate::skills::SkillPromptProvider;
|
||||||
@ -36,7 +37,10 @@ impl InboundProcessor {
|
|||||||
|
|
||||||
// 注册 Session 处理器
|
// 注册 Session 处理器
|
||||||
let cli_sessions = session_manager.cli_sessions();
|
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 处理器
|
// 注册 save_session 处理器
|
||||||
let store = session_manager.store();
|
let store = session_manager.store();
|
||||||
|
|||||||
@ -7,6 +7,7 @@ use crate::command::context::CommandContext;
|
|||||||
use crate::command::handler::CommandRouter;
|
use crate::command::handler::CommandRouter;
|
||||||
use crate::command::handlers::save_session::SaveSessionCommandHandler;
|
use crate::command::handlers::save_session::SaveSessionCommandHandler;
|
||||||
use crate::command::handlers::session::SessionCommandHandler;
|
use crate::command::handlers::session::SessionCommandHandler;
|
||||||
|
use crate::command::handlers::session_query::SessionQueryCommandHandler;
|
||||||
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
||||||
use crate::protocol::{WsInbound, WsOutbound, parse_inbound, serialize_outbound};
|
use crate::protocol::{WsInbound, WsOutbound, parse_inbound, serialize_outbound};
|
||||||
use crate::skills::SkillPromptProvider;
|
use crate::skills::SkillPromptProvider;
|
||||||
@ -221,6 +222,7 @@ async fn handle_inbound(
|
|||||||
|
|
||||||
let mut router = CommandRouter::new();
|
let mut router = CommandRouter::new();
|
||||||
router.register(Box::new(SessionCommandHandler::new(cli_sessions.clone())));
|
router.register(Box::new(SessionCommandHandler::new(cli_sessions.clone())));
|
||||||
|
router.register(Box::new(SessionQueryCommandHandler::new(cli_sessions)));
|
||||||
router.register(Box::new(SaveSessionCommandHandler::new(
|
router.register(Box::new(SaveSessionCommandHandler::new(
|
||||||
store,
|
store,
|
||||||
system_prompt_provider,
|
system_prompt_provider,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user