Skip to Content
BenutzerhandbuchErweiterungExperimentelle Mods

Experimentelle Mods

Qwen Code kann eine kleine Teilmenge der Claude Code Mod API in Headless-Sessions ausführen. Dieses Feature ist opt-in und erfordert einen vertrauenswürdigen Workspace sowie eine aktivierte Claude- oder native Qwen-Erweiterung. Agent Plugins v1-Pakete ignorieren weiterhin Hooks.

Füge diese Einstellung zu deinen Qwen-Einstellungen hinzu:

{ "experimental": { "mods": true } }

Erstelle ein Plugin-Verzeichnis mit diesen Dateien:

hello-mod/ .claude-plugin/plugin.json hooks/hooks.json hooks/register.js

.claude-plugin/plugin.json:

{ "name": "hello-mod", "version": "1.0.0" }

hooks/hooks.json (Modulpfade sind relativ zu dieser Datei):

{ "modules": ["./register.js"] }

hooks/register.js:

let calls = 0; export function register(on) { on('session.start', async ($, e, next) => { await $.command.register({ name: 'hello', description: 'Say hello without a model request', }); return next(e); }); on('command.run', { command: 'hello' }, ($, e) => ({ text: `Hello ${e.args || 'world'}! Call ${++calls}.`, })); on('session.end', ($, e, next) => { $.ui.log(`Finished ${e.sessionId}`, { to: 'debug' }); return next(e); }); }

Installiere das Plugin und führe seinen Befehl aus:

qwen extensions install ./hello-mod --consent qwen -p '/hello Qwen' --output-format json

Die normale CLI-Authentifizierungskonfiguration ist beim Start weiterhin erforderlich. Der Mod- Befehl selbst stellt keine Modellanfrage. Text-, JSON- und Stream-JSON-Headless-Ausgabe werden unterstützt. Ein Stream-JSON-Prozess behält Modulvariablen zwischen Turns.

Unterstützte Teilmenge

OberflächeVerhalten
PackagingEin .js, .mjs, .cjs, .ts, .mts oder .cts ESM-Entry; relative statische Imports und Re-Exports innerhalb des installierten Plugins
Entryregister(on, options); asynchrone Registrierung wird abgewartet; typisierte Nicht-Secret-Optionen werden kopiert und tief eingefroren
LifecycleAlle Module registrieren sich vor der initialen session.start-Kette; session.end bei Exit und Conversation-Replacement; Clear/Resume laden Module nicht neu
CommandsRegistrierung während session.start mit Name, Description und optionalem ArgumentHint; Abfangen registrierter Mod-Befehle über Plugins hinweg; Rückgabe von {text} oder {}
ToolsAbfangen bestehender Main-Session-Tools mit tool.call; Inspizieren und Einschränken nativer Permission-Entscheidungen mit tool.check
MiddlewarePatterns und rekursive Matcher; explizites, wiederholbares next(e); .catch und pro-Plugin-Fehlerwiederherstellung; eingefrorene Eingabe und Host-Identität
API eventscommand.register- und ui.log-Aufrufe durchlaufen die Middleware-Kette mit vom Host gelieferter Origin
Logging$.ui.log gibt void zurück und stellt Lieferung in Warteschlange; Transkript-Logs verwenden stderr im Textmodus oder system/ui_log in JSON-Modi; Debug-Logs bleiben im Debug-Logger; keines wird Model-Input
IsolationSeparate QuickJS-Umgebung pro Plugin innerhalb eines Session-Workers; keine Node-, Filesystem- oder Network-APIs; Speicher-, Stack-, Source/Output- und Ausführungslimits

Dies ist eine unterstützte Teilmenge, keine vollständige Kompatibilität mit bestehenden Claude Mods. JSX/TSX, Runtime-Package-Imports, Built-in-Command-Abfangen, Tool-Registrierung und aktive $.tool-APIs, Context, State/Store, Rendering, Streaming-Events und Hot-Reload sind nicht implementiert. Befehle können noch nicht context, exitCode oder Engine-Referenzen zurückgeben.

Zwei Mods können nicht denselben Befehlsnamen registrieren: die spätere Registrierung wird abgelehnt. Ein Konflikt mit einem Qwen-Built-in-Befehl verwendet weiterhin Qwens Befehl-Konfliktrichtlinie: der Erweiterungsbefehl wird zu extensionName.commandName umbenannt. Deaktivierte Slash- Befehlsnamen werden gegen den resultierenden Namen gematcht.

Middleware und Recovery

Patterns wählen aus session.start, session.end, command.run, command.register, ui.log, tool.call und tool.check. Verwende einen exakten Namen, einen Präfix wie command.*, *, oder eine einzelne Negation wie !session.*. Andere konkrete Events und die Patterns !* und !!... werden abgelehnt.

Matcher sind partielle Objekte. Alle angegebenen Felder müssen matchen; Arrays sind Alternativen, verschachtelte Objekte matchen ihre eigenen Felder, und reguläre Ausdrücke testen die String-Darstellung des Feldes. Matcher werden während der Registrierung kopiert. Sie erlauben höchstens acht Ebenen und 256 Knoten. Stateful g/y-Regexes und verschachtelte Quantifiers werden abgelehnt; Regex-Inputs länger als 65.536 Zeichen matchen konservativ und überlassen die Entscheidung dem Hook. Verschachtelte Objekte sind Daten, nicht Operatoren wie startsWith.

Ein exaktes Event kann eine Registrierung ohne Matcher und mehrere Registrierungen mit Matchern haben, einschließlich gleicher Matcher. Jedes Wildcard- oder negierte Pattern kann einmal registriert werden. Innerhalb eines Plugins bestimmt die Registrierungsreihenfolge die Kette; Consumer laufen vor ihren deklarierten Dependency-Providern. Ein Handler kann früh antworten, auch bei session.start, und verhindert, dass spätere Handler laufen. Nur Befehl-args können bei command.run umgeschrieben werden; Lifecycle- und Befehls-Identitätsfelder bleiben fixiert.

Tool-Abfangen

tool.call empfängt die Qwen-Tool-Argumente als flache Felder neben tool und tool_use_id. Namen, Argument-Schemas und Output-Records verwenden Qwens native Verträge, daher kann ein für Claude’s Built-in-Tools geschriebener Mod Anpassung benötigen. Es gibt kein separates tool.result-Event; inspiziere oder ändere den von await next(e) zurückgegebenen Wert:

export function register(on) { on('tool.call', { tool: 'write_file' }, async ($, e, next) => { if (!e.file_path.endsWith('.txt')) return { deny: 'Only text files are allowed' }; return next({ ...e, content: e.content.trim() }); }); on('tool.check', async ($, e, next) => { const verdict = await next(e); if (e.tool === 'run_shell_command') { return { decision: 'deny', reason: 'Shell is disabled by this Mod' }; } return verdict; }); }

Jedes normale tool.call-Next startet einen frischen nativen Tool-Versuch: Validierung, klassische Hooks, Permissions, Genehmigungen und Invocation-Guards gelten weiterhin. Zusätzliche Versuche zählen gegen --max-tool-calls; Catch-Replay fügt keinen Versuch hinzu. Sessions mit Tool-Middleware serialisieren Tool-Aufrufe, einschließlich paralleler Next-Aufrufe, da umgeschriebene Argumente Side-Effects einführen können. Ein Root- Aufruf erlaubt höchstens 64 native Versuche und 32 ausstehende Host-Operationen. Das 60-Sekunden-Root-Limit beinhaltet native Ausführung und Wartezeit auf Genehmigung. Rückkehr bevor ein ausstehendes Next abgeschlossen ist, bricht diese Arbeit ab und wartet auf deren Abschluss.

tool.check empfängt {tool, input, tool_use_id} nach nativer Validierung und klassischen Input-Umschreibungen. Alle Felder sind fixiert. Sein Next gibt ein deklaratives {decision: 'allow' | 'ask' | 'deny', reason?} zurück, ohne das Tool auszuführen. Qwen mergt die Antwort restriktiv: ein Mod kann ein natives Deny oder Ask nicht zu Allow upgraden. Ein Mod-Ask folgt dem bestehenden Entscheidungsfluss des aktuellen Genehmigungsmodus; erforderliche menschliche Interaktion und klassische erzwungene Asks bleiben maßgeblich. Dies ist absichtlich strenger als Claude’s Consumer-Permission-Overrides. Wenn ein nativer PermissionRequest-Hook die Argumente neu aufbaut, werden sie erneut geprüft. Ein vom Mod verfasster rule, hook oder ceiling kann keine Host-Permissions erstellen.

Gib {deny: reason} zurück zum Blockieren, oder {result: value} zum Liefern eines JSON-Ergebnisses ohne Ausführung. Natives Next gibt {ref, result, text, isError?} zurück. result ist der native Funktionsantwort-Record, typischerweise {output: ...} oder {error: ...}. Eine zurückgegebene Ref mit unverändertem Result behält die Originalausgabe, einschließlich Bilder und Output-Metadaten; Änderung von Text- oder Error-Flags neben dieser Referenz ändert sie nicht. Host-Ausführungsfakten bleiben über alle Versuche hinweg erhalten, auch wenn ein früheres Ergebnis gewählt wird. Um den Output zu ändern, gib ein neues Result zurück: {result: {output: 'Replacement text'}}. Records mit nur String- output/error-Feldern behalten diese Form; anderes JSON wird vollständig in einen output-String serialisiert, sodass native Output-Budgets weiterhin gelten. Der Host besitzt die Funktionsantwort: JSON wird nie als IDs, Media-Parts oder Ausführungsfakten interpretiert. Unbekannte Refs und isReadOnly-Claims gewähren keine Provenienz. Ergebnisse, die zu groß für die Mod-Payload sind, enthalten einen Omission-Hinweis und eine Ref; Rückgabe dieses Paares unverändert bewahrt die vollständige native Antwort.

Native Errors, Genehmigungsanforderungen und Abbruch können nicht in erfolgreiche Ausführung umgewandelt werden durch Änderung des Mod-Ergebnisses. Eine Verweigerung nach Next kann die Effekte eines Tools nicht rückgängig machen: Qwen meldet, dass das Tool bereits lief. Dasselbe gilt, wenn ein früherer Versuch lief und ein späterer Versuch verweigert wurde. Context-Injection und Request-Metadaten werden nicht unterstützt.

Tool-Identität, Invocation-IDs, Consent und Host-Metadaten sind fixiert. Native Argumentnamen, die mit tool, tool_use_id, agentId, consent, requestMeta oder $shadowed kollidieren, werden in einem vom Host erstellten, fixierten $shadowed- Record aufbewahrt und für die Ausführung wiederhergestellt. Gültige deferred tool_call-Anfragen legen den Namen und die Argumente des aufgelösten Ziels offen, während die nativen Permission-Gates des Wrappers erhalten bleiben. Ungültige Wrapper, unbekannte Tools, Fixed-Policy-Aufrufe und das synthetische structured_output-Terminal-Tool bleiben auf ihren ursprünglichen Pfaden. tool_search und search_memory bleiben ebenfalls nativ, damit vom Gast verfasste Ergebnisse nicht ihre nativen Output-Budget-Ausnahmen erben können. Goal-Tool-Aufrufe behalten ebenfalls den nativen Pfad: diese Stufe erlaubt keine vom Mod verfassten Ergebnisse, die als externe Beweise in einen Goal-Katalog gelangen. Child-Agents und verschachtelte Code-Mode-Scheduler erben diese Middleware nicht.

Befehl-Recovery

export function register(on) { on('command.run', { command: /^hello/ }, async ($, e, next) => { const result = await next({ ...e, args: e.args.trim() }); return { text: `[wrapped] ${result.text || ''}` }; }).catch(($, e, next) => next(e)); }

Normales next führt bei jedem Aufruf Downstream aus. Die Registrierungen eines Plugins bilden eine Schicht, die sich einen Failure-Record und ein Zeitbudget teilen. Bei einem Throw, Timeout oder ungültigen Ergebnis werden seine Catches in Registrierungsreihenfolge versucht, bis einer eine nicht-undefined-Antwort zurückgibt oder next aufruft. Ein Catch, der undefined zurückgibt oder wirft, ohne next aufzurufen, erlaubt dem nächsten passenden Catch, zu laufen. Wie in Claude Code 2.1.296 beendet eine ungültige Antwort oder ein Fehlschlag nach next die Catch-Auswahl und verwendet den geteilten Fallback-Record; er versucht keine späteren Catches. next.called im Catch bedeutet, dass das Plugin in das nächste Plugin übergetreten ist, bevor die Recovery begann, nicht nur in eine andere eigene Registrierung.

Wenn das Plugin bereits Downstream aufgerufen hat, wartet die Recovery auf und replayt den letzten gestarteten Aufruf, einschließlich seiner Zurückweisung, ignoriert Replay-Argumente. Wenn es nicht hatte, führt das erste next des Catch Downstream einmal aus und spätere Aufrufe replayen ihn. Ein fehlgeschlagener Catch verwendet denselben Record. Ohne einen erfolgreichen Catch wird das Plugin übersprungen oder sein aufgezeichnetes Ergebnis beibehalten. Ein registrierter Befehl, den niemand beantwortet, schlägt explizit fehl; er wird nicht an das Modell gesendet.

next legt event, is(pattern), eingefrorene origin, signal und einen frischen eingefrorenen budget-Snapshot mit ms und remainingMs offen. Catch empfängt zusätzlich called und error.kind (throw, timeout oder re-entry). Engine-Events verwenden Origin { plugin: 'engine', tier: 'core' }; Mod-API-Events identifizieren das aufrufende Plugin mit Tier user. Managed-Tier-next.to, Tracing und benutzerdefinierte Namespace-lent- Recovery sind nicht verfügbar.

API-Events beginnen am Kettenkopf. Ihr Input ist die Befehlsspezifikation oder der {text, to}-Log-Payload. Gib next(e), {value} zum Antworten, oder {deny: reason} zurück, um den API-Aufruf abzulehnen. Sofortige Selbst-Reentry überspringt diese Registrierung; aktive Vorfahren-Registrierungen können ihren Catch verwenden. Re-Entry-Catch kann next nicht aufrufen, und sein next verwendet das Original-Event einmal. ui.log bleibt eine Void-Benachrichtigung: await $.ui.log(...) wartet nicht auf Interceptoren. Die Headless-Grenze drainet wartende Benachrichtigungen vor der Rückgabe des Outputs.

Eine normale Plugin-Schicht hat 10 Sekunden eigene Zeit, exklusive Wartezeit auf Cross-Plugin-next oder API-Aufrufe. Wartende Void-ui.log-Benachrichtigungen pausieren nicht das Budget des Aufrufers. Catch und Re-Entry-Recovery verwenden ein Ein-Sekunden-Budget. Re-Entry-Dispatch teilt dieses kürzere Budget mit anderen Registrierungen in diesem Plugin. Qwen begrenzt zusätzlich jeden Root-Dispatch auf 60 Sekunden und jedes ununterbrochene Gast-Ausführungsslice auf 10 Sekunden. Alle session.end-Arbeiten teilen sich 1,5 Sekunden. Ein äußerer Handler, der früh fertig wird, gibt ausstehende Downstream- Arbeit auf; Abbruch startet niemals eine Fallback-Operation. Behaltene Next/API-Funktionen können keine Arbeit ausführen, nachdem ihre Invocation endet. Ein Worker, der Abbruch nicht innerhalb von 250 Millisekunden bestätigen kann, wird terminiert, was eine neue Session erfordert.

Es gibt höchstens 32 Mod-Umgebungen, 64 registrierte Befehle, 128 Registrierungen pro Plugin und 4.096 Root/API-Event-Ketten pro Root-Aufruf. Durchreichen durch ein anderes Plugin verbraucht kein zusätzliches Event. Jede Umgebung hat ein 64 MiB Speicherlimit. Logs und Nachrichten-Payloads sind ebenfalls begrenzt. Registrierungs-, Kompilierungs- und Konfigurationsfehler lehnen weiterhin die Initialisierung ab; gewöhnlicher Hook-Fehler kann sich erholen, ohne den Modulzustand zurückzusetzen. Gast-Promise-Jobs laufen nur, während ein Host-Request aktiv ist, einschließlich seines wartenden Benachrichtigungs-Drains.

Mods laufen nicht in TUI, ACP, Managed/SSH, Sandbox oder Session-Agent-Entry-Points in dieser Version. Safe Mode, Bare Mode und disableAllHooks verhindern ebenfalls das Laden. Starte neu nach Änderung von Plugin-Code, Optionen oder Erweiterungsaktivierung. Ein bereits geladener Befehl prüft jedes beteiligte Plugin vor der Ausführung erneut und verweigert eine geänderte oder deaktivierte Middleware-Kette. Externe Einstellungsänderungen werden nach Neustart wirksam. Native Qwen-Erweiterungen mit einem absoluten oder externen Hooks-Manifest-Pfad verwenden weiterhin nur den klassischen Hook-Loader. Claude-Hooks-Pfade, die Konvertierung bereits ignoriert, weil sie absolut oder außerhalb der Erweiterung sind, werden auch von der Mod-Entdeckung übersprungen. Mod-Manifeste müssen relative Pfade innerhalb der Erweiterung sein.

TypeScript und relative Imports

Ändere den Hooks-Entry zu ./register.ts, um TypeScript zu verwenden. Imports und Re-Exports können relative Pfade, erweiterungslose Pfade, Verzeichnis-index-Dateien oder JavaScript-Schreibweisen von TypeScript-Dateien verwenden (./helper.js kann zu helper.ts auflösen). Exakte Dateien gewinnen. Als nächstes kommen .js → .ts, .mjs → .mts, und .cjs → .cts-Substitutionen, gefolgt von .ts, .js, .mjs, .cjs, .mts, .cts-Kandidaten, wobei jede Datei und dann ihr Verzeichnis-Index versucht wird. Alle sechs Suffixe verwenden ESM, einschließlich .cjs und .cts. Modul-Zyklen und geteilter importierter Zustand innerhalb eines Plugins werden unterstützt.

Kompilierung verwendet feste Optionen innerhalb des abbrechbaren Workers; sie type-checkt nicht oder lädt tsconfig.json, Package-Resolver oder Build-Plugins. Type-only Imports, einschließlich Typen aus claude-code, werden gelöscht. Runtime-Value-Imports aus claude-code benötigen APIs außerhalb dieser Teilmenge und werden abgelehnt, ebenso wie Package/Node/URL-Imports, dynamische Imports, require, JSX und importierte Top-Level- await. Gast-Code wird weiterhin nur in QuickJS ausgeführt.

Plugin-Optionen

Deklariere userConfig im Plugin-Manifest:

{ "name": "hello-mod", "version": "1.0.0", "userConfig": { "greeting": { "type": "string", "title": "Greeting", "description": "Greeting used by /hello", "default": "Hello" } } }

Setze Optionen in User- oder System-Settings, dann lies options.greeting aus dem zweiten Argument zu register:

{ "experimental": { "mods": true }, "pluginConfigs": { "hello-mod": { "options": { "greeting": "Welcome" } } } }

Felder erfordern type, title und description. Unterstützte Typen sind string, number, boolean, directory und file. Optionale Constraints umfassen required, default, numerische min/max, String-multiple und String- options-Auswahlmöglichkeiten. Datei- und Verzeichniswerte sind Strings, ohne Existenzprüfung oder hinzugefügten Filesystem-Zugriff. sensitive: true wird explizit nicht unterstützt, auch wenn ein Default angegeben ist; es gibt keine Secret-Speicherung oder Config-UI.

Qwen verwendet SystemDefaults → User → System-Präzedenz, mergt individuelle Options- Schlüssel. Workspace-Einstellungen werden ignoriert, auch in vertrauenswürdigen Workspaces. Qwen mappt Erweiterungen ohne Marketplace-Provenienz auf die Inline-Identität. Sie akzeptieren name und name @inline; letzteres gewinnt innerhalb jeder Einstellungsquelle. Marketplace-Plugins verwenden ihre tatsächliche name @marketplace-Identität. Eine höhere Prioritätsquelle gewinnt über jeden lokalen Alias von einer niedrigeren Prioritätsquelle.

Wie in der inspizierten Claude-Implementierung werden fehlende optionale Werte zu '', außer sie haben Defaults; optionales '' umgeht auch Typprüfungen. Erforderliche Werte lehnen fehlende oder '' ab, während false und 0 gültig bleiben. Ungültige gespeicherte String-Auswahlmöglichkeiten fallen auf Defaults zurück. Bei einem nichtleeren Schema werden unbekannte Options- Schlüssel ebenfalls durchgereicht, begrenzt auf Strings, Zahlen, Booleans oder String-Arrays. Ein abwesendes oder leeres Schema erhält {}. Optionen bleiben bis zum Neustart eingefroren.

Installierte Dependencies

Manifest-dependencies akzeptiert Plugin-Namen, name @marketplace oder Objekte wie { "name": "base", "marketplace": "team", "version": "^1.0.0" }. Bloße Namen beziehen sich auf den Marketplace des Consumers. Ein installiertes Inline-Plugin mit demselben Namen kann eine Marketplace-Dependency überschreiben, ohne Versionsprüfung. Andernfalls werden Versionsbereiche gegen die Manifest-Version der installierten Erweiterung geprüft. Gewöhnliche Erweiterungen ohne Mods können Dependencies erfüllen. Ihre transitiven Dependencies werden geprüft, wenn sie von einem ausgewählten Mod erreichbar sind; Dependency-Deklarationen nicht verwandter gewöhnlicher Erweiterungen nehmen nicht teil.

Dependencies müssen bereits installiert, aktiviert und für diesen Aufruf ausgewählt sein. Qwen holt, installiert oder aktiviert sie nicht automatisch. Ausgewählte Mods werden in stabiler Consumer-vor-Dependency-Reihenfolge für direkte Dependencies zwischen Mods registriert; gewöhnliche Provider sind keine Registrierungs-Knoten. Jeder Mod hat ein separates Realm; Events durchlaufen ihre Handler in dieser Reihenfolge, was Cross-Plugin-Abfangen ohne gemeinsame Modulvariablen ermöglicht.

Fehlende, mehrdeutige, inkompatible oder zyklische Dependencies lehnen den Start ab, bevor irgendein Mod-Worker startet. Ungültige Mod-Optionen lehnen ebenfalls den Start ab. Dies unterscheidet sich von Claude’s selektiver Dependency-Degradierung und Zyklus-Reihenfolge-Fallback; Qwen behält die Whole-Session-Startup-Failure-Policy der experimentellen Teilmenge.

Ressourcenlimits

Der Reader akzeptiert reguläre Dateien bis zu 256 KiB innerhalb der installierten Erweiterung und lehnt symbolische Links innerhalb dieses Roots ab. Graphen sind auf 64 Dateien, 1 MiB Input und 1 MiB kompilierten Code begrenzt. Diese Grenzen und Symlink-Ablehnung sind strenger als Claude’s. Jeder Gast hat ein 64 MiB Speicherlimit und 1 MiB Stack-Limit. Die oben beschriebenen Ausführungsbudgets beinhalten ein 60-Sekunden-Startup-Limit für alle Worker-Initialisierung, Kompilierung und Startup-Hooks zusammen. Das Gast-Speicher- Limit deckt nicht den Host-Speicher des Compilers ab.

Die Author-API basiert auf Claude Code 2.1.296-Deklarationen und der offiziellen Mod-Dokumentation .

Last updated on