fix(acp): keep /acp commands local in bound sessions#68617
fix(acp): keep /acp commands local in bound sessions#68617joeia26 wants to merge 1 commit intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes a routing bug where Confidence Score: 5/5Safe to merge — targeted, well-tested fix with no blocking issues found. All changed code is covered by new and updated unit tests at both the bypass-logic level and the hook-integration level. The behavioral asymmetry (unconditional bypass for No files require special attention. Reviews (1): Last reviewed commit: "fix(acp): keep /acp commands local in bo..." | Re-trigger Greptile |
|
Fixed on This PR had the right local-bypass direction, but the landed fix also covers the important Closing as superseded by the main-branch fix. |
Summary
ACP-bound sessions should keep
/acp ...control commands on the local OpenClaw command path instead of forwarding them into the bound ACP runtime as prompt text.Before this change, commands such as
/acp cancel,/acp status, and/acp doctorcould be claimed by the ACP reply dispatch hook when sent inside an ACP-bound conversation or thread. That meant the command text was routed intoacpManager.runTurn()and delivered to the external ACP harness instead of being handled by OpenClaw's local ACP command handler.This patch treats
/acp ...as a command-bypass candidate in the ACP dispatch bypass logic, matching the existing local-path handling for/newand/reset.Problem
OpenClaw supports ACP-bound sessions where follow-up messages in a conversation or thread are routed directly to an ACP runtime session.
The same bound conversation is also documented as the control surface for commands such as:
/acp cancel/acp status/acp doctor/acp close/acp model/acp permissionsHowever, the ACPX plugin registers a
reply_dispatchhook. That hook runs before the normal local reply/command path and attempts to dispatch ACP-bound messages into the ACP runtime.As a result, when a user sends
/acp ...in an ACP-bound session, the ACP dispatch path can consume the message first.In the failing case,
/acp doctoror/acp cancelis treated as ordinary prompt text for the bound ACP harness instead of being executed by the local OpenClaw command handler.Root cause
shouldBypassAcpDispatchForCommand()only bypassed ACP dispatch for:/new/reset!commands when text command handling is enabled and authorizedIt did not treat
/acp ...as a local control command.Because of that, the ACP reply dispatch hook continued into
tryDispatchAcpReply(), which eventually forwarded the command body toacpManager.runTurn()as ACP prompt text.That downstream ACP dispatch path should handle normal bound-session user turns, not local OpenClaw ACP control commands.
Fix
Add
/acp ...detection tosrc/auto-reply/reply/dispatch-acp-command-bypass.ts.When text command handling is enabled for the current surface,
/acp ...now returnstruefromshouldBypassAcpDispatchForCommand().This causes the ACP reply dispatch hook to decline the message and lets the existing local command handling path process it.
Existing behavior is preserved for:
/new/reset!command handlingTests
Added regression coverage in
src/auto-reply/reply/dispatch-acp-command-bypass.test.ts:/acp cancelnow bypasses ACP dispatch/acp doctornow bypasses ACP dispatchAdded hook-level regression coverage in
src/plugin-sdk/acp-runtime.test.ts:/acp cancelwhen command bypass appliesBefore the fix:
/acp ...did not bypass ACP dispatchAfter the fix:
/acp ...bypasses ACP dispatch/acpcommand handling remains responsible for executing the commandLocal validation:
OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/tmp/openclaw-vitest-cache-plugin-sdk-acp-runtime \ pnpm exec vitest run --configLoader runner \ --config test/vitest/vitest.plugin-sdk-light.config.ts \ src/plugin-sdk/acp-runtime.test.tsResult:
1test file passed8tests passedAlso verified with a targeted runtime assertion that
/acp canceland/acp doctornow returntruefromshouldBypassAcpDispatchForCommand().Notes
This change intentionally only affects
/acp ...local control commands.Normal ACP-bound user messages continue to route through ACP dispatch unchanged.
/newand/resetkeep their existing local reset behavior, and!command handling keeps its existing authorization and command-enable checks.