From eef0d24dcd6f9a65bf09f8c70e210b35850adbc4 Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Fri, 12 Jun 2026 17:36:10 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20topic=5Fid=20=E7=A9=BF=E9=80=8F=E5=88=B0?= =?UTF-8?q?=20ToolContext=EF=BC=8C=E7=BB=9F=E4=B8=80=20todo=20scope=5Fkey?= =?UTF-8?q?=20=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AgentBuildRequest 加 topic_id 字段 - Session.create_agent 传入 current_topic - ToolContext.topic_id 不再硬编码 None - 删除已废弃的 intercept_todo_write_results - 工具/emitter/handler 三处 scope_key 计算全部统一 Co-Authored-By: Claude Opus 4.8 --- src/gateway/agent_factory.rs | 4 +- src/gateway/session.rs | 85 +----------------------------------- 2 files changed, 5 insertions(+), 84 deletions(-) diff --git a/src/gateway/agent_factory.rs b/src/gateway/agent_factory.rs index cc3be2c..efddc66 100644 --- a/src/gateway/agent_factory.rs +++ b/src/gateway/agent_factory.rs @@ -24,6 +24,8 @@ pub(crate) struct AgentBuildRequest<'a> { pub(crate) sender_id: Option<&'a str>, pub(crate) message_id: Option<&'a str>, pub(crate) provider_config: LLMProviderConfig, + /// 当前话题 ID(可选):用于 todo 等按 topic 隔离的工具 + pub(crate) topic_id: Option, /// 取消信号接收端(可选):Agent 在每次迭代时检查是否被取消 pub(crate) cancel_token: Option>, } @@ -73,7 +75,7 @@ impl AgentFactory { sender_id: request.sender_id.map(str::to_string), chat_id: Some(tool_chat_id.to_string()), session_id: Some(session_id), - topic_id: None, + topic_id: request.topic_id.clone(), message_id: request.message_id.map(str::to_string), message_seq: None, subagent_description: None, diff --git a/src/gateway/session.rs b/src/gateway/session.rs index c2bc751..c0208bb 100644 --- a/src/gateway/session.rs +++ b/src/gateway/session.rs @@ -445,89 +445,6 @@ impl Session { let _ = self.user_tx.send(msg).await; } - /// 扫描 agent 结果中的 todo_write 工具消息, - /// 提取 todos 并做持久化 + 前端推送(同步版本)。 - pub(crate) fn intercept_todo_write_results( - &self, - emitted_messages: &[ChatMessage], - chat_id: &str, - ) { - for msg in emitted_messages { - if msg.role != "tool" { - continue; - } - if msg.tool_name.as_deref() != Some("todo_write") { - continue; - } - - // 解析工具返回的 JSON - let parsed: serde_json::Value = match serde_json::from_str(&msg.content) { - Ok(v) => v, - Err(_) => continue, - }; - - let Some(todos_array) = parsed - .get("current_todos") - .and_then(|v| v.as_array()) - else { - continue; - }; - - // 计算持久化所需的 key - let session_id = crate::storage::persistent_session_id(&self.channel_name, chat_id); - let topic_id = self.current_topic(chat_id); - let scope_key = topic_id.map(|t| t.to_string()).unwrap_or_else(|| session_id.clone()); - - // 转换为 TodoRecord 并持久化 - let records: Vec = todos_array - .iter() - .filter_map(|item| { - Some(crate::storage::TodoRecord { - id: item.get("id")?.as_str()?.to_string(), - scope_key: scope_key.clone(), - session_id: session_id.clone(), - topic_id: topic_id.map(|t| t.to_string()), - content: item.get("content")?.as_str()?.to_string(), - status: item.get("status")?.as_str()?.to_string(), - priority: item.get("priority")?.as_str()?.to_string(), - created_at: item.get("created_at")?.as_i64()?, - updated_at: item.get("updated_at")?.as_i64()?, - }) - }) - .collect(); - - // 持久化到 SQLite - tracing::info!( - scope_key = %scope_key, - todo_count = records.len(), - "intercept_todo_write_results: persisting todos" - ); - if let Err(e) = self.store.replace_todos(&scope_key, &records) { - tracing::warn!(error = %e, scope_key = %scope_key, "Failed to persist todo list"); - } - - // 推送到前端(使用 try_send 避免异步) - let summaries: Vec = records - .iter() - .map(|r| crate::protocol::TodoItemSummary { - id: r.id.clone(), - content: r.content.clone(), - status: r.status.clone(), - priority: r.priority.clone(), - created_at: r.created_at, - updated_at: r.updated_at, - }) - .collect(); - - let _ = self.user_tx.try_send(crate::protocol::WsOutbound::TodoList { - todos: summaries, - scope_key: scope_key.clone(), - }); - - break; // 只处理第一个成功的 todo_write - } - } - /// 获取 provider_config 引用 pub fn provider_config(&self) -> &LLMProviderConfig { &self.provider_config @@ -604,9 +521,11 @@ impl Session { ) -> Result { // 消费 pending 的取消信号接收端(如果存在) let cancel_token = self.pending_cancel_tokens.remove(session_chat_id); + let topic_id = self.current_topic(session_chat_id).map(|s| s.to_string()); self.agent_factory.create(AgentBuildRequest { channel_name: &self.channel_name, session_chat_id, + topic_id, notification_chat_id, sender_id, message_id,