feat: 统一路径显示格式,确保跨平台兼容性;重构系统提示词提供者构建逻辑

This commit is contained in:
oudecheng 2026-07-07 09:19:58 +08:00
parent 43f6ea7b08
commit 4329cfbbe9
7 changed files with 93 additions and 44 deletions

View File

@ -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))
}

View File

@ -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()))
}

View File

@ -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<dyn PromptInjectionRepository>,
skills: Arc<SkillRuntime>,
experts: Arc<ExpertRuntime>,
subagent_runtime: Arc<SubagentRuntime>,
) -> Arc<dyn SystemPromptProvider> {
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<ToolRegistry>,
@ -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,

View File

@ -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<dyn crate::agent::SystemPromptProvider> = 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(

View File

@ -302,6 +302,8 @@ pub(crate) fn build_session_manager_with_sender(
Ok((SessionManager::from_services(SessionManagerServices {
tools: tools as Arc<ToolRegistry>,
skills,
experts,
subagent_runtime: subagent_runtime.clone(),
store,
show_tool_results,
lifecycle,

View File

@ -621,6 +621,8 @@ impl Session {
pub struct SessionManager {
tools: Arc<ToolRegistry>,
skills: Arc<SkillRuntime>,
experts: Arc<crate::experts::ExpertRuntime>,
subagent_runtime: Arc<SubagentRuntime>,
store: Arc<SessionStore>,
show_tool_results: bool,
lifecycle: SessionLifecycleService,
@ -634,6 +636,8 @@ pub struct SessionManager {
pub(crate) struct SessionManagerServices {
pub(crate) tools: Arc<ToolRegistry>,
pub(crate) skills: Arc<SkillRuntime>,
pub(crate) experts: Arc<crate::experts::ExpertRuntime>,
pub(crate) subagent_runtime: Arc<SubagentRuntime>,
pub(crate) store: Arc<SessionStore>,
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<crate::experts::ExpertRuntime> {
self.experts.clone()
}
/// 获取子代理运行时实例(与 AgentFactory 共享同一 Arc 实例)
pub fn subagent_runtime(&self) -> Arc<SubagentRuntime> {
self.subagent_runtime.clone()
}
pub(crate) fn cli_sessions(&self) -> CliSessionService {
self.cli_sessions.clone()
}

View File

@ -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<dyn crate::agent::SystemPromptProvider> = 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 处理器