Skip to content

[Feat] Gateway: add commands.list RPC method#62656

Merged
frankekn merged 3 commits into
openclaw:mainfrom
samzong:feat/commands-list-rpc
Apr 10, 2026
Merged

[Feat] Gateway: add commands.list RPC method#62656
frankekn merged 3 commits into
openclaw:mainfrom
samzong:feat/commands-list-rpc

Conversation

@samzong

@samzong samzong commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Remote gateway WS clients (desktop apps, web UIs) have no way to discover available slash commands at runtime. They must hardcode a static list that drifts out of sync and cannot show workspace skills or plugin commands.
  • Why it matters: ClawWork desktop and future remote clients need dynamic command menus. Two prior PRs (Gateway: add commands.list method #17054, Gateway: add commands.list slash command metadata API #13537) attempted this but were auto-closed without review.
  • What changed: Added a commands.list gateway RPC method that returns a structured, machine-readable list of all available commands (native + skill + plugin) with per-command scope, category, source, and argument definitions. Includes provider-aware native name resolution via channel plugin hooks.
  • What did NOT change (scope boundary): No client-side changes. No commands.changed push event. No dynamic choices resolution. No command execution via RPC.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

N/A — new feature.

Regression Test Plan (if applicable)

N/A — new feature. 15 unit tests added covering:

  • All three command sources (native, skill, plugin)
  • Per-command scope exposure (text/native/both)
  • Scope filtering (text-only, native-only, both)
  • Provider-aware native name resolution (Discord aliasing)
  • Argument serialization (static choices, dynamic choices, includeArgs toggle)
  • Agent ID validation (unknown agent rejection)
  • Parameter validation (invalid params rejection)
  • Exported builder function independence

Evidence

  • pnpm check passing (lint + format + type check)
  • pnpm build passing (protocol schema changes affect build output)
  • 15/15 scoped tests passing

Human Verification (required)

  • Verified scenarios: Handler returns correct command metadata for all three sources; scope filtering correctly includes/excludes commands; provider parameter resolves channel-specific names; plugin commands always visible regardless of scope
  • Edge cases checked: Empty agentId (falls back to default); unknown agentId (returns error); whitespace-only agentId (treated as empty); function-based choices marked as dynamic; acceptsArgs=false suppresses arg serialization
  • What you did not verify: Live gateway integration test; actual ClawWork desktop consumption; real workspace skill resolution

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? Yes — purely additive, no existing behavior changed
  • Config/env changes? No
  • Migration needed? No

Risks and Mitigations

  • Risk: CommandCategorySchema is a hand-maintained copy of the CommandCategory TypeScript type. If core adds a new category, the schema must be updated manually.
    • Mitigation: This follows the same pattern as all other protocol schemas in the project. The TypeScript type system will catch mismatches at the handler level.

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui gateway Gateway runtime size: L labels Apr 7, 2026
@greptile-apps

greptile-apps Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a commands.list gateway RPC method that lets remote clients dynamically discover all available commands (native, skill, and plugin) with per-command scope, category, source, and argument definitions. The implementation is purely additive, fully wired into the gateway's method-scope system under READ_SCOPE, and covered by 15 unit tests. The scope-filtering logic, skill source identification via the skill: key prefix, and plugin command passthrough are all correct.

Confidence Score: 5/5

Safe to merge; purely additive change with no impact on existing behavior.

No P0 or P1 issues found. The only finding is a P2 test fixture inaccuracy where a skill command's scope doesn't match what production code generates, which does not represent a logic bug. All other aspects — scope filtering, skill detection, plugin passthrough, schema alignment with CommandCategory, and gateway wiring — are correct.

No files require special attention.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/gateway/server-methods/commands.test.ts
Line: 47-55

Comment:
**Skill fixture scope mismatches production**

The mock skill command uses `scope: "native"`, but `buildSkillCommandDefinitions` in `src/auto-reply/commands-registry.ts` always produces skill commands with `scope: "both"`. As a result, the `scope: "text"` filtering test never exercises the real production path where skills pass through because `"both"` satisfies any filter. Aligning the fixture to `"both"` and adding an assertion for the `scope: "text"` case would close this gap.

```suggestion
  {
    key: "skill:code-review",
    nativeName: "code_review",
    description: "Run code review",
    textAliases: [],
    acceptsArgs: true,
    scope: "both",
    category: "tools",
  },
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Gateway: add commands.list RPC method" | Re-trigger Greptile

Comment thread src/gateway/server-methods/commands.test.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c266596365

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gateway/server-methods/commands.ts Outdated
@samzong

samzong commented Apr 7, 2026

Copy link
Copy Markdown
Contributor Author

Re: Greptile P2 (skill fixture scope) — Good catch, fixed in latest push. Fixture now uses scope: "both" to match buildSkillCommandDefinitions production behavior.

Re: Codex P2 (plugin provider passthrough) — Known v1 limitation. getPluginCommandSpecs(provider) returns [] when the channel lacks nativeCommandsAutoEnabled, which would incorrectly hide all plugin commands. resolvePluginNativeName is file-local and unexportable without core changes. Current behavior (base names + scope: "both") is the correct default for generic WS clients.

frankekn commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Thanks for adding a proper runtime command catalog. I found one blocker before this is safe to land: the plugin-command branch in commands.list is not using the existing provider-aware plugin registry contract.

Today buildCommandsListResult() calls getPluginCommandSpecs() without the requested provider and then appends every plugin command as scope: "both" (src/gateway/server-methods/commands.ts:112). But getPluginCommandSpecs(provider) already does two important things:

  1. hides plugin commands unless the requested channel has nativeCommandsAutoEnabled === true
  2. applies provider-specific plugin native-name overrides

So the current RPC can advertise plugin commands to providers that should not expose them, and it can return the wrong plugin command name for providers with native aliases. There is already existing regression coverage for the first contract in src/plugins/loader.test.ts:1424.

Please thread params.provider into getPluginCommandSpecs(...) and add a commands.list regression test that proves provider-specific plugin omission and/or renaming.

@samzong
samzong force-pushed the feat/commands-list-rpc branch from 71b5af3 to 1f97e28 Compare April 9, 2026 16:57

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f97e28565

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/gateway/server-methods/commands.ts

samzong commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @frankekn, good catch, fixed in the latest push.

@frankekn

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. Keep it up!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@samzong
samzong force-pushed the feat/commands-list-rpc branch from 1f97e28 to 2807ec5 Compare April 10, 2026 03:06
@frankekn frankekn self-assigned this Apr 10, 2026

Copy link
Copy Markdown
Contributor

@codex review

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation app: macos App: macos labels Apr 10, 2026
@frankekn
frankekn force-pushed the feat/commands-list-rpc branch from 1512498 to 6ba9557 Compare April 10, 2026 06:06

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ba9557841

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

...(nativeName ? { nativeName } : {}),
...(cmd.scope !== "native" ? { textAliases: resolveTextAliases(cmd) } : {}),
description: cmd.description,
...(cmd.category ? { category: cmd.category } : {}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Assign a category to skill commands in commands.list

commands.list only emits category when cmd.category is already populated, but skill command definitions built from listSkillCommandsForAgents do not set a category (buildSkillCommandDefinitions in src/auto-reply/commands-registry.ts). As a result, every skill entry is returned without category metadata, which breaks category-based grouping/filtering in clients even though this RPC is intended to provide per-command categorization.

Useful? React with 👍 / 👎.

Comment on lines +157 to +161
for (const spec of listPluginCommands()) {
commands.push({
name: spec.name,
textAliases: [`/${spec.name}`],
description: spec.description,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Include plugin native names in text-surface results

When scope="text", plugin commands are serialized from listPluginCommands() only, so nativeName is never returned even if a provider-specific native alias exists. In the same mode, built-in commands still include nativeName, so clients that request text-surface names but rely on nativeName metadata cannot correctly map plugin commands to provider-native slash names.

Useful? React with 👍 / 👎.

@frankekn
frankekn merged commit 723dec0 into openclaw:main Apr 10, 2026
27 of 34 checks passed

Copy link
Copy Markdown
Contributor

Merged. Thanks @samzong

wongcyrus pushed a commit to wongcyrus/openclaw that referenced this pull request Apr 10, 2026
Merged via squash.

Co-authored-by: samzong <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Reviewed-by: @frankekn
steipete pushed a commit that referenced this pull request Apr 10, 2026
Merged via squash.

Co-authored-by: samzong <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Reviewed-by: @frankekn
samzong added a commit to clawwork-ai/ClawWork that referenced this pull request Apr 16, 2026
Replaces the hardcoded STATIC_SLASH_COMMANDS list with a dynamic catalog
hydrated from the upstream 'commands.list' RPC (openclaw/openclaw#62656).

- shared: add CommandEntry / CommandArg / CommandsListParams /
  CommandsListResponse types mirroring the Gateway schema (7 categories,
  source=native|skill|plugin, scope=text|native|both, structured args)
- main: new gateway-client.listCommands + ws:commands-list IPC handler;
  preload bridge + .d.ts exposure
- core: GatewayTransportPort.listCommands; ui-store.commandCatalogByGateway
  + setter; gateway-dispatcher fetches commands in parallel with
  models/agents/tools/skills on connect
- pwa: mirror the same port on the web adapter
- renderer: slash-commands.ts rewritten around CommandEntry -> SlashCommandView;
  NATIVE_OVERLAY preserves choices for /think /fast /verbose /reasoning
  /usage /elevated /activation when Gateway omits them; mergeWithOverlay
  composes gateway data with local defaults; alias-aware prefix filter
- UI: SlashCommandMenu + SlashCommandDashboard show source badges for
  skill/plugin; Dashboard gains search input, empty state, fixed-height
  scroll (no jitter on result count change); shared CommandSourceBadge
  component
- keyboard: rAF re-focus + window-level keydown fallback in
  useSlashAutocomplete to survive Radix Dialog FocusScope handoff
- i18n: 8 locales updated - 7 category keys + 3 source labels + search
  placeholder + empty state; key parity preserved

Signed-off-by: samzong <[email protected]>

## Considered and deferred

- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-TASTE]: Default category for uncategorized gateway commands resolves to 'status'. Both 'status' and 'tools' are defensible buckets; upstream CommandEntry.category is optional by schema. Revisit after UX feedback.
- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-NIT]: SlashCommandView.acceptsArgs is populated but not currently consumed by any renderer. Kept for parity with upstream CommandEntry shape; safe to drop in a later cleanup.
- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-NIT]: getCommandsForGateway runs two sequential .map passes. Cold path (per-reconnect); fuse if profiling ever points here.
- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-NIT]: filterSlashCommands lower-cases aliases per keystroke. Negligible for <50 commands; pre-lowercase at view-build time if catalog scales.
- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-SCOPE]: No unit tests for mergeWithOverlay / commandEntryToView / detectPickerType / getCommandsForGateway. Follow-up PR to cover overlay precedence and empty-catalog fallback.
- packages/shared/src/types.ts [BOT-NIT]: CommandEntry / CommandArg / CommandsListResponse lack JSDoc anchoring to the Gateway upstream schema. Additive types are back-compat; link in a later docs pass.
- packages/desktop/src/renderer/components/ChatInput/useSlashAutocomplete.ts [BOT-TASTE]: activeGatewayId selector duplicated in SlashCommandDashboard. Pre-existing pattern across 4+ renderer sites; extract useActiveGatewayId() hook in a cleanup PR.
- packages/desktop/src/renderer/components/ChatInput/useSlashAutocomplete.ts [BOT-TASTE]: Window-level keydown fallback survives Radix Dialog FocusScope handoff; document.activeElement guard keeps it a no-op on the happy path. Document the rationale inline in a follow-up.
- packages/desktop/src/renderer/components/ChatInput/useSlashAutocomplete.ts [BOT-TASTE]: buildArgOptions reads useTaskStore and useUiStore via getState(). Functionally correct; consider lifting to explicit selectors for surface clarity.

Signed-off-by: samzong <[email protected]>
samzong added a commit to clawwork-ai/ClawWork that referenced this pull request Apr 16, 2026
Replaces the hardcoded STATIC_SLASH_COMMANDS list with a dynamic catalog
hydrated from the upstream 'commands.list' RPC (openclaw/openclaw#62656).

- shared: add CommandEntry / CommandArg / CommandsListParams /
  CommandsListResponse types mirroring the Gateway schema (7 categories,
  source=native|skill|plugin, scope=text|native|both, structured args)
- main: new gateway-client.listCommands + ws:commands-list IPC handler;
  preload bridge + .d.ts exposure
- core: GatewayTransportPort.listCommands; ui-store.commandCatalogByGateway
  + setter; gateway-dispatcher fetches commands in parallel with
  models/agents/tools/skills on connect
- pwa: mirror the same port on the web adapter
- renderer: slash-commands.ts rewritten around CommandEntry -> SlashCommandView;
  NATIVE_OVERLAY preserves choices for /think /fast /verbose /reasoning
  /usage /elevated /activation when Gateway omits them; mergeWithOverlay
  composes gateway data with local defaults; alias-aware prefix filter
- UI: SlashCommandMenu + SlashCommandDashboard show source badges for
  skill/plugin; Dashboard gains search input, empty state, fixed-height
  scroll (no jitter on result count change); shared CommandSourceBadge
  component
- keyboard: rAF re-focus + window-level keydown fallback in
  useSlashAutocomplete to survive Radix Dialog FocusScope handoff
- i18n: 8 locales updated - 7 category keys + 3 source labels + search
  placeholder + empty state; key parity preserved

Signed-off-by: samzong <[email protected]>

- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-TASTE]: Default category for uncategorized gateway commands resolves to 'status'. Both 'status' and 'tools' are defensible buckets; upstream CommandEntry.category is optional by schema. Revisit after UX feedback.
- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-NIT]: SlashCommandView.acceptsArgs is populated but not currently consumed by any renderer. Kept for parity with upstream CommandEntry shape; safe to drop in a later cleanup.
- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-NIT]: getCommandsForGateway runs two sequential .map passes. Cold path (per-reconnect); fuse if profiling ever points here.
- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-NIT]: filterSlashCommands lower-cases aliases per keystroke. Negligible for <50 commands; pre-lowercase at view-build time if catalog scales.
- packages/desktop/src/renderer/lib/slash-commands.ts [BOT-SCOPE]: No unit tests for mergeWithOverlay / commandEntryToView / detectPickerType / getCommandsForGateway. Follow-up PR to cover overlay precedence and empty-catalog fallback.
- packages/shared/src/types.ts [BOT-NIT]: CommandEntry / CommandArg / CommandsListResponse lack JSDoc anchoring to the Gateway upstream schema. Additive types are back-compat; link in a later docs pass.
- packages/desktop/src/renderer/components/ChatInput/useSlashAutocomplete.ts [BOT-TASTE]: activeGatewayId selector duplicated in SlashCommandDashboard. Pre-existing pattern across 4+ renderer sites; extract useActiveGatewayId() hook in a cleanup PR.
- packages/desktop/src/renderer/components/ChatInput/useSlashAutocomplete.ts [BOT-TASTE]: Window-level keydown fallback survives Radix Dialog FocusScope handoff; document.activeElement guard keeps it a no-op on the happy path. Document the rationale inline in a follow-up.
- packages/desktop/src/renderer/components/ChatInput/useSlashAutocomplete.ts [BOT-TASTE]: buildArgOptions reads useTaskStore and useUiStore via getState(). Functionally correct; consider lifting to explicit selectors for surface clarity.

Signed-off-by: samzong <[email protected]>
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
Merged via squash.

Co-authored-by: samzong <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Reviewed-by: @frankekn
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
Merged via squash.

Co-authored-by: samzong <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Reviewed-by: @frankekn
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
Merged via squash.

Co-authored-by: samzong <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Reviewed-by: @frankekn
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Merged via squash.

Co-authored-by: samzong <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Reviewed-by: @frankekn
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
Merged via squash.

Co-authored-by: samzong <[email protected]>
Co-authored-by: Frank Yang <[email protected]>
Reviewed-by: @frankekn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: macos App: macos app: web-ui App: web-ui docs Improvements or additions to documentation gateway Gateway runtime size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Add commands.list Gateway RPC for remote clients

2 participants