fix: keep control ui slash commands browser-safe

This commit is contained in:
Peter Steinberger
2026-05-02 08:30:19 +01:00
parent 2f0c9358b1
commit 636478c622
4 changed files with 59 additions and 4 deletions

View File

@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
- Discord/gateway: reconnect when the gateway socket closes while waiting for the shared IDENTIFY concurrency window, instead of silently skipping IDENTIFY and leaving the bot online but unresponsive. Fixes #74617. Thanks @zeeskdr-ai.
- Telegram/startup: use the existing `getMe` request guard for the gateway bot probe instead of a fixed 2.5-second budget, and honor higher `timeoutSeconds` configs for slow Telegram API paths. Fixes #75783. Thanks @tankotan.
- Control UI/slash commands: keep fallback command metadata on a browser-safe registry path, so provider thinking runtime imports cannot blank the Web UI with `process is not defined`. Fixes #75987. Thanks @novkien.
- Infer/media: report missing image-understanding and audio-transcription provider configuration for `image describe`, `image describe-many`, and `audio transcribe` instead of blaming the input path when no provider is available. Fixes #73569 and supersedes #73593, #74288, and #74495. Thanks @bittoby, @tmimmanuel, @Linux2010, and @vyctorbrzezowski.
- Docs/health: clarify that session listing surfaces stored conversation rows rather than Discord/channel socket liveness, and point connectivity checks at channel status and health probes. Fixes #70420. Thanks @ashersoutherncities-art and @martingarramon.
- WhatsApp/Cron: keep DM pairing-store approvals out of implicit cron and heartbeat recipient fallback, so scheduled automation only uses explicit targets, active configured recipients, or configured `allowFrom` entries. Fixes #62339. Thanks @kelvinisly-collab.

View File

@@ -6,6 +6,7 @@ import {
defineChatCommand,
} from "./commands-registry.shared.js";
import type { ChatCommandDefinition } from "./commands-registry.types.js";
import { listThinkingLevels } from "./thinking.js";
type ChannelPlugin = ReturnType<typeof listLoadedChannelPlugins>[number];
@@ -28,7 +29,7 @@ let cachedRegistryVersion = -1;
function buildChatCommands(): ChatCommandDefinition[] {
const commands: ChatCommandDefinition[] = [
...buildBuiltinChatCommands(),
...buildBuiltinChatCommands({ listThinkingLevels }),
...listLoadedChannelPlugins()
.filter(supportsNativeCommands)
.map((plugin) => defineDockCommand(plugin)),

View File

@@ -2,11 +2,25 @@ import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
import { COMMAND_ARG_FORMATTERS } from "./commands-args.js";
import type {
ChatCommandDefinition,
CommandArgChoiceContext,
CommandCategory,
CommandScope,
CommandTier,
} from "./commands-registry.types.js";
import { listThinkingLevels } from "./thinking.js";
import { BASE_THINKING_LEVELS, type ThinkLevel } from "./thinking.shared.js";
type ListThinkingLevels = (
provider?: string | null,
model?: string | null,
catalog?: CommandArgChoiceContext["catalog"],
) => ThinkLevel[];
const BROWSER_SAFE_THINKING_LEVELS: ThinkLevel[] = [
...BASE_THINKING_LEVELS,
"xhigh",
"adaptive",
"max",
];
type DefineChatCommandInput = {
key: string;
@@ -121,7 +135,11 @@ export function assertCommandRegistry(commands: ChatCommandDefinition[]): void {
}
}
export function buildBuiltinChatCommands(): ChatCommandDefinition[] {
export function buildBuiltinChatCommands(
params: { listThinkingLevels?: ListThinkingLevels } = {},
): ChatCommandDefinition[] {
const listThinkingLevelChoices =
params.listThinkingLevels ?? (() => BROWSER_SAFE_THINKING_LEVELS);
const commands: ChatCommandDefinition[] = [
defineChatCommand({
key: "help",
@@ -727,7 +745,8 @@ export function buildBuiltinChatCommands(): ChatCommandDefinition[] {
name: "level",
description: "Thinking level",
type: "string",
choices: ({ provider, model, catalog }) => listThinkingLevels(provider, model, catalog),
choices: ({ provider, model, catalog }) =>
listThinkingLevelChoices(provider, model, catalog),
},
],
argsMenu: "auto",

View File

@@ -0,0 +1,34 @@
// @vitest-environment node
import { readFile } from "node:fs/promises";
import { describe, expect, it } from "vitest";
describe("slash command browser import", () => {
it("builds fallback commands from the browser-safe shared registry", async () => {
const mod = await import("./slash-commands.ts?browser-import");
expect(mod.SLASH_COMMANDS.find((command) => command.name === "think")).toMatchObject({
name: "think",
category: "model",
});
});
it("keeps provider thinking runtime out of the Control UI import path", async () => {
const slashCommands = await readFile(new URL("./slash-commands.ts", import.meta.url), "utf8");
const sharedRegistry = await readFile(
new URL("../../../../src/auto-reply/commands-registry.shared.ts", import.meta.url),
"utf8",
);
const serverRegistry = await readFile(
new URL("../../../../src/auto-reply/commands-registry.data.ts", import.meta.url),
"utf8",
);
const mod = await import("./slash-commands.ts?browser-import");
expect(mod.SLASH_COMMANDS.some((command) => command.name === "think")).toBe(true);
expect(slashCommands).toContain("commands-registry.shared.js");
expect(sharedRegistry).toContain("thinking.shared.js");
expect(sharedRegistry).not.toContain("./thinking.js");
expect(sharedRegistry).not.toContain("provider-thinking");
expect(serverRegistry).toContain('from "./thinking.js"');
});
});