# Фаза 4 — исполнитель на Mac + `~/agents` + секция «Agents» в Eagle Dashboard > План + статус. Старт 2026-09-26. > Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фазы 4-5). > Предыдущее: [[personal/projects/balda/balda-kraken-mm-deploy]] (Фаза 3 ✅ — бот отвечает Alex). --- ## 0. Статус шагов | # | Шаг | Статус | |---|---|---| | 1 | `~/agents` + `~/cli-auth` + контейнер | ✅ собрано, запущено | | 2 | Мост FastAPI :8790 | ✅ работает под launchd | | 3 | Сеть (Cloudflare Tunnel) | ⏳ **на Alex** — CF-панель | | 4 | Секция «Agents» в Dashboard | ✅ API + вкладка | | 5 | Связка Балда → мост | ⏳ не начато | | 6 | Приёмка от аккаунта Alex | 🔴 блокер: авторизация CLI | --- ## 1. Решения | # | Вопрос | Решение | |---|---|---| | 1 | Папка для проектов исполнителя | `~/agents` | | 2 | Подтверждения | Full access → `permissions.mode: allow_all` | | 3 | Управление линками в `~/agents` | Секция «Agents» в Eagle Dashboard | | 4 | Auth CLI (claude/codex) | на хосте, `~/cli-auth`, **вне** `~/agents`; логинит **Alex вручную** | | 5 | Сеть Kraken → Mac | Cloudflare Tunnel (существующий cloudflared на Mac) | | 6 | Провайдер исполнителя | `claude` (дефолт в `.env`); `codex` собирается в том же образе, переключается переменной | --- ## 2. Ограничения macOS Docker (проверено тестами) | Ограничение | Итог | |---|---| | Симлинк на внешний путь | битый в контейнере — цель резолвится от корня контейнера | | Live `mount --bind` внутри VM | контейнер не видит (virtiofs строит маппинг на старте) | | Сетевая шара SMB/NFS | в контейнер не протянуть (тот же virtiofs) | | Hardlink на каталог | физически невозможен (ядро запрещает) | | Hardlink через границу ФС | `EXDEV` | **Следствие:** внешние проекты подключаются **только bind-mount'ом через `-v`** + `docker-compose up -d` (2-3 сек). Hardlink — только для файлов внутри одного тома. Детали механизма: [[personal/tech/macos-docker-virtiofs-mount-limits]]. --- ## 3. Архитектура (собрано) ``` Mattermost (Alex) │ «почини баг X в project-y» ▼ Балда-оркестратор (Kraken, DeepSeek, allow_all) │ читает Obsidian, формирует ТЗ, /goalkeeper → work → validate ▼ Cloudflare Tunnel: agents-bridge.qentra.top → localhost:8790 ⏳ │ (существующий cloudflared на Mac, PID + LaunchAgent) ▼ Мост (Mac, FastAPI :8790) — под launchd com.agents.bridge │ idempotency key, таймаут 900 с, лимит 1 задача, shared secret ▼ Контейнер-исполнитель balda-executor (Mac, Docker) │ -v ~/agents:/agents:rw │ -v ~/Developer/:/agents/:rw ← внешние проекты (override) │ -v ~/cli-auth/claude:/root/.claude:rw ← auth на хосте │ -v ~/cli-auth/codex:/root/.codex:rw ← auth на хосте │ Node 24 + claude 2.1.283 + codex 0.157.1 + git ▼ diff + тесты + коммит → Балда → Mattermost ``` ### Auth CLI на хосте | Данные | Хост | В контейнере | Права | |---|---|---|---| | Claude auth | `~/cli-auth/claude` | `/root/.claude` | 0700 | | Codex auth | `~/cli-auth/codex` | `/root/.codex` | 0700 | Пересборка образа и обновление CLI auth не сбрасывают. Токены вне `/agents` → агент их не видит. --- ## 4. Что собрано на диске | Путь | Что | |---|---| | `~/agents/` | рабочая область исполнителя | | `~/cli-auth/{claude,codex}` | 0700, монтируются в контейнер | | `~/Docker/balda-executor/Dockerfile` | Node 24 + claude + codex + git + tini + CA | | `~/Docker/balda-executor/docker-compose.yml` | 4 тома, сеть `executor-net`, build-секция | | `~/Docker/balda-executor/.env` | `EXECUTOR_PROVIDER=claude`, 0600 | | `~/Docker/balda-executor/agents-mounts.yaml` | манифест внешних проектов | | `~/Docker/balda-executor/tools/render-mounts.py` | манифест → `compose.override.yaml` | | `~/Developer/agents-bridge/` | мост: `app.py`, `store.py`, `executor.py`, `config.py` | | `~/Library/LaunchAgents/com.agents.bridge.plist` | автозапуск + KeepAlive | | `~/Developer/eagle-dash/agents.py` | роутер секции «Agents» | **Известные питфоллы (пройдены):** - `docker compose` в этом окружении **отсутствует** — только `docker-compose` (v5.1.3); - `compose.override.yaml` обязателен при старте, иначе `up` падает на монтировании; - компайл: `node:24-bookworm` вместо `scratch` (CA-сертификаты — иначе TLS-фейл); - `psutil` не установлен в venv дашборда → supervisor не может трекать мост по PID; health для моста — `type: http`. --- ## 5. Секция «Agents» в Eagle Dashboard Правки: `main.py` (include_router) · `agents.py` (новый роутер) · `templates/index.html` (вкладка `?tab=agents`) · `services.yaml` (сервис `agents_bridge`). | Функция | Что делает | |---|---| | Список | содержимое `~/agents`: имя, размер, mtime, статус линка | | Создать | `mkdir ~/agents/` | | Подключить внешний | запись в `agents-mounts.yaml` → `render-mounts.py` → `up -d` | | Hardlink | `ln -F` для файлов, проверка `st_dev` | | Отключить | убрать из манифеста + `up -d`; **файлы на диске целы** | | Статус моста | `/api/agents/bridge` — provider, container, active, limit, pid, secret | | Терминал | ссылка на вкладку `files` | **API:** `GET/POST /api/agents` · `POST|DELETE /api/agents/{name}/mount` · `POST /api/agents/{name}/hardlink` · `GET /api/agents/{name}/usage` · `GET /api/agents/mounts` · `POST /api/agents/mounts/render` · `GET /api/agents/bridge`. **Не делает:** не удаляет файлы, не убивает процессы, не создаёт symlink на внешние цели. ### Приёмка секции (2026-09-26) — 13 проверок, все прошли | Проверка | Результат | |---|---| | создание агента | 201 ✅ | | повторное создание | 409 ✅ | | подключение внешнего проекта | 200 + контейнер Recreated ✅ | | проект виден внутри контейнера | да, `ls /agents/testproj` → файлы ✅ | | отвязка | 200; внутри контейнера пусто, на хосте файлы целы ✅ | | hardlink на каталог | 400 (ядро запрещает) ✅ | | hardlink на файл в том же томе | 200, `links=2`, одинаковый inode ✅ | | слишком широкий `host_path` | 400 ✅ | | размер каталога | OK ✅ | | `?tab=agents` рендерится | 200, 75 КБ ✅ | | JS-целостность шаблона | скобки парны, 8 методов на месте ✅ | | Alpine в логе дашборда | ошибок нет ✅ | --- ## 6. Мост `agents-bridge` `~/Developer/agents-bridge/`. Запуск под launchd: `com.agents.bridge`, лог `~/Library/Logs/agents-bridge.err.log`. | Эндпоинт | Назначение | |---|---| | `GET /healthz` | живость моста + контейнера | | `POST /task` | поставить задачу `{prompt, provider?}` | | `GET /task/{id}` | статус + stdout/stderr/exit_code | | `GET /tasks` | последние 20 задач | Безопасность: заголовок `X-Bridge-Secret` (иначе 401), `Idempotency-Key` (повтор возвращает тот же `task_id`), лимит 1 одновременная задача (иначе 429), таймаут 900 с, обрезка вывода 200 КБ. ### Приёмка моста (2026-09-26) — 6 проверок, все прошли | Проверка | Результат | |---|---| | без секрета / неверный секрет | 401 / 401 ✅ | | два POST с одним `Idempotency-Key` | одинаковый `task_id` ✅ | | задача дошла до CLI в контейнере | да ✅ | | ответ CLI вернулся (`Not logged in · Please run /login`) | да — это ответ Claude Code, конвейер рабочий ✅ | | состояние в SQLite (WAL) | ✅ | | `/healthz` видит контейнер | `status: ok` ✅ | **Блокер приёмки:** CLI внутри контейнера не авторизован. Логинит **Alex вручную**: `claude` / `codex` внутри контейнера, credentials кладутся в `~/cli-auth/{claude,codex}`. --- ## 7. Сеть (шаг 3) — на Alex Cloudflare Tunnel: добавить published route `agents-bridge.qentra.top` → HTTP `localhost:8790` + Access Application. Токен и процесс cloudflared не трогаются. ⚠️ hostname — **одноуровневый** (двухуровневые дают TLS handshake failure). Проверка после настройки: `curl https://agents-bridge.qentra.top/healthz`. --- ## 8. Риски | Риск | Оценка | Митигация | |---|---|---| | Мост опубликован в интернет | высокий | Access Application + shared secret | | Полный доступ агента к `~/agents` | средний | бэкапы; `git rm`-семантика вместо `rm` | | Бан подписки за «unusual traffic» | средний | 1 параллельная задача, официальный бинарь, без прокси токена | | Hardlink через ФС → `EXDEV` | высокий | проверка `st_dev` в API | | Mac спит → туннель и мост недоступны | средний | задачи в durable-очереди Балды, идемпотентность | --- ## 9. Открыто 1. Cloudflare Tunnel (шаг 3) — нужен доступ к CF-панели 2. Связка Балда → мост (шаг 5) — MCP-инструмент 3. Логин CLI (Alex вручную) — без него сквозная приёмка невозможна ## Связанные заметки - [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5) - [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅) - [[personal/plans/balda-mm-pr-plan]] — Фаза 2 (PR #8) - [[personal/tech/macos-docker-virtiofs-mount-limits]] — ограничения macOS Docker - [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы, дельта-апплаер - [[family/how-to/kraken-portainer-access]] — cloudflared, routes - скилл `eagle-dashboard` — архитектура дашборда