fix: topic_id 穿透到 ToolContext,统一 todo scope_key 计算
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
ec5ddf644a
commit
eef0d24dcd
@ -24,6 +24,8 @@ pub(crate) struct AgentBuildRequest<'a> {
|
|||||||
pub(crate) sender_id: Option<&'a str>,
|
pub(crate) sender_id: Option<&'a str>,
|
||||||
pub(crate) message_id: Option<&'a str>,
|
pub(crate) message_id: Option<&'a str>,
|
||||||
pub(crate) provider_config: LLMProviderConfig,
|
pub(crate) provider_config: LLMProviderConfig,
|
||||||
|
/// 当前话题 ID(可选):用于 todo 等按 topic 隔离的工具
|
||||||
|
pub(crate) topic_id: Option<String>,
|
||||||
/// 取消信号接收端(可选):Agent 在每次迭代时检查是否被取消
|
/// 取消信号接收端(可选):Agent 在每次迭代时检查是否被取消
|
||||||
pub(crate) cancel_token: Option<tokio::sync::watch::Receiver<()>>,
|
pub(crate) cancel_token: Option<tokio::sync::watch::Receiver<()>>,
|
||||||
}
|
}
|
||||||
@ -73,7 +75,7 @@ impl AgentFactory {
|
|||||||
sender_id: request.sender_id.map(str::to_string),
|
sender_id: request.sender_id.map(str::to_string),
|
||||||
chat_id: Some(tool_chat_id.to_string()),
|
chat_id: Some(tool_chat_id.to_string()),
|
||||||
session_id: Some(session_id),
|
session_id: Some(session_id),
|
||||||
topic_id: None,
|
topic_id: request.topic_id.clone(),
|
||||||
message_id: request.message_id.map(str::to_string),
|
message_id: request.message_id.map(str::to_string),
|
||||||
message_seq: None,
|
message_seq: None,
|
||||||
subagent_description: None,
|
subagent_description: None,
|
||||||
|
|||||||
@ -445,89 +445,6 @@ impl Session {
|
|||||||
let _ = self.user_tx.send(msg).await;
|
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<crate::storage::TodoRecord> = 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<crate::protocol::TodoItemSummary> = 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 引用
|
/// 获取 provider_config 引用
|
||||||
pub fn provider_config(&self) -> &LLMProviderConfig {
|
pub fn provider_config(&self) -> &LLMProviderConfig {
|
||||||
&self.provider_config
|
&self.provider_config
|
||||||
@ -604,9 +521,11 @@ impl Session {
|
|||||||
) -> Result<AgentLoop, AgentError> {
|
) -> Result<AgentLoop, AgentError> {
|
||||||
// 消费 pending 的取消信号接收端(如果存在)
|
// 消费 pending 的取消信号接收端(如果存在)
|
||||||
let cancel_token = self.pending_cancel_tokens.remove(session_chat_id);
|
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 {
|
self.agent_factory.create(AgentBuildRequest {
|
||||||
channel_name: &self.channel_name,
|
channel_name: &self.channel_name,
|
||||||
session_chat_id,
|
session_chat_id,
|
||||||
|
topic_id,
|
||||||
notification_chat_id,
|
notification_chat_id,
|
||||||
sender_id,
|
sender_id,
|
||||||
message_id,
|
message_id,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user