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

11 KiB
Raw Blame History

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 时必须填充 sourcecommandType

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 内置命令声明 supportedModescommandType

为所有 built-in 命令显式声明:

  • commandTypelocal(无 UI 依赖)或 local-jsx(依赖 dialog/React
  • supportedModeslocal 类命令声明 ['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 填充 sourcecommandType 字段
  • 所有 built-in 命令声明 commandTypesupportedModes
  • 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_interactiveacp,并确保其 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 listMCP 服务列表)、show <server>(服务详情)、status(所有服务状态)
/memory 已有 show/add/refresh(确认 non-interactive 下可用)

注意:上述 UI 壳命令不会被删除,/model 不带子命令时仍然打开 dialoginteractive 模式)。新增子命令是 在现有命令上追加,不是替换。

2.2 prompt command 模型调用打通

  • CommandService(或 CommandRegistry)中实现 getModelInvocableCommands(),返回所有 modelInvocable: true 的命令
  • BundledSkillLoaderFileCommandLoader(用户/项目命令)、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 commandsuserInvocable: truemodelInvocable: 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 Commandslocal + 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]
  • 补全菜单展示 argumentHintset <model-id>
  • 近期使用的命令在补全列表中优先出现
  • alias 命中时在补全项中注明原名
  • mid-input slashghost text 提示正确渲染
  • /help 输出按来源分组,每条命令展示支持模式标记
  • ACP available commands 包含 argumentHintsourcesubcommands 字段
  • /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 不互相依赖,可以并行推进(或根据优先级调换部分子项)。