ADR 0108: Встроенный веб-портал для внешних веб-ИИ и мост инструментов через Host Object (WebView → IDE)¶
Статус: Accepted · Implemented
Дата: 2026-05-10
Зафиксированные параметры (2026-05-10): формат executeIdeCommand(string json), PoC-allowlist на чтение, режимы Read / Write-confirm, модель WebView2 ниже страницы (см. §2) — согласованы для первой интеграции в CascadeIDE. Побочный шум: персона Atlas иногда даёт «звучные» рабочие имена в обсуждении — это риторика, не отдельные сущности в архиве репозитория, если явно не заведены как продукт.
Связанные ADR¶
| ADR | Роль |
|---|---|
| 0035 | базовая граница «веб ≠ автоматический MCP»; этот ADR задаёт явный, согласованный канал поверх неё |
| 0093 | MFD / браузер / launch URL |
| 0008 | IdeCommands, MCP, исполняемый контур IDE |
| 0038 | фасад провайдеров и оркестрация тулов |
| 0016 | внешние агенты; ортогонально |
| 0048 | поверхность тулов / паритет |
| ## Резюме |
- Web AI portal на MFD: WebView2, Host Object →
IdeCommands/MCP. - Allowlist, согласие пользователя; PoC (Atlas / Search AI).
- Граница доверия — 0035.
1. Контекст¶
Силы:
- Редактор-хост (например Cursor) периодически уходит в Reconnect или падает — длинные сессии и стабильный доступ к инструментам страдают.
- Локальные LLM на типичной рабочей станции дают долгий трек ответа; закупка выделенного GPU не у всех есть в ближайшей перспективе.
- При этом облачные веб-интерфейсы вендоров (в т.ч. режимы «ИИ в поиске») могут давать приемлемую скорость и качество рассуждения без привязки к циклу релизов локального стека.
Ограничение: веб-ИИ живёт в песочнице origin чужого домена; у страницы нет того же API, что у нативного агента с MCP в процессе IDE. В 0035 §2–3 зафиксировано: произвольный HTTPS-origin не получает автоматического доступа к MCP, файлам workspace и секретам. В 0035 §5 отмечено, что подружить веб с локальными инструментами — отдельная линия с моделью согласия и анализом угроз.
Наблюдение: для сценария «веб-модель рассуждает, а правки/сборка/навигация — в IDE» достаточно узкого, аудируемого моста: страница вызывает не произвольный код хоста, а стабильный контракт (execute_ide_command / command_id + JSON args), который внутри IDE сходится с уже существующим IdeMcpCommandExecutor и каноном MCP-PROTOCOL.md.
Эмпирика: проведён PoC совместно с Atlas на стороне веб-ИИ (в т.ч. ориентация на Google Search AI / веб-оболочку без полноценного локального tool runtime у страницы). Доказано транспортное звено: сигнал из веба доходит до нативного моста (Host Object), дальше в PoC вывод уходил echo в отдельное консольное приложение, слушавшее порт :8080 — без вызова реальных инструментов IDE и без IdeMcpCommandExecutor. То есть проверена идея моста и инъекции, а не конец цепочки «команда → рабочее пространство». Подключение к канону MCP-PROTOCOL.md и whitelist §2.2 — следующий шаг (M1).
2. Решение (намерение)¶
2.1. Единый вызов с JS: executeIdeCommand(string json)¶
-
Один метод на границе Host Object — без зеркала «по одному методу на каждый
ide_*тул». Рекомендуемая сигнатура для AddHostObjectToScript:executeIdeCommand(string json)(или эквивалент с тем же телом запроса). -
Тело JSON — тот же контракт, что у диспетчера MCP: объект с
command_idиargs(как в MCP-PROTOCOL.md / разборIdeMcpServer: возможен верхнеуровневый merge с вложеннымargs). Парсинг, валидация и маршрутизация остаются только в C#; со стороны веб-страницы — один простой интерфейс. -
Транспорт JSON обязателен: расширение полей без пересборки моста; новые команды добавляются через
IdeCommandsи allowlist, а не через новые COM-методы.
Рекомендуемый вывод модели: fenced json-cascade¶
Готовый текст инструкций для вложения во внешний веб-чат оператора: AiPrompts/web-ai-portal-bridge-adr0108.prompts.md (секции ## web_portal_bridge_*). Если веб-слой портит Markdown при копировании из истории чата — плоский вариант без разметки и с однострочным JSON как у буфера моста: AiPrompts/web-ai-portal-bridge-adr0108.chat-paste.txt.
Чтобы отделить команду к IDE от произвольного fenced json в том же ответе, веб-слой договаривается с моделью: один объект { "command_id", "args" } (тот же контракт, что у ide_execute_command) помещается в fenced-блок с info string json-cascade:
```json-cascade
{ "command_id": "codebase_index_search", "args": { "query": "Foo", "top_n": 10 } }
```
Парсер в IDE: WebAiPortalJsonCascadeFence.TryExtractFirst → строка JSON → invokeCSharpAction / мост. Внутри JSON не должно быть закрывающей последовательности fence (обычно не встречается).
2.2. PoC-allowlist (белый список command_id)¶
-
Главный предохранитель безопасности — стабильный whitelist разрешённых
command_id. До отладки контура команды вне списка отклоняются на стороне IDE до вызова исполнителя. -
Стартовый набор PoC (только «глаза», без разрушительных действий): три семантики; в коде CascadeIDE используются канонические идентификаторы из
IdeCommands:
| Семантика (удобно в обсуждении) | Канонический command_id в CascadeIDE |
|---|---|
| чтение / просмотр контекста редактора (аналог «read») | get_editor_content_range (при необходимости в связке с get_editor_state для метаданных активного файла) |
| поиск по локальному индексу кода | codebase_index_search · для стабильного потока в веб чат добавлены read-only codebase_index_status, codebase_index_explain (whitelist моста) |
| диагностики по текущему файлу | get_current_file_diagnostics |
Псевдонимы вроде read_file / search_index / get_diagnostics в продукте не являются вторым источником правды: при необходимости — только тонкий алиас в JS-слое, который маппится в таблицу выше.
2.3. Режимы: Read-only и Write-confirm¶
-
Read-only: команды, классифицированные как чтение/наблюдение (в PoC — весь whitelist §2.2), выполняются без дополнительного диалога (после первичного включения моста и согласия на allowlist).
-
Write-confirm (по умолчанию): любая команда, классифицированная как изменение (запись файла, git с побочными эффектами, сборка, удаление и т.д.), не выполняется молча: IDE показывает явное подтверждение (модальное окно или эквивалентный тост с действием «Разрешить / Отклонить») с понятным описанием намерения («агент запрашивает X»). Это фиксирует модель «предложил → человек подтвердил».
-
Write без подтверждения на каждый шаг (must have для потока): подтверждать каждый write отдельно — неприемлемо для длительной работы. Должна быть отдельная, явная опция уровня «разрешить все write для этой сессии моста» / «доверять записи от этого моста (до отзыва)» — по смыслу как Run everything / аналог в Cursor: один осознанный opt-in, после чего write-команды из allowlist выполняются без модалки на каждый вызов, пока пользователь не отключит режим, не отзовёт мост или не закроет сессию (конкретика UX — продукт, ограничения по времени/scope — по настройкам). Дефолт для новичков остаётся подтверждение на каждую write или узкий режим.
-
Классификация read vs write — в конфигурации моста (таблица или атрибут на
command_id); расширение whitelist обязано сопровождаться классификацией, иначе по умолчанию — write-confirm (безопасный fallback).
2.4. Технические допущения: WebView2 и ограничения «обычного веба»¶
-
Встраивание и размещение: встроенный портал = контролируемый WebView2 (или эквивалент) внутри IDE. Каноничный слот для длительного «веб-чат + мост» — MFD (вторичный контур внимания по 0021; согласуется с 0035 — встроенный браузер как осознанная зона, а не четвёртый «лобовой» семантический якорь). PFD под постоянный веб-чат не резервируем. Альтернатива — отдельное окно / вторичная поверхность по 0017 и пресетам UI (0010).
-
Инъекция моста: взаимодействие с инструментами IDE осуществляется через нативную публикацию объекта в JavaScript (например
AddHostObjectToScript) — вызов идёт в объекте, добавленном хостом, а не черезpostMessageмежду окнами и не через сетевой стек страницы. С точки зрения страницы это прямой вызов JS → C# в процессе IDE. -
Same-Origin Policy и CSP страницы третьей стороны не определяют доступ к host object: политика origin по-прежнему ограничивает сеть страницы, но не отменяет ответственность IDE за то, что именно экспортируется в
windowи какой whitelist применяется на C# стороне. «Обход SOP» касательно сети не даёт права ослаблять allowlist.
2.5. Отложено: присутствие оператора (IsHumanPresent и аналоги)¶
- Вне объёма этого ADR для реализации. Отдельная продуктовая/инженерная линия: детекция «человек у машины», простой, периферия, связь с 0034 — объём работы большой; не входит в M1–M2 дорожной карты §7. Вернуться после стабильного моста (whitelist + Write-confirm). Эвристика присутствия не заменяет явное подтверждение для write-команд.
2.6. Согласие, аудит, исполнитель¶
-
Перед первым использованием моста — явное согласие пользователя на allowlist и режимы; отзыв и пауза моста обязательны.
-
Где рисуется согласие (и чат): основной сценарий — диалог с веб-ИИ идёт во встроенном WebView2, значит онбординг моста, баннер режима и кнопки вроде «разрешить запись на сессию» логично ставить в той же зоне внимания. Допустимые реализации: инъекция в документ (скрипт / оверлей / стартовая обёртка-URL под контролем IDE), либо нативная полоса или модалка вокруг
WebViewв том же слоте (типично MFD). Во всех случаях факт «мост активен / write-all для сессии» хранит и проверяет процесс IDE; отрисовка в WV2 — это UX, а не источник права. -
Исполнение: каждый вызов проходит через тот же контур, что MCP: валидация
command_id, whitelist, класс read/write, маршалинг UI (0004), квоты/таймауты, журналирование без утечки секретов в консоль страницы. -
Сервер истины по аргументам —
IdeCommands+ ProtocolDocGen; дублировать семантику в JS не требуется.
3. Отношение к ADR 0035¶
- 0035 остаётся в силе: произвольная страница без моста по-прежнему не является MCP-клиентом.
- Настоящий ADR описывает исключение по дизайну: мост включается осознанно, с ограниченной поверхностью и тем же исполнителем команд, что и агент IDE — без смешения origin веб-провайдера с полными привилегиями процесса.
4. Угрозы и смягчение (минимум)¶
| Угроза | Смягчение |
|---|---|
| XSS на стороне веб-провайдера выполняет вызовы моста от имени пользователя | Узкий allowlist; опционально подтверждение на чувствительные command_id; отзыв моста |
| Подмена или расширение API через загрузку стороннего скрипта | Мост только на заданных страницах/пресетах; не экспортировать произвольные COM-объекты |
| Утечки пути/контента в облако вендора | Согласовано с 0035 §3: передача контекста в веб — только явные действия пользователя + политика продукта |
| Режим «все write без запроса» (§2.3 п.8) увеличивает blast radius при XSS/захвате сессии страницы | Только после явного opt-in; заметный индикатор в UI; отзыв одним действием; allowlist по-прежнему ограничивает набор команд |
Полный threat model и пентест-план — за рамками этого документа; по мере внедрения M1+ допускается приложение или отдельный security ADR.
5. Отклонённые / отложенные варианты¶
- Полагаться только на копипаст между веб-вкладкой и IDE — достаточно для «второго мнения», недостаточно для цели «прямое управление инструментами» без ручной перекладки каждого шага.
- Дать странице прямой сокет к локальному MCP-серверу — смешивает модель доверия браузера и MCP; отклонено в пользу единого исполнителя внутри процесса IDE.
- Унифицировать веб-ИИ и нативный агент в одну неразличимую сущность в UX — отклонено; пользователь должен понимать, кто исполняет команду (мост под контролем IDE vs внешний хост).
IsHumanPresent/ авто-read-only по отсутствию оператора — отложено (отдельная линия, см. §2.5); не блокирует мост из этого ADR.
6. Последствия¶
- Появляется продуктовая фича: «веб-ипостась + инструменты IDE» без обязательной зависимости от редактора-хоста и без требования локального GPU.
- Реализация тянет WebView, мост, UI согласий, тесты контракта моста ↔
IdeCommands(снапшоты или табличные тесты). - Документация пользователя должна явно отличать: нативный чат / MCP в IDE vs веб-портал с мостом vs обычный внешний браузер.
7. Дорожная карта (норматив минимум)¶
- M0 — PoC транспорта: выполнен (веб → мост → echo в консольный слушатель :8080; см. §1); опционально зафиксировать артефакты вне ADR.
- M1 — Интеграция в продукт:
executeIdeCommand(string json)→IdeMcpCommandExecutor+ whitelist §2.2 + Read / Write-confirm + режим «все write для сессии» (§2.3 п.8) + согласие + логирование (первый реальный вызов инструментов IDE с моста). - M2 — Hardening: метрики злоупотреблений, ревизия whitelist при обновлении
IdeCommands. Политика присутствия (§2.5) — не в M2.
8. Срез реализации (M1)¶
- WebView: пакет
Avalonia.Controls.WebView, контролNativeWebView(Win — WebView2 под капотом), без WinForms. - MFD:
MfdShellPage.WebAiPortal, представлениеViews/WebAiPortalMfdPageView.axaml. - Мост:
Features/WebAiPortal/Application/WebAiPortalCommandBridge.cs→IIdeMcpActions.ExecuteCommandAsyncс whitelist §2.2; транспорт из страницы — канал AvaloniaWebMessageReceived/invokeCSharpAction(тело — тот же JSON сcommand_idиargs). AddHostObjectToScript при необходимости синхронного JS↔C# на Windows — черезTryGetPlatformHandle/ICoreWebView2(см. док. Avalonia «Embedding web content»), не блокирует M1. - Навигация: IDE-команда
show_web_ai_portal_page→ регион MFD + страницаWebAiPortal. - Ручное исполнение (буфер/кнопка): кнопка «Выполнить команду: буфер → последняя на странице» (
WebAiPortalMfdPageView) — (1) clipboard: fencedjson-cascadeили голый JSON{ "command_id", … }(частый кейс «копировать» в UI чата без backtick); (2) иначеNativeWebView.InvokeScript: последний подходящий блок вpre/ голыйcodeв DOM (WebAiPortalLastCommandDomProbe). Далее тот же контур, что иinvokeCSharpAction; результат вWebAiPortalLastBridgeResult. - Hands-free без копипаста и без этой кнопки: чекбокс «Авто: последняя json-cascade на странице → мост (poll…)» при согласии и мост включён (
WebAiPortalMfdPageView):DispatcherTimer~1,1 с выполняет тот же DOM-probe (WebAiPortalLastCommandDomProbe); успешное исполнение дедуплицируется по каноническому JSON (WebAiPortalBridgePayloadDedup); при навигации дедуп сбрасывается — повтор той же команды на новой странице снова возможен. Probe учитывает как fenced``json-cascade, так и **голый** маркерjson-cascade+ перевод строки + JSON (типично для Gemini / ИИ-поиска Google, где нет triple-backtickов в тексте сообщения); поиск выполняется и поdocument.body.innerText, не только поpre/code`. - Не автоматизируется нажатие «Send» в UI вендора: при необходимости остаётся «Подмешать в композитор» или ручная отправка в поле чата сайта (ограничение DOM/политики, не блокер моста).
- Подмешивание ответа IDE в веб-чат (после успешного моста): по флажкам — (1) текст в системный буфер; (2) опционально
InvokeScript/WebAiPortalComposerInjectScriptв фокус страницы. Если ответ очень длинный (типичныйget_editor_stateс большим превью) и включён режим «под лимит чата (~1200)» (по умолчанию): в буфер/композитор идёт не сырой JSON, а компакт с готовымиjson-cascadeдля HCI (codebase_index_search,codebase_index_status,codebase_index_explain) и узких read (get_editor_stateсmax_preview_chars: 0,get_editor_content_rangeоколо каретки). Whitelist моста включаетcodebase_index_statusиcodebase_index_explain(read-only).