第 4 章 · 命令系统
如果说工具系统是 LLM 智能体的"双手",那么命令系统就是用户的"遥控器"——它让用户能够通过简洁的斜杠命令(
/commit、/review、/compact)直接控制系统行为,而不必每次都用自然语言描述意图。本章将带你深入理解命令系统的完整设计:从类型定义到注册机制,从路由分发到执行流程,再到 80+ 个命令的分类全景。
4.1 概述:命令系统的角色
在本项目的架构中,命令(Command) 是用户与系统交互的直接通道。与工具系统不同,命令面向的是人类用户而非 LLM:
| 维度 | 工具系统 | 命令系统 |
|---|---|---|
| 调用者 | LLM 决定调用 | 用户主动输入 |
| 触发方式 | tool_use block | /command [args] |
| 输入格式 | 结构化 JSON(Zod Schema) | 自由文本参数 |
| 执行上下文 | QueryEngine 编排 | processSlashCommand 路由 |
| 典型用途 | 文件操作、搜索、Shell | 配置管理、Git 操作、系统诊断 |
命令系统的核心文件分布如下:
| 文件 | 职责 |
|---|---|
src/types/command.ts | 命令基础类型定义(Command、PromptCommand、LocalCommand 等) |
src/commands.ts | 命令注册表、组装逻辑、查找与过滤 |
src/commands/ | 80+ 个命令的具体实现(每个命令一个子目录或文件) |
src/utils/processUserInput/processSlashCommand.tsx | 命令路由与执行引擎 |
src/utils/processUserInput/processUserInput.ts | 用户输入总入口,决定走命令还是查询 |
4.2 命令类型定义
三种命令类型
src/types/command.ts 定义了命令系统的核心类型。每个命令都是 CommandBase 与三种具体类型之一的联合:
// 命令的联合类型:基础属性 + 三种具体类型之一
export type Command = CommandBase &
(PromptCommand | LocalCommand | LocalJSXCommand)
这三种类型代表了完全不同的执行模式:
CommandBase:公共属性
所有命令共享的基础属性定义在 CommandBase 中:
export type CommandBase = {
// ========== 身份标识 ==========
name: string;
aliases?: string[];
description: string;
argumentHint?: string; // 参数提示(如 "[enable|disable [server-name]]")
// ========== 可见性与启用状态 ==========
availability?: CommandAvailability[]; // 'claude-ai' | 'console'
isEnabled?: () => boolean; // 默认 true,支持特性标志控制
isHidden?: boolean; // 是否从帮助列表中隐藏
// ========== 来源与分类 ==========
loadedFrom?: 'commands_DEPRECATED' | 'skills' | 'plugin'
| 'managed' | 'bundled' | 'mcp';
kind?: 'workflow'; // 工作流命令标记
source?: string; // 命令来源
// ========== 执行控制 ==========
immediate?: boolean; // 是否立即执行(跳过队列)
isSensitive?: boolean; // 参数是否需要脱敏
disableModelInvocation?: boolean; // 禁止 LLM 通过 SkillTool 调用
userInvocable?: boolean; // 用户是否可以直接调用
}
这里有两个重要的设计决策:
availabilityvsisEnabled:availability是静态的身份要求(你是谁),isEnabled是动态的功能开关(功能是否开启)。两者分离让权限检查更清晰loadedFrom:追踪命令的来源,用于 UI 展示和遥测分析
PromptCommand:提示词命令
PromptCommand 是最有趣的命令类型——它不直接执行操作,而是生成一段提示词发送给 LLM,让 LLM 来完成实际工作:
export type PromptCommand = {
type: 'prompt';
progressMessage: string; // 执行时的进度提示
contentLength: number; // 内容长度(用于 Token 估算)
source: SettingSource | 'builtin' | 'mcp' | 'plugin' | 'bundled';
allowedTools?: string[]; // 限制 LLM 可用的工具
model?: string; // 指定使用的模型
context?: 'inline' | 'fork'; // 执行上下文:内联或子智能体
effort?: EffortValue; // 努力程度
paths?: string[]; // 文件路径匹配模式(条件可见性)
hooks?: HooksSettings; // 技能关联的 Hooks
// 核心方法:生成发送给 LLM 的提示词
getPromptForCommand(
args: string,
context: ToolUseContext,
): Promise<ContentBlockParam[]>;
}
PromptCommand 的精妙之处在于 allowedTools 字段——它可以限制 LLM 在处理该命令时只能使用特定的工具。例如 /commit 命令只允许使用 Bash(git add:*)、Bash(git status:*)、Bash(git commit:*) 三个工具,防止 LLM 在提交代码时做出意外操作。
LocalCommand 与 LocalJSXCommand:本地命令
本地命令直接在客户端执行,不经过 LLM:
// 纯文本输出的本地命令
type LocalCommand = {
type: 'local';
supportsNonInteractive: boolean; // 是否支持非交互模式
load: () => Promise<LocalCommandModule>; // 懒加载实现
}
// 渲染 React UI 的本地命令
type LocalJSXCommand = {
type: 'local-jsx';
load: () => Promise<LocalJSXCommandModule>; // 懒加载实现
}
两者都采用了懒加载模式——load() 返回一个 Promise,只有在命令被实际调用时才加载实现代码。这对启动性能至关重要:80+ 个命令中,用户每次会话通常只使用其中几个,没必要在启动时加载所有命令的实现。
- 启动加速:命令注册只需要名称和描述,实现代码延迟加载
- 内存节约:未使用的命令不占用内存
- 代码分割:配合 bundler 实现更细粒度的代码分割
4.3 命令注册机制
commands.ts:命令注册表
src/commands.ts 是整个命令系统的注册中心。它负责导入所有命令、组装命令列表,并提供查找和过滤功能。
静态导入与条件导入
命令的导入分为两类:
// ========== 静态导入:核心命令,始终可用 ==========
import commit from './commands/commit.js'
import compact from './commands/compact/index.js'
import config from './commands/config/index.js'
import doctor from './commands/doctor/index.js'
import help from './commands/help/index.js'
import mcp from './commands/mcp/index.js'
import review, { ultrareview } from './commands/review.js'
// ... 更多静态导入
// ========== 条件导入:通过特性标志控制 ==========
import { feature } from 'bun:bundle'
const bridge = feature('BRIDGE_MODE')
? require('./commands/bridge/index.js').default
: null
const voiceCommand = feature('VOICE_MODE')
? require('./commands/voice/index.js').default
: null
const ultraplan = feature('ULTRAPLAN')
? require('./commands/ultraplan.js').default
: null
这种模式与工具系统(第 3 章)完全一致——通过 bun:bundle 的 feature() 函数实现编译期死代码消除。未启用的命令在构建时被完全移除,不会出现在最终产物中。