Skip to content

fix: emit permission-denied tool result events - #3099

Open
guslegend0510 wants to merge 3 commits into
agentscope-ai:mainfrom
guslegend0510:codex/fix-3097-denied-tool-events
Open

fix: emit permission-denied tool result events#3099
guslegend0510 wants to merge 3 commits into
agentscope-ai:mainfrom
guslegend0510:codex/fix-3097-denied-tool-events

Conversation

@guslegend0510

Copy link
Copy Markdown
Contributor

Fixes #3097

User-denied permission confirmations previously updated the message context without emitting tool-result events, leaving event-driven clients without a tool outcome.

This change:

  • Emits the complete start → text delta → end (DENIED) sequence for user denials.
  • Emits rule-denied results in batches that also contain tools awaiting confirmation.
  • Restores validated tool-call tracking on AG-UI resume without replaying call start/end events.
  • Supports older sessions without persisted confirmation reply IDs.

Validation:

  • Core: 2,338 tests, 9 skipped.
  • Harness: 1,030 tests, 14 skipped.
  • AG-UI: 413 tests.
  • Zero failures or errors; formatting and diff checks passed.

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

@oss-maintainer

Copy link
Copy Markdown
Collaborator

⚠️ Merge conflict detected

@guslegend0510 This PR currently conflicts with main and cannot be merged. Please rebase or merge main into your branch:

git fetch origin
git checkout codex/fix-3097-denied-tool-events
git rebase origin/main
# resolve conflicts, then:
git push --force-with-lease

This is a one-time reminder. Feel free to @mention me for a re-review once conflicts are resolved.


Automated notification by github-manager-bot

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds a shared deniedToolResultEvents(...) helper and routes every permission-denial path (user confirm, auto-deny during acting, rule deny in runToolBatch) through it, with parameterized tests — a genuinely more complete treatment of the denied-event gap than the current state on main. COMMENT because three things need resolution first: this PR currently has merge conflicts, the AG-UI half duplicates machinery that already landed, and the synthesized replyId changes correlation semantics.

Findings

  • [Critical] ReActAgent.java:1913 — a random replyId for the metadata-missing case produces results keyed to an id the client never saw; also now publishes UserConfirmResultEvent unconditionally.
  • [Warning] AguiStreamContext.java:235resumeToolCall overlaps with adoptToolCall + hasKnownToolCall + the existing PermissionConfirmEventConverter adoption on main; likely the conflict source.
  • [Warning] maven-ci.yml:89 — unrelated Windows mvnd→mvn switch that also removes the documented mvnd#161 workaround.
  • [Info] ReActAgent.java:2927 — denied triples are emitted before RequireUserConfirmEvent.
  • [Info] test — blank-replyId branch not covered.

Cross-PR Note

PR #3104 covers the same user-denied gap with a narrower change. Suggest the maintainers pick one home for the shared helper: this PR's deniedToolResultEvents(...) factoring (which also fixes the auto-denied path) looks like the better base once the conflict is resolved and the AG-UI hunks are dropped.


Automated review by github-manager-bot

# -T1: single-threaded build so reactor order is respected (see Linux step).
# -Dmvnd.maxLostKeepAlive=120: avoid StaleAddressException on long javadoc phases (mvnd#161).
& "~\.mvnd\bin\mvnd.cmd" -B -T1 "-Dmvnd.maxLostKeepAlive=120" clean verify
# Use standard Maven to avoid mvnd daemon startup/communication failures.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things here: (1) this CI change is unrelated to the PR's stated purpose (permission-denied tool result events) — it belongs in its own PR so it can be reverted independently; (2) dropping mvnd on Windows also drops the workaround the removed comment described (-Dmvnd.maxLostKeepAlive=120 for StaleAddressException during long javadoc phases, apache/maven-mvnd#161). Falling back to plain mvn -B -T1 clean verify is a legitimate choice, but please confirm Windows CI is green with it and say so in the description, otherwise the next long javadoc build may flake and the cause will be hard to trace back to this PR.

clearPendingRequestReplyId(Msg.METADATA_CONFIRM_REQUEST_REPLY_ID);
if (replyId.isEmpty()) {
// Older persisted ASKING calls may not carry request correlation metadata.
replyId = UUID.randomUUID().toString().replace("-", "");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inventing a fresh replyId when the persisted METADATA_CONFIRM_REQUEST_REPLY_ID is absent breaks the correlation this PR is trying to restore: the client saw RequireUserConfirmEvent under the original reply id, so the ToolResult* events (and the ToolCallResult messageId keyed as replyId + ":" + toolCallId in the AG-UI adapter) will land under an id the client has never seen — an orphan result rather than a resolved pending call. It also makes UserConfirmResultEvent publish unconditionally now, where before it was suppressed for the blank case. Could the blank-metadata case instead recover the id from the last assistant message (the same place persistPendingRequestReplyId writes it), or fall back to the previous no-op behaviour plus a log.warn? A test for the metadata-missing resume path would pin the intent either way.

"permission asking",
GenerateReason.PERMISSION_ASKING));
return Flux.fromIterable(toolCalls)
.filter(tc -> autoDenied.contains(tc.getId()))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emission ordering detail: the auto-denied ToolResult* triples are concatenated before RequireUserConfirmEvent / RequestStopEvent. A consumer that opens a tool-call card on RequireUserConfirmEvent will therefore receive results for tool calls it has not been told are pending yet. Emitting the denied triples after the confirm/stop pair (or right after writeAutoDeniedResults) reads more naturally; either is fine as long as it is deliberate.

}
}

/** Recognize a validated permission resume without replaying the previous run's call events. */

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resumeToolCall overlaps with what is already on main: AguiStreamContext.adoptToolCall(toolCallId) plus the hasKnownToolCall(...) guard on beginToolResult / appendToolResultText / endToolResult, and PermissionConfirmEventConverter already calls adoptToolCall(...) for every validated ConfirmResult. This is very likely the source of the current merge conflict. Please rebase onto main and drop the AguiStreamContext / PermissionConfirmEventConverter hunks — the core-side fix plus the existing adoption path should be sufficient. Note the difference in semantics too: resumeToolCall writes both startedToolCalls and endedToolCalls (which suppresses a later ToolCallEnd for a call that is re-executed in this run), whereas adoptToolCall keeps those sets separate.

assertTrue(foundDenied, "expected a DENIED ToolResultBlock for the rejected tool");
}

@ParameterizedTest

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parameterized user-denied / rule-denied coverage and assertDeniedEvents are a good fit for this change, and promoting AskingTool to assert execution counts is a nice way to prove the denied path never invokes the tool. The one missing case is the blank-replyId resume discussed above, which is exactly the branch whose behaviour changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Permission HITL: user-denied tool calls emit no tool-result events (rule-denied ones do)[Bug]:

3 participants