Дизайн требуемых возможностей скиллов
Статус: заметка к дизайну; данный PR реализует Вариант B и оставляет
required-capabilities как предложение на будущее.
Контекст
Web Shell может рендерить кастомные fenced code блоки с помощью своего markdown-рендерера.
Предложение по рендереру графиков использует fenced code блок echarts-fulldata, чтобы модель
могла возвращать полные опции ECharts и полезные данные (payload) набора данных, которые Web Shell рендерит
как интерактивный график.
Такой контракт вывода полезен только в клиентах, которые могут его отрендерить. В CLI, ACP-клиентах или любом другом интерфейсе без подходящего рендерера тот же ответ отобразится как большой блок кода вместо графика.
Первоначальное предложение по встроенному скиллу для графиков полагалось на формулировки, чтобы сообщить модели, что формат предназначен для Web Shell. Это мягкое ограничение. Если скилл доступен в сессии не Web Shell, модель всё равно может выбрать формат вывода, который клиент не может отрендерить.
В текущем PR Qwen Code сохраняет точку расширения рендерера в Web Shell,
но не включает qwencode-viz в core. Пакет Web Shell включает копируемый, не загружаемый автоматически шаблон скилла, и хосты должны устанавливать или внедрять
этот скилл только в том случае, если они также регистрируют рендерер echarts-fulldata.
Проблема
Qwen Code нужен понятный способ определить, должен ли специфичный для хоста скилл отображаться для модели и для пользователей.
Для qwencode-viz конкретный вопрос звучит так:
- Должен ли core поддерживать универсальное поле метаданных скилла
required-capabilities? - Или
qwencode-vizвообще не должен быть встроенным скиллом core, а вместо этого предоставляться только клиентами Web Shell, которые устанавливают или внедряют его?
Цели
- Предотвратить доступность скиллов, специфичных для рендерера, когда текущий клиент не может удовлетворить их контракт вывода.
- Сохранить согласованность напоминаний о скиллах при запуске, явной активации скиллов, обнаружения slash-команд и валидации скиллов.
- Избежать хардкода
qwencode-vizкак особого случая. - Сохранить существующее поведение скиллов, когда не объявлено требований к возможностям.
- Сохранить расширяемость дизайна для будущих возможностей хоста, не только ECharts.
Не цели
- Реализация самого рендерера ECharts.
- Перепроектирование всего согласования возможностей клиент/сервер.
- Изменение семантики существующего frontmatter скиллов.
- Решение изменений возможностей в общих сессиях с несколькими клиентами в первой версии.
Текущие связанные механизмы
В кодовой базе уже есть несколько элементов управления видимостью, но ни один не представляет возможности рендеринга клиента:
disable-model-invocation: предотвращает автоматический вызов скилла моделью.user-invocable: управляет тем, доступен ли встроенный скилл как команда.paths: ограничивает доступность скилла соответствующими путями в workspace.skills.disabled: отключает настроенные скиллы.allowedTools: в настоящее время используется загрузкой встроенных скиллов для скрытия скиллов, ориентированных на cron, когда инструменты cron недоступны.supportedModesslash-команд: фильтрует команды по режиму выполнения.- Объекты возможностей Daemon и ACP: описывают поддержку протокола или клиента, но в настоящее время не связаны с доступностью скиллов.
Не существует текущего required-capabilities или эквивалентного frontmatter скилла.
Его добавление станет новым контрактом скилла.
Вариант A: Добавить required-capabilities
Добавить универсальное поле frontmatter скилла:
---
name: qwencode-viz
description: Render analytical charts in Web Shell using echarts-fulldata fenced code blocks.
required-capabilities:
- markdown.codeBlock.echarts-fulldata
---Когда текущий клиент/сессия не анонсирует все перечисленные возможности, скилл считается недоступным.
Именование возможностей
Использовать возможности в виде строк с неймспейсами:
markdown.codeBlock.echarts-fulldataЭто сохраняет универсальность поля, делая контракт точным:
markdown: возможность относится к отрендеренному markdown.codeBlock: возможность применяется к рендерингу fenced code блоков.echarts-fulldata: конкретный язык/информационная строка, поддерживаемая рендерером.
Будущие примеры могут включать:
markdown.codeBlock.vega-litemarkdown.codeBlock.mermaid-interactiveartifact.openUrl
Метаданные скилла
Добавить requiredCapabilities?: string[] в конфигурацию скилла после парсинга
ключа frontmatter required-capabilities.
Оба пути парсинга скиллов должны понимать это поле:
packages/core/src/skills/skill-load.tspackages/core/src/skills/skill-manager.ts
Поле должно быть опциональным. Отсутствие или пустое значение означает, что у скилла нет требований к возможностям клиента.
Источник возможностей в рантайме
Добавить возможности клиента/сессии в конфиг рантайма:
interface ConfigParameters {
clientCapabilitiesProvider?: () => ReadonlySet<string>;
}Предоставить хелпер в Config, например:
config.getClientCapabilities(): ReadonlySet<string>Затем централизовать проверку:
function skillMeetsRequiredCapabilities(skill: Skill, config: Config): boolean {
return skill.config.requiredCapabilities.every((capability) =>
config.getClientCapabilities().has(capability),
);
}Точки фильтрации
Фильтр возможностей должен применяться до того, как скиллы станут доступны модели или пользователю:
collectAvailableSkillEntriesвpackages/core/src/tools/skill-utils.tsдолжен пропускать скиллы, у которых отсутствуют требуемые возможности. Это сохранит согласованность напоминаний о скиллах при запуске, delta-напоминаний, валидацииSkillToolи активации вызова моделью.BundledSkillLoaderдолжен пропускать недоступные встроенные скиллы при создании команд для пользователя.SkillCommandLoaderдолжен пропускать недоступные скиллы из файловой системы при создании команд для пользователя.
Важный инвариант заключается в том, что скилл, скрытый от модели, не должен отображаться как вызываемая команда, если только проект намеренно не поддерживает ручное переопределение.
Регистрация в Web Shell
Web Shell должен явно анонсировать поддержку рендерера, а не полагаться на
наличие непрозрачного колбэка renderCodeBlock.
Например:
<WebShell
customization={{
markdown: {
renderableCodeBlockLanguages: ['echarts-fulldata'],
renderCodeBlock(info) {
// render custom blocks
},
},
}}
/>Клиент Web Shell может сопоставить это с:
markdown.codeBlock.echarts-fulldataЭто делает декларацию возможностей стабильной, даже если колбэк рендерера содержит кастомную логику, фолбэки или несколько поддерживаемых языков.
Проброс в Daemon и ACP
Для хостинговых или daemon-сессий набор возможностей клиента должен достигать core до загрузки или вывода списка скиллов. Минимальная версия может передавать возможности при создании сессии:
interface CreateSessionRequest {
clientCapabilities?: string[];
}Daemon-бридж, SDK и флоу создания ACP-сессии могут хранить это как конфиг в скоупе сессии.
В первой версии возможности могут быть ограничены скоупом сессии. Если несколько клиентов подключаются к одной сессии, поведение должно быть задокументировано как использование возможностей, заданных на момент создания сессии.
Плюсы
- Сохраняет
qwencode-vizкак единый канонический встроенный скилл. - Предотвращает утечку специфичных для хоста контрактов вывода в неподдерживаемые клиенты.
- Создает переиспользуемый механизм для будущих скиллов, специфичных для рендерера или хоста.
- Делает зависимость явной и тестируемой.
Минусы
- Добавляет новое сквозное поле метаданных скилла.
- Требует проброса возможностей клиента/сессии через Web Shell, daemon, SDK и поверхности ACP.
- Требует тщательного документирования поведения общих сессий.
- Может содержать больше механизмов, чем необходимо, если
qwencode-viz— единственный ожидаемый скилл, зависящий от возможностей.
Вариант B: Скилл, предоставляемый клиентом
Не добавлять универсальное поле required-capabilities. Вместо этого не включать
qwencode-viz в core. Клиент Web Shell или любой другой клиент, поддерживающий
рендерер, предоставляет сам скилл.
Возможные модели дистрибуции:
- Хост Web Shell устанавливает
.qwen/skills/qwencode-viz/SKILL.md. - Пакет Web Shell поставляется с опциональным, не загружаемым автоматически шаблоном скилла, который хост может скопировать или установить, когда включен рендеринг графиков.
- Интеграция Web Shell поставляется с пакетом расширений скиллов.
- Интеграция Web Shell внедряет эквивалентные инструкции для модели только тогда, когда её рендерер графиков включен.
В этой модели скилл доступен только потому, что клиент с рендерером решил его предоставить.
Интеграция хоста Web Shell
Хост Web Shell, который хочет получать графики, должен явно согласиться на обе части контракта:
- Зарегистрировать рендерер Markdown code блоков
echarts-fulldata. - Предоставить соответствующий скилл для графиков из
packages/web-shell/docs/examples/qwencode-viz/SKILL.md.
Например:
import * as echarts from 'echarts';
import {
WebShellWithProviders,
createEchartsFullDataRenderer,
} from '@qwen-code/web-shell';
<WebShellWithProviders
baseUrl="http://127.0.0.1:4170"
token={token}
sessionId={sessionId}
markdown={{
renderCodeBlock: createEchartsFullDataRenderer({
loadEcharts: () => echarts,
resolveDataRef: async (ref, meta) =>
loadControlledChartDataset(ref, meta),
}),
}}
/>;В этой конфигурации рендерера loadEcharts позволяет хосту предоставить
одобренный рантайм ECharts, либо как статический импорт, либо как лениво загружаемый модуль.
resolveDataRef используется только для блоков графиков data.kind="ref"; это
принадлежащий хосту бридж от видимой для модели ссылки на данные к доверенному набору данных.
Формат конверта, видимый для модели, описан в опциональном шаблоне скилла в
packages/web-shell/docs/examples/qwencode-viz/SKILL.md; валидация на стороне рендерера
находится в
packages/web-shell/client/components/messages/EchartsFullDataBlock.tsx.
Файл скилла должен устанавливаться или внедряться только хостами, которые выполняют эту регистрацию. Простая файловая интеграция может копировать:
packages/web-shell/docs/examples/qwencode-viz/SKILL.mdв директорию скиллов workspace или пользователя, например:
.qwen/skills/qwencode-viz/SKILL.mdИнтеграция с собственным слоем дистрибуции скиллов может вместо этого загружать тот же файл как канонический исходный контент и предоставлять его через этот слой. В обоих случаях core не загружает скилл автоматически; хост сам управляет его включением, потому что хост владеет рендерером.
Для конвертов data.kind="ref" встроенный рендерер проверяет, что data.ref
использует нормализованную ссылку artifact:// или session-file:// перед вызовом
реализации resolveDataRef(ref, meta), контролируемой хостом. Рендерер
также парсит блок как JSON и санирует опции ECharts перед рендерингом;
он не выполняет предоставленный моделью JavaScript, не загружает произвольные URL-адреса и не читает
локальные файлы самостоятельно. Кастомный рендерер должен сохранять то же разделение:
сначала валидация JSON/ссылок/опций на уровне рендерера, затем разрешение артефактов,
принадлежащее хосту.
Хост на базе daemon может рассматривать API файлов workspace как один из бэкендов артефактов.
Например, хост может сохранять артефакты графиков в контролируемой директории workspace,
такой как .qwen/artifacts/, предоставлять видимые для модели ссылки, такие как
artifact://chart-data/orders.csv, и разрешать их через daemon
GET /file?path=.qwen/artifacts/chart-data/orders.csv. Это сохраняет
artifact:// в качестве публичного контракта графиков, позволяя первой
реализации переиспользовать файлы workspace daemon.
Резолвер всё равно должен проверять корень артефактов перед вызовом daemon:
const ARTIFACT_ROOT = '.qwen/artifacts/';
const MAX_CHART_DATA_BYTES = 256 * 1024;
async function resolveDataRef(
ref: string,
meta: { format?: string; dimensions?: string[] },
) {
const artifactPrefix = 'artifact://';
if (!ref.startsWith(artifactPrefix)) {
throw new Error(`Unsupported chart data ref: ${ref}`);
}
const artifactPath = ref.slice(artifactPrefix.length);
if (
artifactPath.length === 0 ||
artifactPath.startsWith('/') ||
artifactPath.includes('\\') ||
artifactPath.split('/').includes('..')
) {
throw new Error(`Invalid chart data ref: ${ref}`);
}
const url = new URL('/file', daemonBaseUrl);
url.searchParams.set('path', `${ARTIFACT_ROOT}${artifactPath}`);
url.searchParams.set('maxBytes', String(MAX_CHART_DATA_BYTES));
const response = await fetch(url, {
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
});
if (!response.ok) {
throw new Error(`Failed to read chart data: ${response.status}`);
}
const file = (await response.json()) as { content: string };
return meta.format === 'csv'
? parseCsvAsArrayRows(file.content, meta.dimensions)
: JSON.parse(file.content);
}В этом примере намеренно сопоставляются только нормализованные пути artifact:// внутри .qwen/artifacts/. Если хост позже перенесет артефакты в объектное хранилище или сервис артефактов с областью видимости сессии, потребуется изменить только resolveDataRef; блок echarts-fulldata, обращенный к модели, сможет продолжать использовать ту же форму ссылки.
Преимущества
- Минимальные изменения в ядре.
- Отсутствие нового глобального контракта метаданных навыков.
- Доступность возможностей естественным образом контролируется клиентом, реализующим рендерер.
- Избегает необходимости в обвязке демона или ACP, если только у клиента уже нет механизма инъекции навыков.
Недостатки
- Отсутствие канонического встроенного навыка, если только все клиенты не скопируют одинаковый контент.
- Большая нагрузка на каждого интегратора Web Shell.
- Пользователи, переходящие между клиентами, могут столкнуться с несогласованной доступностью навыков.
- Не создает общего механизма защиты для будущих навыков, специфичных для хоста.
- Сложнее тестировать в ядре, так как доступность зависит от внешней установки или инъекции.
Рекомендация
Для этого PR используйте Вариант B.
Это сохранит неизменной базовую систему навыков и позволит избежать раскрытия инструкций echarts-fulldata в неподдерживаемых клиентах. Хук рендерера Web Shell останется полезным для любого рендерера блоков, принадлежащего хосту, в то время как специфичные для графиков инструкции для модели станут явным opt-in со стороны хоста.
В более долгосрочной перспективе это следует обсудить как решение о границах продукта/API.
Выберите Вариант A, если мейнтейнеры ожидают, что Qwen Code со временем будет поддерживать больше контрактов на вывод с рендерингом на стороне клиента. В этом случае required-capabilities — это небольшой общий контракт, который обеспечивает корректное предоставление навыков в CLI, Web Shell, ACP и будущих клиентах.
Выберите Вариант B, если ожидается, что qwencode-viz останется расширением только для Web Shell, и мейнтейнеры не хотят, чтобы базовые навыки зависели от функций клиентского рендеринга. В этом случае текущий встроенный навык следует удалить из ядра и предоставлять клиентами Web Shell, которые поддерживают echarts-fulldata.
Рекомендуемым вариантом по умолчанию в будущем является Вариант A, только если мейнтейнеры готовы сделать возможности клиента/сессии частью системы навыков. В противном случае, оставьте навыки хост-рендерера под управлением клиента.
Открытые вопросы
- Должны ли возможности иметь область видимости сессии, запроса или клиента?
- Должны ли отсутствующие возможности скрывать команды, вызываемые пользователем, или только скрывать активацию навыков, вызываемых моделью?
- Должны ли имена возможностей быть произвольными строками или проверяться по известному реестру?
- Следует ли полностью скрывать недоступные навыки из
/skills, или показывать их как отключенные с указанием причины? - Должно ли существовать ручное переопределение для пользователей, которые намеренно хотят генерировать необработанные блоки
echarts-fulldataв неподдерживаемых клиентах? - Должно ли поле называться
required-capabilities,requires-capabilitiesилиclient-capabilities?
План валидации
Если реализован Вариант A, добавьте тесты для:
- Парсинга frontmatter в обоих путях парсинга навыков.
- Скрытия навыка в
collectAvailableSkillEntriesпри отсутствии возможностей. - Отображения того же навыка при наличии возможностей.
- Взаимодействия с
paths,skills.disabledиdisable-model-invocation. - Видимости команд в
BundledSkillLoaderиSkillCommandLoader. - Сопоставления в Web Shell поддерживаемых языков блоков кода с возможностями клиента.
- Сохранения набора возможностей при создании сессии демона или ACP.
- Существующих интеграционных тестов встроенных навыков, чтобы убедиться, что навыки без
required-capabilitiesне изменились.
Миграция
Существующие навыки не требуют миграции, так как новое поле является опциональным.
Для текущего пути Варианта B удалите навык построения графиков из базовых встроенных навыков. Шаблон пакета Web Shell не должен загружаться ядром автоматически; хосты подключают его, устанавливая или внедряя его.
Если Вариант A будет принят, добавьте:
required-capabilities:
- markdown.codeBlock.echarts-fulldataв будущий встроенный qwencode-viz.
Если Вариант B будет принят, удалите навык построения графиков из базовых встроенных навыков и документируйте, как клиенты Web Shell могут установить или внедрить его при регистрации рендерера echarts-fulldata.