[English](../../README.md) · [简体中文](README_zh.md) · [繁體中文](README_zh-TW.md) · [日本語](README_ja.md) · [한국어](README_ko.md) · [Deutsch](README_de.md) · [Español](README_es.md) · [Français](README_fr.md) · **Русский** # dsh-unreal-agentlink ### Плагин DSH, который отдаёт ваш Unreal Editor под управление агента. **Чистый мост. Агент планирует; этот плагин только переводит.** ``` DSH agent ──tool calls──► dsh-unreal-agentlink ◄──WebSocket── Unreal Editor (plans, decides) (this plugin: server, translator) editor dials out ``` [Требования](#требования) · [Быстрый старт](#быстрый-старт) · [Инструменты](#12-инструментов) · [Конфигурация](#конфигурация) · [Документация](#документация) npm install && npm run check
--- Ваш агент уже умеет читать файлы и выполнять команды. Этот плагин отдаёт ему сам редактор: опрашивать уровень, создавать акторов, редактировать чертежи и материалы, рендерить секвенции, делать снимки экрана, которые он действительно *видит*, и откатывать собственную работу, когда что-то идёт не так. Он **не планирует задачи**. Планирование — дело нативного агента DSH. Этот плагин — переводчик со слоем безопасности: он превращает намерение агента в команды UnrealAgentLink, а ответы редактора — в форму, на которую агент может действовать. --- ## Требования | Пункт | Требование | |---|---| | Unreal Engine | 5.0 – 5.8 | | UnrealAgentLink | сам C++-плагин, положите его в `Plugins/` вашего проекта | | Node.js | `^22.19.0 \|\| >=24.0.0` | | Среда выполнения DSH | этот плагин, установленный как bundle | ### UEBox не нужен Настольное приложение UEBox — **не зависимость**. Это другой клиент того же производителя, который просто тоже использует порт 17860, поэтому одновременный запуск обоих — конфликт портов; на этом вся связь между ними. Доказательства: - `UnrealAgentLink.uplugin:25-34` объявляет ровно две зависимости, обе входят в состав движка: `IKRig` и `PythonScriptPlugin`. Никакого модуля uebox. - Каждая запись в `UnrealAgentLink.Build.cs` — модуль движка (UnrealEd / AssetRegistry / MovieScene / Landscape …). Никакого uebox SDK. - Строка `uebox` встречается во всём исходном коде три раза, и все три — заголовки `// Copyright uebox.ai`. - Ничто в исходном коде не обращается к uebox.ai. Единственное использование `FHttpModule` читает настройки прокси локальной машины (`UAL_ProxyDiagnostics.cpp:167`). Как только плагин оказывается в вашем проекте, он работает сам: `StartupModule` безусловно вызывает `Init("ws://127.0.0.1:17860")` (`Core/UnrealAgentLink.cpp:86-97`), отправляет `project.info` в момент подключения и не имеет ни рукопожатия, ни токена, ни аутентификации. > Чтобы сменить порт: `-UALServer=ws://127.0.0.1:17861` на стороне редактора и настройка `port` на этой стороне. --- ## Быстрый старт ```bash npm install npm run check # typecheck (src + tests) + 162 tests + catalog + doc reconciliation, one command ``` Чтобы установить как bundle DSH: `package.json` объявляет `dsh.bundle.patch`, а `cordis.patch.yml` вставляет одну строку по имени публикуемого пакета — `{id: unreal-agentlink, name: dsh-unreal-agentlink}`. > **Наслоение конфигурации**: более поздний слой **заменяет** `config` этой строки целиком, а не глубоко сливает его. Чтобы переопределить что угодно, перепишите все ключи, которые хотите сохранить. **Полный сквозной путь — в [руководстве](../ru/01-tutorial.md)** — установка на стороне UE, первое подключение, типовые рабочие процессы и как читать сбои. --- ## Документация | Документ | Содержание | |---|---| | [Руководство](../ru/01-tutorial.md) | Сквозной путь: установить плагин, подключиться, типовые рабочие процессы, устранение неполадок | | [Справочник по протоколу](../ru/02-protocol.md) | Форматы сообщений, события, 13 кодов состояния, правила сериализации, семантика таймаутов и конкурентности | | [Архитектура](../ru/03-architecture.md) | Цепочка шлюзов, изоляция сессий и откат, трансляция ошибок, стратегия верификации, ловушки, на которые мы наткнулись | | [Справочник команд](../ru/command-reference.md) | Параметры / обязательные ключи / ключи результата / коды состояния для всех 170 команд (**генерируется**) | Все четыре существуют на каждом языке, перечисленном в начале этого файла, в `docs//` — `en` (канонический, прямо в `docs/`), `zh`, `zh-TW`, `ja`, `ko`, `de`, `es`, `fr`, `ru`. > Переводится только *проза*. Имена команд, ключи параметров, ключи результата, коды состояния, ключи конфигурации и ссылки на строки C++ везде остаются дословными, потому что UE молча игнорирует незнакомый ему ключ — переведённый `asset_path` был бы молча мёртвым ключом, а не локализацией. `tests/i18n.test.ts` проверяет инварианты, которые делают это поддерживаемым: у каждого языка свои файлы, в каждой строке переключателя языков перечислены все языки, и каждая внутренняя ссылка и каждый `#anchor` разрешаются. Последнее важно потому, что анкеры выводятся из *переведённого* заголовка, поэтому переформулировка заголовка молча ломает каждую ссылку на него. --- ## Сначала четыре факта Это не выборы архитектуры. Это существующие ограничения UnrealAgentLink, и они определяют, как пишется каждая операция. 1. **Направление обратное.** Этот плагин не звонит редактору — **редактор сам подключается**. Порядок запуска не важен: откройте редактор первым, и он будет повторять попытки каждые 5 секунд, пока плагин не появится. 2. **Аутентификации нет.** Ни токена, ни заголовка, ни подпротокола. **Порт и есть учётные данные**, поэтому по умолчанию он слушает только loopback. Не меняйте его на `0.0.0.0`. 3. **Команды выполняются последовательно в игровом потоке.** И пока выполняется блокирующая команда, **редактор не отвечает ни на что** — даже на запрос прогресса вроде `sequence.render_status`. 4. **Отмены нет.** `query_task` и `cancel_task` не существуют. Убийство задания `ual_task` останавливает только ожидание этого плагина; движок продолжает работу, и результат всё равно придёт. --- ## 12 инструментов Выставить 170 команд как 170 инструментов — значит потратить большую долю промпта каждого запроса на возможности, которых данная задача никогда не коснётся. Поэтому обнаружение возможностей занимает два шага, а не ноль: | Инструмент | Назначение | |---|---| | `ual_status` | Подключения редактора / предохранитель (circuit breaker) / ограничение частоты / сессии / зависшие команды. **Читайте это первым, когда что-то не так** | | `ual_tools` | Список всех 170 команд (22 семейства), с фильтром по семейству или ключевому слову | | `ual_schema` | **Точные имена ключей** для одной команды — проверяйте перед вызовом; UE молча игнорирует неизвестные ключи | | `ual_call` | Выполнить одну команду, вернуть результат редактора дословно | | `ual_batch` | Несколько команд одним обменом (ограничено `maxBatchCalls`; один опасный элемент проводит через утверждение весь пакет) | | `ual_screenshot` | Снять и вернуть **настоящее изображение**, которое видит агент | | `ual_logs` | Читать локальное кольцо журнала (пересылка с стороны UE по умолчанию выключена — см. руководство) | | `ual_undo` / `ual_undo_history` | Откат в рамках сессии / просмотр буфера отмен | | `ual_locks` | Занять блокировки ассетов; редактор рисует значок «ИИ редактирует» | | `ual_editor_bind` | Привязать сессию к одному редактору, когда подключено несколько | | `ual_task` | Превратить долгую блокирующую команду в настоящее фоновое задание, чтобы разговор продолжался | ### Уровни риска read 65 / write 89 / **destructive 2 / dangerous 14** (последние 16 проходят через утверждение). `tools/pre-execute` возвращает `{kind:'ask'}` для destructive- и dangerous-методов (включая любой опасный элемент внутри `ual_batch`), оставляя решение политике сессии. **Незарегистрированный метод отклоняется сразу** и никогда не доходит до редактора. Обратите внимание: при политике `never` запрос `ask` автоотклоняется — что и есть правильное прочтение «неконтролируемые сессии не должны трогать редактор». --- ## Конфигурация Значения по умолчанию — «живой, но ленивый». Полная таблица с обоснованиями — в [руководстве §7](../ru/01-tutorial.md#7-конфигурация). | Ключ | По умолчанию | Значение | |---|---|---| | `host` / `port` | `127.0.0.1` / `17860` | Оставьте на loopback: без аутентификации доступ к порту означает управление редактором | | `timeoutMs` | `180000` | Блокирующие команды поднимают собственные минимумы | | `cacheTtlMs` / `cacheMaxEntries` | `2000` / `128` | Кэш чтения на сессию | | `rateLimitRps` / `rateLimitQueue` | `5` / `32` | Команд в секунду / предел очереди (0 отключает ограничение частоты) | | `breakerThreshold` / `breakerCooldownMs` | `3` / `30000` | Последовательных транспортных сбоев до размыкания / время охлаждения | | `logCapacity` / `logMinLevel` | `512` / `Display` | Кольцо журнала | | `logForwardingEnabled` | `false` | Включайте `true` только после запуска `ual.ForwardLogs` внутри редактора | | `rollbackOnDispose` | `false` | Точен только LIFO-суффикс — см. документ по архитектуре | | `notifyOnEditorChange` | `true` | Вставить строку агенту, когда редактор подключается или отключается | | `screenshotAttachments` / `screenshotMaxBytes` | `true` / 12MB | Блоки изображений / предел размера | | `maxBatchCalls` | `12` | Предел на `ual_batch` | --- ## Разработка ```bash npm run build # tsc → lib/ npm run dev # tsc --watch npm run check # typecheck + tests + catalog reconciliation (run before committing) npm test # tests only npm run typecheck # src and tests typechecked separately ``` ### Структура исходного кода ``` src/ index.ts Cordis lifecycle, approval gate, dispose-time rollback config.ts Schemastery schema (the single source of default values) service.ts call() gate chain: catalog → requires → read cache → breaker → rate limit → send → ledger → translate protocol/{bridge,serialization,types}.ts WS server / outbound validation / wire types catalog/{method-data,method-overrides,methods}.ts 170-command catalog (data extracted from C++) errors/translate.ts Error classification (by class name / status code, never by message text) resilience/breaker.ts Rate limiter + circuit breaker sessions/state.ts Session ledger / LIFO test / lock merging / cache / log filtering tools/ The 12 tools scripts/ Catalog extraction and reconciliation, doc generation, language set docs/ Tutorial / protocol / architecture / command reference, in 9 languages tests/ 162 tests ``` ### Сгенерированные файлы: не править вручную `src/catalog/method-data.ts` и `docs/command-reference.md` оба **генерируются**: ```bash node scripts/extract-methods.mjs .ua-ref/plugin/UnrealAgentLink/Source # rebuild the catalog from C++ npm run build && node scripts/gen-docs.mjs # rebuild the command reference ``` Поддерживаемая вручную таблица, которую никто не импортирует, — повторяющийся класс дефектов в этом проекте (так были пойманы `REQUIRED_KEYS`, `EXTRA_PARAM_KEYS` и `UAL_CODES`). Контрмера — заставить каждую проверку **спрашивать источник**, а не таблицу — см. [архитектура §9](../ru/03-architecture.md#9-сгенерированные-файлы-и-таблицы-которые-никто-не-импортирует). ### Тесты и проверки ```bash npm run check # typecheck (src + tests) + 154 passed + catalog reconciliation npm test # 154 passed node scripts/check-catalog.mjs # 170/170 zero drift; every requires key appears in C++; code set matches C++ node scripts/gen-docs.mjs --check # is the command reference up to date ``` Семь тестовых файлов разделены по **роду дефектов, которые они способны найти**, и каждый — по своей причине — см. [архитектура §11](../ru/03-architecture.md#11-стратегия-верификации). `requires` не пишется руками: он выводится из собственных сообщений `SendError(..., 400, "Missing field: X")` в обработчиках (28 мест), потому что неверный обязательный ключ заставляет мост отклонить *корректный* вызов ещё до того, как его увидит движок. Результат попадает в `method-data.ts` как `REQUIRED_KEYS`, а `requiresOf()` в `methods.ts` встраивает его в работу во время выполнения (раннее отклонение в `service.call()`, строка `REQUIRED:` в `ual_schema` и подсказки в ошибках — всё через эту одну функцию). Он намеренно **сужает, но никогда не расширяет**: сообщение, содержащее `or` (`"Missing field: name or path"`), пропускается целиком, а ключи, добытые из вызванной функции в том же файле, отбрасываются как локальные для ветки — см. `REQUIRED_DROPPED` в `scripts/extract-methods.mjs`, где каждая запись ссылается на номер своей строки C++. Так что «ключей не добыто» — безопасное поведение по умолчанию, а не пропущенная проверка. --- ## Основы протокола ``` UE → plugin: { ver:"1.0", type:"req", method, id, params } // id is generated by the server plugin → UE: { ver:"1.0", type:"res", id, code, result } // the key is omitted when empty UE → plugin: { ver, type:"evt", method, payload } ``` - Сбой выражается **только через `code`**. Поля `error` на уровне конверта нет. - Читаемый человеком текст лежит в `result.message` (на китайском или английском в зависимости от локали редактора — **не парсите его**); машинно-читаемые подробности — в `result.details`. - **Многие обработчики возвращают `200` при `result.ok === false`** (`editor.undo`, `blueprint.compile`, `pie.observe`), поэтому плагин судит об успехе через `judge()`, а не по одному коду. Есть 55 мест `SetStringField(TEXT("error"), ...)` в 14 файлах C++ — это норма, а не аномалия. - `content.import_folder` / `content.import_assets` — **исходящие push'ы в режиме «отправил и забыл»**: ни `id`, ни ожидания ответа. Плагин превращает их в записи журнала и уведомления агента и **не фабрикует `res`** — ответ `ok:false` лишь показал бы пользователю ложный диалог «импорт не удался», что хуже молчания. Полные подробности (все 13 кодов состояния, правила сериализации, семантика таймаутов) — в [справочнике по протоколу](../ru/02-protocol.md). --- ## Лицензия MIT