qwen-code/docs/design/slash-command/roadmap.md
顾盼 a82d766727
refactor(cli): replace slash command whitelist with capability-based filtering (Phase 1) (#3283)
* refactor(cli): replace slash command whitelist with capability-based filtering (Phase 1)

## Summary

Replace the hardcoded ALLOWED_BUILTIN_COMMANDS_NON_INTERACTIVE whitelist with a
unified, capability-based command metadata model. This is Phase 1 of the slash
command architecture refactor described in docs/design/slash-command/.

## Key changes

### New types (types.ts)
- Add ExecutionMode ('interactive' | 'non_interactive' | 'acp')
- Add CommandSource ('builtin-command' | 'bundled-skill' | 'skill-dir-command' |
  'plugin-command' | 'mcp-prompt')
- Add CommandType ('prompt' | 'local' | 'local-jsx')
- Extend SlashCommand interface with: source, sourceLabel, commandType,
  supportedModes, userInvocable, modelInvocable, argumentHint, whenToUse,
  examples (all optional, backward-compatible)

### New module (commandUtils.ts + commandUtils.test.ts)
- getEffectiveSupportedModes(): 3-priority inference
  (explicit supportedModes > commandType > CommandKind fallback)
- filterCommandsForMode(): replaces filterCommandsForNonInteractive()
- 18 unit tests

### Whitelist removal (nonInteractiveCliCommands.ts)
- Remove ALLOWED_BUILTIN_COMMANDS_NON_INTERACTIVE constant
- Remove filterCommandsForNonInteractive() function
- Replace with CommandService.getCommandsForMode(mode)

### CommandService enhancements (CommandService.ts)
- Add getCommandsForMode(mode: ExecutionMode): filters by mode, excludes hidden
- Add getModelInvocableCommands(): reserved for Phase 3 model tool-call use

### Built-in command annotations (41 files)
Annotate every built-in command with commandType:
- commandType='local' + supportedModes all-modes: btw, bug, compress, context,
  init, summary (replaces the 6-command whitelist)
- commandType='local' interactive-only: export, memory, plan, insight
- commandType='local-jsx' interactive-only: all remaining ~31 commands

### Loader metadata injection (4 files)
Each loader stamps source/sourceLabel/commandType/modelInvocable on every
command it emits:
- BuiltinCommandLoader: source='builtin-command', modelInvocable=false
- BundledSkillLoader: source='bundled-skill', commandType='prompt',
  modelInvocable=true
- command-factory (FileCommandLoader): source per extension/user origin,
  commandType='prompt', modelInvocable=!extensionName
- McpPromptLoader: source='mcp-prompt', commandType='prompt', modelInvocable=true

### Bug fix
MCP_PROMPT commands were incorrectly excluded from non-interactive/ACP modes by
the old whitelist logic. commandType='prompt' now correctly allows them in all
modes.

### Session.ts / nonInteractiveHelpers.ts
- ACP session calls getAvailableCommands with explicit 'acp' mode
- Remove allowedBuiltinCommandNames parameter from buildSystemMessage() —
  capability filtering is now self-contained in CommandService

* fix test ci

* fix memory command

* fix: pass 'non_interactive' mode explicitly to getAvailableCommands

- Fix critical bug in nonInteractiveHelpers.ts: loadSlashCommandNames was
  calling getAvailableCommands without specifying mode, causing it to default
  to 'acp' instead of 'non_interactive'. Commands with supportedModes that
  include 'non_interactive' but not 'acp' would be silently excluded.
- Apply the same fix in systemController.ts for the same reason.
- Update test mock to delegate filtering to production filterCommandsForMode()
  instead of duplicating the logic inline, preventing divergence.

Fixes review comments by wenshao and tanzhenxin on PR #3283.

* fix: resolve TypeScript type error in nonInteractiveHelpers.test.ts

* fix test ci
2026-04-20 14:34:43 +08:00

263 lines
11 KiB
Markdown
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.

# Slash Command 重构路线图
## 总体目标
用 Qwen 内部架构风格,交付一个在外部体验上 95% 对齐 Claude Code 的 command 平台同时修复三模式分裂、命令来源单一、prompt command 无法被模型调用三个核心问题。
---
## 核心设计原则
1. **每个 Phase 可独立 ship**:完成后行为是自洽的,不依赖未来 Phase 才能运行
2. **Phase 1 是纯基础设施**:除修复 MCP_PROMPT 被错误拦截外,不改变任何现有可用命令集
3. **行为变化与架构变化分开**Phase 1 做架构Phase 2 做能力扩展
4. **不照搬 Claude Code 内部架构**:但对齐用户可感知的能力面
---
## Phase 1基础设施重建纯架构零行为变化
### 目标
建立统一的命令元数据模型和跨模式管理机制,为后续所有 Phase 提供底层支撑。
### 功能点
#### 1.1 扩展 `SlashCommand` 元数据模型
在现有 `SlashCommand` 接口上新增以下字段:
**来源字段**
- `source: CommandSource`:命令来源枚举(`builtin-command` / `bundled-skill` / `skill-dir-command` / `plugin-command` / `mcp-prompt` 等)
- `sourceLabel?: string`:展示用的来源标签(如 `"Built-in"` / `"MCP: github-server"`
**模式能力字段**
- `supportedModes: ExecutionMode[]`:声明在哪些运行模式下可用(`interactive` / `non_interactive` / `acp`
**执行类型字段**
- `commandType: CommandType`:声明执行类型(`prompt` / `local` / `local-jsx`
**可见性字段**
- `userInvocable: boolean`:用户是否可通过 slash command 调用(默认 `true`
- `modelInvocable: boolean`:模型是否可通过 tool call 调用(默认 `false`
**辅助元数据字段**(为 Phase 3 预留Phase 1 仅定义,不使用)
- `argumentHint?: string`:参数提示,如 `"<model-id>"` / `"show|list|set"`
- `whenToUse?: string`:何时调用该命令的说明(供模型使用)
- `examples?: string[]`:使用示例
#### 1.2 Loader 填充 source/commandType 字段
每个 Loader 在构建 `SlashCommand` 时必须填充 `source``commandType`
| Loader | source | commandType |
| -------------------------------- | ------------------- | ------------------------------------- |
| `BuiltinCommandLoader` | `builtin-command` | 由各命令声明(`local` / `local-jsx` |
| `BundledSkillLoader` | `bundled-skill` | `prompt` |
| `FileCommandLoader`(用户/项目) | `skill-dir-command` | `prompt` |
| `FileCommandLoader`(插件) | `plugin-command` | `prompt` |
| `McpPromptLoader` | `mcp-prompt` | `prompt` |
#### 1.3 内置命令声明 `supportedModes` 和 `commandType`
为所有 built-in 命令显式声明:
- `commandType``local`(无 UI 依赖)或 `local-jsx`(依赖 dialog/React
- `supportedModes``local` 类命令声明 `['interactive', 'non_interactive', 'acp']``local-jsx` 类命令声明 `['interactive']`
#### 1.4 用 capability-based 过滤替换硬编码白名单
- 删除 `ALLOWED_BUILTIN_COMMANDS_NON_INTERACTIVE` 常量
- 删除 `filterCommandsForNonInteractive` 函数
- 新增 `filterCommandsForMode(commands, mode)` 函数,基于 `supportedModes` 字段过滤
- 新增 `getEffectiveSupportedModes(cmd)` 工具函数(考虑 CommandKind 默认策略)
- 修改 `handleSlashCommand` / `getAvailableCommands` 函数签名,移除 `allowedBuiltinCommandNames` 参数
#### 1.5 CommandService 升级为统一 Registry
- 新增 `getCommandsForMode(mode: ExecutionMode)` 方法
- 新增 `getModelInvocableCommands()` 方法Phase 2/3 使用Phase 1 提供接口)
- 现有 `getCommands()` 保持不变interactive 使用)
### 验收标准
- [ ] `SlashCommand` 接口包含所有新字段TypeScript 编译通过
- [ ] 所有 Loader 填充 `source``commandType` 字段
- [ ] 所有 built-in 命令声明 `commandType``supportedModes`
- [ ] `ALLOWED_BUILTIN_COMMANDS_NON_INTERACTIVE` 已删除,被 capability filter 取代
- [ ] **non-interactive 下可用命令集与重构前完全一致**(现有测试不 break
- [ ] MCP prompt commands 在 non-interactive/acp 下可正常执行(修复原有错误限制)
- [ ] `CommandService.getCommandsForMode('non_interactive')` 返回正确的命令集
- [ ] 所有现有测试通过
---
## Phase 2能力扩展命令整理与 prompt command 模型调用)
### 目标
基于 Phase 1 的元数据基础,扩展三种模式下的命令可用范围,并打通 prompt command 的模型调用通路。
### 功能点
#### 2.1 扩展 non-interactive / acp 可用命令集
将以下命令的 `supportedModes` 扩展到包含 `non_interactive``acp`,并确保其 action 实现可在无 UI 环境运行:
**直接可扩展**action 已无 UI 依赖):
- `/export`:文件 I/O返回 `message`
- `/memory`:文件 I/O返回 `message`
- `/plan`:返回 `submit_prompt`
- `/tools`:改为返回 `message`(文本列表,替换 UI 渲染)
- `/stats`:改为返回 `message`(文本格式,替换 UI 渲染)
**需要 local 子命令拆分**(当前只有 `local-jsx` 壳):
| 命令 | 新增的 local 子命令 |
| -------------- | ----------------------------------------------------------------------------- |
| `/model` | `show`(当前模型)、`list`(可选列表)、`set <id>`(切换) |
| `/permissions` | `show`(当前权限模式)、`set <mode>`(设置) |
| `/mcp` | `list`MCP 服务列表)、`show <server>`(服务详情)、`status`(所有服务状态) |
| `/memory` | 已有 `show`/`add`/`refresh`(确认 non-interactive 下可用) |
> **注意**:上述 UI 壳命令不会被删除,`/model` 不带子命令时仍然打开 dialoginteractive 模式)。新增子命令是 **在现有命令上追加**,不是替换。
#### 2.2 prompt command 模型调用打通
-`CommandService`(或 `CommandRegistry`)中实现 `getModelInvocableCommands()`,返回所有 `modelInvocable: true` 的命令
-`BundledSkillLoader``FileCommandLoader`(用户/项目命令)、`McpPromptLoader` 加载的命令标记为 `modelInvocable: true`
- 改造 `SkillTool`:从只消费 `SkillManager.listSkills()` 改为同时消费 `CommandService.getModelInvocableCommands()`
- 构建统一的模型可调用命令描述,注入 `SkillTool` 的 description
#### 2.3 mid-input slash command 检测(基础版)
-`InputPrompt` 中检测光标附近的 slash token不限于行首
- 检测到 slash token 后触发补全菜单(展示命令名 + description
- 补全菜单弹出位置跟随光标
- **不**包含 argument hints、source badge 等Phase 3 做)
### 验收标准
- [ ] `/export``/memory``/plan``/tools``/stats` 在 non-interactive 模式下可正常执行并返回结构化输出
- [ ] `/model show``/model set <id>` 在 non-interactive / acp 下可执行
- [ ] `/permissions show``/permissions set <mode>` 在 non-interactive / acp 下可执行
- [ ] `/mcp list``/mcp show <server>` 在 non-interactive / acp 下可执行
- [ ] 模型在对话中可以通过 `SkillTool` 调用 bundled skill、file command用户/项目、MCP prompt
- [ ] 模型不可以调用 built-in commands`userInvocable: true``modelInvocable: false`
- [ ] mid-input slash在正文中输入 `/` 后触发命令补全菜单
- [ ] `SkillTool` 的 description 包含所有 `modelInvocable` 命令的描述
---
## Phase 3体验对齐补全增强 + Claude Code 命令补齐)
### 目标
在 Phase 1/2 的元数据和命令能力基础上,补齐补全体验,并补充 Claude Code 中存在而 Qwen Code 缺失的命令。
### 功能点
#### 3.1 补全体验增强
**source badge**
- 在补全菜单中展示命令来源标签(`[MCP]` 已有,扩展为 `[Skill]``[Custom]` 等)
- 使用 `source` / `sourceLabel` 字段渲染
**argument hint**
- 补全菜单中命令名后展示 `argumentHint`(如 `set <model-id>`
- `argumentHint` 由 Phase 1 元数据字段提供
**recently used 排序**
- 记录用户最近使用的命令session 级别,无需持久化)
- 在补全排序中给近期使用的命令加权
**alias 命中高亮**
- 当补全命中 `altNames` 而非主名时,在展示时注明(如 `help (alias: ?)`
**冲突策略对齐**
- 明确优先级built-in > bundled/skill-dir > plugin > mcp
- 冲突时将低优先级命令重命名(如 `pluginName.commandName`
#### 3.2 mid-input slash command 完整版
- 在 Phase 2 基础版上增加 argument hints 和 source badge 展示
- ghost text 提示(输入 `/he` 时显示 `/help` 的淡色提示)
- 有效命令 token 高亮(已完成匹配的 slash command 显示不同颜色)
#### 3.3 Help 目录重构
`/help` 从平铺列表改为分组目录:
- **Built-in Commands**local + local-jsx注明 mode
- **Bundled Skills**
- **Custom Commands**(用户/项目 file commands
- **Plugin Commands**
- **MCP Commands**
每条命令展示名称、argumentHint、description、source、supportedModes 标记
#### 3.4 ACP available commands 元数据增强
`sendAvailableCommandsUpdate()` 中将更多元数据暴露给 ACP 客户端:
- `argumentHint`
- `source`
- `supportedModes`
- `subcommands`(名称列表)
- `modelInvocable`
#### 3.5 Claude Code 缺失命令补齐
补充 Qwen Code 当前没有、Claude Code 有且常用的命令:
| 命令 | 类型 | 说明 |
| ---------------- | ------- | ---------------------------------------- |
| `/doctor` | `local` | 环境自检,输出配置/连接/工具状态诊断 |
| `/release-notes` | `local` | 展示当前版本的更新日志 |
| `/cost` | `local` | 展示当前 session 的 token 消耗和费用估算 |
> 注:`/review`、`/commit` 等任务类命令以 bundled skill 形式提供,不在此列。
### 验收标准
- [ ] 补全菜单展示 source badge`[MCP]``[Skill]``[Custom]`
- [ ] 补全菜单展示 argumentHint`set <model-id>`
- [ ] 近期使用的命令在补全列表中优先出现
- [ ] alias 命中时在补全项中注明原名
- [ ] mid-input slashghost text 提示正确渲染
- [ ] `/help` 输出按来源分组,每条命令展示支持模式标记
- [ ] ACP available commands 包含 `argumentHint``source``subcommands` 字段
- [ ] `/doctor``/release-notes``/cost` 三个命令可用
- [ ] `/doctor` 在 non-interactive 模式下可执行(返回 `message`
---
## 各 Phase 依赖关系
```
Phase 1元数据 + 统一过滤)
├──► Phase 2能力扩展
│ │
│ ├──► slash command 子命令拆分
│ └──► prompt command 模型调用(需要 getModelInvocableCommands()
└──► Phase 3体验对齐
├──► source badge需要 Phase 1 source 字段)
├──► argument hint需要 Phase 1 argumentHint 字段)
└──► Help 分组(需要 Phase 1 source 字段)
```
Phase 2 和 Phase 3 不互相依赖,可以并行推进(或根据优先级调换部分子项)。