[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