Design für erforderliche Skill-Capabilities
Status: Design-Notiz; dieser PR setzt Option B um und lässt
required-capabilities als zukünftigen Vorschlag offen.
Kontext
Web Shell kann benutzerdefinierte Fenced Code Blocks über seinen Markdown-Renderer rendern. Der
Vorschlag für den Chart-Renderer verwendet einen echarts-fulldata Fenced Code Block, damit das
Modell eine vollständige ECharts-Option und einen Dataset-Payload zurückgeben kann, den Web Shell
als interaktives Chart rendert.
Dieser Output-Contract ist nur in Clients nützlich, die ihn rendern können. In der CLI, bei ACP-Clients oder jeder anderen Oberfläche ohne passenden Renderer würde dieselbe Antwort als großer Code-Block und nicht als Chart erscheinen.
Der ursprüngliche Vorschlag für die gebündelte Chart-Skill verließ sich auf Formulierungen, um dem Modell mitzuteilen, dass das Format für Web Shell gedacht ist. Dies ist ein Soft Guard. Wenn die Skill in einer Nicht-Web-Shell-Session verfügbar gemacht wird, kann das Modell immer noch ein Output-Format wählen, das der Client nicht rendern kann.
Für den aktuellen PR behält Qwen Code den Renderer-Extension-Point in Web Shell bei, bündelt aber
qwencode-viz nicht im Core. Das Web-Shell-Paket enthält eine kopierbare, nicht automatisch
geladene Skill-Vorlage, und Hosts sollten diese Skill nur installieren oder injizieren, wenn sie
auch einen echarts-fulldata-Renderer registrieren.
Problem
Qwen Code benötigt eine klare Möglichkeit zu entscheiden, ob eine host-spezifische Skill dem Modell und den Benutzern angezeigt werden soll.
Für qwencode-viz lautet die konkrete Frage:
- Sollte der Core ein generisches
required-capabilitiesSkill-Metadatenfeld unterstützen? - Oder sollte
qwencode-vizgar keine im Core gebündelte Skill sein, sondern nur von Web-Shell-Clients bereitgestellt werden, die sie installieren oder injizieren?
Ziele
- Verhindern, dass renderer-spezifische Skills verfügbar gemacht werden, wenn der aktuelle Client ihren Output-Contract nicht erfüllen kann.
- Startup-Skill-Reminder, explizite Skill-Aktivierung, Slash-Command-Discovery und Skill-Validierung konsistent halten.
- Vermeiden,
qwencode-vizals Sonderfall hart zu codieren. - Bestehendes Skill-Verhalten beibehalten, wenn keine Capability-Anforderung deklariert ist.
- Das Design erweiterbar halten für zukünftige Host-Capabilities, nicht nur für ECharts.
Nicht-Ziele
- Die Implementierung des ECharts-Renderers selbst.
- Neugestaltung der gesamten Client/Server-Capability-Verhandlung.
- Änderung der Semantik bestehender Skill-Frontmatter.
- Lösung von Capability-Änderungen in Multi-Client-Shared-Sessions in der ersten Version.
Aktuelle verwandte Mechanismen
Die Codebase verfügt bereits über mehrere Sichtbarkeitskontrollen, aber keine davon repräsentiert Client-Rendering-Capabilities:
disable-model-invocation: Verhindert, dass eine Skill automatisch vom Modell aufgerufen wird.user-invocable: Steuert, ob eine gebündelte Skill als Befehl verfügbar ist.paths: Begrenzt die Skill-Verfügbarkeit auf passende Workspace-Pfade.skills.disabled: Deaktiviert konfigurierte Skills.allowedTools: Wird derzeit vom Laden gebündelter Skills verwendet, um Cron-orientierte Skills auszublenden, wenn Cron-Tools nicht verfügbar sind.- Slash-Command
supportedModes: Filtert Befehle nach Ausführungsmodus. - Daemon- und ACP-Capability-Objekte: Beschreiben Protokoll- oder Client-Support, sind aber derzeit nicht mit der Skill-Verfügbarkeit verbunden.
Es gibt kein bestehendes required-capabilities oder äquivalentes Skill-Frontmatter.
Das Hinzufügen wäre ein neuer Skill-Contract.
Option A: required-capabilities hinzufügen
Ein generisches Skill-Frontmatter-Feld hinzufügen:
---
name: qwencode-viz
description: Render analytical charts in Web Shell using echarts-fulldata fenced code blocks.
required-capabilities:
- markdown.codeBlock.echarts-fulldata
---Wenn der aktuelle Client/Session nicht alle aufgeführten Capabilities bewirbt, wird die Skill als nicht verfügbar behandelt.
Capability-Benennung
Namespaced String-Capabilities verwenden:
markdown.codeBlock.echarts-fulldataDies hält das Feld generisch, während der Contract präzise bleibt:
markdown: Die Capability gehört zu gerendertem Markdown.codeBlock: Die Capability gilt für das Rendering von Fenced Code Blocks.echarts-fulldata: Der spezifische vom Renderer unterstützte Language/Info-String.
Zukünftige Beispiele könnten sein:
markdown.codeBlock.vega-litemarkdown.codeBlock.mermaid-interactiveartifact.openUrl
Skill-Metadaten
Füge requiredCapabilities?: string[] zur Skill-Konfiguration hinzu, nachdem der
Frontmatter-Schlüssel required-capabilities geparst wurde.
Beide Skill-Parsing-Pfade sollten das Feld verstehen:
packages/core/src/skills/skill-load.tspackages/core/src/skills/skill-manager.ts
Das Feld sollte optional sein. Fehlend oder leer bedeutet, dass die Skill keine Client-Capability-Anforderung hat.
Runtime-Capability-Quelle
Client/Session-Capabilities zur Runtime-Konfiguration hinzufügen:
interface ConfigParameters {
clientCapabilitiesProvider?: () => ReadonlySet<string>;
}Einen Helper auf Config bereitstellen, zum Beispiel:
config.getClientCapabilities(): ReadonlySet<string>Dann die Prüfung zentralisieren:
function skillMeetsRequiredCapabilities(skill: Skill, config: Config): boolean {
return skill.config.requiredCapabilities.every((capability) =>
config.getClientCapabilities().has(capability),
);
}Filterungspunkte
Der Capability-Filter sollte angewendet werden, bevor Skills entweder dem Modell oder dem Benutzer verfügbar gemacht werden:
collectAvailableSkillEntriesinpackages/core/src/tools/skill-utils.tssollte Skills überspringen, deren erforderliche Capabilities fehlen. Dies hält Startup-Skill-Reminder, Delta-Reminder,SkillTool-Validierung und modell-aufrufbare Aktivierung synchron.BundledSkillLoadersollte nicht verfügbare gebündelte Skills überspringen, wenn benutzerzugängliche Befehle erstellt werden.SkillCommandLoadersollte nicht verfügbare Dateisystem-Skills überspringen, wenn benutzerzugängliche Befehle erstellt werden.
Die wichtige Invariante ist, dass eine vor dem Modell versteckte Skill nicht weiterhin als aufrufbarer Befehl erscheinen sollte, es sei denn, das Projekt unterstützt absichtlich ein manuelles Override.
Web-Shell-Registrierung
Web Shell sollte Renderer-Support explizit bewerben, anstatt sich auf das Vorhandensein eines
undurchsichtigen renderCodeBlock-Callbacks zu verlassen.
Zum Beispiel:
<WebShell
customization={{
markdown: {
renderableCodeBlockLanguages: ['echarts-fulldata'],
renderCodeBlock(info) {
// render custom blocks
},
},
}}
/>Der Web-Shell-Client kann das auf Folgendes mappen:
markdown.codeBlock.echarts-fulldataDies macht die Capability-Deklaration stabil, selbst wenn der Renderer-Callback benutzerdefinierte Logik, Fallbacks oder mehrere unterstützte Sprachen enthält.
Daemon- und ACP-Propagation
Für gehostete oder Daemon-basierte Sessions muss das Client-Capability-Set den Core erreichen, bevor Skills geladen oder aufgelistet werden. Eine minimale Version kann Capabilities beim Erstellen einer Session übergeben:
interface CreateSessionRequest {
clientCapabilities?: string[];
}Der Daemon-Bridge-, SDK- und ACP-Session-Erstellungs-Flow kann dies als session-scoped Konfiguration speichern.
In der ersten Version können Capabilities session-scoped sein. Wenn mehrere Clients mit derselben Session verbunden sind, sollte das Verhalten so dokumentiert werden, dass die Capabilities aus der Session-Erstellungszeit verwendet werden.
Vorteile
- Hält
qwencode-vizals eine kanonische gebündelte Skill. - Verhindert, dass host-spezifische Output-Contracts in nicht unterstützte Clients durchsickern.
- Schafft einen wiederverwendbaren Mechanismus für zukünftige renderer-spezifische oder host-spezifische Skills.
- Macht die Abhängigkeit explizit und testbar.
Nachteile
- Fügt ein neues übergreifendes Skill-Metadatenfeld hinzu.
- Erfordert das Durchreichen von Client/Session-Capabilities über Web-Shell-, Daemon-, SDK- und ACP-Oberflächen.
- Benötigt sorgfältige Dokumentation für das Shared-Session-Verhalten.
- Könnte mehr Mechanik sein als nötig, wenn
qwencode-vizdie einzige erwartete capability-gegate Skill ist.
Option B: Vom Client bereitgestellte Skill
Kein generisches required-capabilities-Feld hinzufügen. Stattdessen qwencode-viz nicht im
Core bündeln. Der Web-Shell-Client oder jeder Client, der den Renderer unterstützt, stellt die
Skill selbst bereit.
Mögliche Verteilungsmodelle:
- Der Web-Shell-Host installiert
.qwen/skills/qwencode-viz/SKILL.md. - Das Web-Shell-Paket liefert eine optionale, nicht automatisch geladene Skill-Vorlage mit, die ein Host kopieren oder installieren kann, wenn das Chart-Rendering aktiviert ist.
- Die Web-Shell-Integration liefert ein Extension-Skill-Paket mit.
- Die Web-Shell-Integration injiziert äquivalente Modell-Anweisungen nur, wenn ihr Chart-Renderer aktiviert ist.
In diesem Modell ist die Skill nur verfügbar, weil der rendernde Client sich entschieden hat, sie bereitzustellen.
Web-Shell-Host-Integration
Ein Web-Shell-Host, der Chart-Output möchte, sollte sich für beide Hälften des Contracts entscheiden:
- Einen
echarts-fulldataMarkdown-Code-Block-Renderer registrieren. - Die passende Chart-Skill aus
packages/web-shell/docs/examples/qwencode-viz/SKILL.mdbereitstellen.
Zum Beispiel:
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),
}),
}}
/>;In dieser Renderer-Konfiguration ermöglicht loadEcharts dem Host, die genehmigte ECharts-Runtime
bereitzustellen, entweder als statischen Import oder als lazy-loaded Modul. resolveDataRef wird
nur für data.kind="ref" Chart-Blöcke verwendet; es ist die host-eigene Bridge von einer für das
Modell sichtbaren Datenreferenz zu einem vertrauenswürdigen Dataset. Das für das Modell sichtbare
Envelope-Format wird durch die optionale Skill-Vorlage in
packages/web-shell/docs/examples/qwencode-viz/SKILL.md beschrieben; die rendererseitige
Validierung befindet sich in
packages/web-shell/client/components/messages/EchartsFullDataBlock.tsx.
Die Skill-Datei sollte nur von Hosts installiert oder injiziert werden, die diese Registrierung vornehmen. Eine einfache dateibasierte Integration kann Folgendes kopieren:
packages/web-shell/docs/examples/qwencode-viz/SKILL.mdin das Workspace- oder Benutzer-Skill-Verzeichnis, zum Beispiel:
.qwen/skills/qwencode-viz/SKILL.mdEine Integration mit eigener Skill-Verteilungsschicht kann stattdessen dieselbe Datei als kanonischen Quellinhalt laden und über diese Schicht verfügbar machen. In beiden Fällen lädt der Core die Skill nicht automatisch; der Host ist für die Aktivierung verantwortlich, da der Host den Renderer besitzt.
Für data.kind="ref" Envelopes validiert der eingebaute Renderer, dass data.ref eine
normalisierte artifact://- oder session-file://-Referenz verwendet, bevor er die
host-gesteuerte resolveDataRef(ref, meta)-Implementierung aufruft. Der Renderer parst den
Block außerdem als JSON und bereinigt die ECharts-Option vor dem Rendern; er wertet kein vom
Modell bereitgestelltes JavaScript aus, ruft keine beliebigen URLs ab und liest auch keine
lokalen Dateien selbst. Ein benutzerdefinierter Renderer sollte dieselbe Aufteilung beibehalten:
Zuerst die JSON/Ref/Option-Validierung auf Renderer-Ebene, zweitens die host-eigene
Artifact-Auflösung.
Ein Daemon-gestützter Host kann die Workspace-File-API als ein Artifact-Backend behandeln.
Zum Beispiel kann der Host Chart-Artifacts in einem kontrollierten Workspace-Verzeichnis wie
.qwen/artifacts/ persistieren, für das Modell sichtbare Referenzen wie
artifact://chart-data/orders.csv verfügbar machen und sie über den Daemon
GET /file?path=.qwen/artifacts/chart-data/orders.csv auflösen. Dies behält artifact:// als
öffentlichen Chart-Contract bei, während es der ersten Implementierung ermöglicht,
Daemon-Workspace-Dateien wiederzuverwenden.
Der Resolver muss dennoch das Artifact-Root erzwingen, bevor er den Daemon aufruft:
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);
}Dieses Beispiel bildet absichtlich nur normalisierte artifact://-Pfade unter
.qwen/artifacts/ ab. Wenn ein Host später Artefakte in einen Object Storage oder einen
sitzungsbezogenen Artefakt-Dienst verschiebt, muss nur resolveDataRef geändert werden; der
für das Modell sichtbare echarts-fulldata-Block kann weiterhin dieselbe Referenz-Struktur verwenden.
Vorteile
- Minimale Änderungen am Core.
- Kein neuer globaler Skill-Metadaten-Contract.
- Die Verfügbarkeit von Capabilities liegt naturgemäß beim Client, der den Renderer implementiert.
- Vermeidet Daemon- oder ACP-Infrastruktur, es sei denn, der Client verfügt bereits über einen Skill-Injection-Mechanismus.
Nachteile
- Kein kanonischer mitgelieferter Skill, es sei denn, alle Clients kopieren denselben Inhalt.
- Höherer Aufwand für jeden Web-Shell-Integrator.
- Benutzer, die zwischen Clients wechseln, sehen möglicherweise eine inkonsistente Skill-Verfügbarkeit.
- Schafft keine allgemeine Absicherung für zukünftige host-spezifische Skills.
- Im Core schwerer zu testen, da die Verfügbarkeit von einer externen Installation oder Injection abhängt.
Empfehlung
Verwende für diesen PR Option B.
Das hält das Core-Skill-System unverändert und vermeidet es,
echarts-fulldata-Anweisungen in nicht unterstützten Clients offenzulegen. Der Web-Shell-Renderer-
Hook bleibt für jeden vom Host betriebenen Block-Renderer nützlich, während diagrammspezifische
Modell-Anweisungen zu einem expliziten Opt-in des Hosts werden.
Langfristig sollte dies als Entscheidung über die Produkt-/API-Grenzen diskutiert werden.
Wähle Option A, wenn die Maintainer erwarten, dass Qwen Code im Laufe der Zeit mehr client-gerenderte
Output-Contracts unterstützt. In diesem Fall ist required-capabilities ein kleiner,
allgemeiner Contract, der die Skill-Verfügbarkeit über CLI, Web Shell, ACP
und zukünftige Clients hinweg konsistent hält.
Wähle Option B, wenn qwencode-viz voraussichtlich eine reine Web-Shell-
Erweiterung bleibt und die Maintainer nicht möchten, dass Core-Skills von Client-Rendering-
Funktionen abhängen. In diesem Fall sollte der aktuelle mitgelieferte Skill aus dem Core entfernt
und von Web-Shell-Clients bereitgestellt werden, die echarts-fulldata unterstützen.
Die empfohlene zukünftige Standardeinstellung ist Option A nur, wenn die Maintainer damit einverstanden sind, Client-/Sitzungs-Capabilities Teil des Skill-Systems werden zu lassen. Andernfalls sollten Host-Renderer-Skills in Client-Verwaltung bleiben.
Offene Fragen
- Sollen Capabilities Session-, Request- oder Client-spezifisch sein?
- Sollen fehlende Capabilities benutzeraufrufbare Befehle ausblenden oder nur die modellaufrufbare Skill-Aktivierung ausblenden?
- Sollen Capability-Namen Freiform-Strings sein oder gegen eine bekannte Registry validiert werden?
- Sollen nicht verfügbare Skills vollständig vor
/skillsausgeblendet oder als deaktiviert mit einer Begründung angezeigt werden? - Sollte es ein manuelles Override für Benutzer geben, die absichtlich rohe
echarts-fulldata-Blöcke in nicht unterstützten Clients ausgeben möchten? - Sollte das Feld
required-capabilities,requires-capabilitiesoderclient-capabilitiesheißen?
Validierungsplan
Wenn Option A implementiert wird, füge Tests hinzu für:
- Frontmatter-Parsing in beiden Skill-Parsing-Pfaden.
collectAvailableSkillEntries, das einen Skill ausblendet, wenn Capabilities fehlen.- Dasselbe Skill, das angezeigt wird, wenn Capabilities vorhanden sind.
- Interaktion mit
paths,skills.disabledunddisable-model-invocation. - Befehlssichtbarkeit von
BundledSkillLoaderundSkillCommandLoader. - Web-Shell-Mapping von unterstützten Codeblock-Sprachen zu Client-Capabilities.
- Daemon- oder ACP-Session-Erstellung, die das Capability-Set beibehält.
- Bestehende Integrationstests für mitgelieferte Skills, um sicherzustellen, dass Skills ohne
required-capabilitiesunverändert bleiben.
Migration
Bestehende Skills erfordern keine Migration, da das neue Feld optional ist.
Für den aktuellen Option-B-Pfad entferne den Chart-Skill aus den im Core mitgelieferten Skills. Das Web-Shell-Paket-Template darf nicht automatisch vom Core geladen werden; Hosts aktivieren es explizit, indem sie es installieren oder injizieren.
Wenn Option A akzeptiert wird, füge:
required-capabilities:
- markdown.codeBlock.echarts-fulldatazu einem zukünftigen mitgelieferten qwencode-viz hinzu.
Wenn Option B akzeptiert wird, entferne den Chart-Skill aus den im Core mitgelieferten Skills und
dokumentiere, wie Web-Shell-Clients ihn installieren oder injizieren können, wenn sie einen
echarts-fulldata-Renderer registrieren.