Skip to content

Commit beaff3c

Browse files
committed
fix: clarify plugin command alias diagnostics (#64242) (thanks @feiskyer)
1 parent 8cb45c0 commit beaff3c

13 files changed

Lines changed: 214 additions & 38 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Docs: https://docs.openclaw.ai
104104
- Dreaming/startup: keep plugin-registered startup hooks alive across workspace hook reloads and include dreaming startup owners in the gateway startup plugin scope, so managed Dreaming cron registration comes back reliably after gateway boot. (#62327) Thanks @mbelinky.
105105
- Plugins: treat duplicate `registerService` calls from the same plugin id as idempotent so snapshot and activation loads no longer emit spurious `service already registered` diagnostics. (#62033, #64128) Thanks @ly85206559.
106106
- Discord/TTS: route auto voice replies through the native voice-note path so Discord receives Opus voice messages instead of regular audio attachments. (#64096) Thanks @LiuHuaize.
107+
- Config/plugins: use plugin-owned command alias metadata when `plugins.allow` contains runtime command names like `dreaming`, and point users at the owning plugin instead of stale plugin-not-found guidance. (#64242) Thanks @feiskyer.
107108

108109
## 2026.4.9
109110

docs/plugins/manifest.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ Those belong in your plugin code and `package.json`.
147147
| `providers` | No | `string[]` | Provider ids owned by this plugin. |
148148
| `modelSupport` | No | `object` | Manifest-owned shorthand model-family metadata used to auto-load the plugin before runtime. |
149149
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
150+
| `commandAliases` | No | `object[]` | Command names owned by this plugin that should produce plugin-aware config and CLI diagnostics before runtime loads. |
150151
| `providerAuthEnvVars` | No | `Record<string, string[]>` | Cheap provider-auth env metadata that OpenClaw can inspect without loading plugin code. |
151152
| `providerAuthAliases` | No | `Record<string, string>` | Provider ids that should reuse another provider id for auth lookup, for example a coding provider that shares the base provider API key and auth profiles. |
152153
| `channelEnvVars` | No | `Record<string, string[]>` | Cheap channel env metadata that OpenClaw can inspect without loading plugin code. Use this for env-driven channel setup or auth surfaces that generic startup/config helpers should see. |
@@ -183,6 +184,30 @@ OpenClaw reads this before provider runtime loads.
183184
| `cliDescription` | No | `string` | Description used in CLI help. |
184185
| `onboardingScopes` | No | `Array<"text-inference" \| "image-generation">` | Which onboarding surfaces this choice should appear in. If omitted, it defaults to `["text-inference"]`. |
185186

187+
## commandAliases reference
188+
189+
Use `commandAliases` when a plugin owns a runtime command name that users may
190+
mistakenly put in `plugins.allow` or try to run as a root CLI command. OpenClaw
191+
uses this metadata for diagnostics without importing plugin runtime code.
192+
193+
```json
194+
{
195+
"commandAliases": [
196+
{
197+
"name": "dreaming",
198+
"kind": "runtime-slash",
199+
"cliCommand": "memory"
200+
}
201+
]
202+
}
203+
```
204+
205+
| Field | Required | Type | What it means |
206+
| ------------ | -------- | ----------------- | ----------------------------------------------------------------------- |
207+
| `name` | Yes | `string` | Command name that belongs to this plugin. |
208+
| `kind` | No | `"runtime-slash"` | Marks the alias as a chat slash command rather than a root CLI command. |
209+
| `cliCommand` | No | `string` | Related root CLI command to suggest for CLI operations, if one exists. |
210+
186211
## uiHints reference
187212

188213
`uiHints` is a map from config field names to small rendering hints.

extensions/device-pair/openclaw.plugin.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
"enabledByDefault": true,
44
"name": "Device Pairing",
55
"description": "Generate setup codes and approve device pairing requests.",
6+
"commandAliases": [
7+
{
8+
"name": "pair",
9+
"kind": "runtime-slash"
10+
}
11+
],
612
"configSchema": {
713
"type": "object",
814
"additionalProperties": false,

extensions/memory-core/openclaw.plugin.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
{
22
"id": "memory-core",
33
"kind": "memory",
4+
"commandAliases": [
5+
{
6+
"name": "dreaming",
7+
"kind": "runtime-slash",
8+
"cliCommand": "memory"
9+
}
10+
],
411
"uiHints": {
512
"dreaming.frequency": {
613
"label": "Dreaming Frequency",

extensions/memory-core/src/cli.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,13 +914,14 @@ describe("memory cli", () => {
914914
it("previews rem harness output as json", async () => {
915915
await withTempWorkspace(async (workspaceDir) => {
916916
const nowMs = Date.now();
917+
const isoDay = new Date(nowMs).toISOString().slice(0, 10);
917918
await recordShortTermRecalls({
918919
workspaceDir,
919920
query: "weather plans",
920921
nowMs,
921922
results: [
922923
{
923-
path: "memory/2026-04-03.md",
924+
path: `memory/${isoDay}.md`,
924925
startLine: 2,
925926
endLine: 3,
926927
score: 0.92,

extensions/phone-control/openclaw.plugin.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
"enabledByDefault": true,
44
"name": "Phone Control",
55
"description": "Arm/disarm high-risk phone node commands (camera/screen/writes) with an optional auto-expiry.",
6+
"commandAliases": [
7+
{
8+
"name": "phone",
9+
"kind": "runtime-slash"
10+
}
11+
],
612
"configSchema": {
713
"type": "object",
814
"additionalProperties": false,

extensions/talk-voice/openclaw.plugin.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
"enabledByDefault": true,
44
"name": "Talk Voice",
55
"description": "Manage Talk voice selection (list/set).",
6+
"commandAliases": [
7+
{
8+
"name": "voice",
9+
"kind": "runtime-slash"
10+
}
11+
],
612
"configSchema": {
713
"type": "object",
814
"additionalProperties": false,

src/cli/run-main.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,29 @@ describe("resolveMissingPluginCommandMessage", () => {
123123
expect(message).toContain("runtime slash command");
124124
expect(message).not.toContain("plugins.allow");
125125
});
126+
127+
it("points command names in plugins.allow at their parent plugin", () => {
128+
const message = resolveMissingPluginCommandMessage("dreaming", {
129+
plugins: {
130+
allow: ["dreaming"],
131+
},
132+
});
133+
expect(message).toContain('"dreaming" is not a plugin');
134+
expect(message).toContain('"memory-core"');
135+
expect(message).toContain("plugins.allow");
136+
});
137+
138+
it("explains parent plugin disablement for runtime command aliases", () => {
139+
const message = resolveMissingPluginCommandMessage("dreaming", {
140+
plugins: {
141+
entries: {
142+
"memory-core": {
143+
enabled: false,
144+
},
145+
},
146+
},
147+
});
148+
expect(message).toContain("plugins.entries.memory-core.enabled=false");
149+
expect(message).not.toContain("runtime slash command");
150+
});
126151
});

src/cli/run-main.ts

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { isMainModule } from "../infra/is-main.js";
1111
import { ensureOpenClawCliOnPath } from "../infra/path-env.js";
1212
import { assertSupportedRuntime } from "../infra/runtime-guard.js";
1313
import { enableConsoleCapture } from "../logging.js";
14+
import { resolveManifestCommandAliasOwner } from "../plugins/manifest-registry.js";
1415
import { hasMemoryRuntime } from "../plugins/memory-state.js";
1516
import {
1617
normalizeLowercaseStringOrEmpty,
@@ -63,15 +64,6 @@ export function shouldUseRootHelpFastPath(argv: string[]): boolean {
6364
return resolveCliArgvInvocation(argv).isRootHelpInvocation;
6465
}
6566

66-
/**
67-
* Maps well-known runtime command names to the plugin that provides them.
68-
* Used to give actionable guidance when users try to run a runtime slash
69-
* command (e.g. `/dreaming`) as a CLI command (`openclaw dreaming`).
70-
*/
71-
const RUNTIME_COMMAND_TO_PLUGIN_ID: Record<string, string> = {
72-
dreaming: "memory-core",
73-
};
74-
7567
export function resolveMissingPluginCommandMessage(
7668
pluginId: string,
7769
config?: OpenClawConfig,
@@ -80,24 +72,45 @@ export function resolveMissingPluginCommandMessage(
8072
if (!normalizedPluginId) {
8173
return null;
8274
}
83-
84-
// Check if this is a runtime slash command rather than a CLI command.
85-
const parentPluginId = RUNTIME_COMMAND_TO_PLUGIN_ID[normalizedPluginId];
86-
if (parentPluginId) {
87-
return (
88-
`"${normalizedPluginId}" is a runtime slash command (/${normalizedPluginId}), not a CLI command. ` +
89-
`It is provided by the "${parentPluginId}" plugin. ` +
90-
`Use \`openclaw memory\` for CLI memory operations, or \`/${normalizedPluginId}\` in a chat session.`
91-
);
92-
}
93-
9475
const allow =
9576
Array.isArray(config?.plugins?.allow) && config.plugins.allow.length > 0
9677
? config.plugins.allow
9778
.filter((entry): entry is string => typeof entry === "string")
9879
.map((entry) => normalizeOptionalLowercaseString(entry))
9980
.filter(Boolean)
10081
: [];
82+
const commandAlias = resolveManifestCommandAliasOwner({
83+
command: normalizedPluginId,
84+
config,
85+
});
86+
const parentPluginId = commandAlias?.pluginId;
87+
if (parentPluginId) {
88+
if (allow.length > 0 && !allow.includes(parentPluginId)) {
89+
return (
90+
`"${normalizedPluginId}" is not a plugin; it is a command provided by the ` +
91+
`"${parentPluginId}" plugin. Add "${parentPluginId}" to \`plugins.allow\` ` +
92+
`instead of "${normalizedPluginId}".`
93+
);
94+
}
95+
if (config?.plugins?.entries?.[parentPluginId]?.enabled === false) {
96+
return (
97+
`The \`openclaw ${normalizedPluginId}\` command is unavailable because ` +
98+
`\`plugins.entries.${parentPluginId}.enabled=false\`. Re-enable that entry if you want ` +
99+
"the bundled plugin command surface."
100+
);
101+
}
102+
if (commandAlias.kind === "runtime-slash") {
103+
const cliHint = commandAlias.cliCommand
104+
? `Use \`openclaw ${commandAlias.cliCommand}\` for related CLI operations, or `
105+
: "Use ";
106+
return (
107+
`"${normalizedPluginId}" is a runtime slash command (/${normalizedPluginId}), not a CLI command. ` +
108+
`It is provided by the "${parentPluginId}" plugin. ` +
109+
`${cliHint}\`/${normalizedPluginId}\` in a chat session.`
110+
);
111+
}
112+
}
113+
101114
if (allow.length > 0 && !allow.includes(normalizedPluginId)) {
102115
return (
103116
`The \`openclaw ${normalizedPluginId}\` command is unavailable because ` +

src/config/config.web-search-provider.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ vi.mock("../plugins/manifest-registry.js", () => {
218218
params?.contract === "webSearchProviders"
219219
? mockWebSearchProviders.find((provider) => provider.id === params.value)?.pluginId
220220
: undefined,
221+
resolveManifestCommandAliasOwner: () => undefined,
221222
};
222223
});
223224

0 commit comments

Comments
 (0)