Forge
markdowndeeb25a2
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
961. **Исходящие события** — подмножество [0099](0099-ide-databus-typed-events-and-projections.md) / проекций ([0045](0045-agent-chat-persistence-event-log-and-projections.md)): сериализованный DTO, без секретов и без полных путей к `.env` / `ai-keys.toml`.
972. **Входящие команды** — узкий allowlist (`operator.reply`, `operator.approve`, `operator.reject`, `operator.pause_agent`, …), маппинг на существующие контуры IDE (Intercom, PFD confirmations), **не** прямой произвольный `ide_execute_command` с интернета.
983. **Транспорт:** предпочтительно **WebSocket или SSE + REST** на `localhost` с **TLS** и reverse proxy только при явной настройке; default — **loopback** + pairing token.
994. **Паритет восстановления** — по духу [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
1181. Оператор отошёл от рабочей станции; на телефоне открыт пульт (или PWA).
1192. Агент в IDE запросил уточнение или подтверждение.
1203. Оператор **видит** запрос в течение разумного TTL и может **ответить** или **отклонить** без установки полного CIDE на телефон.
1214. Агент **продолжает** на десктопе; в логе аудита есть связка `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
1731. **Встроенный gateway** vs отдельный процесс `cascade-operator-gateway` (проще обновлять, сложнее deploy)?
1742. **Один workspace — несколько remote clients** (семья устройств) или один активный пульт?
1753. **Связь с ACP [0016](0016-agent-client-protocol-external-agent.md):** remote surface управляет только встроенным агентом IDE или также внешним ACP?
1764. **Офлайн:** очередь команд на клиенте vs жёсткий отказ?
1775. Пересечение с **[0034](0034-pilot-incapacitation-emergency-mode-and-presence-sensing.md)** (emergency / presence) — общий канал уведомлений или отдельный?
1786. **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
View only · write via MCP/CIDE