| 1 | # ADR 0117: Remote operator surface — мультидевайсность оператора (пульт, не mobile IDE) |
| 2 | |
| 3 | **Статус:** Proposed |
| 4 | **Дата:** 2026-05-16 |
| 5 | **Обновлено:** 2026-05-16 — клиент remote surface: **PWA** (каноничный выбор). |
| 6 | |
| 7 | ## Связанные ADR |
| 8 | |
| 9 | | ADR | Роль | |
| 10 | |-----|------| |
| 11 | | [0017](0017-multi-window-workspace-and-agent-surfaces.md) | Мультиоконность на одной станции (**не** remote) | |
| 12 | | [0108](0108-web-ai-portal-host-object-tools-bridge.md) | Веб в MFD → `IdeCommands` (ортогонально) | |
| 13 | | [0035](0035-mfd-embedded-webview-external-llm-and-mcp-boundary.md) | Граница доверия веб ↔ MCP | |
| 14 | | [0016](0016-agent-client-protocol-external-agent.md) | Внешний агент по ACP | |
| 15 | | [0031](0031-agent-chat-clarification-batches-and-threading.md) | Уточнения Intercom vs PFD | |
| 16 | | [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | `command_id`, подтверждения | |
| 17 | | [0008](0008-mcp-contracts-and-testable-infrastructure.md) | `IdeCommands`, MCP | |
| 18 | | [0043](0043-mcp-transport-recovery-human-agent-parity.md) | Восстановление транспорта | |
| 19 | | [0099](0099-ide-databus-typed-events-and-projections.md) | События IDE → проекции | |
| 20 | | [0045](0045-agent-chat-persistence-event-log-and-projections.md) | История чата / события | |
| 21 | |
| 22 | ## Резюме |
| 23 | |
| 24 | - **Remote operator:** PWA-пульт с телефона/другого ПК, Operator Gateway. |
| 25 | - Не mobile IDE; дополняет мультиоконность ([0017](0017-multi-window-workspace-and-agent-surfaces.md)). |
| 26 | - Связь с web-portal bridge ([0108](0108-web-ai-portal-host-object-tools-bridge.md)). |
| 27 | |
| 28 | **Вне репо:** vision **agent-forge** (худший сценарий, API + браузер) — личный канон `agent-notes`. |
| 29 | |
| 30 | --- |
| 31 | |
| 32 | ## 1. Контекст |
| 33 | |
| 34 | **Силы:** |
| 35 | |
| 36 | - Оператор (человек) работает не только за **одним** рабочим столом: второй ПК, планшет, телефон — для **наблюдения**, **ответа агенту**, **подтверждений**, смены режима WORK/HUMAN, без переноса полного IDE. |
| 37 | - **[ADR 0017](0017-multi-window-workspace-and-agent-surfaces.md)** решает **мультимониторность на одной станции** (`MainWindow`, `MfdHostWindow`, пресеты `presentation`). Это **не** сессия «ушёл из кабинета — продолжил с телефона». |
| 38 | - **[ADR 0108](0108-web-ai-portal-host-object-tools-bridge.md)** даёт **встроенный** веб в MFD с мостом к IDE для **внешней** веб-модели. Это не **пульт оператора** с другого устройства и не замена нативного Intercom. |
| 39 | - Линия **Friction / environment-first:** удобство меряется по **худшему пути** (слабая сеть, только браузер, первый раз). Если там нельзя **увидеть статус** и **принять решение**, а тяжёлое закрытие задачи возможно только за десктопом — это приемлемо **только** если десктоп явно в контуре; иначе оператор на втором устройстве — «второй сорт». |
| 40 | |
| 41 | **Проблема:** без явной архитектуры легко скатиться в (a) **mobile IDE** — нереалистичный паритет Roslyn/отладки; (b) **голый чат** без привязки к сессии IDE; (c) **небезопасный** открытый порт без pairing. |
| 42 | |
| 43 | --- |
| 44 | |
| 45 | ## 2. Решение (намерение) |
| 46 | |
| 47 | ### 2.1. Три контура (разделение ответственности) |
| 48 | |
| 49 | | Контур | Где | Назначение | |
| 50 | |--------|-----|------------| |
| 51 | | **Тяжёлый (cockpit)** | CascadeIDE на рабочей станции | редактор, Roslyn, build, отладка, полный HUD, MCP в процессе | |
| 52 | | **Лёгкий (remote operator surface)** | **PWA** на другом устройстве (телефон, планшет, второй ПК) | статус сессии, Intercom (ограниченно), approve/reject, pause/stop, уведомления «агент ждёт» | |
| 53 | | **Машинный** | git, MCP remote, CI, Issues | закрытие задачи без UI; агент и автоматизация | |
| 54 | |
| 55 | **Принцип:** remote surface **не** дублирует cockpit. Он **наблюдает и решает**; исполнение тяжёлого — на станции с IDE или через уже существующие **MCP / `IdeCommands`** (с политикой на gateway). |
| 56 | |
| 57 | ### 2.2. Remote operator surface (ROS) — scope v1 |
| 58 | |
| 59 | **Входит (целевая фаза 1–2):** |
| 60 | |
| 61 | - Подписка на **снимок состояния** сессии: workspace id, активная тема/тред Intercom (кратко), флаг «ожидает человека», последние N сообщений агента, IDE health / failed step (без утечки полного workspace tree). |
| 62 | - Действия оператора с **явным подтверждением** на десктопе при риске: ответ в чат (follow-up), approve/reject для зон «только человек» ([0030](0030-command-ids-hotkeys-and-ui-registry-layers.md), pre-flight — [0042](0042-pre-flight-planned-changes-and-review-before-apply.md) когда появится). |
| 63 | - **Push / pull уведомления:** «агент заблокирован на вопросе» (интеграция с внешним каналом — Telegram, email — **вне** ядра ADR, через адаптер). |
| 64 | - **Pairing** удалённого клиента с инстансом IDE (одноразовый код, TTL, отзыв). |
| 65 | |
| 66 | **Не входит в v1:** |
| 67 | |
| 68 | - Полноценный редактор кода в браузере. |
| 69 | - Произвольный вызов всех `IdeCommands` с телефона без allowlist. |
| 70 | - Публичный облачный relay без E2E / без привязки к паре устройств. |
| 71 | - **Нативные** companion-приложения (iOS/Android/desktop) — отложены; при необходимости позже как оболочка над тем же API gateway. |
| 72 | |
| 73 | ### 2.2.1. Клиент: PWA (канон) |
| 74 | |
| 75 | **Решение:** remote operator surface реализуется как **Progressive Web App**, а не как отдельные нативные приложения и не как «просто вкладка без install». |
| 76 | |
| 77 | **Почему PWA:** |
| 78 | |
| 79 | | Критерий | PWA | |
| 80 | |----------|-----| |
| 81 | | Один артефакт | телефон, планшет, второй ПК — один UI (HTML/CSS/TS), без store-релизов | |
| 82 | | Friction / худший путь | «открыл в браузере → Add to Home Screen» без установки CIDE | |
| 83 | | Транспорт | тот же **Operator Gateway** (SSE/WebSocket + REST); PWA — только клиент | |
| 84 | | Уведомления (фаза 3) | **Web Push** + service worker при явном согласии; fallback — pull/SSE | |
| 85 | | Офлайн | service worker: кэш оболочки UI + последний snapshot; **команды** при offline — очередь или отказ (см. §7) | |
| 86 | | Безопасность | origin пульта = origin gateway (loopback или VPN); **не** смешать с [0108](0108-web-ai-portal-host-object-tools-bridge.md) | |
| 87 | |
| 88 | **Поставка:** статика PWA раздаётся **с того же хоста**, что Operator Gateway (например `Features/OperatorGateway/www/` или embedded `wwwroot`), чтобы не плодить CORS и второй порт без нужды. После pairing пользователь сохраняет ярлык «Cascade Operator». |
| 89 | |
| 90 | **Минимальный UX v1:** одна страница «сессия» (статус + лента + поле ответа); mobile-first layout; без сложной навигации кокпита. |
| 91 | |
| 92 | ### 2.3. Транспорт и граница (Operator Gateway) |
| 93 | |
| 94 | Между **CascadeIDE (host)** и **remote client** — логический **Operator Gateway** (процесс или встроенный модуль): |
| 95 | |
| 96 | 1. **Исходящие события** — подмножество [0099](0099-ide-databus-typed-events-and-projections.md) / проекций ([0045](0045-agent-chat-persistence-event-log-and-projections.md)): сериализованный DTO, без секретов и без полных путей к `.env` / `ai-keys.toml`. |
| 97 | 2. **Входящие команды** — узкий allowlist (`operator.reply`, `operator.approve`, `operator.reject`, `operator.pause_agent`, …), маппинг на существующие контуры IDE (Intercom, PFD confirmations), **не** прямой произвольный `ide_execute_command` с интернета. |
| 98 | 3. **Транспорт:** предпочтительно **WebSocket или SSE + REST** на `localhost` с **TLS** и reverse proxy только при явной настройке; default — **loopback** + pairing token. |
| 99 | 4. **Паритет восстановления** — по духу [0043](0043-mcp-transport-recovery-human-agent-parity.md): remote client при обрыве показывает «сессия недоступна», не симулирует успех. |
| 100 | |
| 101 | **Связь с [0108](0108-web-ai-portal-host-object-tools-bridge.md):** Web AI Portal — **чужой origin** в WebView2; ROS-PWA — **свой** origin gateway, отдельный allowlist. Не смешивать портал и пульт. |
| 102 | |
| 103 | ### 2.4. Отличие от мультиоконности [0017](0017-multi-window-workspace-and-agent-surfaces.md) |
| 104 | |
| 105 | | | ADR 0017 | ADR 0117 | |
| 106 | |---|----------|----------| |
| 107 | | Устройства | один ПК, N мониторов | N устройств, 1 «тяжёлая» станция | |
| 108 | | UI | Avalonia `TopLevel` | **PWA** (канон) | |
| 109 | | Синхронизация | общий процесс, общий `DataContext` | сеть / loopback gateway | |
| 110 | | Критерий | раскладка кокпита | наблюдение + решения вне кабины | |
| 111 | |
| 112 | Оба могут работать **одновременно** (второй монитор + телефон как пульт). |
| 113 | |
| 114 | ### 2.5. Критерий Friction (негативный сценарий) |
| 115 | |
| 116 | Считаем ROS успешным для сценария **S**: |
| 117 | |
| 118 | 1. Оператор отошёл от рабочей станции; на телефоне открыт пульт (или PWA). |
| 119 | 2. Агент в IDE запросил уточнение или подтверждение. |
| 120 | 3. Оператор **видит** запрос в течение разумного TTL и может **ответить** или **отклонить** без установки полного CIDE на телефон. |
| 121 | 4. Агент **продолжает** на десктопе; в логе аудита есть связка `operator_device_id` + действие. |
| 122 | |
| 123 | Если для S доступен только «открой ноутбук с IDE» — ROS **не** снял трение для этого класса задач. |
| 124 | |
| 125 | --- |
| 126 | |
| 127 | ## 3. Фазы (дорожная карта) |
| 128 | |
| 129 | | Фаза | Содержание | Критерий готовности | |
| 130 | |------|------------|---------------------| |
| 131 | | **0** | Этот ADR; перечень DTO и allowlist в чертеже | Согласованы границы с 0017/0108/0031 | |
| 132 | | **1** | Read-only: gateway + **PWA** (manifest, service worker shell); snapshot + SSE | PWA на loopback: статус и «ждёт оператора» | |
| 133 | | **2** | Write + pairing; installable PWA на телефоне в LAN | Сценарий S проходит | |
| 134 | | **3** | Web Push / адаптеры; опционально TLS + VPN до gateway | Оператор вне LAN с защищённым каналом | |
| 135 | |
| 136 | **MVP-ноль (вне кода CIDE):** async через Issues / org Discussions / Telegram-relay MCP — **не** заменяет ROS, допустим как временный обход. |
| 137 | |
| 138 | --- |
| 139 | |
| 140 | ## 4. Безопасность и доверие |
| 141 | |
| 142 | - **Секреты** не передаются на remote client; `ai-keys.toml` и MCP-токены остаются на станции IDE. |
| 143 | - **Pairing:** короткоживущий код; список отозванных устройств; по умолчанию gateway слушает **127.0.0.1**. |
| 144 | - **Allowlist команд** на gateway — отдельный от [0108](0108-web-ai-portal-host-object-tools-bridge.md) и от полного MCP. |
| 145 | - **Аудит:** каждое remote-действие логируется с `principal=human`, `channel=remote_operator`, `device_id`. |
| 146 | - **Не** экспонировать gateway в интернет без явной политики org (VPN, mTLS). |
| 147 | |
| 148 | --- |
| 149 | |
| 150 | ## 5. Альтернативы (отклонены или отложены) |
| 151 | |
| 152 | | Альтернатива | Почему не целевой путь | |
| 153 | |--------------|------------------------| |
| 154 | | **Полный web IDE** | Паритет Roslyn/отладки; огромное трение; дублирует cockpit | |
| 155 | | **Только RDP/VNC на десктоп** | Работает, но не mobile-friendly; не снижает трение сценария S | |
| 156 | | **Публичный MCP endpoint** без слоёв | Риск утечки workspace; см. backlog remote MCP в agent-notes | |
| 157 | | **Единый чат-бот без привязки к IDE** | Нет истины сессии; галлюцинация «я сделал» | |
| 158 | | **Нативный companion (v1)** | Два контура поддержки; PWA закрывает сценарий S; native — при доказанной необходимости | |
| 159 | |
| 160 | --- |
| 161 | |
| 162 | ## 6. Последствия |
| 163 | |
| 164 | - Появится новый **feature slice** (например `Features/OperatorGateway/`): host service, DTO, allowlist, **статика PWA** (`manifest.webmanifest`, service worker, UI). |
| 165 | - Потребуются **тесты контракта** snapshot для wire-DTO ([0008](0008-mcp-contracts-and-testable-infrastructure.md), [0052](0052-agent-contract-cli-and-snapshot-tests.md)). |
| 166 | - **Intercom / 0031 / 0042:** remote approve должны сходиться с теми же машинами состояний, что PFD/MFD на десктопе — не второй параллельный «источник правды». |
| 167 | - Документация оператора: отдельная страница в handbook/kb-public **после** фазы 1 (не в этом ADR). |
| 168 | |
| 169 | --- |
| 170 | |
| 171 | ## 7. Открытые вопросы |
| 172 | |
| 173 | 1. **Встроенный gateway** vs отдельный процесс `cascade-operator-gateway` (проще обновлять, сложнее deploy)? |
| 174 | 2. **Один workspace — несколько remote clients** (семья устройств) или один активный пульт? |
| 175 | 3. **Связь с ACP [0016](0016-agent-client-protocol-external-agent.md):** remote surface управляет только встроенным агентом IDE или также внешним ACP? |
| 176 | 4. **Офлайн:** очередь команд на клиенте vs жёсткий отказ? |
| 177 | 5. Пересечение с **[0034](0034-pilot-incapacitation-emergency-mode-and-presence-sensing.md)** (emergency / presence) — общий канал уведомлений или отдельный? |
| 178 | 6. **Web Push:** VAPID и доставка через gateway vs только внешний адаптер (Telegram) в фазе 2? |
| 179 | |
| 180 | --- |
| 181 | |
| 182 | ## 8. Статус реализации |
| 183 | |
| 184 | | Область | Состояние | |
| 185 | |---------|-----------| |
| 186 | | ADR / границы | этот документ | |
| 187 | | Operator Gateway в коде | нет | |
| 188 | | PWA (manifest + SW + UI) | нет | |
| 189 | | Pairing | нет | |
| 190 | | Интеграция DataBus / Intercom | нет | |
| 191 | |
| 192 | При появлении кода — обновить §8 и статус ADR на **Accepted · Implemented** (по [status-lifecycle.md](status-lifecycle.md)). |
| 193 | |