Slash Command: дорожная карта рефакторинга
Общая цель
Создать платформу команд, которая по внешнему восприятию на 95% соответствует Claude Code, используя внутреннюю архитектурную стилистику Qwen, и одновременно устранить три ключевые проблемы: расщепление на три режима, единый источник команд, невозможность вызова prompt command моделью.
Основные принципы проектирования
- Каждый Phase может быть выпущен независимо: поведение после завершения самосогласованно, не требует последующих Phases для работы.
- Phase 1 — чистая инфраструктура: не меняет ни один из доступных наборов команд, за исключением исправления ошибочного перехвата MCP_PROMPT.
- Изменения поведения и архитектуры разделены: Phase 1 занимается архитектурой, Phase 2 — расширением возможностей.
- Не копируем внутреннюю архитектуру Claude Code: но выравниваемся по воспринимаемым пользователем возможностям.
Phase 1: Перестройка инфраструктуры (чистая архитектура, ноль изменений поведения)
Цель
Создать единую модель метаданных команд и механизм управления между режимами, обеспечивающий фундамент для всех последующих Phases.
Функциональные пункты
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 (по умолчанию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 Замена жёстко заданного белого списка на фильтрацию на основе возможностей
- Удалить константу
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удалена, заменена на фильтр возможностей - Набор команд, доступных в 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. - Для операций с состоянием необходимо пояснять семантику: при однократном неинтерактивном вызове (например, параметр CLI
-p) изменения в командах с состоянием (/model set,/language set) действуют только в рамках текущей сессии. Это должно быть указано в тексте ответа команды. - Только чтение vs c побочными эффектами: команды только для чтения (
/about,/stats) возвращают текущий текст состояния; команды с побочными эффектами (/model set,/language set) должны в ответе подтвердить результат операции. - Избегать побочных эффектов, зависящих от окружения: операции, требующие графического окружения (открытие браузера (
/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 | Открыть диалог выбора модели | Вернуть текущее имя модели и пояснительный текст |
/approval-mode | Открыть диалог режима утверждения | Вернуть текущий режим утверждения и пояснительный текст |
Класс B: внутри action используется context.ui.addItem() для рендеринга React-компонентов, требуется добавить ветку режима, возвращающую plain text
| Команда | Поведение в interactive | Возвращаемое содержимое в non_interactive/acp |
|---|---|---|
/about | Рендеринг React-компонента версии/конфигурации | Версия, текущая модель, ключевые настройки в формате plain text |
/stats | Рендеринг компонента статистики токенов/затрат | Статистика сессии в формате plain text |
/insight | Рендеринг компонента анализа + открытие браузера | non_interactive — синхронная генерация, возвращает путь к файлу; acp — отправка прогресса и результата через stream_messages |
/docs | Рендеринг точки входа в документацию + открытие браузера | Возвращает URL документации, браузер не открывается |
Класс C: особая обработка
| Команда | Поведение в interactive | Поведение в non_interactive/acp |
|---|---|---|
/clear | Вызов context.ui.clear() для очистки отображения терминала | Возвращает сообщение-маркер границы контекста, содержимое: "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() - Построить единое описание вызываемых моделью команд и внедрить его в description
SkillTool
2.3 Обнаружение slash-команды в середине ввода (базовая версия)
- В
InputPromptобнаруживать токен slash рядом с курсором (не только в начале строки) - При обнаружении токена slash отображать через inline ghost text имя наиболее подходящей команды (Tab для принятия)
- Не включает выпадающее меню автодополнения, подсказки аргументов, 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 возвращают plain text,/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 команды (
userInvocable: true,modelInvocable: false) - Description
SkillToolсодержит описания всех команд сmodelInvocable
2.3 Slash в середине ввода
- 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для рендеринга
Подсказка аргументов
- Отображать
argumentHintпосле имени команды в меню автодополнения (например,set <model-id>) argumentHintберётся из поля метаданных Phase 1
Сортировка по недавно использованным
- Записывать недавно использованные пользователем команды (на уровне сессии, без персистентности)
- При сортировке автодополнений отдавать приоритет недавно использованным командам
Подсветка совпадения по псевдониму
- Когда автодополнение совпадает по
altNames, а не по основному имени, указывать это при отображении (например,help (alias: ?))
Стратегия разрешения конфликтов
- Явный приоритет: built-in > bundled/skill-dir > plugin > mcp
- При конфликте команда с низким приоритетом переименовывается (например,
pluginName.commandName)
3.2 Slash-команда в середине ввода (полная версия)
- На основе базовой версии Phase 2 добавить отображение подсказок аргументов и source badge
- Подсказка ghost text (при вводе
/heотображается приглушённая подсказка/help) - Подсветка токена команды (уже совпавший slash command отображается другим цветом)
3.3 Реструктуризация справки
Изменить /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-клиенту больше метаданных:
argumentHintsourcesupportedModessubcommands(список имён)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>) - Недавно использованные команды отображаются в списке автодополнений с приоритетом
- При совпадении по псевдониму в элементе автодополнения указывается исходное имя
- Slash в середине ввода: ghost text отображается корректно
-
/helpотображается в стиле Claude Code с разделением на вкладки, без нагромождения команд, и на странице команд отображаются метки поддерживаемых режимов - ACP available commands содержит поля
argumentHint,source,subcommands - Команда
/doctorдоступна - Команда
/doctorвыполняется в режиме non-interactive (возвращаетmessage) - Команда
/release-notesне добавляется
Зависимости между Phases
Phase 1 (метаданные + единая фильтрация)
│
├──► Phase 2 (расширение возможностей)
│ │
│ ├──► разделение подкоманд slash command
│ └──► вызов prompt command моделью (требует getModelInvocableCommands())
│
└──► Phase 3 (выравнивание опыта)
│
├──► source badge (требует поле source из Phase 1)
├──► argument hint (требует поле argumentHint из Phase 1)
└──► группировка Help (требует поле source из Phase 1)Phase 2 и Phase 3 не зависят друг от друга и могут выполняться параллельно (или порядок некоторых подпунктов может быть изменён в зависимости от приоритетов).