第三节:命令处理流程
从输入到执行

作者:小学子 📚 | 日期:2026年4月2日 | 第三阶段 · 模块三


# 第三阶段 · 模块三 · 第三节:命令处理流程

核心问题

命令是如何被解析和执行的?命令执行器的架构是什么?命令结果如何返回给用户?


◇ 本节位置


        Claude Code 全局架构
        
        ┌─────────────────────────────────────────────────────────────────────┐
        │  命令系统                                                          │
        │                                                                      │
        │  用户输入 ──> 解析器 ──> 执行器 ──> 结果返回 ← 本节              │
        └─────────────────────────────────────────────────────────────────────┘
        


一、命令处理流程

1.1 完整流程


        用户输入: "/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);                                           │
        └─────────────────────────────────────────────────────────────────────┘
        


二、命令解析器

2.1 parseSlashCommand 实现

源码位置:`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);
        }
        

2.2 五问分析

问 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"
        


三、命令注册表

3.1 CommandRegistry


        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();
        

3.2 命令注册


        // 注册内置命令
        commandRegistry.register(helpCommand);
        commandRegistry.register(compactCommand);
        commandRegistry.register(clearCommand);
        
        // 支持动态注册
        commandRegistry.register(customCommand);
        

3.3 五问分析

问 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();  // 动态加载
          }
        }
        


四、命令执行器

4.1 CommandExecutor


        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}`,
              };
            }
          }
        }
        

4.2 五问分析

问 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 };
        }
        


五、命令结果处理

5.1 CommandResult


        interface CommandResult {
          type: 'text' | 'error' | 'stop';
          content: string;
        
          // 可选效果
          replaceInput?: string;   // 替换用户输入
          clearContext?: boolean;   // 清空上下文
          switchModel?: string;     // 切换模型
        }
        

5.2 结果处理流程


        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();
          }
        }
        

5.3 五问分析

问 1:replaceInput 有什么用?


        // 用于实现命令展开
        // 用户输入: /h
        // 实际执行: /help
        
        return {
          type: 'text',
          content: 'Showing help...',
          replaceInput: '/help',  // 替换输入显示
        };
        

问 2:stop 类型的命令?


        // 用于退出程序
        return { type: 'stop', content: 'Goodbye!' };
        // → exitProgram() 被调用
        


六、命令与 query 的交互

6.1 命令如何影响 query


        // 命令可以修改 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)],  // 替换消息
          };
        }
        

6.2 五问分析

问 1:命令和 query 谁先执行?


        // 命令在 query 之前执行
        if (isSlashCommand(input)) {
          await executeCommand(input);  // 先执行命令
        } else {
          await executeQuery(input);    // 再执行 query
        }
        

问 2:命令可以调用工具吗?


        // 理论上可以,但通常不这样做
        // 命令是用户操作,工具是模型操作
        // 混合使用会增加复杂度
        


七、思考题

思考题 1:命令历史如何实现?

问题:如何实现类似 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;
          }
        }
        


思考题 2:如何实现命令别名?

问题:如何给命令添加别名(如 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));
        


思考题 3:命令的执行顺序?

问题:如果用户快速输入多个命令,如何处理?

答案


        // 默认:顺序执行
        // 当前命令执行完成后,才能执行下一个
        
        // 也可以实现队列
        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 命令实现


九、下节预告

下一节我们将深入 自定义命令开发

- 如何注册自定义命令?

- 命令的开发规范

- 命令的测试方法


*- 第一轮:□ 事实准确性*

*- 第二轮:□ 深度与洞见*

*- 第三轮:□ 可读性与价值*