openclaw - 💡(How to fix) Fix Plugin slash commands aren't dispatched in `openclaw agent` (--local and gateway) or TUI [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#78347Fetched 2026-05-07 03:38:01
View on GitHub
Comments
1
Participants
2
Timeline
1
Reactions
3
Timeline (top)
commented ×1

Plugin-registered slash commands (api.registerCommand({ name: "remember", ... })) are correctly persisted to the central registry but never fire when the user runs openclaw agent --message "/remember ...". Both the --local (embedded) and gateway agent paths take the message verbatim and hand it to the LLM without ever calling the plugin command dispatcher. The TUI behaves the same way.

This is an OpenClaw core gap, not a plugin gap. Slash commands work fine in the channels that wire dispatch up themselves (Telegram, Discord, and any channel routed through the auto-reply pipeline), but the agent CLI surface is missing the equivalent hook.

Root Cause

Plugin-registered slash commands (api.registerCommand({ name: "remember", ... })) are correctly persisted to the central registry but never fire when the user runs openclaw agent --message "/remember ...". Both the --local (embedded) and gateway agent paths take the message verbatim and hand it to the LLM without ever calling the plugin command dispatcher. The TUI behaves the same way.

This is an OpenClaw core gap, not a plugin gap. Slash commands work fine in the channels that wire dispatch up themselves (Telegram, Discord, and any channel routed through the auto-reply pipeline), but the agent CLI surface is missing the equivalent hook.

Fix Action

Fix / Workaround

Plugin-registered slash commands (api.registerCommand({ name: "remember", ... })) are correctly persisted to the central registry but never fire when the user runs openclaw agent --message "/remember ...". Both the --local (embedded) and gateway agent paths take the message verbatim and hand it to the LLM without ever calling the plugin command dispatcher. The TUI behaves the same way.

This is an OpenClaw core gap, not a plugin gap. Slash commands work fine in the channels that wire dispatch up themselves (Telegram, Discord, and any channel routed through the auto-reply pipeline), but the agent CLI surface is missing the equivalent hook.

So when the user types --message "/<cmd>", the dispatcher is loaded in memory but nobody calls it on this path.

Code Example

$ openclaw agent --local --message "/recall favorite color" --to "+15555550999"

---

$ git grep -nE "executePluginCommand|matchPluginCommand" -- "*.ts"
extensions/discord/src/monitor/native-command.runtime.tsDiscord channel
src/telegram/bot-native-commands.tsTelegram channel
src/auto-reply/reply/commands-plugin.ts                     ← generic auto-reply
src/plugins/commands.ts                                      (declarations only)

---

$ git grep -nE "handleCommands|handlePluginCommand|matchPluginCommand|executePluginCommand" \
    src/commands/agent.ts src/commands/agent-via-gateway.ts
(no matches)

---

import { handleCommands } from "../auto-reply/reply/commands.js";

// Inside agentCommand, after `body` is normalised:
const commandResult = await handleCommands({
  command: {
    commandBodyNormalized: body,
    senderId: opts.to ?? "cli",
    channel: opts.channel ?? "cli",
    isAuthorizedSender: true,
  },
  cfg,
}, /* allowTextCommands */ true);

if (commandResult && !commandResult.shouldContinue) {
  // Print/return the plugin command's reply and exit early.
  runtime.log?.(commandResult.reply.text ?? JSON.stringify(commandResult.reply));
  return;
}
// ...existing flow: hand `body` to the embedded agent runner.
RAW_BUFFERClick to expand / collapse

Summary

Plugin-registered slash commands (api.registerCommand({ name: "remember", ... })) are correctly persisted to the central registry but never fire when the user runs openclaw agent --message "/remember ...". Both the --local (embedded) and gateway agent paths take the message verbatim and hand it to the LLM without ever calling the plugin command dispatcher. The TUI behaves the same way.

This is an OpenClaw core gap, not a plugin gap. Slash commands work fine in the channels that wire dispatch up themselves (Telegram, Discord, and any channel routed through the auto-reply pipeline), but the agent CLI surface is missing the equivalent hook.

Reproduction

With any plugin that registers a slash command (e.g. @maximem/memory-plugin exposes /remember and /recall):

$ openclaw agent --local --message "/recall favorite color" --to "+15555550999"

Expected: the /recall handler fires, returns its reply. Actual: "/recall favorite color" is sent to the LLM as a regular user prompt. The slash never matches.

Confirmed at the source level on origin/main:

$ git grep -nE "executePluginCommand|matchPluginCommand" -- "*.ts"
extensions/discord/src/monitor/native-command.runtime.ts   ← Discord channel
src/telegram/bot-native-commands.ts                         ← Telegram channel
src/auto-reply/reply/commands-plugin.ts                     ← generic auto-reply
src/plugins/commands.ts                                     ← (declarations only)

Three call sites, all in channel-handling code. Neither src/commands/agent.ts (--local path → agentCommand) nor src/commands/agent-via-gateway.ts (gateway path → agentViaGatewayCommand) reference handleCommands, handlePluginCommand, matchPluginCommand, or executePluginCommand:

$ git grep -nE "handleCommands|handlePluginCommand|matchPluginCommand|executePluginCommand" \
    src/commands/agent.ts src/commands/agent-via-gateway.ts
(no matches)

So when the user types --message "/<cmd>", the dispatcher is loaded in memory but nobody calls it on this path.

Why it matters

  1. Plugin authors can't dev-loop locally. Building any plugin that registers slash commands requires hooking up a real Telegram/Discord/etc. channel just to verify the command runs. There is no terminal-driven equivalent today.
  2. CI / smoke tests can't exercise slash flows. Verifying a plugin command via openclaw agent --local is the natural minimum-viable integration test, but it can't observe the slash path.
  3. Inconsistent surface. TUI and the agent CLI silently send /<cmd> to the LLM, which is surprising. Users reasonably expect a /-prefixed message to behave the same way it does in their channels.

Proposed fix

Call the existing handleCommands wrapper (the same one the auto-reply pipeline uses) in both agent paths, before the message is forwarded to the LLM. The wrapper already returns null when no plugin command matches, so non-slash messages flow through unchanged.

Sketch (pseudo-diff against src/commands/agent.ts near where body is sent into the agent runner):

import { handleCommands } from "../auto-reply/reply/commands.js";

// Inside agentCommand, after `body` is normalised:
const commandResult = await handleCommands({
  command: {
    commandBodyNormalized: body,
    senderId: opts.to ?? "cli",
    channel: opts.channel ?? "cli",
    isAuthorizedSender: true,
  },
  cfg,
}, /* allowTextCommands */ true);

if (commandResult && !commandResult.shouldContinue) {
  // Print/return the plugin command's reply and exit early.
  runtime.log?.(commandResult.reply.text ?? JSON.stringify(commandResult.reply));
  return;
}
// ...existing flow: hand `body` to the embedded agent runner.

The same insertion point applies in agentViaGatewayCommand — either dispatch locally before sending to the gateway, or have the gateway invoke handleCommands on inbound agent requests (cleaner, but a bigger change). Local dispatch keeps --local and gateway behaviourally aligned with channels that already do this themselves.

The same hook is also worth adding to the TUI input path so /<cmd> typed in the TUI fires the same handlers.

Acceptance

  • openclaw agent --local --message "/<registered-cmd>" invokes the registered handler and returns its reply.
  • openclaw agent --message "/<registered-cmd>" (gateway path) does the same.
  • Messages that don't match any plugin command flow through to the LLM unchanged (verified by passing a plain prompt and confirming the LLM is invoked).
  • openclaw tui slash input fires plugin commands.

Happy to follow up with a PR if there's interest in the local-dispatch shape proposed above. The Telegram and Discord call sites are clean precedents to mirror.

References

  • src/commands/agent.tsagentCommand (the --local entry)
  • src/commands/agent-via-gateway.tsagentViaGatewayCommand (gateway entry)
  • src/auto-reply/reply/commands.ts — exports handleCommands
  • src/auto-reply/reply/commands-plugin.tshandlePluginCommand implementation
  • src/plugins/commands.tsmatchPluginCommand / executePluginCommand declarations
  • src/plugins/registry.ts:422 — confirms plugin registrations land in the central registry correctly; the gap is only on the dispatch side.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING