feat: add /help command to display all supported commands
- Implemented HelpCommandHandler to handle the /help command. - Added CommandMetadata struct to store command metadata. - Registered new command handlers for GetCurrentSession, ListSessions, LoadSession, and SwitchSession. - Updated existing command handlers to provide metadata for help command. - Removed deprecated SessionQueryCommandHandler. - Added new command handlers for listing sessions and loading sessions.
This commit is contained in:
parent
20f32a3f96
commit
3591822145
@ -105,6 +105,11 @@ impl InputAdapter for ChannelInputAdapter {
|
||||
return Ok(Some(Command::GetCurrentSession));
|
||||
}
|
||||
|
||||
// 解析 /help 命令 - 显示所有支持的命令
|
||||
if trimmed == "/help" {
|
||||
return Ok(Some(Command::Help));
|
||||
}
|
||||
|
||||
// 不是命令,返回 None
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@ -106,6 +106,11 @@ impl InputAdapter for CliInputAdapter {
|
||||
return Ok(Some(Command::GetCurrentSession));
|
||||
}
|
||||
|
||||
// 解析 /help 命令 - 显示所有支持的命令
|
||||
if trimmed == "/help" {
|
||||
return Ok(Some(Command::Help));
|
||||
}
|
||||
|
||||
// 不是命令,返回 None
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@ -5,6 +5,15 @@ use crate::command::Command;
|
||||
use crate::agent::AgentError;
|
||||
use crate::gateway::session::SessionManager;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 命令元数据(用于帮助系统)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CommandMetadata {
|
||||
pub name: &'static str,
|
||||
pub description: &'static str,
|
||||
pub usage: &'static str,
|
||||
}
|
||||
|
||||
/// 命令处理器 trait
|
||||
///
|
||||
@ -15,6 +24,11 @@ pub trait CommandHandler: Send + Sync {
|
||||
/// 是否可以处理此命令
|
||||
fn can_handle(&self, cmd: &Command) -> bool;
|
||||
|
||||
/// 返回命令元数据(用于 /help 命令)
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
None
|
||||
}
|
||||
|
||||
/// 执行命令
|
||||
///
|
||||
/// # Arguments
|
||||
@ -64,6 +78,7 @@ pub trait InChatCommandHandler: Send + Sync {
|
||||
/// 负责将命令分发到合适的处理器
|
||||
pub struct CommandRouter {
|
||||
handlers: Vec<Box<dyn CommandHandler>>,
|
||||
metadata: Arc<std::sync::Mutex<Vec<CommandMetadata>>>,
|
||||
}
|
||||
|
||||
impl CommandRouter {
|
||||
@ -71,6 +86,7 @@ impl CommandRouter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
handlers: Vec::new(),
|
||||
metadata: Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,9 +95,17 @@ impl CommandRouter {
|
||||
/// # Arguments
|
||||
/// * `handler` - 要注册的处理器
|
||||
pub fn register(&mut self, handler: Box<dyn CommandHandler>) {
|
||||
if let Some(meta) = handler.metadata() {
|
||||
self.metadata.lock().unwrap().push(meta);
|
||||
}
|
||||
self.handlers.push(handler);
|
||||
}
|
||||
|
||||
/// 获取已注册命令的元数据列表(用于 Help 命令)
|
||||
pub fn metadata_arc(&self) -> Arc<std::sync::Mutex<Vec<CommandMetadata>>> {
|
||||
self.metadata.clone()
|
||||
}
|
||||
|
||||
/// 分发命令到合适的处理器
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
96
src/command/handlers/get_current.rs
Normal file
96
src/command/handlers/get_current.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::{CommandHandler, CommandMetadata};
|
||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||
use crate::command::Command;
|
||||
use crate::storage::SessionStore;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 获取当前话题命令处理器
|
||||
pub struct GetCurrentSessionCommandHandler {
|
||||
store: Arc<SessionStore>,
|
||||
}
|
||||
|
||||
impl GetCurrentSessionCommandHandler {
|
||||
pub fn new(store: Arc<SessionStore>) -> Self {
|
||||
Self { store }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandHandler for GetCurrentSessionCommandHandler {
|
||||
fn can_handle(&self, cmd: &Command) -> bool {
|
||||
matches!(cmd, Command::GetCurrentSession)
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "current",
|
||||
description: "获取当前话题信息",
|
||||
usage: "/current",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
cmd: Command,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
match cmd {
|
||||
Command::GetCurrentSession => handle_get_current_session(self, ctx).await,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_get_current_session(
|
||||
handler: &GetCurrentSessionCommandHandler,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
let topic_id = ctx.topic_id.as_deref()
|
||||
.ok_or_else(|| CommandError::new("NO_CURRENT_TOPIC", "No current topic"))?;
|
||||
|
||||
let topic = handler
|
||||
.store
|
||||
.get_topic(topic_id)
|
||||
.map_err(|e| CommandError::new("GET_TOPIC_ERROR", e.to_string()))?
|
||||
.ok_or_else(|| CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", topic_id)))?;
|
||||
|
||||
let last_active = format_time_ago(topic.last_active_at);
|
||||
let created_at = format_time_ago(topic.created_at);
|
||||
|
||||
let message = format!(
|
||||
"Current Topic:\n\n Topic ID: {}\n Title: {}\n Messages: {}\n Created: {}\n Last Active: {}",
|
||||
topic.id,
|
||||
topic.title,
|
||||
topic.message_count,
|
||||
created_at,
|
||||
last_active
|
||||
);
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Notification, &message)
|
||||
.with_metadata("topic_id", &topic.id)
|
||||
.with_metadata("title", &topic.title)
|
||||
.with_metadata("message_count", &topic.message_count.to_string()))
|
||||
}
|
||||
|
||||
fn format_time_ago(timestamp_ms: i64) -> String {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
|
||||
let diff_ms = now - timestamp_ms;
|
||||
let diff_secs = diff_ms / 1000;
|
||||
|
||||
if diff_secs < 60 {
|
||||
"just now".to_string()
|
||||
} else if diff_secs < 3600 {
|
||||
format!("{} mins ago", diff_secs / 60)
|
||||
} else if diff_secs < 86400 {
|
||||
format!("{} hours ago", diff_secs / 3600)
|
||||
} else {
|
||||
format!("{} days ago", diff_secs / 86400)
|
||||
}
|
||||
}
|
||||
61
src/command/handlers/help.rs
Normal file
61
src/command/handlers/help.rs
Normal file
@ -0,0 +1,61 @@
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::{CommandHandler, CommandMetadata};
|
||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||
use crate::command::Command;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
/// Help 命令处理器
|
||||
///
|
||||
/// 显示所有支持的命令列表
|
||||
pub struct HelpCommandHandler {
|
||||
metadata: Arc<Mutex<Vec<CommandMetadata>>>,
|
||||
}
|
||||
|
||||
impl HelpCommandHandler {
|
||||
/// 创建新的 Help 命令处理器
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `metadata` - CommandRouter 的元数据列表引用
|
||||
pub fn new(metadata: Arc<Mutex<Vec<CommandMetadata>>>) -> Self {
|
||||
Self { metadata }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandHandler for HelpCommandHandler {
|
||||
fn can_handle(&self, cmd: &Command) -> bool {
|
||||
matches!(cmd, Command::Help)
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "help",
|
||||
description: "显示所有支持的命令",
|
||||
usage: "/help",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
_cmd: Command,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
let metadata = self.metadata.lock().unwrap();
|
||||
let help_text = format_help(&metadata);
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Text, &help_text))
|
||||
}
|
||||
}
|
||||
|
||||
/// 格式化帮助文本
|
||||
fn format_help(commands: &[CommandMetadata]) -> String {
|
||||
let mut output = String::from("# 支持的命令\n\n");
|
||||
|
||||
for cmd in commands {
|
||||
output.push_str(&format!("**{}** - {}\n", cmd.usage, cmd.description));
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
136
src/command/handlers/list_sessions.rs
Normal file
136
src/command/handlers/list_sessions.rs
Normal file
@ -0,0 +1,136 @@
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::{CommandHandler, CommandMetadata};
|
||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||
use crate::command::Command;
|
||||
use crate::storage::SessionStore;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 列出话题命令处理器
|
||||
pub struct ListSessionsCommandHandler {
|
||||
store: Arc<SessionStore>,
|
||||
}
|
||||
|
||||
impl ListSessionsCommandHandler {
|
||||
pub fn new(store: Arc<SessionStore>) -> Self {
|
||||
Self { store }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandHandler for ListSessionsCommandHandler {
|
||||
fn can_handle(&self, cmd: &Command) -> bool {
|
||||
matches!(cmd, Command::ListSessions { .. })
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "list",
|
||||
description: "列出所有话题",
|
||||
usage: "/list [all]",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
cmd: Command,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
match cmd {
|
||||
Command::ListSessions { include_archived } => {
|
||||
handle_list_sessions(self, include_archived, ctx).await
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_list_sessions(
|
||||
handler: &ListSessionsCommandHandler,
|
||||
_include_archived: bool,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
let session_id = ctx.session_id.as_deref()
|
||||
.ok_or_else(|| CommandError::new("NO_SESSION", "No active session"))?;
|
||||
|
||||
let topics = handler
|
||||
.store
|
||||
.list_topics(session_id)
|
||||
.map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?;
|
||||
|
||||
let current_topic_id = ctx.topic_id.as_deref().unwrap_or("");
|
||||
|
||||
let message = if topics.is_empty() {
|
||||
"No topics found. Use /new <title> to create a topic.".to_string()
|
||||
} else {
|
||||
let mut lines = vec![format!("Found {} topic(s):", topics.len())];
|
||||
lines.push(String::new());
|
||||
lines.push("┌────┬─────────────────┬──────────────────────┬──────────┬─────────────────┐".to_string());
|
||||
lines.push("│ No │ Topic ID │ Title │ Messages │ Last Active │".to_string());
|
||||
lines.push("├────┼─────────────────┼──────────────────────┼──────────┼─────────────────┤".to_string());
|
||||
|
||||
for (idx, topic) in topics.iter().enumerate() {
|
||||
let row_num = idx + 1;
|
||||
let is_current = topic.id == current_topic_id;
|
||||
let num_marker = if is_current { " * ".to_string() } else { format!(" {:<2}", row_num) };
|
||||
|
||||
let topic_id_display = if topic.id.len() > 15 {
|
||||
format!("{}...", &topic.id[..12])
|
||||
} else {
|
||||
topic.id.clone()
|
||||
};
|
||||
let title_display = if topic.title.len() > 20 {
|
||||
format!("{}...", &topic.title[..17])
|
||||
} else {
|
||||
topic.title.clone()
|
||||
};
|
||||
|
||||
let last_active = format_time_ago(topic.last_active_at);
|
||||
|
||||
lines.push(format!(
|
||||
"│{}│ {:<15} │ {:<20} │ {:<8} │ {:<15} │",
|
||||
num_marker,
|
||||
topic_id_display,
|
||||
title_display,
|
||||
topic.message_count,
|
||||
last_active
|
||||
));
|
||||
}
|
||||
|
||||
lines.push("└────┴─────────────────┴──────────────────────┴──────────┴─────────────────┘".to_string());
|
||||
lines.push(String::new());
|
||||
lines.push("* = current topic".to_string());
|
||||
lines.push("Use /use <number> or /use <topic_id> to switch".to_string());
|
||||
|
||||
lines.join("\n")
|
||||
};
|
||||
|
||||
let topics_json = serde_json::to_string(&topics)
|
||||
.map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?;
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Notification, &message)
|
||||
.with_metadata("topics", &topics_json)
|
||||
.with_metadata("count", &topics.len().to_string())
|
||||
.with_metadata("current_topic_id", current_topic_id))
|
||||
}
|
||||
|
||||
fn format_time_ago(timestamp_ms: i64) -> String {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
|
||||
let diff_ms = now - timestamp_ms;
|
||||
let diff_secs = diff_ms / 1000;
|
||||
|
||||
if diff_secs < 60 {
|
||||
"just now".to_string()
|
||||
} else if diff_secs < 3600 {
|
||||
format!("{} mins ago", diff_secs / 60)
|
||||
} else if diff_secs < 86400 {
|
||||
format!("{} hours ago", diff_secs / 3600)
|
||||
} else {
|
||||
format!("{} days ago", diff_secs / 86400)
|
||||
}
|
||||
}
|
||||
64
src/command/handlers/load_session.rs
Normal file
64
src/command/handlers/load_session.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::{CommandHandler, CommandMetadata};
|
||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||
use crate::command::Command;
|
||||
use crate::storage::SessionStore;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 加载话题命令处理器
|
||||
pub struct LoadSessionCommandHandler {
|
||||
store: Arc<SessionStore>,
|
||||
}
|
||||
|
||||
impl LoadSessionCommandHandler {
|
||||
pub fn new(store: Arc<SessionStore>) -> Self {
|
||||
Self { store }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandHandler for LoadSessionCommandHandler {
|
||||
fn can_handle(&self, cmd: &Command) -> bool {
|
||||
matches!(cmd, Command::LoadSession { .. })
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "load",
|
||||
description: "加载指定话题",
|
||||
usage: "/load <session_id>",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
cmd: Command,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
match cmd {
|
||||
Command::LoadSession { session_id } => {
|
||||
handle_load_session(self, session_id, ctx).await
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_load_session(
|
||||
handler: &LoadSessionCommandHandler,
|
||||
topic_id: String,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
let topic = handler
|
||||
.store
|
||||
.get_topic(&topic_id)
|
||||
.map_err(|e| CommandError::new("LOAD_TOPIC_ERROR", e.to_string()))?
|
||||
.ok_or_else(|| CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", topic_id)))?;
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Notification, &topic.title)
|
||||
.with_metadata("topic_id", &topic.id)
|
||||
.with_metadata("title", &topic.title)
|
||||
.with_metadata("message_count", &topic.message_count.to_string()))
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
pub mod get_current;
|
||||
pub mod help;
|
||||
pub mod list_sessions;
|
||||
pub mod load_session;
|
||||
pub mod save_session;
|
||||
pub mod save_topic;
|
||||
pub mod session;
|
||||
pub mod session_query;
|
||||
pub mod switch_session;
|
||||
|
||||
// 导出公共函数供其他模块复用
|
||||
pub use save_session::{
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::agent::{SystemPrompt, SystemPromptContext, SystemPromptProvider};
|
||||
use crate::bus::InboundMessage;
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::{CommandHandler, InChatCommandHandler};
|
||||
use crate::command::handler::{CommandHandler, CommandMetadata, InChatCommandHandler};
|
||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||
use crate::command::Command;
|
||||
use crate::storage::{SessionRecord, SessionStore};
|
||||
@ -107,6 +107,14 @@ impl CommandHandler for SaveSessionCommandHandler {
|
||||
matches!(cmd, Command::SaveSession { .. })
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "save-session",
|
||||
description: "保存当前会话到 Markdown 文件",
|
||||
usage: "/save-session [all] [filepath]",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
cmd: Command,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::agent::{SystemPrompt, SystemPromptContext, SystemPromptProvider};
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::CommandHandler;
|
||||
use crate::command::handler::{CommandHandler, CommandMetadata};
|
||||
use crate::command::handlers::{
|
||||
escape_yaml_string, format_timestamp, generate_messages_markdown,
|
||||
generate_system_prompt_markdown,
|
||||
@ -176,6 +176,14 @@ impl CommandHandler for SaveTopicCommandHandler {
|
||||
matches!(cmd, Command::SaveTopic { .. })
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "save",
|
||||
description: "保存当前话题到 Markdown 文件",
|
||||
usage: "/save [filepath]",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
cmd: Command,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::CommandHandler;
|
||||
use crate::command::handler::{CommandHandler, CommandMetadata};
|
||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||
use crate::command::Command;
|
||||
use crate::gateway::session::SessionManager;
|
||||
@ -40,6 +40,14 @@ impl CommandHandler for SessionCommandHandler {
|
||||
matches!(cmd, Command::CreateSession { .. })
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "new",
|
||||
description: "创建新话题",
|
||||
usage: "/new [title]",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
cmd: Command,
|
||||
|
||||
@ -1,368 +0,0 @@
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::CommandHandler;
|
||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||
use crate::command::Command;
|
||||
use crate::gateway::session::SessionManager;
|
||||
use crate::storage::SessionStore;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 会话查询命令处理器
|
||||
///
|
||||
/// 处理 ListSessions、LoadSession 和 SwitchSession 命令(现在操作 Topic)
|
||||
pub struct SessionQueryCommandHandler {
|
||||
store: Arc<SessionStore>,
|
||||
session_manager: Option<SessionManager>,
|
||||
}
|
||||
|
||||
impl SessionQueryCommandHandler {
|
||||
/// 创建新的会话查询命令处理器
|
||||
pub fn new(store: Arc<SessionStore>) -> Self {
|
||||
Self {
|
||||
store,
|
||||
session_manager: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置 SessionManager(用于 SwitchSession 命令)
|
||||
pub fn with_session_manager(mut self, session_manager: SessionManager) -> Self {
|
||||
self.session_manager = Some(session_manager);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandHandler for SessionQueryCommandHandler {
|
||||
fn can_handle(&self, cmd: &Command) -> bool {
|
||||
matches!(cmd, Command::ListSessions { .. } | Command::LoadSession { .. } | Command::SwitchSession { .. } | Command::GetCurrentSession)
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
cmd: Command,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
match cmd {
|
||||
Command::ListSessions { include_archived } => {
|
||||
handle_list_sessions(self, include_archived, ctx).await
|
||||
}
|
||||
Command::LoadSession { session_id } => {
|
||||
handle_load_session(self, session_id, ctx).await
|
||||
}
|
||||
Command::SwitchSession { session_id } => {
|
||||
handle_switch_session(self, session_id, ctx).await
|
||||
}
|
||||
Command::GetCurrentSession => {
|
||||
handle_get_current_session(self, ctx).await
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理列出话题命令
|
||||
async fn handle_list_sessions(
|
||||
handler: &SessionQueryCommandHandler,
|
||||
_include_archived: bool,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
// 获取当前 session_id
|
||||
let session_id = ctx.session_id.as_deref()
|
||||
.ok_or_else(|| CommandError::new("NO_SESSION", "No active session"))?;
|
||||
|
||||
// 查询该 session 的所有 topic
|
||||
let topics = handler
|
||||
.store
|
||||
.list_topics(session_id)
|
||||
.map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?;
|
||||
|
||||
// 获取当前 topic ID
|
||||
let current_topic_id = ctx.topic_id.as_deref().unwrap_or("");
|
||||
|
||||
// 构建表格格式的话题列表消息
|
||||
let message = if topics.is_empty() {
|
||||
"No topics found. Use /new <title> to create a topic.".to_string()
|
||||
} else {
|
||||
let mut lines = vec![format!("Found {} topic(s):", topics.len())];
|
||||
lines.push(String::new());
|
||||
|
||||
// 表格头部
|
||||
lines.push("┌────┬─────────────────┬──────────────────────┬──────────┬─────────────────┐".to_string());
|
||||
lines.push("│ No │ Topic ID │ Title │ Messages │ Last Active │".to_string());
|
||||
lines.push("├────┼─────────────────┼──────────────────────┼──────────┼─────────────────┤".to_string());
|
||||
|
||||
// 表格内容
|
||||
for (idx, topic) in topics.iter().enumerate() {
|
||||
let row_num = idx + 1;
|
||||
let is_current = topic.id == current_topic_id;
|
||||
let num_marker = if is_current { " * ".to_string() } else { format!(" {:<2}", row_num) };
|
||||
|
||||
// 截断过长的字段
|
||||
let topic_id_display = if topic.id.len() > 15 {
|
||||
format!("{}...", &topic.id[..12])
|
||||
} else {
|
||||
topic.id.clone()
|
||||
};
|
||||
let title_display = if topic.title.len() > 20 {
|
||||
format!("{}...", &topic.title[..17])
|
||||
} else {
|
||||
topic.title.clone()
|
||||
};
|
||||
|
||||
let last_active = format_time_ago(topic.last_active_at);
|
||||
|
||||
lines.push(format!(
|
||||
"│{}│ {:<15} │ {:<20} │ {:<8} │ {:<15} │",
|
||||
num_marker,
|
||||
topic_id_display,
|
||||
title_display,
|
||||
topic.message_count,
|
||||
last_active
|
||||
));
|
||||
}
|
||||
|
||||
// 表格底部
|
||||
lines.push("└────┴─────────────────┴──────────────────────┴──────────┴─────────────────┘".to_string());
|
||||
lines.push(String::new());
|
||||
lines.push("* = current topic".to_string());
|
||||
lines.push("Use /use <number> or /use <topic_id> to switch".to_string());
|
||||
|
||||
lines.join("\n")
|
||||
};
|
||||
|
||||
let topics_json = serde_json::to_string(&topics)
|
||||
.map_err(|e| CommandError::new("SERIALIZE_ERROR", e.to_string()))?;
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Notification, &message)
|
||||
.with_metadata("topics", &topics_json)
|
||||
.with_metadata("count", &topics.len().to_string())
|
||||
.with_metadata("current_topic_id", current_topic_id))
|
||||
}
|
||||
|
||||
/// 处理加载话题命令
|
||||
async fn handle_load_session(
|
||||
handler: &SessionQueryCommandHandler,
|
||||
topic_id: String,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
let topic = handler
|
||||
.store
|
||||
.get_topic(&topic_id)
|
||||
.map_err(|e| CommandError::new("LOAD_TOPIC_ERROR", e.to_string()))?
|
||||
.ok_or_else(|| CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", topic_id)))?;
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Notification, &topic.title)
|
||||
.with_metadata("topic_id", &topic.id)
|
||||
.with_metadata("title", &topic.title)
|
||||
.with_metadata("message_count", &topic.message_count.to_string()))
|
||||
}
|
||||
|
||||
/// 格式化时间为相对时间(如 "2 mins ago")
|
||||
fn format_time_ago(timestamp_ms: i64) -> String {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
|
||||
let diff_ms = now - timestamp_ms;
|
||||
let diff_secs = diff_ms / 1000;
|
||||
|
||||
if diff_secs < 60 {
|
||||
"just now".to_string()
|
||||
} else if diff_secs < 3600 {
|
||||
format!("{} mins ago", diff_secs / 60)
|
||||
} else if diff_secs < 86400 {
|
||||
format!("{} hours ago", diff_secs / 3600)
|
||||
} else {
|
||||
format!("{} days ago", diff_secs / 86400)
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理获取当前话题命令
|
||||
async fn handle_get_current_session(
|
||||
handler: &SessionQueryCommandHandler,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
let topic_id = ctx.topic_id.as_deref()
|
||||
.ok_or_else(|| CommandError::new("NO_CURRENT_TOPIC", "No current topic"))?;
|
||||
|
||||
let topic = handler
|
||||
.store
|
||||
.get_topic(topic_id)
|
||||
.map_err(|e| CommandError::new("GET_TOPIC_ERROR", e.to_string()))?
|
||||
.ok_or_else(|| CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", topic_id)))?;
|
||||
|
||||
let last_active = format_time_ago(topic.last_active_at);
|
||||
let created_at = format_time_ago(topic.created_at);
|
||||
|
||||
let message = format!(
|
||||
"Current Topic:\n\n Topic ID: {}\n Title: {}\n Messages: {}\n Created: {}\n Last Active: {}",
|
||||
topic.id,
|
||||
topic.title,
|
||||
topic.message_count,
|
||||
created_at,
|
||||
last_active
|
||||
);
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Notification, &message)
|
||||
.with_metadata("topic_id", &topic.id)
|
||||
.with_metadata("title", &topic.title)
|
||||
.with_metadata("message_count", &topic.message_count.to_string()))
|
||||
}
|
||||
|
||||
/// 处理切换话题命令
|
||||
async fn handle_switch_session(
|
||||
handler: &SessionQueryCommandHandler,
|
||||
topic_id: String,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
// 获取当前 session_id 和 chat_id
|
||||
let session_id = ctx.session_id.as_deref()
|
||||
.ok_or_else(|| CommandError::new("NO_SESSION", "No active session"))?;
|
||||
let chat_id = ctx.chat_id.as_deref()
|
||||
.ok_or_else(|| CommandError::new("NO_CHAT_ID", "No chat_id in context"))?;
|
||||
|
||||
// 尝试解析为序号
|
||||
let target_topic_id = if let Ok(index) = topic_id.parse::<usize>() {
|
||||
let topics = handler
|
||||
.store
|
||||
.list_topics(session_id)
|
||||
.map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?;
|
||||
|
||||
let index = index.saturating_sub(1);
|
||||
if index >= topics.len() {
|
||||
return Err(CommandError::new(
|
||||
"INVALID_TOPIC_INDEX",
|
||||
format!("Topic index {} is out of range (1-{})", index + 1, topics.len())
|
||||
));
|
||||
}
|
||||
topics[index].id.clone()
|
||||
} else {
|
||||
topic_id
|
||||
};
|
||||
|
||||
// 验证目标话题存在
|
||||
let topic = handler
|
||||
.store
|
||||
.get_topic(&target_topic_id)
|
||||
.map_err(|e| CommandError::new("SWITCH_TOPIC_ERROR", e.to_string()))?
|
||||
.ok_or_else(|| CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", target_topic_id)))?;
|
||||
|
||||
// 如果有 SessionManager,实际切换话题历史
|
||||
if let Some(ref session_manager) = handler.session_manager {
|
||||
if let Some(session) = session_manager.get(&ctx.channel_name).await {
|
||||
let mut session_guard = session.lock().await;
|
||||
session_guard.switch_topic(chat_id, &target_topic_id)
|
||||
.map_err(|e| CommandError::new("SWITCH_TOPIC_ERROR", e.to_string()))?;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回切换成功响应
|
||||
let message = format!(
|
||||
"✓ Switched to topic: {} ({} messages)",
|
||||
topic.title, topic.message_count
|
||||
);
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Notification, &message)
|
||||
.with_metadata("topic_id", &topic.id)
|
||||
.with_metadata("title", &topic.title)
|
||||
.with_metadata("message_count", &topic.message_count.to_string()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::storage::SessionStore;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn create_test_handler() -> SessionQueryCommandHandler {
|
||||
let store = Arc::new(SessionStore::in_memory().unwrap());
|
||||
SessionQueryCommandHandler::new(store)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_sessions_empty() {
|
||||
let handler = create_test_handler();
|
||||
// 需要先创建一个 session 和 topic
|
||||
let store = handler.store.clone();
|
||||
let session = store.create_session("cli", Some("test")).unwrap();
|
||||
|
||||
let ctx = CommandContext::new("test", "cli")
|
||||
.with_session_id(&session.id);
|
||||
let cmd = Command::ListSessions {
|
||||
include_archived: false,
|
||||
};
|
||||
|
||||
let result = handler.handle(cmd, ctx).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let resp = result.unwrap();
|
||||
assert!(resp.success);
|
||||
assert!(resp.messages[0].content.contains("No topics"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_sessions_with_items() {
|
||||
let handler = create_test_handler();
|
||||
let store = handler.store.clone();
|
||||
let session = store.create_session("cli", Some("test")).unwrap();
|
||||
|
||||
// 创建一个 topic
|
||||
store.create_topic(&session.id, "Test Topic", None).unwrap();
|
||||
|
||||
let ctx = CommandContext::new("test", "cli")
|
||||
.with_session_id(&session.id);
|
||||
let cmd = Command::ListSessions {
|
||||
include_archived: false,
|
||||
};
|
||||
|
||||
let result = handler.handle(cmd, ctx).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let resp = result.unwrap();
|
||||
assert!(resp.success);
|
||||
assert!(resp.metadata.contains_key("topics"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_load_session_not_found() {
|
||||
let handler = create_test_handler();
|
||||
let store = handler.store.clone();
|
||||
let session = store.create_session("cli", Some("test")).unwrap();
|
||||
|
||||
let ctx = CommandContext::new("test", "test")
|
||||
.with_session_id(&session.id);
|
||||
let cmd = Command::LoadSession {
|
||||
session_id: "nonexistent".to_string(),
|
||||
};
|
||||
|
||||
let result = handler.handle(cmd, ctx).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_load_session_success() {
|
||||
let handler = create_test_handler();
|
||||
let store = handler.store.clone();
|
||||
let session = store.create_session("cli", Some("test")).unwrap();
|
||||
let topic = store.create_topic(&session.id, "Test Topic", None).unwrap();
|
||||
|
||||
let ctx = CommandContext::new("test", "test")
|
||||
.with_session_id(&session.id);
|
||||
let cmd = Command::LoadSession {
|
||||
session_id: topic.id.clone(),
|
||||
};
|
||||
|
||||
let result = handler.handle(cmd, ctx).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let resp = result.unwrap();
|
||||
assert!(resp.success);
|
||||
assert_eq!(resp.metadata.get("topic_id").unwrap(), &topic.id);
|
||||
}
|
||||
}
|
||||
130
src/command/handlers/switch_session.rs
Normal file
130
src/command/handlers/switch_session.rs
Normal file
@ -0,0 +1,130 @@
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::{CommandHandler, CommandMetadata};
|
||||
use crate::command::response::{CommandError, CommandResponse, MessageKind};
|
||||
use crate::command::Command;
|
||||
use crate::gateway::session::SessionManager;
|
||||
use crate::storage::SessionStore;
|
||||
use async_trait::async_trait;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// 切换话题命令处理器
|
||||
pub struct SwitchSessionCommandHandler {
|
||||
store: Arc<SessionStore>,
|
||||
session_manager: Option<SessionManager>,
|
||||
}
|
||||
|
||||
impl SwitchSessionCommandHandler {
|
||||
pub fn new(store: Arc<SessionStore>) -> Self {
|
||||
Self { store, session_manager: None }
|
||||
}
|
||||
|
||||
pub fn with_session_manager(mut self, session_manager: SessionManager) -> Self {
|
||||
self.session_manager = Some(session_manager);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandHandler for SwitchSessionCommandHandler {
|
||||
fn can_handle(&self, cmd: &Command) -> bool {
|
||||
matches!(cmd, Command::SwitchSession { .. })
|
||||
}
|
||||
|
||||
fn metadata(&self) -> Option<CommandMetadata> {
|
||||
Some(CommandMetadata {
|
||||
name: "use",
|
||||
description: "切换到指定话题",
|
||||
usage: "/use <session_id>",
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle(
|
||||
&self,
|
||||
cmd: Command,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
match cmd {
|
||||
Command::SwitchSession { session_id } => {
|
||||
handle_switch_session(self, session_id, ctx).await
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_switch_session(
|
||||
handler: &SwitchSessionCommandHandler,
|
||||
topic_id: String,
|
||||
ctx: CommandContext,
|
||||
) -> Result<CommandResponse, CommandError> {
|
||||
let session_id = ctx.session_id.as_deref()
|
||||
.ok_or_else(|| CommandError::new("NO_SESSION", "No active session"))?;
|
||||
let chat_id = ctx.chat_id.as_deref()
|
||||
.ok_or_else(|| CommandError::new("NO_CHAT_ID", "No chat_id in context"))?;
|
||||
|
||||
// 尝试解析为序号
|
||||
let target_topic_id = if let Ok(index) = topic_id.parse::<usize>() {
|
||||
let topics = handler
|
||||
.store
|
||||
.list_topics(session_id)
|
||||
.map_err(|e| CommandError::new("LIST_TOPICS_ERROR", e.to_string()))?;
|
||||
|
||||
let index = index.saturating_sub(1);
|
||||
if index >= topics.len() {
|
||||
return Err(CommandError::new(
|
||||
"INVALID_TOPIC_INDEX",
|
||||
format!("Topic index {} is out of range (1-{})", index + 1, topics.len())
|
||||
));
|
||||
}
|
||||
topics[index].id.clone()
|
||||
} else {
|
||||
topic_id
|
||||
};
|
||||
|
||||
// 验证目标话题存在
|
||||
let topic = handler
|
||||
.store
|
||||
.get_topic(&target_topic_id)
|
||||
.map_err(|e| CommandError::new("SWITCH_TOPIC_ERROR", e.to_string()))?
|
||||
.ok_or_else(|| CommandError::new("TOPIC_NOT_FOUND", format!("Topic not found: {}", target_topic_id)))?;
|
||||
|
||||
// 如果有 SessionManager,实际切换话题历史
|
||||
if let Some(ref session_manager) = handler.session_manager {
|
||||
if let Some(session) = session_manager.get(&ctx.channel_name).await {
|
||||
let mut session_guard = session.lock().await;
|
||||
session_guard.switch_topic(chat_id, &target_topic_id)
|
||||
.map_err(|e| CommandError::new("SWITCH_TOPIC_ERROR", e.to_string()))?;
|
||||
}
|
||||
}
|
||||
|
||||
let message = format!(
|
||||
"✓ Switched to topic: {} ({} messages)",
|
||||
topic.title, topic.message_count
|
||||
);
|
||||
|
||||
Ok(CommandResponse::success(ctx.request_id)
|
||||
.with_message(MessageKind::Notification, &message)
|
||||
.with_metadata("topic_id", &topic.id)
|
||||
.with_metadata("title", &topic.title)
|
||||
.with_metadata("message_count", &topic.message_count.to_string()))
|
||||
}
|
||||
|
||||
fn format_time_ago(timestamp_ms: i64) -> String {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis() as i64;
|
||||
|
||||
let diff_ms = now - timestamp_ms;
|
||||
let diff_secs = diff_ms / 1000;
|
||||
|
||||
if diff_secs < 60 {
|
||||
"just now".to_string()
|
||||
} else if diff_secs < 3600 {
|
||||
format!("{} mins ago", diff_secs / 60)
|
||||
} else if diff_secs < 86400 {
|
||||
format!("{} hours ago", diff_secs / 3600)
|
||||
} else {
|
||||
format!("{} days ago", diff_secs / 86400)
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,8 @@ pub enum Command {
|
||||
SwitchSession { session_id: String },
|
||||
/// 获取当前话题信息
|
||||
GetCurrentSession,
|
||||
/// 显示所有支持的命令
|
||||
Help,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@ -41,6 +43,7 @@ impl Command {
|
||||
Command::LoadSession { .. } => "load_session",
|
||||
Command::SwitchSession { .. } => "switch_session",
|
||||
Command::GetCurrentSession => "get_current_session",
|
||||
Command::Help => "help",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,10 +7,14 @@ use crate::bus::{InboundMessage, MessageBus, OutboundMessage};
|
||||
use crate::command::adapter::InputAdapter;
|
||||
use crate::command::adapters::channel::ChannelInputAdapter;
|
||||
use crate::command::handler::CommandRouter;
|
||||
use crate::command::handlers::get_current::GetCurrentSessionCommandHandler;
|
||||
use crate::command::handlers::help::HelpCommandHandler;
|
||||
use crate::command::handlers::list_sessions::ListSessionsCommandHandler;
|
||||
use crate::command::handlers::load_session::LoadSessionCommandHandler;
|
||||
use crate::command::handlers::save_session::SaveSessionCommandHandler;
|
||||
use crate::command::handlers::save_topic::SaveTopicCommandHandler;
|
||||
use crate::command::handlers::session::SessionCommandHandler;
|
||||
use crate::command::handlers::session_query::SessionQueryCommandHandler;
|
||||
use crate::command::handlers::switch_session::SwitchSessionCommandHandler;
|
||||
use crate::config::LLMProviderConfig;
|
||||
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
||||
use crate::skills::SkillPromptProvider;
|
||||
@ -43,13 +47,21 @@ impl InboundProcessor {
|
||||
.with_session_manager(session_manager.clone());
|
||||
command_router.register(Box::new(session_handler));
|
||||
|
||||
// 注册 session_query 处理器
|
||||
let session_query_handler = SessionQueryCommandHandler::new(store)
|
||||
// 注册 list_sessions 处理器
|
||||
command_router.register(Box::new(ListSessionsCommandHandler::new(store.clone())));
|
||||
|
||||
// 注册 switch_session 处理器
|
||||
let switch_handler = SwitchSessionCommandHandler::new(store.clone())
|
||||
.with_session_manager(session_manager.clone());
|
||||
command_router.register(Box::new(session_query_handler));
|
||||
command_router.register(Box::new(switch_handler));
|
||||
|
||||
// 注册 get_current 处理器
|
||||
command_router.register(Box::new(GetCurrentSessionCommandHandler::new(store.clone())));
|
||||
|
||||
// 注册 load_session 处理器
|
||||
command_router.register(Box::new(LoadSessionCommandHandler::new(store.clone())));
|
||||
|
||||
// 注册 save_session 处理器
|
||||
let store = session_manager.store();
|
||||
let skills = session_manager.skills();
|
||||
let prompt_repository = session_manager.store().clone();
|
||||
let system_prompt_provider: Arc<dyn crate::agent::SystemPromptProvider> = Arc::new(CompositeSystemPromptProvider::new(vec![
|
||||
@ -67,10 +79,14 @@ impl InboundProcessor {
|
||||
|
||||
// 注册 save_topic 处理器
|
||||
command_router.register(Box::new(SaveTopicCommandHandler::new(
|
||||
store,
|
||||
store.clone(),
|
||||
system_prompt_provider,
|
||||
)));
|
||||
|
||||
// 注册 help 处理器(最后注册,获取所有已注册命令的元数据)
|
||||
let metadata = command_router.metadata_arc();
|
||||
command_router.register(Box::new(HelpCommandHandler::new(metadata)));
|
||||
|
||||
Self {
|
||||
bus,
|
||||
session_manager,
|
||||
|
||||
@ -5,9 +5,13 @@ use crate::command::adapter::{InputAdapter, OutputAdapter};
|
||||
use crate::command::adapters::websocket::{WebSocketInputAdapter, WebSocketOutputAdapter};
|
||||
use crate::command::context::CommandContext;
|
||||
use crate::command::handler::CommandRouter;
|
||||
use crate::command::handlers::get_current::GetCurrentSessionCommandHandler;
|
||||
use crate::command::handlers::help::HelpCommandHandler;
|
||||
use crate::command::handlers::list_sessions::ListSessionsCommandHandler;
|
||||
use crate::command::handlers::load_session::LoadSessionCommandHandler;
|
||||
use crate::command::handlers::save_session::SaveSessionCommandHandler;
|
||||
use crate::command::handlers::session::SessionCommandHandler;
|
||||
use crate::command::handlers::session_query::SessionQueryCommandHandler;
|
||||
use crate::command::handlers::switch_session::SwitchSessionCommandHandler;
|
||||
use crate::gateway::agent_prompt_provider::AgentPromptProvider;
|
||||
use crate::protocol::{WsInbound, WsOutbound, parse_inbound, serialize_outbound};
|
||||
use crate::skills::SkillPromptProvider;
|
||||
@ -224,18 +228,27 @@ async fn handle_inbound(
|
||||
]));
|
||||
|
||||
let mut router = CommandRouter::new();
|
||||
// 注册 Session 处理器,添加 SessionManager
|
||||
// 注册 Session 处理器
|
||||
let session_handler = SessionCommandHandler::new(store.clone())
|
||||
.with_session_manager(state.session_manager.clone());
|
||||
router.register(Box::new(session_handler));
|
||||
// 注册 SessionQuery 处理器
|
||||
let session_query_handler = SessionQueryCommandHandler::new(store.clone())
|
||||
// 注册 list_sessions 处理器
|
||||
router.register(Box::new(ListSessionsCommandHandler::new(store.clone())));
|
||||
// 注册 switch_session 处理器
|
||||
let switch_handler = SwitchSessionCommandHandler::new(store.clone())
|
||||
.with_session_manager(state.session_manager.clone());
|
||||
router.register(Box::new(session_query_handler));
|
||||
router.register(Box::new(switch_handler));
|
||||
// 注册 get_current 处理器
|
||||
router.register(Box::new(GetCurrentSessionCommandHandler::new(store.clone())));
|
||||
// 注册 load_session 处理器
|
||||
router.register(Box::new(LoadSessionCommandHandler::new(store.clone())));
|
||||
router.register(Box::new(SaveSessionCommandHandler::new(
|
||||
store,
|
||||
system_prompt_provider,
|
||||
store.clone(),
|
||||
system_prompt_provider.clone(),
|
||||
)));
|
||||
// 注册 help 处理器
|
||||
let metadata = router.metadata_arc();
|
||||
router.register(Box::new(HelpCommandHandler::new(metadata)));
|
||||
|
||||
// 构建命令上下文
|
||||
tracing::debug!(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user