Skip to content

fix(acp): keep /acp commands local in bound sessions#68617

Closed
joeia26 wants to merge 1 commit intoopenclaw:mainfrom
joeia26:fix-acp-bound-slash-commands
Closed

fix(acp): keep /acp commands local in bound sessions#68617
joeia26 wants to merge 1 commit intoopenclaw:mainfrom
joeia26:fix-acp-bound-slash-commands

Conversation

@joeia26
Copy link
Copy Markdown

@joeia26 joeia26 commented Apr 18, 2026

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 doctor could be claimed by the ACP reply dispatch hook when sent inside an ACP-bound conversation or thread. That meant the command text was routed into acpManager.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 /new and /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 permissions

However, the ACPX plugin registers a reply_dispatch hook. 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 doctor or /acp cancel is 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
  • selected ! commands when text command handling is enabled and authorized

It 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 to acpManager.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 to src/auto-reply/reply/dispatch-acp-command-bypass.ts.

When text command handling is enabled for the current surface, /acp ... now returns true from shouldBypassAcpDispatchForCommand().

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:

  • plain ACP user turns
  • /new
  • /reset
  • ! command handling
  • surfaces where text command handling is disabled

Tests

Added regression coverage in src/auto-reply/reply/dispatch-acp-command-bypass.test.ts:

  • /acp cancel now bypasses ACP dispatch
  • /acp doctor now bypasses ACP dispatch

Added hook-level regression coverage in src/plugin-sdk/acp-runtime.test.ts:

  • ACP reply dispatch does not claim /acp cancel when command bypass applies
  • the hook returns unhandled so local command handling can continue

Before the fix:

  • /acp ... did not bypass ACP dispatch
  • the ACP hook could claim the message before local command handling

After the fix:

  • /acp ... bypasses ACP dispatch
  • the hook declines the message
  • local /acp command handling remains responsible for executing the command

Local 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.ts

Result:

  • 1 test file passed
  • 8 tests passed

Also verified with a targeted runtime assertion that /acp cancel and /acp doctor now return true from shouldBypassAcpDispatchForCommand().

Notes

This change intentionally only affects /acp ... local control commands.

Normal ACP-bound user messages continue to route through ACP dispatch unchanged. /new and /reset keep their existing local reset behavior, and ! command handling keeps its existing authorization and command-enable checks.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 18, 2026

Greptile Summary

This PR fixes a routing bug where /acp ... control commands (e.g. /acp cancel, /acp doctor) sent inside ACP-bound sessions were being claimed by the reply_dispatch hook and forwarded as prompt text to the ACP harness instead of being handled by the local OpenClaw command path. The fix adds isAcpCommandCandidate() to dispatch-acp-command-bypass.ts and gates bypass on allowTextCommands, mirroring the existing !-command path rather than the unconditional reset-command path.

Confidence Score: 5/5

Safe 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 /new//reset vs. allowTextCommands-gated bypass for /acp) is explicitly documented in the PR and validated by the pre-existing "returns false for slash commands when text commands are disabled" test. No logic errors, security concerns, or contract breakage found.

No files require special attention.

Reviews (1): Last reviewed commit: "fix(acp): keep /acp commands local in bo..." | Re-trigger Greptile

@prtags
Copy link
Copy Markdown

prtags Bot commented Apr 21, 2026

Related work from PRtags group great-loon-t2te

Title: ACP duplicate: bound-session /acp command dispatch

Number Title
#66407 fix(acp): bypass ACP dispatch for /acp text commands in bound threads
#68617* fix(acp): keep /acp commands local in bound sessions

* This PR

@steipete
Copy link
Copy Markdown
Contributor

Fixed on main in a6d9926d1d.

This PR had the right local-bypass direction, but the landed fix also covers the important commands.text=false escape hatch so /acp close can still recover an ACP-bound thread. Regression tests and docs landed with the fix; pnpm check:changed passed after rebase.

Closing as superseded by the main-branch fix.

@steipete steipete closed this Apr 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants