Skip to Content
Руководство для пользователейРасширениеЭкспериментальные моды

Экспериментальные моды

Qwen Code может выполнять небольшую часть Mod API из Claude Code в headless-сессиях. Эта возможность включается явно и требует доверенного рабочего пространства и включенного расширения Claude или нативного расширения Qwen. Пакеты Agent Plugins v1 по-прежнему игнорируют хуки.

Добавьте эту настройку в настройки Qwen:

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

Создайте каталог плагина со следующими файлами:

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 (пути к модулям указаны относительно этого файла):

{ "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); }); }

Установите плагин и выполните его команду:

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

Обычная конфигурация аутентификации CLI по-прежнему требуется при запуске. Сама команда мода не делает запроса к модели. Поддерживаются текстовый, JSON и stream-json headless-вывод. Процесс stream-json сохраняет переменные модуля между ходами.

Поддерживаемое подмножество

ПоверхностьПоведение
УпаковкаОдна точка входа ESM: .js, .mjs, .cjs, .ts, .mts или .cts; относительные статические импорты и реэкспорты внутри установленного плагина
Точка входаregister(on, options); асинхронная регистрация ожидает завершения; типизированные несекретные опции копируются и глубоко замораживаются
Жизненный циклВсе модули регистрируются до начальной цепочки session.start; session.end при выходе и замене разговора; clear/resume не перезагружают модули
КомандыРегистрация во время session.start с использованием name, description и необязательного argumentHint; перехват зарегистрированных команд модов между плагинами; возврат {text} или {}
ИнструментыПерехват существующих инструментов основной сессии через tool.call; проверка и ограничение нативных решений о разрешениях через tool.check
Промежуточный слойПаттерны и рекурсивные матчеры; явные, повторяемые next(e); .catch и восстановление после сбоев для каждого плагина; замороженные входные данные и идентификация хоста
API-событияВызовы command.register и ui.log проходят через цепочку промежуточного слоя с указанием происхождения от хоста
Логирование$.ui.log возвращает void и ставит доставку в очередь; логи транскрипта используют stderr в текстовом режиме или system/ui_log в режимах JSON; отладочные логи остаются в отладочном логгере; ни то, ни другое не становится входом для модели
ИзоляцияОтдельное окружение QuickJS для каждого плагина внутри воркера сессии; нет API Node, файловой системы или сети; лимиты памяти, стека, источника/вывода и выполнения

Это поддерживаемое подмножество, а не полная совместимость с существующими модами Claude. JSX/TSX, импорт пакетов во время выполнения, перехват встроенных команд, регистрация инструментов и активные API $.tool, контекст, состояние/хранилище, рендеринг, потоковые события и горячая перезагрузка не реализованы. Команды пока не могут возвращать context, exitCode или ссылки на движок.

Два мода не могут зарегистрировать одно и то же имя команды: более поздняя регистрация отклоняется. Конфликт со встроенной командой Qwen по-прежнему использует политику конфликтов команд Qwen: команда расширения переименовывается в extensionName.commandName. Имена отключённых слеш-команд сопоставляются с полученным именем.

Промежуточный слой и восстановление

Паттерны выбирают среди session.start, session.end, command.run, command.register, ui.log, tool.call и tool.check. Используйте точное имя, префикс, такой как command.*, *, или одиночное отрицание, такое как !session.*. Другие конкретные события и паттерны !* и !!... отклоняются.

Матчеры — это частичные объекты. Все указанные поля должны совпадать; массивы — это альтернативы, вложенные объекты проверяют свои собственные поля, а регулярные выражения проверяют строковое представление поля. Матчеры копируются при регистрации. Они допускают не более восьми уровней и 256 узлов. Stateful-регулярные выражения с g/y и вложенные квантификаторы отклоняются; входные данные регулярных выражений длиннее 65 536 символов дают консервативное совпадение, оставляя решение за хуком. Вложенные объекты — это данные, а не операторы, такие как startsWith.

Точное событие может иметь одну регистрацию без матчера и несколько регистраций с матчерами, включая одинаковые матчеры. Каждый паттерн с wildcard или отрицанием может быть зарегистрирован один раз. Внутри плагина порядок регистрации определяет цепочку; потребители выполняются до объявленных поставщиков зависимостей. Обработчик может ответить рано, в том числе на session.start, предотвращая выполнение более поздних обработчиков. Только args команды могут быть перезаписаны при command.run; поля идентификации жизненного цикла и команды остаются зафиксированными.

Перехват инструментов

tool.call получает аргументы инструмента Qwen как плоские поля наряду с tool и tool_use_id. Имена, схемы аргументов и записи вывода используют нативные контракты Qwen, поэтому мод, написанный для встроенных инструментов Claude, может потребовать адаптации. Нет отдельного события tool.result; проверяйте или изменяйте значение, возвращённое через await next(e):

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; }); }

Каждый обычный next для tool.call начинает новую попытку нативного инструмента: валидация, классические хуки, разрешения, одобрения и охранители вызова по-прежнему применяются. Дополнительные попытки учитываются в --max-tool-calls; catch при воспроизведении не добавляет попытки. Сессии с промежуточным слоем инструментов сериализуют вызовы инструментов, включая параллельные вызовы next, потому что перезаписанные аргументы могут вносить побочные эффекты. Один корневой вызов допускает не более 64 нативных попыток и 32 незавершённых операций хоста. Корневой лимит 60 секунд включает нативное выполнение и время ожидания одобрения. Возврат до завершения незавершённого next отменяет эту работу и ожидает её урегулирования.

tool.check получает {tool, input, tool_use_id} после нативной валидации и классических перезаписей входа. Все поля зафиксированы. Его next возвращает декларативный {decision: 'allow' | 'ask' | 'deny', reason?} без выполнения инструмента. Qwen объединяет ответ ограничительно: мод не может повысить нативный deny или ask до allow. Запрос ask от мода следует существующему потоку решений текущего режима одобрения; обязательное взаимодействие с человеком и классические принудительные ask остаются авторитетными. Это намеренно строже, чем переопределения разрешений потребителя в Claude. Если нативный хук PermissionRequest перестраивает аргументы, они проверяются снова. rule, hook или ceiling, написанные модом, не могут создавать разрешения хоста.

Верните {deny: reason} для блокировки или {result: value} для предоставления JSON-результата без выполнения. Нативный next возвращает {ref, result, text, isError?}. result — это нативная запись ответа функции, обычно {output: ...} или {error: ...}. Возвращённый ref с неизменным результатом сохраняет исходный вывод, включая изображения и метаданные вывода; изменение text или флагов ошибок рядом с этой ссылкой не изменяет её. Факты выполнения хоста сохраняются во всех попытках, даже когда выбран более ранний результат. Чтобы изменить вывод, верните новый результат: {result: {output: 'Replacement text'}}. Записи, содержащие только строковые поля output/error, сохраняют эту форму; другой JSON сериализуется полностью в строку output, чтобы нативные бюджеты вывода по-прежнему применялись. Хост владеет ответом функции: JSON никогда не интерпретируется как ID, части медиа или факты выполнения. Неизвестные ref и заявления isReadOnly не предоставляют происхождение. Результаты, слишком большие для полезной нагрузки мода, содержат уведомление о пропуске и ref; возврат этой пары без изменений сохраняет полный нативный ответ.

Нативные ошибки, требования одобрения и отмена не могут быть превращены в успешное выполнение путём изменения результата мода. Отказ после next не может отменить эффекты инструмента: Qwen сообщает, что инструмент уже выполнился. То же самое применяется, когда более ранняя попытка выполнилась, а более поздняя была отклонена. Внедрение контекста и метаданные запроса не поддерживаются.

Идентификация инструмента, ID вызова, согласие и метаданные хоста зафиксированы. Нативные имена аргументов, которые конфликтуют с tool, tool_use_id, agentId, consent, requestMeta или $shadowed, сохраняются в созданной хостом, зафиксированной записи $shadowed и восстанавливаются для выполнения. Валидные отложенные запросы tool_call раскрывают имя и аргументы разрешённой цели, сохраняя нативные ограничения разрешений обёртки. Невалидные обёртки, неизвестные инструменты, вызовы с фиксированной политикой и синтетический терминальный инструмент structured_output остаются на своих исходных путях. tool_search и search_memory также остаются нативными, чтобы результаты, написанные гостем, не могли наследовать их исключения из нативного бюджета вывода. Вызовы инструментов Goal также сохраняют нативный путь: этот этап не позволяет результатам, написанным модом, попадать в каталог Goal как внешние доказательства. Дочерние агенты и вложенные планировщики code-mode не наследуют этот промежуточный слой.

Восстановление команд

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)); }

Обычный next выполняет нижестоящую цепочку при каждом вызове. Регистрации плагина образуют один слой, разделяя запись о сбоях и бюджет времени. При выбросе, таймауте или невалидном результате его catch пробуются в порядке регистрации, пока один не вернёт не-undefined ответ или не вызовет next. Catch, который возвращает undefined или выбрасывает без вызова next, позволяет запуститься следующему совпадающему catch. Как в Claude Code 2.1.296, невалидный ответ или сбой после next завершает выбор catch и использует общую запись фолбэка; он не пробует более поздние catch. next.called в catch означает, что плагин перешёл в следующий плагин до начала восстановления, а не просто в другую свою регистрацию.

Если плагин уже вызвал нижестоящую цепочку, восстановление ожидает и воспроизводит последний начатый вызов, включая его отклонение, игнорируя аргументы воспроизведения. Если он не вызывал, первый next в catch запускает нижестоящую цепочку один раз, а последующие вызовы воспроизводят его. Неудачный catch использует ту же запись. Без успешного catch плагин пропускается или его записанный результат сохраняется. Зарегистрированная команда, на которую никто не отвечает, явно завершается ошибкой; она не отправляется модели.

next раскрывает event, is(pattern), замороженный origin, signal и свежий замороженный снимок budget с ms и remainingMs. Catch дополнительно получает called и error.kind (throw, timeout или re-entry). События движка используют происхождение { plugin: 'engine', tier: 'core' }; события Mod API идентифицируют вызывающий плагин с уровнем user. Управляемые next.to, трассировка и пользовательское пространство имён lent восстановление недоступны.

API-события начинаются с головы цепочки. Их вход — это спецификация команды или полезная нагрузка лога {text, to}. Верните next(e), {value} для ответа или {deny: reason} для отклонения вызова API. Немедленный повторный вход пропускает эту регистрацию; активные регистрации предка могут использовать свой catch. Catch повторного входа не может вызвать next API, и его next использует исходное событие один раз. ui.log остаётся уведомлением void: await $.ui.log(...) не ждёт перехватчиков. Граница headless-режима дренирует уведомления в очереди перед возвратом вывода.

Обычный слой плагина имеет 10 секунд собственного времени, исключая время ожидания межплагинного next или вызовов API. Ожидающие void-уведомления ui.log не приостанавливают бюджет вызывающего. Catch и восстановление повторного входа используют бюджет в одну секунду. Диспетчеризация повторного входа разделяет этот более короткий бюджет с другими регистрациями в этом плагине. Qwen дополнительно ограничивает каждый корневой диспетчер 60 секундами и каждый непрерывный срез выполнения гостя — 10 секундами. Вся работа session.end разделяет 1,5 секунды. Внешний обработчик, завершившийся рано, отказывается от ожидающей нижестоящей работы; отмена никогда не запускает операцию фолбэка. Сохранённые функции next/API не могут выполнять работу после завершения их вызова. Воркер, который не может подтвердить отмену в течение 250 миллисекунд, завершается, требуя новой сессии.

Существует не более 32 окружений модов, 64 зарегистрированных команд, 128 регистраций на плагин и 4 096 корневых/API-цепочек событий на корневой вызов. Прохождение через другой плагин не потребляет дополнительное событие. Каждое окружение имеет лимит памяти 64 МиБ. Логи и полезные нагрузки сообщений также ограничены. Сбои регистрации, компиляции и конфигурации по-прежнему отклоняют инициализацию; обычный сбой хука может восстановиться без сброса состояния модуля. Гостевые задачи промисов выполняются только пока запрос хоста активен, включая его дренирование очереди уведомлений.

Моды не выполняются в точках входа TUI, ACP, managed/SSH, песочницы или сессионного агента в этой версии. Безопасный режим, голый режим и disableAllHooks также предотвращают загрузку. Перезапустите после изменения кода плагина, опций или активации расширения. Уже загруженная команда повторно проверяет каждый участвующий плагин перед выполнением и отклоняет изменённую или отключённую цепочку промежуточного слоя. Внешние изменения настроек вступают в силу после перезапуска. Нативные расширения Qwen с абсолютным или внешним путём манифеста хуков продолжают использовать только классический загрузчик хуков. Пути хуков Claude, которые конвертация уже игнорирует, потому что они абсолютные или вне расширения, также пропускаются обнаружением модов. Манифесты модов должны быть относительными путями внутри расширения.

TypeScript и относительные импорты

Измените точку входа хуков на ./register.ts, чтобы использовать TypeScript. Импорты и реэкспорты могут использовать относительные пути, пути без расширений, index-файлы каталогов или JavaScript-написания файлов TypeScript (./helper.js может разрешиться в helper.ts). Точные файлы выигрывают. Далее идут замены .js → .ts, .mjs → .mts и .cjs → .cts, за которыми следуют кандидаты .ts, .js, .mjs, .cjs, .mts, .cts, пробуя каждый файл, а затем его индекс каталога. Все шесть суффиксов используют ESM, включая .cjs и .cts. Циклы модулей и общее импортированное состояние в пределах одного плагина поддерживаются.

Компиляция использует фиксированные опции внутри отменяемого воркера; она не проверяет типы и не загружает tsconfig.json, резолверы пакетов или плагины сборки. Импорты только типов, включая типы из claude-code, стираются. Импорты runtime-значений из claude-code требуют API вне этого подмножества и отклоняются, как и импорты пакетов/Node/URL, динамические импорты, require, JSX и импортированный верхнеуровневый await. Гостевой код по-прежнему выполняется только в QuickJS.

Опции плагина

Объявите userConfig в манифесте плагина:

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

Установите опции в пользовательских или системных настройках, затем читайте options.greeting из второго аргумента register:

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

Поля требуют type, title и description. Поддерживаемые типы: string, number, boolean, directory и file. Необязательные ограничения включают required, default, числовые min/max, строковое multiple и строковые варианты options. Значения file и directory — это строки без проверки существования или добавленного доступа к файловой системе. sensitive: true явно не поддерживается, даже когда предоставлено значение по умолчанию; нет хранилища секретов или UI конфигурации.

Qwen использует порядок приоритета SystemDefaults → User → System, объединяя отдельные ключи опций. Настройки рабочего пространства игнорируются даже в доверенных рабочих пространствах. Qwen сопоставляет расширения без происхождения из маркетплейса со встроенной идентификацией. Они принимают name и name @inline; последний выигрывает в каждом источнике настроек. Плагины маркетплейса используют свою фактическую идентификацию name @marketplace. Источник с более высоким приоритетом выигрывает у любого локального алиаса из источника с более низким приоритетом.

Как в проверенной реализации Claude, отсутствующие необязательные значения становятся '', если у них нет значений по умолчанию; необязательный '' также обходит проверки типов. Обязательные значения отклоняют отсутствующие или '', тогда как false и 0 остаются валидными. Невалидные сохранённые строковые варианты возвращаются к значениям по умолчанию. При непустой схеме неизвестные ключи опций также проходят, ограниченные строками, числами, булевыми значениями или строковыми массивами. Отсутствующая или пустая схема получает {}. Опции остаются замороженными до перезапуска.

Установленные зависимости

Манифест dependencies принимает имена плагинов, name @marketplace или объекты, такие как { "name": "base", "marketplace": "team", "version": "^1.0.0" }. Голые имена ссылаются на маркетплейс потребителя. Установленный встроенный плагин с тем же именем может переопределить зависимость маркетплейса без проверки версии. В противном случае диапазоны версий проверяются по версии манифеста установленного расширения. Обычные расширения без модов могут удовлетворять зависимости. Их транзитивные зависимости проверяются, когда они достижимы из выбранного мода; объявления зависимостей несвязанных обычных расширений не участвуют.

Зависимости уже должны быть установлены, включены и выбраны для этого вызова. Qwen не получает, не устанавливает и не включает их автоматически. Выбранные моды регистрируются в стабильном порядке «потребитель перед зависимостью» для прямых зависимостей между модами; обычные поставщики не являются узлами регистрации. Каждый мод имеет отдельную область; события проходят через их обработчики в этом порядке, позволяя межплагинный перехват без общего использования переменных модуля.

Отсутствующие, неоднозначные, несовместимые или циклические зависимости отклоняют запуск до того, как запустится любой воркер мода. Невалидные опции мода также отклоняют запуск. Это отличается от выборочного понижения зависимостей и фолбэка в порядке цикла в Claude; Qwen сохраняет политику сбоя запуска всей сессии экспериментального подмножества.

Ограничения ресурсов

Читатель принимает обычные файлы до 256 КиБ внутри установленного расширения и отклоняет символические ссылки в пределах этого корня. Графы ограничены 64 файлами, 1 МиБ входа и 1 МиБ скомпилированного кода. Эти границы и отклонение символических ссылок строже, чем в Claude. Каждый гость имеет лимит памяти 64 МиБ и лимит стека 1 МиБ. Бюджеты выполнения, описанные выше, включают 60-секундный лимит запуска для всей инициализации воркера, компиляции и хуков запуска вместе. Лимит гостевой памяти не покрывает хостовую память компилятора.

API автора основан на объявлениях Claude Code 2.1.296 и официальной документации модов .

Last updated on