Дорожная карта рефакторинга Slash Command
Общая цель
Создать платформу команд в стиле внутренней архитектуры Qwen, обеспечивающую 95% совпадение внешнего UX с Claude Code, и одновременно решить три ключевые проблемы: разделение на три режима, единый источник команд и невозможность вызова prompt command моделью.
Основные принципы проектирования
- Каждый Phase можно выпускать независимо: после завершения поведение самодостаточно и не требует будущих Phase для работы
- Phase 1 — чистая инфраструктура: за исключением исправления ошибочной блокировки MCP_PROMPT, не изменяет ни один из существующих доступных наборов команд
- Изменения поведения и архитектуры разделены: Phase 1 занимается архитектурой, Phase 2 — расширением возможностей
- Не копировать внутреннюю архитектуру 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 Заполнение полей source/commandType в Loader
Каждый 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 Замена жестко заданного белого списка на фильтрацию на основе capabilities
- Удалить константу
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 полностью совпадает с состоянием до рефакторинга (существующие тесты не ломаются)
- 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 необходимо соблюдать следующие принципы:
- Разные получатели: в режиме ACP получателем сообщений является IDE (плагин Zed/VS Code), а не конечный пользователь. Содержимое сообщений должно быть в формате plain text или Markdown, без ANSI-стилей, специфичных для терминала.
- Стратегия реализации — добавление ветвления по режимам, а не замена: правильный подход — добавить проверку режима внутри
actionкоманды. Путь interactive сохраняет текущую логику рендеринга UI, путь non_interactive/acp возвращаетmessageилиsubmit_prompt, пригодные для машинной обработки. Оба пути сосуществуют в одной функцииaction. - Stateful-операции требуют пояснения семантики: при однократном неинтерактивном вызове (например, флаг CLI
-p) изменения stateful-команд вроде/model set,/language setдействуют только в рамках текущей session. Это необходимо указать в тексте ответа команды. - Read-only vs side-effects: read-only команды (например,
/about,/stats) напрямую возвращают текст текущего состояния; команды с side-effects (например,/model set,/language set) должны подтверждать результат операции в ответе. - Избегать side-effects, зависящих от окружения: операции, зависящие от графического окружения, такие как открытие браузера (
/docs,/insight) или работа с буфером обмена (/copy), должны пропускаться в пути non_interactive/acp. Вместо этого в тексте ответа следует возвращать соответствующие URL или само содержимое.
Обзор команд для расширения
Примечание:
btw,bug,compress,context,init,summaryуже расширены на все режимы в Phase 1 и не входят в список данного этапа.
Следующие 13 команд будут расширены на режимы non_interactive и acp в Phase 2:
Категория 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-компонентов, требуется добавить ветвление по режимам для возврата plain text
| Команда | Поведение interactive | Возвращаемое содержимое для non_interactive/acp |
|---|---|---|
/about | Рендерит React-компонент версии/конфигурации | Plain text-сводка: версия, текущая модель, ключевые конфигурации |
/stats | Рендерит компонент статистики токенов/расходов | Статистика session в формате plain text |
/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 моделью
- Реализовать
getModelInvocableCommands()вCommandService(илиCommandRegistry), возвращающий все команды сmodelInvocable: true - Пометить команды, загруженные через
BundledSkillLoaderиFileCommandLoader(пользовательские/проектные команды), какmodelInvocable: true - MCP prompt не помечаются как
modelInvocable: MCP prompt вызываются моделью через независимый механизм MCP tool call, без посредничестваSkillTool - Модифицировать
SkillTool: вместо потребления толькоSkillManager.listSkills()добавить потреблениеCommandService.getModelInvocableCommands() - Сформировать единое описание вызываемых моделью команд и внедрить его в
descriptionSkillTool
2.3 Обнаружение mid-input slash command (базовая версия)
- Обнаруживать slash token рядом с курсором в
InputPrompt(не только в начале строки) - При обнаружении 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возвращают plain text в non-interactive/acp,/docsне открывает браузер;/insightвnon_interactiveсинхронно генерирует и возвращает message с путем к файлу, вacpотправляет прогресс черезstream_messages - Категория C:
/clearв non-interactive/acp возвращает message-маркер границы контекста, не вызываяcontext.ui.clear() - Поведение всех расширенных команд в режиме interactive полностью совпадает с состоянием до рефакторинга (без регрессий)
2.2 Вызов моделью
- Модель может вызывать bundled skill и file command (пользовательские/проектные) через
SkillToolв ходе диалога - MCP prompt не проходят через
SkillTool, вызываются моделью нативно через механизм MCP tool call - Модель не может вызывать built-in commands (
userInvocable: true,modelInvocable: false) -
descriptionSkillToolсодержит описания всех команд сmodelInvocable
2.3 mid-input slash
- mid-input slash: при вводе
/в тексте показывается наиболее подходящая команда через inline ghost text (принимается по Tab)
Phase 3: Выравнивание UX (улучшение автодополнения + добавление команд из Claude Code)
Цель
На основе метаданных и возможностей команд из Phase 1/2 доработать UX автодополнения и добавить команды, присутствующие в Claude Code, но отсутствующие в Qwen Code.
Функциональные возможности
3.1 Улучшение UX автодополнения
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
- Добавить отображение argument hints и source badge к базовой версии из Phase 2
- Подсказка ghost text (при вводе
/heпоказывать бледную подсказку/help) - Подсветка валидных command 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
Раскрыть дополнительные метаданные для ACP-клиента в sendAvailableCommandsUpdate():
argumentHintsourcesupportedModessubcommands(список имен)modelInvocable
3.5 Добавление отсутствующих команд из Claude Code
Добавить часто используемые команды, которые есть в Claude Code, но отсутствуют в Qwen Code:
| Команда | Тип | Описание |
|---|---|---|
/doctor | local | Самодиагностика окружения, вывод диагностики состояния конфигурации/подключений/инструментов |
/release-notes | local | Отображение changelog текущей версии |
/cost | local | Отображение расхода токенов и оценки стоимости для текущей session |
Примечание: task-команды вроде
/review,/commitпредоставляются в виде bundled skill и не входят в этот список.
Критерии приемки
- Меню автодополнения отображает source badge (
[MCP],[Skill],[Custom]) - Меню автодополнения отображает argumentHint (например,
set <model-id>) - Недавно использованные команды появляются в списке автодополнения первыми
- При совпадении по alias в элементе автодополнения указывается оригинальное имя
- mid-input slash: подсказка ghost text корректно рендерится
- Вывод
/helpсгруппирован по источникам, для каждой команды отображается метка поддерживаемых режимов - ACP available commands содержат поля
argumentHint,source,subcommands - Команды
/doctor,/release-notes,/costдоступны -
/doctorвыполняется в режиме non-interactive (возвращаетmessage)
Зависимости между фазами
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 не зависят друг от друга, их можно развивать параллельно (или менять порядок отдельных подзадач в зависимости от приоритетов).