refactor(todo): 统一子代理 scope_key 为全局唯一的 task_id
- 修改 list_todos 处理器,子代理使用 task_id 作为 scope_key,替代原先的 sub:{parent_session_id}:{task_id}
- 调整 todo_write 的 scope_key_from_context 函数,子/孙代理使用 task_id 隔离,避免与父代理污染
- 修正子任务消息的 task_id 传递逻辑,在 useChat hook 中为子代理视图的孙子任务回填正确的 task_id
- 清理 MessageBubble 组件中多余的 isSubAgent 变量声明
This commit is contained in:
parent
421714dfa3
commit
e585ec71b1
@ -41,10 +41,10 @@ impl CommandHandler for ListTodosCommandHandler {
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 子代理:scope_key = sub:{parent_session_id}:{task_id}
|
// 子代理:scope_key = task_id(全局唯一,与 todo_write 保持一致)
|
||||||
// 主代理:scope_key = topic_id.unwrap_or(session_id)
|
// 主代理:scope_key = topic_id.unwrap_or(session_id)
|
||||||
let scope_key = if let (Some(tid), Some(parent_sid)) = (task_id.as_deref(), ctx.session_id.as_deref()) {
|
let scope_key = if let Some(tid) = task_id.as_deref() {
|
||||||
format!("sub:{}:{}", parent_sid, tid)
|
tid.to_string()
|
||||||
} else {
|
} else {
|
||||||
ctx.topic_id
|
ctx.topic_id
|
||||||
.as_deref()
|
.as_deref()
|
||||||
|
|||||||
@ -330,10 +330,14 @@ impl Tool for TodoWriteTool {
|
|||||||
|
|
||||||
/// 计算 scope_key:
|
/// 计算 scope_key:
|
||||||
/// - 主代理 (nesting_depth == 0):优先 topic_id,否则 session_id
|
/// - 主代理 (nesting_depth == 0):优先 topic_id,否则 session_id
|
||||||
/// - 子/孙代理 (nesting_depth > 0):使用 session_id 隔离,避免污染父代理 todo
|
/// - 子/孙代理 (nesting_depth > 0):使用 task_id 隔离(全局唯一,与 list_todos 保持一致)
|
||||||
pub(crate) fn scope_key_from_context(context: &ToolContext) -> Option<String> {
|
pub(crate) fn scope_key_from_context(context: &ToolContext) -> Option<String> {
|
||||||
if context.nesting_depth > 0 {
|
if context.nesting_depth > 0 {
|
||||||
context.session_id.clone().filter(|s| !s.is_empty())
|
// 使用 task_id 而不是 session_id 作为 scope_key。
|
||||||
|
// session_id 对于孙智能体包含父链(如 sub:sub:root:parent:task),
|
||||||
|
// 而 list_todos handler 用根 session + task_id 拼接,两者不匹配。
|
||||||
|
// task_id 是全局唯一的 UUID(task:xxx),直接使用可避免层级不一致。
|
||||||
|
context.task_id.clone().filter(|s| !s.is_empty())
|
||||||
} else {
|
} else {
|
||||||
let tid = context.topic_id.as_deref().filter(|t| !t.is_empty());
|
let tid = context.topic_id.as_deref().filter(|t| !t.is_empty());
|
||||||
let sid = context.session_id.as_deref().filter(|s| !s.is_empty());
|
let sid = context.session_id.as_deref().filter(|s| !s.is_empty());
|
||||||
|
|||||||
@ -364,7 +364,6 @@ export function MessageBubble({ message, onNavigateToSubAgent, showThinking = tr
|
|||||||
|
|
||||||
const isTaskTool = message.toolName === 'task'
|
const isTaskTool = message.toolName === 'task'
|
||||||
const taskResult = isTaskTool && hasResult ? parseTaskResult(displayContent) : null
|
const taskResult = isTaskTool && hasResult ? parseTaskResult(displayContent) : null
|
||||||
const isSubAgent = !!message.subagentTaskId
|
|
||||||
const subagentType = (message.arguments as Record<string, unknown> | null)?.subagent_type as string || 'general'
|
const subagentType = (message.arguments as Record<string, unknown> | null)?.subagent_type as string || 'general'
|
||||||
const taskDescription = (message.arguments as Record<string, unknown> | null)?.description as string || ''
|
const taskDescription = (message.arguments as Record<string, unknown> | null)?.description as string || ''
|
||||||
const taskPrompt = (message.arguments as Record<string, unknown> | null)?.prompt as string || ''
|
const taskPrompt = (message.arguments as Record<string, unknown> | null)?.prompt as string || ''
|
||||||
|
|||||||
@ -395,6 +395,34 @@ export function useChat(): UseChatReturn {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backfill grandchild task_id on task tool_call in sub-agent view.
|
||||||
|
// When the sub-agent spawns a grandchild via the task tool, the
|
||||||
|
// protocol's subagent_task_id field carries the parent task_id for
|
||||||
|
// routing, not the new child task_id. We must update it to the
|
||||||
|
// child's task_id from the task_started event so that "查看实时进度"
|
||||||
|
// navigates to the correct (grandchild) session.
|
||||||
|
if (message.type === 'task_started') {
|
||||||
|
const msg = message as TaskStarted
|
||||||
|
if (msg.parent_task_id === currentSubAgentView.taskId) {
|
||||||
|
setSubAgentStack((prev) => {
|
||||||
|
if (prev.length === 0) return prev
|
||||||
|
const top = prev[prev.length - 1]
|
||||||
|
const updatedMessages = [...top.messages]
|
||||||
|
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
||||||
|
const m = updatedMessages[i]
|
||||||
|
if (m.type === 'tool_call' && m.toolName === 'task' && m.subagentTaskId === currentSubAgentView.taskId) {
|
||||||
|
updatedMessages[i] = { ...m, subagentTaskId: msg.task_id }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const newStack = [...prev]
|
||||||
|
newStack[newStack.length - 1] = { ...top, messages: updatedMessages }
|
||||||
|
return newStack
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Only accept messages explicitly tagged with matching subagent_task_id.
|
// Only accept messages explicitly tagged with matching subagent_task_id.
|
||||||
// History messages are now tagged by the backend (send_task_messages),
|
// History messages are now tagged by the backend (send_task_messages),
|
||||||
// and live sub-agent messages are tagged by SubAgentEmitter.
|
// and live sub-agent messages are tagged by SubAgentEmitter.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user