From aff8a0c0e759cced832016158aeb99d7c2536882 Mon Sep 17 00:00:00 2001 From: Pengfei Ni Date: Mon, 13 Apr 2026 00:32:11 +0800 Subject: [PATCH] fix(config): resolve CLI command aliases against parent plugin in plugins.allow (#64748) (#64779) * fix(config): resolve CLI command aliases against parent plugin in plugins.allow (#64748) The CLI allow guard checked command names (e.g. 'wiki') directly against plugins.allow, missing the parent plugin ('memory-wiki'). Additionally, memory-wiki did not declare 'wiki' as a commandAlias, so doctor --fix would remove it as stale. - Add commandAliases entry for 'wiki' in memory-wiki plugin manifest - Check parent plugin ID in the CLI fallback allow guard - Add tests for both allow and deny cases * fix(cli): inject manifest registry for alias diagnostics * Update CHANGELOG.md --------- Co-authored-by: Vincent Koc --- CHANGELOG.md | 1 + extensions/memory-wiki/openclaw.plugin.json | 3 +- src/cli/run-main.test.ts | 37 +++++++++++++++++++++ src/cli/run-main.ts | 6 ++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b45e70032dd..8709df1b256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai - Plugins/memory: restore cached memory capability public artifacts on plugin-registry cache hits so memory-backed artifact surfaces stay visible after warm loads. Thanks @sercada and @vincentkoc. - Gateway/cron: preserve requested isolated-agent config across runtime reloads so subagent jobs and heartbeat overrides keep the right workspace and heartbeat settings when the hot-loaded snapshot is stale. Thanks @l0cka and @vincentkoc. - Gateway/plugins: always send a non-empty `idempotencyKey` for plugin subagent runs, so dreaming narrative jobs stop failing gateway schema validation. (#65354) Thanks @CodeForgeNet and @vincentkoc. +- CLI/plugins: honor `memory-wiki` when `plugins.allow` is set for `openclaw wiki`, and register `wiki` as the plugin-owned command alias so doctor/config stop treating it as stale. (#64779) Thanks @feiskyer and @vincentkoc. - Cron/isolated sessions: persist the right transcript path for each isolated run, including fresh session rollovers, so cron runs stop appending to stale session files. Thanks @samrusani and @vincentkoc. - CLI/memory-wiki: pass the active app config into the metadata registrar so built `openclaw wiki` commands resolve the live wiki plugin config instead of silently falling back to defaults. (#65012) Thanks @leonardsellem and @vincentkoc. - Dreaming/cron: wake managed dreaming jobs immediately instead of waiting for the next heartbeat, so scheduled dreaming runs start when the cron fires. (#65053) Thanks @l0cka and @vincentkoc. diff --git a/extensions/memory-wiki/openclaw.plugin.json b/extensions/memory-wiki/openclaw.plugin.json index cbd0c19bfb5..babe468814b 100644 --- a/extensions/memory-wiki/openclaw.plugin.json +++ b/extensions/memory-wiki/openclaw.plugin.json @@ -173,5 +173,6 @@ }, "configContracts": { "compatibilityMigrationPaths": ["plugins.entries.memory-wiki.config.bridge.readMemoryCore"] - } + }, + "commandAliases": [{ "name": "wiki" }] } diff --git a/src/cli/run-main.test.ts b/src/cli/run-main.test.ts index e95980faad7..160d424083f 100644 --- a/src/cli/run-main.test.ts +++ b/src/cli/run-main.test.ts @@ -6,6 +6,15 @@ import { shouldUseRootHelpFastPath, } from "./run-main.js"; +const memoryWikiCommandAliasRegistry = { + plugins: [ + { + id: "memory-wiki", + commandAliases: [{ name: "wiki" }], + }, + ], +}; + describe("rewriteUpdateFlagArgv", () => { it("leaves argv unchanged when --update is absent", () => { const argv = ["node", "entry.js", "status"]; @@ -148,4 +157,32 @@ describe("resolveMissingPluginCommandMessage", () => { expect(message).toContain("plugins.entries.memory-core.enabled=false"); expect(message).not.toContain("runtime slash command"); }); + + it("allows CLI commands when their parent plugin is in plugins.allow", () => { + const message = resolveMissingPluginCommandMessage( + "wiki", + { + plugins: { + allow: ["memory-wiki"], + }, + }, + { registry: memoryWikiCommandAliasRegistry }, + ); + expect(message).toBeNull(); + }); + + it("blocks CLI commands when parent plugin is NOT in plugins.allow", () => { + const message = resolveMissingPluginCommandMessage( + "wiki", + { + plugins: { + allow: ["telegram"], + }, + }, + { registry: memoryWikiCommandAliasRegistry }, + ); + expect(message).not.toBeNull(); + expect(message).toContain('"memory-wiki"'); + expect(message).toContain("plugins.allow"); + }); }); diff --git a/src/cli/run-main.ts b/src/cli/run-main.ts index 04eea72b896..7c4904d6d6a 100644 --- a/src/cli/run-main.ts +++ b/src/cli/run-main.ts @@ -13,6 +13,7 @@ import { ensureOpenClawCliOnPath } from "../infra/path-env.js"; import { assertSupportedRuntime } from "../infra/runtime-guard.js"; import { enableConsoleCapture } from "../logging.js"; import { resolveManifestCommandAliasOwner } from "../plugins/manifest-command-aliases.runtime.js"; +import type { PluginManifestRegistry } from "../plugins/manifest-registry.js"; import { hasMemoryRuntime } from "../plugins/memory-state.js"; import { maybeWarnAboutDebugProxyCoverage } from "../proxy-capture/coverage.js"; import { @@ -73,6 +74,7 @@ export function shouldUseRootHelpFastPath(argv: string[]): boolean { export function resolveMissingPluginCommandMessage( pluginId: string, config?: OpenClawConfig, + options?: { registry?: PluginManifestRegistry }, ): string | null { const normalizedPluginId = normalizeLowercaseStringOrEmpty(pluginId); if (!normalizedPluginId) { @@ -88,6 +90,7 @@ export function resolveMissingPluginCommandMessage( const commandAlias = resolveManifestCommandAliasOwner({ command: normalizedPluginId, config, + registry: options?.registry, }); const parentPluginId = commandAlias?.pluginId; if (parentPluginId) { @@ -118,6 +121,9 @@ export function resolveMissingPluginCommandMessage( } if (allow.length > 0 && !allow.includes(normalizedPluginId)) { + if (parentPluginId && allow.includes(parentPluginId)) { + return null; + } return ( `The \`openclaw ${normalizedPluginId}\` command is unavailable because ` + `\`plugins.allow\` excludes "${normalizedPluginId}". Add "${normalizedPluginId}" to ` +