diff --git a/mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/StepExecutionNode.java b/mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/StepExecutionNode.java index d19066e3..59c4ca8a 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/StepExecutionNode.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/graph/plan/node/StepExecutionNode.java @@ -30,10 +30,13 @@ import vip.mate.planning.service.PlanningService; import vip.mate.agent.context.ChatOrigin; import vip.mate.skill.runtime.SkillCatalogRenderer; import vip.mate.tool.builtin.DelegateAgentTool; +import vip.mate.tool.builtin.DelegationContext; +import vip.mate.tool.builtin.ToolExecutionContext; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; /** * 步骤执行节点 @@ -221,7 +224,7 @@ public class StepExecutionNode implements NodeAction { if (delegateAgentTool != null && assignedAgentId != null && !assignedAgentId.equals(parseLongOrNull(agentId))) { return executeDelegatedStep(accessor, stepIndex, step, planId, assignedAgentId, - chatOrigin, events, iterationEventsOn); + conversationId, chatOrigin, events, iterationEventsOn); } if (iterationEventsOn) { @@ -658,7 +661,7 @@ public class StepExecutionNode implements NodeAction { */ private Map executeDelegatedStep( PlanStateAccessor accessor, int stepIndex, String step, Long planId, - Long assignedAgentId, ChatOrigin chatOrigin, + Long assignedAgentId, String conversationId, ChatOrigin chatOrigin, List events, boolean iterationEventsOn) { if (iterationEventsOn) { @@ -672,12 +675,28 @@ public class StepExecutionNode implements NodeAction { log.info("[StepExecution] Delegating step {} to agent {}", stepIndex + 1, assignedAgentId); + // Seed the delegation context with the plan's REAL conversation id (from + // graph state) so the delegated child conversation is parented to it and + // stays hidden from the user's conversation list. The ChatOrigin in the + // plan-execute path carries no conversationId, so delegateByAgentId can't + // derive the parent on its own — we provide it here. + boolean seeded = false; + if (conversationId != null && !conversationId.isBlank() + && DelegationContext.parentConversationId() == null + && ToolExecutionContext.conversationId() == null) { + DelegationContext.enter(conversationId, Set.of(), conversationId, null, 0); + seeded = true; + } String result; try { result = delegateAgentTool.delegateByAgentId(assignedAgentId, step, chatOrigin); } catch (Exception e) { log.error("[StepExecution] Delegated step {} threw: {}", stepIndex, e.getMessage(), e); result = "[错误] 委派执行异常:" + e.getMessage(); + } finally { + if (seeded) { + DelegationContext.exit(); + } } String finalResult = result != null ? result : ""; diff --git a/mateclaw-server/src/main/java/vip/mate/tool/builtin/DelegateAgentTool.java b/mateclaw-server/src/main/java/vip/mate/tool/builtin/DelegateAgentTool.java index 4fd4de33..b12a0ec8 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/builtin/DelegateAgentTool.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/builtin/DelegateAgentTool.java @@ -340,8 +340,30 @@ public class DelegateAgentTool { if (target == null || !Boolean.TRUE.equals(target.getEnabled())) { return "[错误] 未找到 id=" + agentId + " 的已启用 Agent。"; } - ToolContext ctx = (parentOrigin != null ? parentOrigin : ChatOrigin.EMPTY).toToolContext(); - return delegateToAgent(target.getName(), task, false, ctx); + ChatOrigin origin = parentOrigin != null ? parentOrigin : ChatOrigin.EMPTY; + ToolContext ctx = origin.toToolContext(); + // A graph-node-initiated delegation (e.g. a plan step) runs outside any + // tool-execution / delegation context, so resolveParentConversationId() + // would return null and the child conversation would be created with a + // null parent — leaking it into the user's top-level conversation list + // (the list filters on parentConversationId IS NULL). Seed a depth-0 + // delegation frame carrying the parent conversation id from the + // ChatOrigin so the child is correctly parented and hidden, mirroring how + // a tool-initiated delegation gets its conv id from ToolExecutionContext. + String parentConvId = origin.conversationId(); + boolean seedContext = parentConvId != null && !parentConvId.isBlank() + && ToolExecutionContext.conversationId() == null + && DelegationContext.parentConversationId() == null; + if (seedContext) { + DelegationContext.enter(parentConvId, Set.of(), parentConvId, null, 0); + } + try { + return delegateToAgent(target.getName(), task, false, ctx); + } finally { + if (seedContext) { + DelegationContext.exit(); + } + } } // ==================== Parallel delegation ====================