fix(core): prevent pending recovery from consuming HITL approvals#2109
fix(core): prevent pending recovery from consuming HITL approvals#2109hanydd wants to merge 3 commits into
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
oss-maintainer
left a comment
There was a problem hiding this comment.
Summary
This PR modifies 2 file(s) (+207 -10).
Reviewed the changes and they look reasonable overall. No critical issues found.
Automated review by github-manager-bot
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
Fixes pending-tool-recovery consuming HITL approvals. Dual-layer protection: outer skip on ConfirmResult, inner filter ASKING tools. 3 tests. Concern: hasConfirmResultInput lacks null guard on getMetadata().
|
|
||
| List<ConfirmResult> confirmResults = extractConfirmResults(msgs); | ||
| boolean hasConfirmResultInput = | ||
| msgs != null |
There was a problem hiding this comment.
[minor] msg.getMetadata().containsKey() lacks null guard (getMetadata() can return null). Use already-extracted confirmResults.isEmpty() instead.
There was a problem hiding this comment.
正常的 Msg 构造路径不会在这里产生 NPE:构造器始终将 metadata 初始化为 HashMap,传入 null 时得到的也是空 map。这里已经补上 null guard,与 getMetadata() 当前的 Javadoc 保持一致。没有改成 confirmResults.isEmpty(),因为两者语义不同:空或格式错误的确认载荷会让 confirmResults 为空,但 metadata key 的存在仍表示这是一次审批输入。此时必须跳过 recovery,避免同一批次中的其他 pending tool call 被提前补成失败结果。
There was a problem hiding this comment.
✅ Verified as addressed in f3ba2a3: Null guard added via .filter(Objects::nonNull) at line 1510, resolving the NPE risk. The alternative suggestion (use confirmResults.isEmpty()) was deliberately rejected with valid semantic reasoning — metadata key presence vs confirmResults emptiness serve different purposes for recovery logic.
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
All previously raised review comments have been addressed.
AgentScope-Java Version
2.0.1-SNAPSHOT
Description
Problem
When
enablePendingToolRecoveryis enabled, a permission-gated tool call in theASKINGstate is treated as an ordinary dangling tool call before the incomingConfirmResultis read. Recovery writes a synthetic failure result first, clears the pending ID, and routes the approval message through normal user-message processing.As a result:
DENIEDresult;Background
Pending tool recovery was introduced in #956 as an explicit opt-in mechanism for repairing orphaned tool calls. During that PR, maintainers called out that automatic result patching must not hide incorrect HITL usage or create unintended resume paths. The merged implementation therefore skipped recovery when callers supplied manual
ToolResultBlocks and kept the feature disabled by default.Permission HITL now resumes through
ConfirmResultobjects stored underMsg.METADATA_CONFIRM_RESULTS, while pending recovery still recognizes onlyToolResultBlockinput. WithenablePendingToolRecovery=true, anASKINGtool call is consequently patched as a failed orphan before itsConfirmResultcan be applied. Approval, denial, modified arguments, and accepted permission rules are then ignored.Relevant #956 discussions:
Changes
ToolCallState.ASKINGcalls from orphan recovery as a state-level safeguard.This does not change the recovery default, introduce a new policy, or alter recovery for ordinary orphaned tool calls.
Validation
Result: all tests passed, including the existing pending recovery scenarios.
Checklist
Please check the following items before code is ready to be reviewed.
mvn spotless:applymvn test) — focused HITL and pending recovery tests pass; the full repository suite was not run