# Контракт интерфейс ↔ движок Mantia (MVP-вертикаль)

Это договор между фронтендом (дизайн — Codex) и локальным движком. Фронтенд —
одностраничное приложение, движок — локальный сервер на `127.0.0.1:8756`.
Фронтенд не ходит в интернет и не знает про CDP: он показывает состояние и
живой поток шагов агента.

## Что видит пользователь

Запускает программу → открывается окно антидетект-браузера Mantia. Слева —
профили (пока один). Справа — композитор задачи: поле «что сделать» словами,
выбор профиля, кнопка «Запустить». Снизу или в центре — **живой лог работы
агента**: что он видит на странице, что решил сделать, что сделал. Плюс живой
эскиз (скриншот) открытого профиля.

Жмёт «Запустить» → открывается настоящее окно Chrome (профиль), и агент
начинает работать: наблюдает страницу, решает действие, выполняет по-человечески.
Каждый шаг течёт в интерфейс в реальном времени.

## HTTP (JSON, `application/json`)

Базовый префикс `/api`.

### `GET /api/state`
Полное состояние для первичной отрисовки.
```json
{
  "product": "Mantia",
  "host": {"os": "Windows", "gpu_class": "software", "cpu_cores": 2,
           "ram_gb": 5.9, "max_profiles": 1, "warnings": ["GPU отсутствует…"]},
  "llm": {"provider": "groq", "model": "llama-3.3-70b-versatile",
          "daily_budget_usd": 20.0, "spent_today_usd": 0.03},
  "profiles": [ <profile>… ],
  "active_run": <run|null>
}
```

### `GET /api/profiles` → `[ <profile> ]`
```json
{
  "id": "prf_01J…",
  "account_ref": "demo@grailed",
  "site": "grailed.com",
  "state": "ready",                         // ready|cooldown|quarantine|running|manual_review
  "chromium_version": "150.0.7871.124",
  "proxy": {"country": "US", "city": null, "exit_ip": "142.252.8.33",
            "kind": "residential", "alive": true},
  "persona": {"timezone": "America/New_York", "locale": "en-US"},
  "coverage": {"webgl": "software", "canvas": "unmanaged", "note": "…"},
  "last_session_at": null
}
```
`coverage` — честная карта того, что реально контролируется на этом хосте (§5.1).
Показывать пользователю, не прятать.

### `POST /api/task/run`
```json
{"profile_id": "prf_…", "goal": "зайди на grailed, найди Stone Island, посмотри выдачу как человек",
 "max_steps": 40, "start_url": "https://www.grailed.com/"}
```
→ `{"run_id": "run_01J…"}` или `4xx {"error": "…"}` (профиль занят, бюджет исчерпан).

### `POST /api/task/stop` `{"run_id": "run_…"}` → `{"ok": true}`
Мягкая остановка: агент доводит текущее действие и закрывает профиль через
`Browser.close` (не kill).

### `GET /api/runs/{run_id}` → полная история шагов (для перезагрузки страницы).

## WebSocket `/api/stream`

Сервер шлёт события по мере работы. Клиент только слушает (команды — по HTTP).
Каждое событие — JSON-объект с полем `type` и `ts` (ISO-8601).

```jsonc
{"type": "run.started", "run_id": "run_…", "profile_id": "prf_…", "goal": "…"}

// Агент сделал снимок страницы (то, что он «видит»)
{"type": "observe", "run_id": "…", "seq": 3, "url": "https://…",
 "page_kind": "search_results", "elements": 22, "snapshot": "url: …\n[e1] …"}

// Модель решила следующее действие
{"type": "decide", "run_id": "…", "seq": 3,
 "action": {"kind": "click", "ref": "e14", "target": "Stone Island Jacket",
            "reason": "открываю первую подходящую карточку"},
 "tokens": {"in": 1840, "out": 62}, "cost_usd": 0.0004}

// Действие выполнено слоем ввода
{"type": "act", "run_id": "…", "seq": 3, "kind": "click",
 "target": "Stone Island Jacket", "verdict": "satisfied", "duration_ms": 1420}

// Живой эскиз профиля (jpeg data-url, ~10 КБ, раз в несколько секунд)
{"type": "frame", "run_id": "…", "image": "data:image/jpeg;base64,…"}

// Человеко-понятная строка для ленты
{"type": "log", "run_id": "…", "level": "info", "message": "Профиль открыт, IP 142.252.8.33"}

// Требуется человек (CAPTCHA / MFA / challenge)
{"type": "intervention", "run_id": "…", "reason": "device_confirmation_required",
 "message": "Площадка запросила подтверждение устройства"}

{"type": "run.finished", "run_id": "…", "status": "done",   // done|partial|failed|needs_attention|stopped
 "reason": "completed", "stats": {"steps": 12, "clicks": 3, "llm_calls": 12,
 "cost_usd": 0.005, "duration_s": 94}}
```

## Заметки для дизайна

- **Тон.** Инструмент профессионала: плотная информация, тёмная тема уместна,
  но не обязательна. Не «игрушка».
- **Живой лог — герой экрана.** Пользователь должен видеть, что агент делает
  осмысленные вещи, а не «крутится спиннер».
- **Эскиз профиля.** `frame`-события дают маленький скриншот — показывать как
  живое превью открытого окна.
- **Честность по железу.** Блок host.warnings и profile.coverage показывать,
  а не прятать: продукт продаёт результат, а не иллюзию (§5).
- **Один профиль сейчас.** Массовость (сетка профилей) — потом; заложить место
  под список, но не делать вид, что их много.
- Всё на русском в интерфейсе (пользователь русскоязычный).
- Фронт статический: один `index.html` со встроенными CSS и JS, без сборки и
  без внешних CDN (движок отдаёт файл как есть).
