qwen-code/docs/design/slash-command/roadmap.md
顾盼 b55b52543a
feat(cli): improve slash command discovery (#3736)
* feat(cli): improve slash command discovery

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): update input prompt completion expectations

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): address review feedback on slash command phase 3

- Fix getBestSlashCommandMatch sort order: completionPriority first,
  recentOrder second — consistent with compareRankedCommandMatches in
  useSlashCompletion.ts so ghost text and dropdown agree on best match

- Fix findSlashCommandTokens to index altNames into commandMap so alias
  tokens (e.g. /usage for /stats) are highlighted as valid instead of
  being marked invalid

- Fix getRecentScore decay formula: 10 * Math.max(0, 1 - ageMs /
  RECENT_DECAY_MS) so the recent boost truly decays to 0 within the
  10-minute window named by RECENT_DECAY_MS

- Fix Help.tsx CommandsHelp scroll indicator to show command count
  range (e.g. 1-12/49) instead of raw line count (18/108), which was
  confusing because each command expands into 2-3 render lines

- Fix Help.tsx CommandLine key prop: use stable type:text:index key
  instead of scrollOffset-index to avoid remounting every line on scroll

- Internationalize Help.tsx tab labels via t() instead of hardcoded
  English strings

- Add Tab/Shift+Tab to switch tabs hint in Help footer alongside Esc

- Add commandMetadata.test.ts with full branch coverage for all 6
  exported functions (getCommandSourceBadge, getCommandSourceGroup,
  formatSupportedModes, getCommandDisplayName, getCommandSubcommandNames,
  formatCommandSourceLabel)

- Add direct unit tests for getBestSlashCommandMatch covering: null on
  empty input, null on no match, null for non-modelInvocable commands,
  completionPriority ordering, recentCommands tie-breaking, argumentHint
  return path, exact-match exclusion without hint, inclusion with hint

- Update Session.test.ts expectation for sendAvailableCommandsUpdate to
  include _meta field that was added in this PR

* test(cli): add missing test coverage for slash completion

- Add test: midInputGhostText is null when only non-modelInvocable commands match
- Add test: recentCommands boosts non-root prefix suggestions via recentScore

Both tests address coverage gaps identified in code review.

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-05-09 14:25:44 +08:00

301 lines
15 KiB
Markdown
Raw Permalink 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 可用命令集
**ACP 语义设计原则**
将命令扩展到 ACP/non-interactive 模式前,需遵循以下设计原则:
1. **接收方不同**ACP 模式下消息的接收方是 IDEZed/VS Code 插件),而非终端用户。消息内容以纯文本或 Markdown 格式为宜,不应包含 terminal 专用的 ANSI 样式。
2. **实现策略是增加模式分支,而非替换**:正确做法是在命令的 `action` 内部新增模式判断——interactive 路径保持现有 UI 渲染逻辑不变non_interactive/acp 路径返回适合机器消费的 `message``submit_prompt`。两条路径共存于同一个 `action` 函数中。
3. **有状态操作需说明语义**:在单次非交互调用中(如 CLI `-p` 参数),`/model set``/language set` 等有状态命令的变更仅在本次 session 内有效,应在命令响应文本中注明。
4. **只读 vs 有副作用**:只读命令(如 `/about``/stats`)直接返回当前状态文本;有副作用命令(如 `/model set``/language set`)需在响应中确认操作结果。
5. **避免环境相关副作用**:打开浏览器(`/docs``/insight`)、操作剪贴板(`/copy`)等依赖图形环境的操作,在 non_interactive/acp 路径下应跳过,改为在响应文本中返回相关 URL 或内容本身。
**待扩展命令总览**
> 注:`btw`、`bug`、`compress`、`context`、`init`、`summary` 已在 Phase 1 中扩展到全模式,不在本阶段列表中。
以下 13 个命令将在 Phase 2 中扩展到 `non_interactive``acp` 模式:
**A 类action 已返回 `message` 或 `submit_prompt`,只需扩展 `supportedModes` 并设计 ACP 消息内容**
| 命令 | 返回类型 | ACP/non-interactive 处理要点 |
| ------------- | --------------- | -------------------------------------------------- |
| `/copy` | `message` | ACP 下无剪贴板,改为在响应文本中返回内容本身或提示 |
| `/export` | `message` | 返回导出文件的完整路径 |
| `/plan` | `submit_prompt` | 无需改动,直接扩展模式 |
| `/restore` | `message` | 返回恢复操作的结果描述 |
| `/language` | `message` | 返回当前语言设置或变更确认文本 |
| `/statusline` | `submit_prompt` | 无需改动,直接扩展模式 |
**A' 类:有参数时正常执行,无参数时触发 dialog需增加无参数路径的 non-interactive 处理)**
| 命令 | 无参数 interactive 行为 | 无参数 non_interactive/acp 行为 |
| ---------------- | ----------------------- | ------------------------------- |
| `/model` | 打开模型选择 dialog | 返回当前模型名称及说明文本 |
| `/approval-mode` | 打开审批模式 dialog | 返回当前审批模式及说明文本 |
**B 类action 内部使用 `context.ui.addItem()` 渲染 React 组件,需增加模式分支返回纯文本**
| 命令 | interactive 行为 | non_interactive/acp 返回内容 |
| ---------- | ------------------------- | ----------------------------------------------------------------------------------- |
| `/about` | 渲染版本/配置 React 组件 | 版本号、当前模型、关键配置的纯文本摘要 |
| `/stats` | 渲染 token/费用统计组件 | session 统计数据的纯文本格式 |
| `/insight` | 渲染分析组件 + 打开浏览器 | `non_interactive` 同步生成返回文件路径;`acp` 通过 `stream_messages` 推送进度和结果 |
| `/docs` | 渲染文档入口 + 打开浏览器 | 返回文档 URL不打开浏览器 |
**C 类:特殊处理**
| 命令 | interactive 行为 | non_interactive/acp 行为 |
| -------- | -------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `/clear` | 调用 `context.ui.clear()` 清空终端显示 | 返回上下文边界标记 message内容为 `"Context cleared. Previous messages are no longer in context."` |
#### 2.2 prompt command 模型调用打通
-`CommandService`(或 `CommandRegistry`)中实现 `getModelInvocableCommands()`,返回所有 `modelInvocable: true` 的命令
-`BundledSkillLoader``FileCommandLoader`(用户/项目命令)加载的命令标记为 `modelInvocable: true`
- **MCP prompt 不标记为 `modelInvocable`**MCP prompt 通过独立的 MCP tool call 机制由模型调用,无需经过 `SkillTool` 中转
- 改造 `SkillTool`:从只消费 `SkillManager.listSkills()` 改为同时消费 `CommandService.getModelInvocableCommands()`
- 构建统一的模型可调用命令描述,注入 `SkillTool` 的 description
#### 2.3 mid-input slash command 检测(基础版)
-`InputPrompt` 中检测光标附近的 slash token不限于行首
- 检测到 slash token 后通过 inline ghost text 提示最佳匹配命令名Tab 接受)
- **不**包含 dropdown 补全菜单、argument hints、source badge 等Phase 3 做)
- ghost text 候选集仅限 `modelInvocable: true` 的命令skill / file command
### 验收标准
**2.1 命令扩展**
- [ ] A 类:`/copy``/export``/plan``/restore``/language``/statusline` 在 non-interactive 和 acp 模式下可正常执行并返回有意义的文本输出
- [ ] A' 类:`/model``/approval-mode` 无参数时在 non-interactive/acp 下返回当前状态文本(不触发 dialog有参数时执行变更并返回确认文本
- [ ] B 类:`/about``/stats``/docs` 在 non-interactive/acp 下返回纯文本,`/docs` 不打开浏览器;`/insight``non_interactive` 下同步生成并返回文件路径 message`acp` 下通过 `stream_messages` 推送进度
- [ ] C 类:`/clear` 在 non-interactive/acp 下返回上下文边界标记 message不调用 `context.ui.clear()`
- [ ] 所有扩展命令在 interactive 模式下行为与重构前完全一致(无退化)
**2.2 模型调用**
- [ ] 模型在对话中可以通过 `SkillTool` 调用 bundled skill、file command用户/项目)
- [ ] MCP prompt 不经过 `SkillTool`,通过 MCP tool call 机制由模型原生调用
- [ ] 模型不可以调用 built-in commands`userInvocable: true``modelInvocable: false`
- [ ] `SkillTool` 的 description 包含所有 `modelInvocable` 命令的描述
**2.3 mid-input slash**
- [ ] mid-input slash在正文中输入 `/` 后通过 inline ghost text 提示最佳匹配命令Tab 接受)
---
## 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 已有的 `/doctor` 命令;`/release-notes` 不纳入本阶段,避免引入无明确产品需求的 built-in 命令表面。
| 命令 | 类型 | 说明 |
| --------- | ------- | ------------------------------------ |
| `/doctor` | `local` | 环境自检,输出配置/连接/工具状态诊断 |
> 注:`/review`、`/commit` 等任务类命令以 bundled skill 形式提供,不在此列。
### 验收标准
- [ ] 补全菜单展示 source badge`[MCP]``[Skill]``[Custom]`
- [ ] 补全菜单展示 argumentHint`set <model-id>`
- [ ] 近期使用的命令在补全列表中优先出现
- [ ] alias 命中时在补全项中注明原名
- [ ] mid-input slashghost text 提示正确渲染
- [ ] `/help` 以 Claude Code 风格分 tab 展示,避免命令堆砌,并在命令页展示支持模式标记
- [ ] ACP available commands 包含 `argumentHint``source``subcommands` 字段
- [ ] `/doctor` 命令可用
- [ ] `/doctor` 在 non-interactive 模式下可执行(返回 `message`
- [ ] 不新增 `/release-notes`
---
## 各 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 不互相依赖,可以并行推进(或根据优先级调换部分子项)。