From fbeece2d891fcf8da22861805ab2a88d9b9f23e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=99=88=E5=AD=90=E9=BB=98?= <925456043@qq.com>
Date: Thu, 23 Jul 2026 19:45:49 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AE=8C=E5=96=84=E5=B7=A5=E5=85=B7?=
=?UTF-8?q?=E5=AE=A1=E6=89=B9=E8=B0=83=E7=94=A8=E7=BB=91=E5=AE=9A?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 以 toolCallId、工具名称和入参绑定一次性执行授权
- 支持批次审批、重复调用去重及拒绝过期处理
- 补充多工具审批与授权消费回归测试
---
.../agent/runtime/AgentResumeRequest.java | 4 +-
.../agentscope/AgentScopeReActRuntime.java | 103 ++--
.../interceptor/ToolHitlInterceptor.java | 276 ++++++++-
.../hitl/AgentToolApprovalCoordinator.java | 526 +++++++++++++++++-
.../hitl/AgentToolApprovalResolution.java | 93 ++++
.../AgentScopeStatefulRuntimeTest.java | 281 ++++++++++
.../AgentToolApprovalCoordinatorTest.java | 258 +++++++++
7 files changed, 1454 insertions(+), 87 deletions(-)
create mode 100644 easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/hitl/AgentToolApprovalResolution.java
create mode 100644 easy-agents-agent-runtime/src/test/java/com/easyagents/agent/runtime/hitl/AgentToolApprovalCoordinatorTest.java
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/AgentResumeRequest.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/AgentResumeRequest.java
index b0e7494..e042bfc 100644
--- a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/AgentResumeRequest.java
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/AgentResumeRequest.java
@@ -35,7 +35,9 @@ public class AgentResumeRequest {
*
*
该字段仅供服务端集成层使用。普通调用方不应设置该标记;设置后 runtime 会跳过
* 当前进程内 {@code AgentToolApprovalCoordinator} 的 token 存在性校验,用于服务重启或跨节点后
- * 从 AgentScope session 中继续 pending tool。
+ * 从 AgentScope session 中继续 pending tool。批准请求必须在 metadata 中提供
+ * {@code toolCallId/toolName/toolInput},多个调用使用 {@code approvedToolCalls} 列表,
+ * 以便 runtime 将持久化审批结果绑定到实际工具调用。
*/
private boolean trusted;
diff --git a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentScopeReActRuntime.java b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentScopeReActRuntime.java
index 7530e33..1dcbdef 100644
--- a/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentScopeReActRuntime.java
+++ b/easy-agents-agent-runtime/src/main/java/com/easyagents/agent/runtime/agentscope/AgentScopeReActRuntime.java
@@ -9,7 +9,9 @@ import com.easyagents.agent.runtime.event.observer.AgentRuntimeErrorObserver;
import com.easyagents.agent.runtime.event.observer.ReasoningLifecycleObserver;
import com.easyagents.agent.runtime.event.observer.SkillExecutionObserver;
import com.easyagents.agent.runtime.event.observer.ToolExecutionObserver;
+import com.easyagents.agent.runtime.hitl.AgentPendingState;
import com.easyagents.agent.runtime.hitl.AgentToolApprovalCoordinator;
+import com.easyagents.agent.runtime.hitl.AgentToolApprovalResolution;
import com.easyagents.agent.runtime.hitl.AgentToolApprovalRejectedException;
import com.easyagents.agent.runtime.knowledge.AgentKnowledgeSpec;
import com.easyagents.agent.runtime.knowledge.citation.AgentKnowledgeCitationMatcher;
@@ -153,6 +155,9 @@ public class AgentScopeReActRuntime implements AgentRuntime {
*/
@Override
public void close() {
+ if (approvalCoordinator != null) {
+ approvalCoordinator.cancelAll("Agent runtime has been closed.");
+ }
closeMcpClients();
initialized.set(false);
}
@@ -188,17 +193,27 @@ public class AgentScopeReActRuntime implements AgentRuntime {
return Flux.error(new AgentRuntimeException("Agent runtime is already streaming."));
}
AgentRuntimeExecutionContext executionContext = createResumeExecutionContext(request);
+ AgentToolApprovalResolution resolution = null;
try {
- if (!request.isTrusted()) {
- approvalCoordinator.consume(request);
+ if (request.isTrusted()) {
+ approvalCoordinator.authorizeTrustedExecution(request);
+ } else {
+ resolution = approvalCoordinator.resolve(request);
}
} catch (RuntimeException error) {
running.set(false);
throw error;
}
- // 审批拒绝
- if (!request.isApproved()) {
- executionContext.setCancelReason(request.getRejectReason());
+ if (resolution != null
+ && resolution.getStatus() == AgentToolApprovalResolution.Status.WAITING) {
+ return waitingForRemainingApprovals(executionContext, resolution);
+ }
+ if (!request.isApproved()
+ || resolution != null
+ && (resolution.getStatus() == AgentToolApprovalResolution.Status.REJECTED
+ || resolution.getStatus() == AgentToolApprovalResolution.Status.EXPIRED)) {
+ String cancelReason = resolution == null ? request.getRejectReason() : resolution.getReason();
+ executionContext.setCancelReason(cancelReason);
return Flux.defer(() -> {
saveSession();
return Flux.just(started(executionContext), cancelled(executionContext));
@@ -240,8 +255,6 @@ public class AgentScopeReActRuntime implements AgentRuntime {
AtomicReference finalMessage = new AtomicReference<>();
// HITL 暂停事件。被设置后,本轮以 SUSPENDED 挂起而不是 COMPLETED 结束。
AtomicReference suspendedEvent = new AtomicReference<>();
- // 本轮 HITL 待审批项来自旁路交互事件,最终会合并进 SUSPENDED 挂起事件。
- List