mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix: reserve legacy tool cli token
This commit is contained in:
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Control UI/chat: keep live replies visible when a raw session alias such as `main` sends the chat turn but Gateway emits events under the canonical session key for the same run. Fixes #73716. Thanks @teebes.
|
||||
- CLI: stop treating the legacy singular `openclaw tool ...` token as a plugin id under restrictive `plugins.allow`, so it falls through as a normal unknown/reserved command instead of suggesting a stale allowlist entry. Fixes #64732. Thanks @efe-arv, @SweetSophia, and @hashtag1974.
|
||||
- Media: write inbound media buffers through same-directory temp files before rename, so failed disk writes do not leave zero-byte artifacts for later voice transcription. Fixes #55966. Thanks @OpenCodeEngineer.
|
||||
- TTS/Telegram: keep trusted local audio generated by the TTS tool queued for voice-note delivery even when the run-level built-in tool list omits the raw `tts` name. Fixes #74752. Thanks @Loveworld3033 and @andyliu.
|
||||
- TTS: require explicit user or config audio intent for the agent speech tool so dashboard chats stay text unless audio is requested. Fixes #69777. Thanks @alexandre-leng.
|
||||
|
||||
@@ -241,6 +241,10 @@ export const cliCommandCatalog: readonly CliCommandCatalogEntry[] = [
|
||||
},
|
||||
route: { id: "tasks-list" },
|
||||
},
|
||||
{
|
||||
commandPath: ["tool"],
|
||||
policy: { loadPlugins: "never", ensureCliPath: false, networkProxy: "bypass" },
|
||||
},
|
||||
{
|
||||
commandPath: ["tools"],
|
||||
policy: { loadPlugins: "never", ensureCliPath: false, networkProxy: "bypass" },
|
||||
|
||||
@@ -151,6 +151,9 @@ describe("command-path-policy", () => {
|
||||
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "googlemeet", "login"])).toBe(
|
||||
"default",
|
||||
);
|
||||
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "tool", "image_generate"])).toBe(
|
||||
"bypass",
|
||||
);
|
||||
expect(resolveCliNetworkProxyPolicy(["node", "openclaw", "tools", "effective"])).toBe("bypass");
|
||||
});
|
||||
|
||||
|
||||
@@ -50,6 +50,13 @@ describe("command-registration-policy", () => {
|
||||
hasBuiltinPrimary: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
shouldSkipPluginCommandRegistration({
|
||||
argv: ["node", "openclaw", "tool", "image_generate"],
|
||||
primary: "tool",
|
||||
hasBuiltinPrimary: false,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldSkipPluginCommandRegistration({
|
||||
argv: ["node", "openclaw", "tools", "effective"],
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { isTruthyEnvValue } from "../infra/env.js";
|
||||
import { resolveCliArgvInvocation } from "./argv-invocation.js";
|
||||
|
||||
const RESERVED_NON_PLUGIN_COMMAND_ROOTS = new Set(["tools"]);
|
||||
const RESERVED_NON_PLUGIN_COMMAND_ROOTS = new Set(["tool", "tools"]);
|
||||
|
||||
export function isReservedNonPluginCommandRoot(primary: string | null | undefined): boolean {
|
||||
return typeof primary === "string" && RESERVED_NON_PLUGIN_COMMAND_ROOTS.has(primary);
|
||||
}
|
||||
|
||||
export function shouldRegisterPrimaryCommandOnly(argv: string[]): boolean {
|
||||
const invocation = resolveCliArgvInvocation(argv);
|
||||
@@ -23,7 +27,7 @@ export function shouldSkipPluginCommandRegistration(params: {
|
||||
if (!params.primary) {
|
||||
return invocation.hasHelpOrVersion;
|
||||
}
|
||||
if (RESERVED_NON_PLUGIN_COMMAND_ROOTS.has(params.primary)) {
|
||||
if (isReservedNonPluginCommandRoot(params.primary)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
resolveCliCommandPathPolicy,
|
||||
resolveCliNetworkProxyPolicy,
|
||||
} from "./command-path-policy.js";
|
||||
import { isReservedNonPluginCommandRoot } from "./command-registration-policy.js";
|
||||
|
||||
const ROOT_HELP_ALIASES = new Set(["tools"]);
|
||||
|
||||
@@ -152,6 +153,10 @@ export function resolveMissingPluginCommandMessage(
|
||||
}
|
||||
}
|
||||
|
||||
if (isReservedNonPluginCommandRoot(normalizedPluginId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (allow.length > 0 && !allow.includes(normalizedPluginId)) {
|
||||
if (parentPluginId && allow.includes(parentPluginId)) {
|
||||
return null;
|
||||
|
||||
@@ -363,6 +363,7 @@ describe("runCli exit behavior", () => {
|
||||
["models list", ["node", "openclaw", "models", "list"]],
|
||||
["models status without live probe", ["node", "openclaw", "models", "status"]],
|
||||
["tasks list", ["node", "openclaw", "tasks", "list"]],
|
||||
["legacy singular tool namespace", ["node", "openclaw", "tool", "image_generate"]],
|
||||
["gateway tools namespace typo", ["node", "openclaw", "tools", "effective"]],
|
||||
["migrate", ["node", "openclaw", "migrate"]],
|
||||
])("skips managed proxy routing for %s", (_name, argv) => {
|
||||
@@ -385,24 +386,22 @@ describe("runCli exit behavior", () => {
|
||||
expect(startProxyMock).toHaveBeenCalledWith(undefined);
|
||||
});
|
||||
|
||||
it("keeps gateway tool RPC names out of plugin command discovery", async () => {
|
||||
it.each([
|
||||
["tool", ["node", "openclaw", "tool", "image_generate"]],
|
||||
["tools", ["node", "openclaw", "tools", "effective"]],
|
||||
])("keeps reserved %s command roots out of plugin command discovery", async (_name, argv) => {
|
||||
const parseAsync = vi.fn().mockResolvedValueOnce(undefined);
|
||||
buildProgramMock.mockReturnValueOnce({
|
||||
commands: [],
|
||||
parseAsync,
|
||||
});
|
||||
|
||||
await runCli(["node", "openclaw", "tools", "effective"]);
|
||||
await runCli(argv);
|
||||
|
||||
expect(startProxyMock).not.toHaveBeenCalled();
|
||||
expect(registerSubCliByNameMock).toHaveBeenCalledWith(expect.anything(), "tools", [
|
||||
"node",
|
||||
"openclaw",
|
||||
"tools",
|
||||
"effective",
|
||||
]);
|
||||
expect(registerSubCliByNameMock).toHaveBeenCalledWith(expect.anything(), argv[2], argv);
|
||||
expect(registerPluginCliCommandsFromValidatedConfigMock).not.toHaveBeenCalled();
|
||||
expect(parseAsync).toHaveBeenCalledWith(["node", "openclaw", "tools", "effective"]);
|
||||
expect(parseAsync).toHaveBeenCalledWith(argv);
|
||||
});
|
||||
|
||||
it("fails protected commands when managed proxy activation fails", async () => {
|
||||
|
||||
@@ -213,6 +213,15 @@ describe("resolveMissingPluginCommandMessage", () => {
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it("does not classify reserved non-plugin command roots as plugin allowlist misses", () => {
|
||||
const message = resolveMissingPluginCommandMessage("tool", {
|
||||
plugins: {
|
||||
allow: ["browser"],
|
||||
},
|
||||
});
|
||||
expect(message).toBeNull();
|
||||
});
|
||||
|
||||
it("explains that dreaming is a runtime slash command, not a CLI command", () => {
|
||||
const message = resolveMissingPluginCommandMessage(
|
||||
"dreaming",
|
||||
|
||||
Reference in New Issue
Block a user