Перейти к содержанию

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)

  1. Один метод на границе Host Object — без зеркала «по одному методу на каждый ide_* тул». Рекомендуемая сигнатура для AddHostObjectToScript: executeIdeCommand(string json) (или эквивалент с тем же телом запроса).

  2. Тело JSON — тот же контракт, что у диспетчера MCP: объект с command_id и args (как в MCP-PROTOCOL.md / разбор IdeMcpServer: возможен верхнеуровневый merge с вложенным args). Парсинг, валидация и маршрутизация остаются только в C#; со стороны веб-страницы — один простой интерфейс.

  3. Транспорт 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)

  1. Главный предохранитель безопасности — стабильный whitelist разрешённых command_id. До отладки контура команды вне списка отклоняются на стороне IDE до вызова исполнителя.

  2. Стартовый набор 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

  1. Read-only: команды, классифицированные как чтение/наблюдение (в PoC — весь whitelist §2.2), выполняются без дополнительного диалога (после первичного включения моста и согласия на allowlist).

  2. Write-confirm (по умолчанию): любая команда, классифицированная как изменение (запись файла, git с побочными эффектами, сборка, удаление и т.д.), не выполняется молча: IDE показывает явное подтверждение (модальное окно или эквивалентный тост с действием «Разрешить / Отклонить») с понятным описанием намерения («агент запрашивает X»). Это фиксирует модель «предложил → человек подтвердил».

  3. Write без подтверждения на каждый шаг (must have для потока): подтверждать каждый write отдельно — неприемлемо для длительной работы. Должна быть отдельная, явная опция уровня «разрешить все write для этой сессии моста» / «доверять записи от этого моста (до отзыва)» — по смыслу как Run everything / аналог в Cursor: один осознанный opt-in, после чего write-команды из allowlist выполняются без модалки на каждый вызов, пока пользователь не отключит режим, не отзовёт мост или не закроет сессию (конкретика UX — продукт, ограничения по времени/scope — по настройкам). Дефолт для новичков остаётся подтверждение на каждую write или узкий режим.

  4. Классификация read vs write — в конфигурации моста (таблица или атрибут на command_id); расширение whitelist обязано сопровождаться классификацией, иначе по умолчанию — write-confirm (безопасный fallback).

2.4. Технические допущения: WebView2 и ограничения «обычного веба»

  1. Встраивание и размещение: встроенный портал = контролируемый WebView2 (или эквивалент) внутри IDE. Каноничный слот для длительного «веб-чат + мост» — MFD (вторичный контур внимания по 0021; согласуется с 0035 — встроенный браузер как осознанная зона, а не четвёртый «лобовой» семантический якорь). PFD под постоянный веб-чат не резервируем. Альтернатива — отдельное окно / вторичная поверхность по 0017 и пресетам UI (0010).

  2. Инъекция моста: взаимодействие с инструментами IDE осуществляется через нативную публикацию объекта в JavaScript (например AddHostObjectToScript) — вызов идёт в объекте, добавленном хостом, а не через postMessage между окнами и не через сетевой стек страницы. С точки зрения страницы это прямой вызов JS → C# в процессе IDE.

  3. Same-Origin Policy и CSP страницы третьей стороны не определяют доступ к host object: политика origin по-прежнему ограничивает сеть страницы, но не отменяет ответственность IDE за то, что именно экспортируется в window и какой whitelist применяется на C# стороне. «Обход SOP» касательно сети не даёт права ослаблять allowlist.

2.5. Отложено: присутствие оператора (IsHumanPresent и аналоги)

  1. Вне объёма этого ADR для реализации. Отдельная продуктовая/инженерная линия: детекция «человек у машины», простой, периферия, связь с 0034 — объём работы большой; не входит в M1–M2 дорожной карты §7. Вернуться после стабильного моста (whitelist + Write-confirm). Эвристика присутствия не заменяет явное подтверждение для write-команд.

2.6. Согласие, аудит, исполнитель

  1. Перед первым использованием моста — явное согласие пользователя на allowlist и режимы; отзыв и пауза моста обязательны.

  2. Где рисуется согласие (и чат): основной сценарий — диалог с веб-ИИ идёт во встроенном WebView2, значит онбординг моста, баннер режима и кнопки вроде «разрешить запись на сессию» логично ставить в той же зоне внимания. Допустимые реализации: инъекция в документ (скрипт / оверлей / стартовая обёртка-URL под контролем IDE), либо нативная полоса или модалка вокруг WebView в том же слоте (типично MFD). Во всех случаях факт «мост активен / write-all для сессии» хранит и проверяет процесс IDE; отрисовка в WV2 — это UX, а не источник права.

  3. Исполнение: каждый вызов проходит через тот же контур, что MCP: валидация command_id, whitelist, класс read/write, маршалинг UI (0004), квоты/таймауты, журналирование без утечки секретов в консоль страницы.

  4. Сервер истины по аргументам — 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. Дорожная карта (норматив минимум)

  1. M0 — PoC транспорта: выполнен (веб → мост → echo в консольный слушатель :8080; см. §1); опционально зафиксировать артефакты вне ADR.
  2. M1 — Интеграция в продукт: executeIdeCommand(string json)IdeMcpCommandExecutor + whitelist §2.2 + Read / Write-confirm + режим «все write для сессии» (§2.3 п.8) + согласие + логирование (первый реальный вызов инструментов IDE с моста).
  3. 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.csIIdeMcpActions.ExecuteCommandAsync с whitelist §2.2; транспорт из страницы — канал Avalonia WebMessageReceived / 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: fenced json-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).