Files
openclaw/src/auto-reply/commands-registry.data.ts
Tyler Yust b8f66c260d Agents: add nested subagent orchestration controls and reduce subagent token waste (#14447)
* Agents: add subagent orchestration controls

* Agents: add subagent orchestration controls (WIP uncommitted changes)

* feat(subagents): add depth-based spawn gating for sub-sub-agents

* feat(subagents): tool policy, registry, and announce chain for nested agents

* feat(subagents): system prompt, docs, changelog for nested sub-agents

* fix(subagents): prevent model fallback override, show model during active runs, and block context overflow fallback

Bug 1: When a session has an explicit model override (e.g., gpt/openai-codex),
the fallback candidate logic in resolveFallbackCandidates silently appended the
global primary model (opus) as a backstop. On reinjection/steer with a transient
error, the session could fall back to opus which has a smaller context window
and crash. Fix: when storedModelOverride is set, pass fallbacksOverride ?? []
instead of undefined, preventing the implicit primary backstop.

Bug 2: Active subagents showed 'model n/a' in /subagents list because
resolveModelDisplay only read entry.model/modelProvider (populated after run
completes). Fix: fall back to modelOverride/providerOverride fields which are
populated at spawn time via sessions.patch.

Bug 3: Context overflow errors (prompt too long, context_length_exceeded) could
theoretically escape runEmbeddedPiAgent and be treated as failover candidates
in runWithModelFallback, causing a switch to a model with a smaller context
window. Fix: in runWithModelFallback, detect context overflow errors via
isLikelyContextOverflowError and rethrow them immediately instead of trying the
next model candidate.

* fix(subagents): track spawn depth in session store and fix announce routing for nested agents

* Fix compaction status tracking and dedupe overflow compaction triggers

* fix(subagents): enforce depth block via session store and implement cascade kill

* fix: inject group chat context into system prompt

* fix(subagents): always write model to session store at spawn time

* Preserve spawnDepth when agent handler rewrites session entry

* fix(subagents): suppress announce on steer-restart

* fix(subagents): fallback spawned session model to runtime default

* fix(subagents): enforce spawn depth when caller key resolves by sessionId

* feat(subagents): implement active-first ordering for numeric targets and enhance task display

- Added a test to verify that subagents with numeric targets follow an active-first list ordering.
- Updated `resolveSubagentTarget` to sort subagent runs based on active status and recent activity.
- Enhanced task display in command responses to prevent truncation of long task descriptions.
- Introduced new utility functions for compacting task text and managing subagent run states.

* fix(subagents): show model for active runs via run record fallback

When the spawned model matches the agent's default model, the session
store's override fields are intentionally cleared (isDefault: true).
The model/modelProvider fields are only populated after the run
completes. This left active subagents showing 'model n/a'.

Fix: store the resolved model on SubagentRunRecord at registration
time, and use it as a fallback in both display paths (subagents tool
and /subagents command) when the session store entry has no model info.

Changes:
- SubagentRunRecord: add optional model field
- registerSubagentRun: accept and persist model param
- sessions-spawn-tool: pass resolvedModel to registerSubagentRun
- subagents-tool: pass run record model as fallback to resolveModelDisplay
- commands-subagents: pass run record model as fallback to resolveModelDisplay

* feat(chat): implement session key resolution and reset on sidebar navigation

- Added functions to resolve the main session key and reset chat state when switching sessions from the sidebar.
- Updated the `renderTab` function to handle session key changes when navigating to the chat tab.
- Introduced a test to verify that the session resets to "main" when opening chat from the sidebar navigation.

* fix: subagent timeout=0 passthrough and fallback prompt duplication

Bug 1: runTimeoutSeconds=0 now means 'no timeout' instead of applying 600s default
- sessions-spawn-tool: default to undefined (not 0) when neither timeout param
  is provided; use != null check so explicit 0 passes through to gateway
- agent.ts: accept 0 as valid timeout (resolveAgentTimeoutMs already handles
  0 → MAX_SAFE_TIMEOUT_MS)

Bug 2: model fallback no longer re-injects the original prompt as a duplicate
- agent.ts: track fallback attempt index; on retries use a short continuation
  message instead of the full original prompt since the session file already
  contains it from the first attempt
- Also skip re-sending images on fallback retries (already in session)

* feat(subagents): truncate long task descriptions in subagents command output

- Introduced a new utility function to format task previews, limiting their length to improve readability.
- Updated the command handler to use the new formatting function, ensuring task descriptions are truncated appropriately.
- Adjusted related tests to verify that long task descriptions are now truncated in the output.

* refactor(subagents): update subagent registry path resolution and improve command output formatting

- Replaced direct import of STATE_DIR with a utility function to resolve the state directory dynamically.
- Enhanced the formatting of command output for active and recent subagents, adding separators for better readability.
- Updated related tests to reflect changes in command output structure.

* fix(subagent): default sessions_spawn to no timeout when runTimeoutSeconds omitted

The previous fix (75a791106) correctly handled the case where
runTimeoutSeconds was explicitly set to 0 ("no timeout"). However,
when models omit the parameter entirely (which is common since the
schema marks it as optional), runTimeoutSeconds resolved to undefined.

undefined flowed through the chain as:
  sessions_spawn → timeout: undefined (since undefined != null is false)
  → gateway agent handler → agentCommand opts.timeout: undefined
  → resolveAgentTimeoutMs({ overrideSeconds: undefined })
  → DEFAULT_AGENT_TIMEOUT_SECONDS (600s = 10 minutes)

This caused subagents to be killed at exactly 10 minutes even though
the user's intent (via TOOLS.md) was for subagents to run without a
timeout.

Fix: default runTimeoutSeconds to 0 (no timeout) when neither
runTimeoutSeconds nor timeoutSeconds is provided by the caller.
Subagent spawns are long-running by design and should not inherit the
600s agent-command default timeout.

* fix(subagent): accept timeout=0 in agent-via-gateway path (second 600s default)

* fix: thread timeout override through getReplyFromConfig dispatch path

getReplyFromConfig called resolveAgentTimeoutMs({ cfg }) with no override,
always falling back to the config default (600s). Add timeoutOverrideSeconds
to GetReplyOptions and pass it through as overrideSeconds so callers of the
dispatch chain can specify a custom timeout (0 = no timeout).

This complements the existing timeout threading in agentCommand and the
cron isolated-agent runner, which already pass overrideSeconds correctly.

* feat(model-fallback): normalize OpenAI Codex model references and enhance fallback handling

- Added normalization for OpenAI Codex model references, specifically converting "gpt-5.3-codex" to "openai-codex" before execution.
- Updated the `resolveFallbackCandidates` function to utilize the new normalization logic.
- Enhanced tests to verify the correct behavior of model normalization and fallback mechanisms.
- Introduced a new test case to ensure that the normalization process works as expected for various input formats.

* feat(tests): add unit tests for steer failure behavior in openclaw-tools

- Introduced a new test file to validate the behavior of subagents when steer replacement dispatch fails.
- Implemented tests to ensure that the announce behavior is restored correctly and that the suppression reason is cleared as expected.
- Enhanced the subagent registry with a new function to clear steer restart suppression.
- Updated related components to support the new test scenarios.

* fix(subagents): replace stop command with kill in slash commands and documentation

- Updated the `/subagents` command to replace `stop` with `kill` for consistency in controlling sub-agent runs.
- Modified related documentation to reflect the change in command usage.
- Removed legacy timeoutSeconds references from the sessions-spawn-tool schema and tests to streamline timeout handling.
- Enhanced tests to ensure correct behavior of the updated commands and their interactions.

* feat(tests): add unit tests for readLatestAssistantReply function

- Introduced a new test file for the `readLatestAssistantReply` function to validate its behavior with various message scenarios.
- Implemented tests to ensure the function correctly retrieves the latest assistant message and handles cases where the latest message has no text.
- Mocked the gateway call to simulate different message histories for comprehensive testing.

* feat(tests): enhance subagent kill-all cascade tests and announce formatting

- Added a new test to verify that the `kill-all` command cascades through ended parents to active descendants in subagents.
- Updated the subagent announce formatting tests to reflect changes in message structure, including the replacement of "Findings:" with "Result:" and the addition of new expectations for message content.
- Improved the handling of long findings and stats in the announce formatting logic to ensure concise output.
- Refactored related functions to enhance clarity and maintainability in the subagent registry and tools.

* refactor(subagent): update announce formatting and remove unused constants

- Modified the subagent announce formatting to replace "Findings:" with "Result:" and adjusted related expectations in tests.
- Removed constants for maximum announce findings characters and summary words, simplifying the announcement logic.
- Updated the handling of findings to retain full content instead of truncating, ensuring more informative outputs.
- Cleaned up unused imports in the commands-subagents file to enhance code clarity.

* feat(tests): enhance billing error handling in user-facing text

- Added tests to ensure that normal text mentioning billing plans is not rewritten, preserving user context.
- Updated the `isBillingErrorMessage` and `sanitizeUserFacingText` functions to improve handling of billing-related messages.
- Introduced new test cases for various scenarios involving billing messages to ensure accurate processing and output.
- Enhanced the subagent announce flow to correctly manage active descendant runs, preventing premature announcements.

* feat(subagent): enhance workflow guidance and auto-announcement clarity

- Added a new guideline in the subagent system prompt to emphasize trust in push-based completion, discouraging busy polling for status updates.
- Updated documentation to clarify that sub-agents will automatically announce their results, improving user understanding of the workflow.
- Enhanced tests to verify the new guidance on avoiding polling loops and to ensure the accuracy of the updated prompts.

* fix(cron): avoid announcing interim subagent spawn acks

* chore: clean post-rebase imports

* fix(cron): fall back to child replies when parent stays interim

* fix(subagents): make active-run guidance advisory

* fix(subagents): update announce flow to handle active descendants and enhance test coverage

- Modified the announce flow to defer announcements when active descendant runs are present, ensuring accurate status reporting.
- Updated tests to verify the new behavior, including scenarios where no fallback requester is available and ensuring proper handling of finished subagents.
- Enhanced the announce formatting to include an `expectFinal` flag for better clarity in the announcement process.

* fix(subagents): enhance announce flow and formatting for user updates

- Updated the announce flow to provide clearer instructions for user updates based on active subagent runs and requester context.
- Refactored the announcement logic to improve clarity and ensure internal context remains private.
- Enhanced tests to verify the new message expectations and formatting, including updated prompts for user-facing updates.
- Introduced a new function to build reply instructions based on session context, improving the overall announcement process.

* fix: resolve prep blockers and changelog placement (#14447) (thanks @tyler6204)

* fix: restore cron delivery-plan import after rebase (#14447) (thanks @tyler6204)

* fix: resolve test failures from rebase conflicts (#14447) (thanks @tyler6204)

* fix: apply formatting after rebase (#14447) (thanks @tyler6204)
2026-02-14 22:03:45 -08:00

651 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type {
ChatCommandDefinition,
CommandCategory,
CommandScope,
} from "./commands-registry.types.js";
import { listChannelDocks } from "../channels/dock.js";
import { getActivePluginRegistry } from "../plugins/runtime.js";
import { COMMAND_ARG_FORMATTERS } from "./commands-args.js";
import { listThinkingLevels } from "./thinking.js";
type DefineChatCommandInput = {
key: string;
nativeName?: string;
description: string;
args?: ChatCommandDefinition["args"];
argsParsing?: ChatCommandDefinition["argsParsing"];
formatArgs?: ChatCommandDefinition["formatArgs"];
argsMenu?: ChatCommandDefinition["argsMenu"];
acceptsArgs?: boolean;
textAlias?: string;
textAliases?: string[];
scope?: CommandScope;
category?: CommandCategory;
};
function defineChatCommand(command: DefineChatCommandInput): ChatCommandDefinition {
const aliases = (command.textAliases ?? (command.textAlias ? [command.textAlias] : []))
.map((alias) => alias.trim())
.filter(Boolean);
const scope =
command.scope ?? (command.nativeName ? (aliases.length ? "both" : "native") : "text");
const acceptsArgs = command.acceptsArgs ?? Boolean(command.args?.length);
const argsParsing = command.argsParsing ?? (command.args?.length ? "positional" : "none");
return {
key: command.key,
nativeName: command.nativeName,
description: command.description,
acceptsArgs,
args: command.args,
argsParsing,
formatArgs: command.formatArgs,
argsMenu: command.argsMenu,
textAliases: aliases,
scope,
category: command.category,
};
}
type ChannelDock = ReturnType<typeof listChannelDocks>[number];
function defineDockCommand(dock: ChannelDock): ChatCommandDefinition {
return defineChatCommand({
key: `dock:${dock.id}`,
nativeName: `dock_${dock.id}`,
description: `Switch to ${dock.id} for replies.`,
textAliases: [`/dock-${dock.id}`, `/dock_${dock.id}`],
category: "docks",
});
}
function registerAlias(commands: ChatCommandDefinition[], key: string, ...aliases: string[]): void {
const command = commands.find((entry) => entry.key === key);
if (!command) {
throw new Error(`registerAlias: unknown command key: ${key}`);
}
const existing = new Set(command.textAliases.map((alias) => alias.trim().toLowerCase()));
for (const alias of aliases) {
const trimmed = alias.trim();
if (!trimmed) {
continue;
}
const lowered = trimmed.toLowerCase();
if (existing.has(lowered)) {
continue;
}
existing.add(lowered);
command.textAliases.push(trimmed);
}
}
function assertCommandRegistry(commands: ChatCommandDefinition[]): void {
const keys = new Set<string>();
const nativeNames = new Set<string>();
const textAliases = new Set<string>();
for (const command of commands) {
if (keys.has(command.key)) {
throw new Error(`Duplicate command key: ${command.key}`);
}
keys.add(command.key);
const nativeName = command.nativeName?.trim();
if (command.scope === "text") {
if (nativeName) {
throw new Error(`Text-only command has native name: ${command.key}`);
}
if (command.textAliases.length === 0) {
throw new Error(`Text-only command missing text alias: ${command.key}`);
}
} else if (!nativeName) {
throw new Error(`Native command missing native name: ${command.key}`);
} else {
const nativeKey = nativeName.toLowerCase();
if (nativeNames.has(nativeKey)) {
throw new Error(`Duplicate native command: ${nativeName}`);
}
nativeNames.add(nativeKey);
}
if (command.scope === "native" && command.textAliases.length > 0) {
throw new Error(`Native-only command has text aliases: ${command.key}`);
}
for (const alias of command.textAliases) {
if (!alias.startsWith("/")) {
throw new Error(`Command alias missing leading '/': ${alias}`);
}
const aliasKey = alias.toLowerCase();
if (textAliases.has(aliasKey)) {
throw new Error(`Duplicate command alias: ${alias}`);
}
textAliases.add(aliasKey);
}
}
}
let cachedCommands: ChatCommandDefinition[] | null = null;
let cachedRegistry: ReturnType<typeof getActivePluginRegistry> | null = null;
let cachedNativeCommandSurfaces: Set<string> | null = null;
let cachedNativeRegistry: ReturnType<typeof getActivePluginRegistry> | null = null;
function buildChatCommands(): ChatCommandDefinition[] {
const commands: ChatCommandDefinition[] = [
defineChatCommand({
key: "help",
nativeName: "help",
description: "Show available commands.",
textAlias: "/help",
category: "status",
}),
defineChatCommand({
key: "commands",
nativeName: "commands",
description: "List all slash commands.",
textAlias: "/commands",
category: "status",
}),
defineChatCommand({
key: "skill",
nativeName: "skill",
description: "Run a skill by name.",
textAlias: "/skill",
category: "tools",
args: [
{
name: "name",
description: "Skill name",
type: "string",
required: true,
},
{
name: "input",
description: "Skill input",
type: "string",
captureRemaining: true,
},
],
}),
defineChatCommand({
key: "status",
nativeName: "status",
description: "Show current status.",
textAlias: "/status",
category: "status",
}),
defineChatCommand({
key: "allowlist",
description: "List/add/remove allowlist entries.",
textAlias: "/allowlist",
acceptsArgs: true,
scope: "text",
category: "management",
}),
defineChatCommand({
key: "approve",
nativeName: "approve",
description: "Approve or deny exec requests.",
textAlias: "/approve",
acceptsArgs: true,
category: "management",
}),
defineChatCommand({
key: "context",
nativeName: "context",
description: "Explain how context is built and used.",
textAlias: "/context",
acceptsArgs: true,
category: "status",
}),
defineChatCommand({
key: "tts",
nativeName: "tts",
description: "Control text-to-speech (TTS).",
textAlias: "/tts",
category: "media",
args: [
{
name: "action",
description: "TTS action",
type: "string",
choices: [
{ value: "on", label: "On" },
{ value: "off", label: "Off" },
{ value: "status", label: "Status" },
{ value: "provider", label: "Provider" },
{ value: "limit", label: "Limit" },
{ value: "summary", label: "Summary" },
{ value: "audio", label: "Audio" },
{ value: "help", label: "Help" },
],
},
{
name: "value",
description: "Provider, limit, or text",
type: "string",
captureRemaining: true,
},
],
argsMenu: {
arg: "action",
title:
"TTS Actions:\n" +
"• On Enable TTS for responses\n" +
"• Off Disable TTS\n" +
"• Status Show current settings\n" +
"• Provider Set voice provider (edge, elevenlabs, openai)\n" +
"• Limit Set max characters for TTS\n" +
"• Summary Toggle AI summary for long texts\n" +
"• Audio Generate TTS from custom text\n" +
"• Help Show usage guide",
},
}),
defineChatCommand({
key: "whoami",
nativeName: "whoami",
description: "Show your sender id.",
textAlias: "/whoami",
category: "status",
}),
defineChatCommand({
key: "subagents",
nativeName: "subagents",
description: "List, kill, log, or steer subagent runs for this session.",
textAlias: "/subagents",
category: "management",
args: [
{
name: "action",
description: "list | kill | log | info | send | steer",
type: "string",
choices: ["list", "kill", "log", "info", "send", "steer"],
},
{
name: "target",
description: "Run id, index, or session key",
type: "string",
},
{
name: "value",
description: "Additional input (limit/message)",
type: "string",
captureRemaining: true,
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "kill",
nativeName: "kill",
description: "Kill a running subagent (or all).",
textAlias: "/kill",
category: "management",
args: [
{
name: "target",
description: "Label, run id, index, or all",
type: "string",
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "steer",
nativeName: "steer",
description: "Send guidance to a running subagent.",
textAlias: "/steer",
category: "management",
args: [
{
name: "target",
description: "Label, run id, or index",
type: "string",
},
{
name: "message",
description: "Steering message",
type: "string",
captureRemaining: true,
},
],
}),
defineChatCommand({
key: "config",
nativeName: "config",
description: "Show or set config values.",
textAlias: "/config",
category: "management",
args: [
{
name: "action",
description: "show | get | set | unset",
type: "string",
choices: ["show", "get", "set", "unset"],
},
{
name: "path",
description: "Config path",
type: "string",
},
{
name: "value",
description: "Value for set",
type: "string",
captureRemaining: true,
},
],
argsParsing: "none",
formatArgs: COMMAND_ARG_FORMATTERS.config,
}),
defineChatCommand({
key: "debug",
nativeName: "debug",
description: "Set runtime debug overrides.",
textAlias: "/debug",
category: "management",
args: [
{
name: "action",
description: "show | reset | set | unset",
type: "string",
choices: ["show", "reset", "set", "unset"],
},
{
name: "path",
description: "Debug path",
type: "string",
},
{
name: "value",
description: "Value for set",
type: "string",
captureRemaining: true,
},
],
argsParsing: "none",
formatArgs: COMMAND_ARG_FORMATTERS.debug,
}),
defineChatCommand({
key: "usage",
nativeName: "usage",
description: "Usage footer or cost summary.",
textAlias: "/usage",
category: "options",
args: [
{
name: "mode",
description: "off, tokens, full, or cost",
type: "string",
choices: ["off", "tokens", "full", "cost"],
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "stop",
nativeName: "stop",
description: "Stop the current run.",
textAlias: "/stop",
category: "session",
}),
defineChatCommand({
key: "restart",
nativeName: "restart",
description: "Restart OpenClaw.",
textAlias: "/restart",
category: "tools",
}),
defineChatCommand({
key: "activation",
nativeName: "activation",
description: "Set group activation mode.",
textAlias: "/activation",
category: "management",
args: [
{
name: "mode",
description: "mention or always",
type: "string",
choices: ["mention", "always"],
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "send",
nativeName: "send",
description: "Set send policy.",
textAlias: "/send",
category: "management",
args: [
{
name: "mode",
description: "on, off, or inherit",
type: "string",
choices: ["on", "off", "inherit"],
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "reset",
nativeName: "reset",
description: "Reset the current session.",
textAlias: "/reset",
acceptsArgs: true,
category: "session",
}),
defineChatCommand({
key: "new",
nativeName: "new",
description: "Start a new session.",
textAlias: "/new",
acceptsArgs: true,
category: "session",
}),
defineChatCommand({
key: "compact",
nativeName: "compact",
description: "Compact the session context.",
textAlias: "/compact",
category: "session",
args: [
{
name: "instructions",
description: "Extra compaction instructions",
type: "string",
captureRemaining: true,
},
],
}),
defineChatCommand({
key: "think",
nativeName: "think",
description: "Set thinking level.",
textAlias: "/think",
category: "options",
args: [
{
name: "level",
description: "off, minimal, low, medium, high, xhigh",
type: "string",
choices: ({ provider, model }) => listThinkingLevels(provider, model),
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "verbose",
nativeName: "verbose",
description: "Toggle verbose mode.",
textAlias: "/verbose",
category: "options",
args: [
{
name: "mode",
description: "on or off",
type: "string",
choices: ["on", "off"],
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "reasoning",
nativeName: "reasoning",
description: "Toggle reasoning visibility.",
textAlias: "/reasoning",
category: "options",
args: [
{
name: "mode",
description: "on, off, or stream",
type: "string",
choices: ["on", "off", "stream"],
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "elevated",
nativeName: "elevated",
description: "Toggle elevated mode.",
textAlias: "/elevated",
category: "options",
args: [
{
name: "mode",
description: "on, off, ask, or full",
type: "string",
choices: ["on", "off", "ask", "full"],
},
],
argsMenu: "auto",
}),
defineChatCommand({
key: "exec",
nativeName: "exec",
description: "Set exec defaults for this session.",
textAlias: "/exec",
category: "options",
args: [
{
name: "options",
description: "host=... security=... ask=... node=...",
type: "string",
},
],
argsParsing: "none",
}),
defineChatCommand({
key: "model",
nativeName: "model",
description: "Show or set the model.",
textAlias: "/model",
category: "options",
args: [
{
name: "model",
description: "Model id (provider/model or id)",
type: "string",
},
],
}),
defineChatCommand({
key: "models",
nativeName: "models",
description: "List model providers or provider models.",
textAlias: "/models",
argsParsing: "none",
acceptsArgs: true,
category: "options",
}),
defineChatCommand({
key: "queue",
nativeName: "queue",
description: "Adjust queue settings.",
textAlias: "/queue",
category: "options",
args: [
{
name: "mode",
description: "queue mode",
type: "string",
choices: ["steer", "interrupt", "followup", "collect", "steer-backlog"],
},
{
name: "debounce",
description: "debounce duration (e.g. 500ms, 2s)",
type: "string",
},
{
name: "cap",
description: "queue cap",
type: "number",
},
{
name: "drop",
description: "drop policy",
type: "string",
choices: ["old", "new", "summarize"],
},
],
argsParsing: "none",
formatArgs: COMMAND_ARG_FORMATTERS.queue,
}),
defineChatCommand({
key: "bash",
description: "Run host shell commands (host-only).",
textAlias: "/bash",
scope: "text",
category: "tools",
args: [
{
name: "command",
description: "Shell command",
type: "string",
captureRemaining: true,
},
],
}),
...listChannelDocks()
.filter((dock) => dock.capabilities.nativeCommands)
.map((dock) => defineDockCommand(dock)),
];
registerAlias(commands, "whoami", "/id");
registerAlias(commands, "think", "/thinking", "/t");
registerAlias(commands, "verbose", "/v");
registerAlias(commands, "reasoning", "/reason");
registerAlias(commands, "elevated", "/elev");
registerAlias(commands, "steer", "/tell");
assertCommandRegistry(commands);
return commands;
}
export function getChatCommands(): ChatCommandDefinition[] {
const registry = getActivePluginRegistry();
if (cachedCommands && registry === cachedRegistry) {
return cachedCommands;
}
const commands = buildChatCommands();
cachedCommands = commands;
cachedRegistry = registry;
cachedNativeCommandSurfaces = null;
return commands;
}
export function getNativeCommandSurfaces(): Set<string> {
const registry = getActivePluginRegistry();
if (cachedNativeCommandSurfaces && registry === cachedNativeRegistry) {
return cachedNativeCommandSurfaces;
}
cachedNativeCommandSurfaces = new Set(
listChannelDocks()
.filter((dock) => dock.capabilities.nativeCommands)
.map((dock) => dock.id),
);
cachedNativeRegistry = registry;
return cachedNativeCommandSurfaces;
}