# 第三阶段 · 模块三 · 第三节:命令处理流程
命令是如何被解析和执行的?命令执行器的架构是什么?命令结果如何返回给用户?
Claude Code 全局架构
┌─────────────────────────────────────────────────────────────────────┐
│ 命令系统 │
│ │
│ 用户输入 ──> 解析器 ──> 执行器 ──> 结果返回 ← 本节 │
└─────────────────────────────────────────────────────────────────────┘
用户输入: "/compact aggressive"
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 1: 解析输入 │
│ parseSlashCommand("/compact aggressive") │
│ → { command: "compact", args: "aggressive" } │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 2: 查找命令 │
│ commandRegistry.get("compact") │
│ → compactCommand │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 3: 执行命令 │
│ compactCommand.getPromptForCommand("aggressive", context) │
│ → { type: 'text', content: 'Compacting...', clearContext: true } │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 4: 处理结果 │
│ if (result.clearContext) { clearConversationHistory(); } │
│ display(result.content); │
└─────────────────────────────────────────────────────────────────────┘
源码位置:`src/commands.ts`
function parseSlashCommand(input: string): ParsedCommand | null {
// 1. 去除首尾空白
const trimmed = input.trim();
// 2. 必须以 / 开头
if (!trimmed.startsWith('/')) {
return null;
}
// 3. 提取命令名和参数
const withoutSlash = trimmed.slice(1);
const parts = withoutSlash.split(/\s+/);
const commandName = parts[0]?.toLowerCase() || '';
const args = parts.slice(1).join(' ');
// 4. 验证命令名
if (!isValidCommandName(commandName)) {
return null;
}
return { command: commandName, args };
}
function isValidCommandName(name: string): boolean {
// 命令名只能是字母、数字、连字符
return /^[a-z0-9-]+$/.test(name);
}
问 1:为什么不支持嵌套命令?
// "/help /compact" 被解析为
// command = "help", args = "/compact"
// 因为解析器遇到第一个空格就分割
const parts = "/help /compact".slice(1).split(/\s+/);
// ["help", "/compact"]
问 2:命令名大小写敏感吗?
// 不敏感
/HELP
/Help
/help
// 都被解析为 command = "help"
问 3:空白字符如何处理?
// 多余的空格被忽略
"/compact aggressive"
// → command = "compact", args = "aggressive"
class CommandRegistry {
private commands: Map<string, Command> = new Map();
register(command: Command): void {
this.commands.set(command.name, command);
}
get(name: string): Command | undefined {
return this.commands.get(name.toLowerCase());
}
has(name: string): boolean {
return this.commands.has(name.toLowerCase());
}
list(): Command[] {
return Array.from(this.commands.values());
}
}
// 全局注册表
export const commandRegistry = new CommandRegistry();
// 注册内置命令
commandRegistry.register(helpCommand);
commandRegistry.register(compactCommand);
commandRegistry.register(clearCommand);
// 支持动态注册
commandRegistry.register(customCommand);
问 1:命令可以覆盖吗?
// 后注册的命令会覆盖先注册的同名命令
commandRegistry.register(cmdA); // name = "test"
commandRegistry.register(cmdB); // name = "test" → 覆盖 cmdA
// 用于实现命令别名
commandRegistry.register({
...originalCommand,
name: 'h', // 别名
});
问 2:命令加载时机?
// 使用 lazy load
const compact = {
name: 'compact',
load: () => import('./compact.js'), // 延迟到执行时加载
};
// 只有当命令被调用时才加载
async function executeCommand(name: string) {
const command = commandRegistry.get(name);
if (command.load) {
await command.load(); // 动态加载
}
}
class CommandExecutor {
async execute(
input: string,
context: CommandContext,
): Promise<CommandResult> {
// Step 1: 解析
const parsed = parseSlashCommand(input);
if (!parsed) {
return { type: 'error', content: 'Invalid command' };
}
// Step 2: 查找
const command = commandRegistry.get(parsed.command);
if (!command) {
return {
type: 'error',
content: `Unknown command: /${parsed.command}`,
};
}
// Step 3: 权限检查
if (!this.hasPermission(command, context)) {
return { type: 'error', content: 'Permission denied' };
}
// Step 4: 执行
try {
return await command.getPromptForCommand(parsed.args, context);
} catch (error) {
return {
type: 'error',
content: `Command failed: ${error.message}`,
};
}
}
}
问 1:命令执行失败怎么办?
try {
return await command.getPromptForCommand(args, context);
} catch (error) {
return {
type: 'error',
content: `Command failed: ${error.message}`,
};
}
问 2:命令可以异步吗?
// getPromptForCommand 返回 Promise
async getPromptForCommand(
args: string,
context: CommandContext,
): Promise<CommandResult> {
// 可以执行异步操作
const result = await fetchRemoteData();
return { type: 'text', content: result };
}
interface CommandResult {
type: 'text' | 'error' | 'stop';
content: string;
// 可选效果
replaceInput?: string; // 替换用户输入
clearContext?: boolean; // 清空上下文
switchModel?: string; // 切换模型
}
async function handleCommandResult(result: CommandResult): Promise<void> {
// 1. 显示结果
display(result.content);
// 2. 处理特殊效果
if (result.clearContext) {
clearConversationHistory();
}
if (result.replaceInput) {
replaceUserInput(result.replaceInput);
}
if (result.switchModel) {
await switchModel(result.switchModel);
}
// 3. 记录日志
logCommandExecution(result);
// 4. 决定下一步
if (result.type === 'stop') {
exitProgram();
}
}
问 1:replaceInput 有什么用?
// 用于实现命令展开
// 用户输入: /h
// 实际执行: /help
return {
type: 'text',
content: 'Showing help...',
replaceInput: '/help', // 替换输入显示
};
问 2:stop 类型的命令?
// 用于退出程序
return { type: 'stop', content: 'Goodbye!' };
// → exitProgram() 被调用
// 命令可以修改 query 的状态
interface CommandContext {
messages: Message[]; // 对话历史
model: string; // 当前模型
systemPrompt: string; // 系统提示词
}
// /compact 修改 messages
async function compactCommand(args, context) {
const summary = await generateSummary(context.messages);
return {
type: 'text',
content: 'Context compacted.',
replaceMessages: [createSummaryMessage(summary)], // 替换消息
};
}
问 1:命令和 query 谁先执行?
// 命令在 query 之前执行
if (isSlashCommand(input)) {
await executeCommand(input); // 先执行命令
} else {
await executeQuery(input); // 再执行 query
}
问 2:命令可以调用工具吗?
// 理论上可以,但通常不这样做
// 命令是用户操作,工具是模型操作
// 混合使用会增加复杂度
问题:如何实现类似 shell 的命令历史(↑↓ 键导航)?
答案:
class CommandHistory {
private history: string[] = [];
private position: number = -1;
private maxSize: number = 100;
add(command: string): void {
// 避免重复连续的命令
if (this.history[0] !== command) {
this.history.unshift(command);
if (this.history.length > this.maxSize) {
this.history.pop();
}
}
this.position = -1;
}
up(): string | null {
if (this.position < this.history.length - 1) {
this.position++;
return this.history[this.position];
}
return null;
}
down(): string | null {
if (this.position > 0) {
this.position--;
return this.history[this.position];
}
this.position = -1;
return null;
}
}
问题:如何给命令添加别名(如 h → help)?
答案:
// 方式 1:注册时添加别名
commandRegistry.register({
...helpCommand,
name: 'h',
});
// 方式 2:解析时转换
function resolveAlias(name: string): string {
const aliases = { h: 'help', c: 'compact', cl: 'clear' };
return aliases[name] || name;
}
// 使用
const command = commandRegistry.get(resolveAlias(parsed.command));
问题:如果用户快速输入多个命令,如何处理?
答案:
// 默认:顺序执行
// 当前命令执行完成后,才能执行下一个
// 也可以实现队列
class CommandQueue {
private queue: string[] = [];
private executing: boolean = false;
async add(input: string): Promise<void> {
this.queue.push(input);
if (!this.executing) {
await this.processQueue();
}
}
private async processQueue(): Promise<void> {
while (this.queue.length > 0) {
this.executing = true;
const input = this.queue.shift()!;
await executor.execute(input);
}
this.executing = false;
}
}
| 文件 | 核心内容 |
| `src/commands.ts` | 命令系统入口 |
| `src/commands/compact/` | compact 命令实现 |
| `src/commands/help/` | help 命令实现 |
下一节我们将深入 自定义命令开发:
- 如何注册自定义命令?
- 命令的开发规范
- 命令的测试方法
*- 第一轮:□ 事实准确性*
*- 第二轮:□ 深度与洞见*
*- 第三轮:□ 可读性与价值*