Download src/auto-reply/command-status-builders.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 7.44 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/auto-reply/command-status-builders.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/auto-reply/command-status-builders.ts
-
curl -L -o command-status-builders.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/auto-reply/command-status-builders.ts
7.44 kB
| /** Formats /help and /commands output for text and native command-list surfaces. */ | |
| import { | |
| normalizeLowercaseStringOrEmpty, | |
| normalizeOptionalLowercaseString, | |
| normalizeOptionalString, | |
| } from "@openclaw/normalization-core/string-coerce"; | |
| import { getChannelPlugin } from "../channels/plugins/index.js"; | |
| import { isCommandFlagEnabled } from "../config/commands.flags.js"; | |
| import type { OpenClawConfig } from "../config/types.openclaw.js"; | |
| import { listPluginCommands } from "../plugins/commands.js"; | |
| import type { SkillCommandSpec } from "../skills/types.js"; | |
| import { | |
| listChatCommands, | |
| listChatCommandsForConfig, | |
| type ChatCommandDefinition, | |
| } from "./commands-registry.js"; | |
| import type { CommandCategory } from "./commands-registry.types.js"; | |
| type DisplayCategory = Exclude<CommandCategory, "docks">; | |
| const CATEGORY_LABELS: Record<DisplayCategory, string> = { | |
| session: "Session", | |
| options: "Options", | |
| status: "Status", | |
| management: "Management", | |
| media: "Media", | |
| tools: "Tools", | |
| }; | |
| const CATEGORY_ORDER: DisplayCategory[] = [ | |
| "session", | |
| "options", | |
| "status", | |
| "management", | |
| "media", | |
| "tools", | |
| ]; | |
| function groupCommandsByCategory( | |
| commands: ChatCommandDefinition[], | |
| ): Map<DisplayCategory, ChatCommandDefinition[]> { | |
| const grouped = new Map<DisplayCategory, ChatCommandDefinition[]>(); | |
| for (const category of CATEGORY_ORDER) { | |
| grouped.set(category, []); | |
| } | |
| for (const command of commands) { | |
| const category = command.category === "docks" ? "tools" : (command.category ?? "tools"); | |
| const list = grouped.get(category) ?? []; | |
| list.push(command); | |
| grouped.set(category, list); | |
| } | |
| return grouped; | |
| } | |
| /** Builds the compact slash-command help text shown by `/help`. */ | |
| export function buildHelpMessage(cfg?: OpenClawConfig): string { | |
| const lines = ["ℹ️ Help", ""]; | |
| lines.push("Session"); | |
| lines.push(" /new | /reset | /compact [instructions] | /stop"); | |
| lines.push(""); | |
| const optionParts = [ | |
| "/think <level|default>", | |
| "/model <id>", | |
| "/fast status|auto|on|off|default", | |
| "/verbose on|off|full", | |
| "/trace on|off|raw", | |
| ]; | |
| if (isCommandFlagEnabled(cfg, "config")) { | |
| optionParts.push("/config"); | |
| } | |
| if (isCommandFlagEnabled(cfg, "debug")) { | |
| optionParts.push("/debug"); | |
| } | |
| lines.push("Options"); | |
| lines.push(` ${optionParts.join(" | ")}`); | |
| lines.push(""); | |
| lines.push("Status"); | |
| lines.push(" /status | /tasks | /whoami | /context"); | |
| lines.push(""); | |
| lines.push("Skills"); | |
| lines.push(" /skill <name> [input]"); | |
| lines.push(""); | |
| lines.push("More: /commands for full list, /tools for available capabilities"); | |
| return lines.join("\n"); | |
| } | |
| const COMMANDS_PER_PAGE = 8; | |
| /** Options for rendering `/commands` output for a specific channel surface. */ | |
| export type CommandsMessageOptions = { | |
| page?: number; | |
| surface?: string; | |
| forcePaginatedList?: boolean; | |
| }; | |
| /** Rendered `/commands` text plus pagination metadata for channel-native lists. */ | |
| export type CommandsMessageResult = { | |
| text: string; | |
| totalPages: number; | |
| currentPage: number; | |
| hasNext: boolean; | |
| hasPrev: boolean; | |
| }; | |
| function formatCommandEntry(command: ChatCommandDefinition): string { | |
| const primary = command.nativeName | |
| ? `/${command.nativeName}` | |
| : normalizeOptionalString(command.textAliases[0]) || `/${command.key}`; | |
| const seen = new Set<string>(); | |
| const aliases = command.textAliases | |
| .map((alias) => alias.trim()) | |
| .filter(Boolean) | |
| .filter( | |
| (alias) => | |
| normalizeLowercaseStringOrEmpty(alias) !== normalizeLowercaseStringOrEmpty(primary), | |
| ) | |
| .filter((alias) => { | |
| const key = normalizeLowercaseStringOrEmpty(alias); | |
| if (seen.has(key)) { | |
| return false; | |
| } | |
| seen.add(key); | |
| return true; | |
| }); | |
| const aliasLabel = aliases.length ? ` (${aliases.join(", ")})` : ""; | |
| const scopeLabel = command.scope === "text" ? " [text]" : ""; | |
| return `${primary}${aliasLabel}${scopeLabel} - ${command.description}`; | |
| } | |
| type CommandsListItem = { | |
| label: string; | |
| text: string; | |
| }; | |
| function buildCommandItems( | |
| commands: ChatCommandDefinition[], | |
| pluginCommands: ReturnType<typeof listPluginCommands>, | |
| ): CommandsListItem[] { | |
| const grouped = groupCommandsByCategory(commands); | |
| const items: CommandsListItem[] = []; | |
| for (const category of CATEGORY_ORDER) { | |
| const categoryCommands = grouped.get(category) ?? []; | |
| if (categoryCommands.length === 0) { | |
| continue; | |
| } | |
| const label = CATEGORY_LABELS[category]; | |
| for (const command of categoryCommands) { | |
| items.push({ label, text: formatCommandEntry(command) }); | |
| } | |
| } | |
| for (const command of pluginCommands) { | |
| const pluginLabel = command.pluginId ? ` (${command.pluginId})` : ""; | |
| items.push({ | |
| label: "Plugins", | |
| text: `/${command.name}${pluginLabel} - ${command.description}`, | |
| }); | |
| } | |
| return items; | |
| } | |
| function formatCommandList(items: CommandsListItem[]): string { | |
| const lines: string[] = []; | |
| let currentLabel: string | null = null; | |
| for (const item of items) { | |
| if (item.label !== currentLabel) { | |
| if (lines.length > 0) { | |
| lines.push(""); | |
| } | |
| lines.push(item.label); | |
| currentLabel = item.label; | |
| } | |
| lines.push(` ${item.text}`); | |
| } | |
| return lines.join("\n"); | |
| } | |
| /** Builds `/commands` text, returning only the rendered message body. */ | |
| export function buildCommandsMessage( | |
| cfg?: OpenClawConfig, | |
| skillCommands?: SkillCommandSpec[], | |
| options?: CommandsMessageOptions, | |
| ): string { | |
| const result = buildCommandsMessagePaginated(cfg, skillCommands, options); | |
| return result.text; | |
| } | |
| /** Builds `/commands` text and pagination metadata for surfaces with native list controls. */ | |
| export function buildCommandsMessagePaginated( | |
| cfg?: OpenClawConfig, | |
| skillCommands?: SkillCommandSpec[], | |
| options?: CommandsMessageOptions, | |
| ): CommandsMessageResult { | |
| const page = Math.max(1, options?.page ?? 1); | |
| const surface = normalizeOptionalLowercaseString(options?.surface); | |
| // Surfaces with native command-list UI need page metadata; plain text surfaces get one full list. | |
| const prefersPaginatedList = | |
| options?.forcePaginatedList === true || | |
| Boolean(surface && getChannelPlugin(surface)?.commands?.buildCommandsListChannelData); | |
| const commands = cfg | |
| ? listChatCommandsForConfig(cfg, { skillCommands }) | |
| : listChatCommands({ skillCommands }); | |
| const pluginCommands = listPluginCommands(); | |
| const items = buildCommandItems(commands, pluginCommands); | |
| if (!prefersPaginatedList) { | |
| const lines = ["ℹ️ Slash commands", ""]; | |
| lines.push(formatCommandList(items)); | |
| lines.push("", "More: /tools for available capabilities"); | |
| return { | |
| text: lines.join("\n").trim(), | |
| totalPages: 1, | |
| currentPage: 1, | |
| hasNext: false, | |
| hasPrev: false, | |
| }; | |
| } | |
| const totalCommands = items.length; | |
| const totalPages = Math.max(1, Math.ceil(totalCommands / COMMANDS_PER_PAGE)); | |
| const currentPage = Math.min(page, totalPages); | |
| const startIndex = (currentPage - 1) * COMMANDS_PER_PAGE; | |
| const endIndex = startIndex + COMMANDS_PER_PAGE; | |
| const pageItems = items.slice(startIndex, endIndex); | |
| const lines = [`ℹ️ Commands (${currentPage}/${totalPages})`, ""]; | |
| lines.push(formatCommandList(pageItems)); | |
| return { | |
| text: lines.join("\n").trim(), | |
| totalPages, | |
| currentPage, | |
| hasNext: currentPage < totalPages, | |
| hasPrev: currentPage > 1, | |
| }; | |
| } | |