diff --git a/src/command/handlers/save_session.rs b/src/command/handlers/save_session.rs index 10a0198..9092920 100644 --- a/src/command/handlers/save_session.rs +++ b/src/command/handlers/save_session.rs @@ -213,9 +213,11 @@ async fn handle_save_session( Ok(CommandResponse::success(ctx.request_id) .with_message( MessageKind::Notification, - &format!("Session saved to: {}", output_path.display()), + // 路径中的反斜杠在 Markdown 渲染时会被当作转义符吃掉, + // 统一转换为正斜杠以保证显示完整(跨平台兼容) + &format!("Session saved to: {}", output_path.display().to_string().replace('\\', "/")), ) - .with_metadata("filepath", output_path.to_string_lossy().as_ref()) + .with_metadata("filepath", &output_path.display().to_string().replace('\\', "/")) .with_metadata("message_count", &message_count.to_string())) } @@ -703,7 +705,7 @@ impl InChatCommandHandler for SaveSessionInChatHandler { // 返回成功或失败消息 match result { Ok(output_path) => { - let msg = format!("Session saved to: {}", output_path.display()); + let msg = format!("Session saved to: {}", output_path.display().to_string().replace('\\', "/")); tracing::info!("{}", msg); Ok(Some(msg)) } diff --git a/src/command/handlers/save_topic.rs b/src/command/handlers/save_topic.rs index 9b28f85..003e088 100644 --- a/src/command/handlers/save_topic.rs +++ b/src/command/handlers/save_topic.rs @@ -279,8 +279,10 @@ async fn handle_save_topic( Ok(CommandResponse::success(ctx.request_id) .with_message( MessageKind::Notification, - &format!("Topic saved to: {}", output_path.display()), + // 路径中的反斜杠在 Markdown 渲染时会被当作转义符吃掉, + // 统一转换为正斜杠以保证显示完整(跨平台兼容) + &format!("Topic saved to: {}", output_path.display().to_string().replace('\\', "/")), ) - .with_metadata("filepath", output_path.to_string_lossy().as_ref()) + .with_metadata("filepath", &output_path.display().to_string().replace('\\', "/")) .with_metadata("message_count", &message_count.to_string())) } \ No newline at end of file diff --git a/src/gateway/agent_factory.rs b/src/gateway/agent_factory.rs index 9c508b0..ac1994f 100644 --- a/src/gateway/agent_factory.rs +++ b/src/gateway/agent_factory.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::agent::{AgentError, AgentLoop, CompositeSystemPromptProvider}; +use crate::agent::{AgentError, AgentLoop, CompositeSystemPromptProvider, SystemPromptProvider}; use crate::config::LLMProviderConfig; use crate::experts::ExpertPromptProvider; use crate::experts::ExpertRuntime; @@ -12,6 +12,33 @@ use crate::storage::PromptInjectionRepository; use crate::tools::task::runtime::{SubagentPromptProvider, SubagentRuntime}; use crate::tools::{ToolContext, ToolRegistry}; +/// 构建与 Agent 实际使用的完全一致的组合系统提示词 Provider。 +/// +/// 单一来源:AgentFactory::create 与命令侧(/save、/save-session、/current) +/// 都调用此函数,确保保存到文件的系统提示词与 LLM 实际接收的提示词一致。 +/// +/// Provider 顺序:AgentPrompt → SkillPrompt → ExpertPrompt → SubagentPrompt → TodoPrompt +pub(crate) fn build_system_prompt_provider( + reinject_every: usize, + provider_config: LLMProviderConfig, + prompt_repository: Arc, + skills: Arc, + experts: Arc, + subagent_runtime: Arc, +) -> Arc { + Arc::new(CompositeSystemPromptProvider::new(vec![ + Box::new(AgentPromptProvider::new( + reinject_every, + provider_config, + prompt_repository, + )), + Box::new(SkillPromptProvider::new(skills)), + Box::new(ExpertPromptProvider::new(experts)), + Box::new(SubagentPromptProvider::new(subagent_runtime)), + Box::new(TodoPromptProvider::new()), + ])) +} + #[derive(Clone)] pub(crate) struct AgentFactory { tools: Arc, @@ -78,19 +105,15 @@ impl AgentFactory { "AgentFactory: creating agent with config" ); - // 创建组合的系统提示词提供者 - // 顺序:AgentPrompt → SkillPrompt → ExpertPrompt → SubagentPrompt → TodoPrompt - let system_prompt_provider = Arc::new(CompositeSystemPromptProvider::new(vec![ - Box::new(AgentPromptProvider::new( - self.reinject_every, - request.provider_config.clone(), - self.prompt_repository.clone(), - )), - Box::new(SkillPromptProvider::new(self.skills.clone())), - Box::new(ExpertPromptProvider::new(self.experts.clone())), - Box::new(SubagentPromptProvider::new(self.subagent_runtime.clone())), - Box::new(TodoPromptProvider::new()), - ])); + // 创建组合的系统提示词提供者(与命令侧 /save 等共享同一构建逻辑) + let system_prompt_provider = build_system_prompt_provider( + self.reinject_every, + request.provider_config.clone(), + self.prompt_repository.clone(), + self.skills.clone(), + self.experts.clone(), + self.subagent_runtime.clone(), + ); AgentLoop::with_tools_and_system_prompt_provider( request.provider_config, diff --git a/src/gateway/processor.rs b/src/gateway/processor.rs index f70a9d4..b86765a 100644 --- a/src/gateway/processor.rs +++ b/src/gateway/processor.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, Mutex}; use tokio::sync::Semaphore; -use crate::agent::{AgentError, CompositeSystemPromptProvider, PersistingEmittedMessageHandler}; +use crate::agent::{AgentError, PersistingEmittedMessageHandler}; use crate::bus::{InboundMessage, MessageBus, OutboundMessage}; use crate::command::adapter::InputAdapter; use crate::command::adapters::channel::ChannelInputAdapter; @@ -19,10 +19,9 @@ use crate::command::handlers::session::SessionCommandHandler; use crate::command::handlers::stop_execution::StopExecutionCommandHandler; use crate::command::handlers::switch_topic::SwitchTopicCommandHandler; use crate::config::LLMProviderConfig; -use crate::gateway::agent_prompt_provider::AgentPromptProvider; +use crate::gateway::agent_factory::build_system_prompt_provider; use crate::gateway::cancel_manager::CancelManager; use crate::providers::{create_provider, ProviderRuntimeConfig}; -use crate::skills::SkillPromptProvider; use crate::storage::persistent_session_id; use crate::topic_description::generate_topic_description; @@ -65,16 +64,16 @@ impl InboundProcessor { command_router.register(Box::new(switch_handler)); // 创建 system_prompt_provider(用于 save_session, save_topic, get_current) - let skills = session_manager.skills(); - let prompt_repository = session_manager.store().clone(); - let system_prompt_provider: Arc = Arc::new(CompositeSystemPromptProvider::new(vec![ - Box::new(AgentPromptProvider::new( - 0, // 不需要 reinject 逻辑 - provider_config.clone(), - prompt_repository, - )), - Box::new(SkillPromptProvider::new(skills)), - ])); + // 与 AgentFactory::create 共享同一构建逻辑,确保保存到文件的系统提示词 + // 与 LLM 实际接收的提示词完全一致(含 Expert/Subagent/Todo) + let system_prompt_provider = build_system_prompt_provider( + 0, // 命令侧不需要 reinject 逻辑 + provider_config.clone(), + session_manager.store().clone(), + session_manager.skills(), + session_manager.experts(), + session_manager.subagent_runtime(), + ); // 注册 get_current 处理器 command_router.register(Box::new( diff --git a/src/gateway/runtime.rs b/src/gateway/runtime.rs index 7d7ec76..e62ed79 100644 --- a/src/gateway/runtime.rs +++ b/src/gateway/runtime.rs @@ -302,6 +302,8 @@ pub(crate) fn build_session_manager_with_sender( Ok((SessionManager::from_services(SessionManagerServices { tools: tools as Arc, skills, + experts, + subagent_runtime: subagent_runtime.clone(), store, show_tool_results, lifecycle, diff --git a/src/gateway/session.rs b/src/gateway/session.rs index cdc415b..d685a3b 100644 --- a/src/gateway/session.rs +++ b/src/gateway/session.rs @@ -621,6 +621,8 @@ impl Session { pub struct SessionManager { tools: Arc, skills: Arc, + experts: Arc, + subagent_runtime: Arc, store: Arc, show_tool_results: bool, lifecycle: SessionLifecycleService, @@ -634,6 +636,8 @@ pub struct SessionManager { pub(crate) struct SessionManagerServices { pub(crate) tools: Arc, pub(crate) skills: Arc, + pub(crate) experts: Arc, + pub(crate) subagent_runtime: Arc, pub(crate) store: Arc, pub(crate) show_tool_results: bool, pub(crate) lifecycle: SessionLifecycleService, @@ -649,6 +653,8 @@ impl SessionManager { Self { tools: services.tools, skills: services.skills, + experts: services.experts, + subagent_runtime: services.subagent_runtime, store: services.store, show_tool_results: services.show_tool_results, lifecycle: services.lifecycle, @@ -716,6 +722,16 @@ impl SessionManager { self.skills.clone() } + /// 获取专家运行时实例(与 AgentFactory、HTTP API 共享同一 Arc 实例) + pub fn experts(&self) -> Arc { + self.experts.clone() + } + + /// 获取子代理运行时实例(与 AgentFactory 共享同一 Arc 实例) + pub fn subagent_runtime(&self) -> Arc { + self.subagent_runtime.clone() + } + pub(crate) fn cli_sessions(&self) -> CliSessionService { self.cli_sessions.clone() } diff --git a/src/gateway/ws.rs b/src/gateway/ws.rs index a51fccf..0336a02 100644 --- a/src/gateway/ws.rs +++ b/src/gateway/ws.rs @@ -1,5 +1,5 @@ use super::GatewayState; -use crate::agent::{AgentError, CompositeSystemPromptProvider}; +use crate::agent::AgentError; use crate::bus::{InboundMessage, MediaItem}; use crate::command::adapter::{InputAdapter, OutputAdapter}; use crate::command::adapters::websocket::{WebSocketInputAdapter, WebSocketOutputAdapter}; @@ -25,9 +25,8 @@ use crate::command::handlers::save_topic::SaveTopicCommandHandler; use crate::command::handlers::session::SessionCommandHandler; use crate::command::handlers::stop_execution::StopExecutionCommandHandler; use crate::command::handlers::switch_topic::SwitchTopicCommandHandler; -use crate::gateway::agent_prompt_provider::AgentPromptProvider; +use crate::gateway::agent_factory::build_system_prompt_provider; use crate::protocol::{WsInbound, WsOutbound, MediaSummary, parse_inbound, serialize_outbound}; -use crate::skills::SkillPromptProvider; use crate::storage::persistent_session_id; use crate::tools::task::repository::TaskRepository; use crate::tools::task::types::TaskSessionState; @@ -399,14 +398,16 @@ async fn handle_inbound( .map_err(|e| AgentError::Other(e.to_string()))?; let prompt_repository = state.session_manager.store().clone(); - let system_prompt_provider: Arc = Arc::new(CompositeSystemPromptProvider::new(vec![ - Box::new(AgentPromptProvider::new( - 0, - provider_config.clone(), - prompt_repository.clone(), - )), - Box::new(SkillPromptProvider::new(skills)), - ])); + // 与 AgentFactory::create 共享同一构建逻辑,确保 /save、/save-session、 + // /current 保存/展示的系统提示词与 LLM 实际接收的完全一致 + let system_prompt_provider = build_system_prompt_provider( + 0, // 命令侧不需要 reinject 逻辑 + provider_config.clone(), + prompt_repository, + skills, + state.session_manager.experts(), + state.session_manager.subagent_runtime(), + ); let mut router = CommandRouter::new(); // 注册 Session 处理器 @@ -426,7 +427,11 @@ async fn handle_inbound( .with_session_manager(state.session_manager.clone()); router.register(Box::new(switch_handler)); // 注册 get_current 处理器 - router.register(Box::new(GetCurrentSessionCommandHandler::new(store.clone()))); + router.register(Box::new( + GetCurrentSessionCommandHandler::new(store.clone()) + .with_session_manager(state.session_manager.clone()) + .with_system_prompt_provider(system_prompt_provider.clone()), + )); // 注册 load_topic 处理器 router.register(Box::new(LoadTopicCommandHandler::new(store.clone()))); // 注册 load_task_messages 处理器