[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/tech/macos-docker-virtiofs-mount-limits.md
This commit is contained in:
1 parent
1eecc3011e
commit
5163c1c251
2 files changed
+149
-247
No files matched your search
@@ -1,27 +1,34 @@
|
||||
# Фаза 4 — исполнитель на Mac + `~/agents` + секция «Agents» в Eagle Dashboard
|
||||
|
||||
> План. **Шаги 1-3 выполнены** (2026-09-26): образ собран, контейнер `balda-executor` Up (healthy),
|
||||
> `claude 2.1.283` + `codex 0.157.1` внутри, монтирования проверены в обе стороны.
|
||||
> Мост `agents-bridge` :8790 — под **launchd** (`com.agents.bridge`, `RunAtLoad` + `KeepAlive`),
|
||||
> протестирован (auth 401, идемпотентность, сквозной вызов CLI).
|
||||
> Шаг 4 — **код написан, не активирован** (нужен перезапуск дашборда).
|
||||
> 🔴 Осталось: **логин CLI делает Alex сам**; активация секции «Agents»; шаг 3 (туннель);
|
||||
> шаги 5-6.
|
||||
> План + статус. Старт 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` |
|
||||
| 3 | Управление линками в `~/agents` | Секция «Agents» в Eagle Dashboard |
|
||||
| 4 | Auth CLI (claude/codex) | на хосте, `~/cli-auth`, **вне** `~/agents`; логинит **Alex вручную** |
|
||||
| 5 | Сеть Kraken → Mac | Cloudflare Tunnel (существующий cloudflared на Mac) |
|
||||
| 6 | Провайдер исполнителя (открытый п.1) | `claude` — оба CLI стоят в образе, переключение через `EXECUTOR_PROVIDER` в `.env` |
|
||||
| 6 | Провайдер исполнителя | `claude` (дефолт в `.env`); `codex` собирается в том же образе, переключается переменной |
|
||||
|
||||
---
|
||||
|
||||
@@ -36,13 +43,13 @@
|
||||
| Hardlink через границу ФС | `EXDEV` |
|
||||
|
||||
**Следствие:** внешние проекты подключаются **только bind-mount'ом через `-v`** +
|
||||
`docker compose up -d` (2-3 сек). Hardlink — только для файлов внутри одного тома.
|
||||
`docker-compose up -d` (2-3 сек). Hardlink — только для файлов внутри одного тома.
|
||||
|
||||
Детали механизма: [[personal/tech/macos-docker-virtiofs-mount-limits]].
|
||||
|
||||
---
|
||||
|
||||
## 3. Архитектура
|
||||
## 3. Архитектура (собрано)
|
||||
|
||||
```
|
||||
Mattermost (Alex)
|
||||
@@ -51,18 +58,18 @@ Mattermost (Alex)
|
||||
Балда-оркестратор (Kraken, DeepSeek, allow_all)
|
||||
│ читает Obsidian, формирует ТЗ, /goalkeeper → work → validate
|
||||
▼
|
||||
Cloudflare Tunnel: agents-bridge.qentra.top → localhost:8790
|
||||
Cloudflare Tunnel: agents-bridge.qentra.top → localhost:8790 ⏳
|
||||
│ (существующий cloudflared на Mac, PID + LaunchAgent)
|
||||
▼
|
||||
Мост (Mac, FastAPI :8790)
|
||||
│ idempotency key, таймаут, лимит 1-2 параллельных задач, shared secret
|
||||
Мост (Mac, FastAPI :8790) — под launchd com.agents.bridge
|
||||
│ idempotency key, таймаут 900 с, лимит 1 задача, shared secret
|
||||
▼
|
||||
Контейнер-исполнитель (Mac, Docker)
|
||||
Контейнер-исполнитель balda-executor (Mac, Docker)
|
||||
│ -v ~/agents:/agents:rw
|
||||
│ -v ~/Developer/<proj>:/agents/<name>:rw ← внешние проекты
|
||||
│ -v ~/Developer/<proj>:/agents/<name>:rw ← внешние проекты (override)
|
||||
│ -v ~/cli-auth/claude:/root/.claude:rw ← auth на хосте
|
||||
│ -v ~/cli-auth/codex:/root/.codex:rw ← auth на хосте
|
||||
│ Node 24 + codex + claude + git
|
||||
│ Node 24 + claude 2.1.283 + codex 0.157.1 + git
|
||||
▼
|
||||
diff + тесты + коммит → Балда → Mattermost
|
||||
```
|
||||
@@ -73,274 +80,136 @@ diff + тесты + коммит → Балда → Mattermost
|
||||
|---|---|---|---|
|
||||
| Claude auth | `~/cli-auth/claude` | `/root/.claude` | 0700 |
|
||||
| Codex auth | `~/cli-auth/codex` | `/root/.codex` | 0700 |
|
||||
| OAuth-токен Claude | `~/Docker/balda-executor/.env` | env | 0600 |
|
||||
|
||||
📌 **Авторизацию CLI делает Alex сам** (решение 2026-09-26). Копирование auth-файлов из
|
||||
`~/.claude`/`~/.codex` в `~/cli-auth` **отклонено** — я эти файлы не читаю и не копирую.
|
||||
До его логина любая задача вернёт `Not logged in · Please run /login` (это ответ Claude Code,
|
||||
а не ошибка моста — конвейер при этом доказанно рабочий).
|
||||
|
||||
Токен `CLAUDE_CODE_OAUTH_TOKEN` в `.env` контейнера — запасной путь для headless без монтирования.
|
||||
Пересборка образа и обновление CLI auth не сбрасывают. Токены вне `/agents` → агент их не видит.
|
||||
|
||||
---
|
||||
|
||||
## 4. Секция «Agents» в Eagle Dashboard
|
||||
## 4. Что собрано на диске
|
||||
|
||||
Правки: `main.py` (роуты) · `templates/index.html` (вкладка `?tab=agents`) ·
|
||||
`services.yaml` (запись `agents-bridge`, `health.type: http`) · `health.py`.
|
||||
| Путь | Что |
|
||||
|---|---|
|
||||
| `~/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, статус линка |
|
||||
| Список | содержимое `~/agents`: имя, размер, mtime, статус линка |
|
||||
| Создать | `mkdir ~/agents/<name>` |
|
||||
| Подключить внешний | запись в `agents-mounts.yaml` → `compose up -d` |
|
||||
| Подключить внешний | запись в `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 /api/agents → список
|
||||
POST /api/agents {name} → mkdir
|
||||
POST /api/agents/{name}/mount {host_path} → bind + recreate
|
||||
DELETE /api/agents/{name}/mount → отвязать (файлы целы)
|
||||
POST /api/agents/{name}/hardlink {file, name} → ln -F со st_dev
|
||||
GET /api/agents/{name}/usage → размер
|
||||
```
|
||||
**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 на внешние цели.
|
||||
|
||||
---
|
||||
|
||||
## 5. Подтверждения
|
||||
|
||||
Режимы Balda: `allow_all` (выбран) · `ask` · `deny_all`.
|
||||
`ask` поддержан **только для Telegram и Slackagent**; для Mattermost фейлится закрыто.
|
||||
Сценарий «подтвердить из чата» требует: **A** — дописать `ask` в MM (~1-2 дня, расширение PR #8),
|
||||
либо **B** — апрув в мосте (~0.5 дня). При `allow_all` не нужно; начинать с B, когда понадобится.
|
||||
|
||||
---
|
||||
|
||||
## 6. Шаги
|
||||
|
||||
### ✅ Шаг 1 ВЫПОЛНЕН (2026-09-26)
|
||||
|
||||
Артефакты на диске (все сверены и рабочие; черновики от 15:36 доведены до состояния «собрано и запущено»):
|
||||
|
||||
| Путь | Что | Состояние |
|
||||
|---|---|---|
|
||||
| `~/agents` | рабочая область (`admin:staff`) | ✅ монтируется в `/agents`, запись в обе стороны проверена |
|
||||
| `~/cli-auth/{claude,codex}` | auth CLI, 0700, **вне** `~/agents` | ✅ монтируются в `/root/.claude`, `/root/.codex` |
|
||||
| `~/Docker/balda-executor/Dockerfile` | `node:24-bookworm` + claude + codex + git + `tini` | ✅ исправлен (см. ниже) |
|
||||
| `~/Docker/balda-executor/docker-compose.yml` | 3 тома, сеть `executor-net`, `build:` | ✅ исправлен (см. ниже) |
|
||||
| `~/Docker/balda-executor/agents-mounts.yaml` | манифест внешних проектов (пустой список) | ✅ создан |
|
||||
| `~/Docker/balda-executor/tools/render-mounts.py` | генератор `compose.override.yaml` | ✅ создан, `--check` работает |
|
||||
| `~/Docker/balda-executor/compose.override.yaml` | **генерируется**, руками не править | ✅ сгенерирован |
|
||||
| `~/Docker/balda-executor/.env` | 0600, `EXECUTOR_PROVIDER=claude` | ✅ создан (без секретов) |
|
||||
|
||||
**Что уже работает (проверено):**
|
||||
```
|
||||
balda-executor Up (healthy) balda-executor:0.1.0
|
||||
claude → 2.1.283 (Claude Code)
|
||||
codex → codex-cli 0.157.1
|
||||
```
|
||||
`~/agents` ↔ `/agents` пишется в обе стороны; рестарт контейнера состояние сохраняет;
|
||||
auth-каталоги хоста видны внутри.
|
||||
|
||||
#### 🔴 Три исправленных дефекта черновика (не повторять)
|
||||
|
||||
| Дефект | Симптом | Фикс |
|
||||
|---|---|---|
|
||||
| `env_file: .env` без файла | `docker compose up` падает на старте | `.env` создаётся **первым** шагом, 0600 |
|
||||
| Нет `compose.override.yaml` | `up` падает на монтировании несуществующего пути | `tools/render-mounts.py` запускается **до** `up` |
|
||||
| `FROM node:24-bookworm` без явных CA | — | `ca-certificates` + `update-ca-certificates` в Dockerfile (условие работы HTTPS у CLI) |
|
||||
|
||||
#### ⚠️ На macOS этой машины — `docker-compose`, НЕ `docker compose`
|
||||
|
||||
Docker CLI 29.4.1 + buildx как плагин (`~/.docker/cli-plugins/`), а compose — **отдельный бинарь**
|
||||
`/opt/homebrew/bin/docker-compose` (v5.1.3) с симлинком в `/opt/homebrew/lib/docker/cli-plugins/`.
|
||||
`docker compose` → `docker: unknown command: docker compose`; `docker compose config --quiet` →
|
||||
`unknown flag: --quiet`.
|
||||
|
||||
📌 **Все команды в этом проекте — через `docker-compose`**, включая `docker-compose config` (без `--quiet`).
|
||||
|
||||
#### Сборка образа (сработавшая команда)
|
||||
|
||||
```bash
|
||||
cd ~/Docker/balda-executor
|
||||
python3 tools/render-mounts.py --check # 0 записей, ошибок нет
|
||||
python3 tools/render-mounts.py # → compose.override.yaml
|
||||
docker-compose config --services # → executor (валидация)
|
||||
docker buildx build --builder arm64builder --platform linux/arm64 \
|
||||
--provenance=false --load -t balda-executor:0.1.0 .
|
||||
docker-compose up -d
|
||||
docker-compose restart # проверка переживания рестарта
|
||||
```
|
||||
|
||||
⚠️ Билдер — `arm64builder` (colima, `docker-container` driver). Дефолтный билдер
|
||||
(`default` → `/var/run/docker.sock`) в статусе `error` — не использовать.
|
||||
⚠️ `deploy.resources.limits.memory: 8G` из черновика **убран**: в не-swarm compose
|
||||
эта секция молча игнорируется, лимит создавал ложное ощущение защиты.
|
||||
|
||||
#### ✅ Логин CLI — зона Alex (не автоматизируется)
|
||||
|
||||
`~/cli-auth/claude` пуст, в `~/cli-auth/codex` только `tmp/`.
|
||||
|
||||
🔴 Копирование auth-файлов **отклонено Alex** («авторизую я, копировать из кредсы не надо»).
|
||||
Не предлагать повторно, не читать `~/.claude/.credentials.json`, `~/.codex/auth.json`.
|
||||
Признак незалогиненного исполнителя в ответе задачи: `Not logged in · Please run /login`.
|
||||
|
||||
### ✅ Шаг 2 ВЫПОЛНЕН (2026-09-26) — мост `agents-bridge`
|
||||
|
||||
Проект: `~/Developer/agents-bridge/` (FastAPI, python из `~/Developer/eagle-dash/.venv`, py3.14).
|
||||
|
||||
| Файл | Роль |
|
||||
|---|---|
|
||||
| `app.py` | роуты, авторизация, лимит параллельности, ThreadPoolExecutor |
|
||||
| `store.py` | SQLite WAL: `tasks` + UNIQUE-индекс по `idempotency_key` |
|
||||
| `executor.py` | `docker exec` в контейнер, провайдеры claude/codex, таймаут, обрезка вывода |
|
||||
| `config.py` | чтение `.env` (минимальный парсер, без python-dotenv на старте) |
|
||||
| `.env` | 0600, `BRIDGE_SECRET` (64 симв.), **не коммитить** |
|
||||
| `.env.example` | шаблон без секретов |
|
||||
| `state.sqlite3` | статусы задач, WAL |
|
||||
|
||||
**API:**
|
||||
```
|
||||
GET /healthz → статус моста + контейнера (без секрета)
|
||||
POST /task → 202 {task_id, status} | 200 (идемпотентный повтор) | 401 | 429 | 400
|
||||
GET /task/{task_id} → статус + stdout/stderr/exit_code
|
||||
GET /tasks?limit=N → последние задачи
|
||||
```
|
||||
|
||||
**Авторизация:** заголовок `X-Bridge-Secret` на всех `/task*`. Идемпотентность — заголовок
|
||||
`Idempotency-Key`; повтор возвращает **тот же** `task_id` со HTTP **200** (первый ответ — 202).
|
||||
|
||||
**Проверено живьём (2026-09-26):**
|
||||
### Приёмка секции (2026-09-26) — 13 проверок, все прошли
|
||||
|
||||
| Проверка | Результат |
|
||||
|---|---|
|
||||
| `POST /task` без секрета / с неверным | 401 / 401 ✅ |
|
||||
| Два POST с одним `Idempotency-Key` | один `task_id` ✅ |
|
||||
| Задача дошла до CLI в контейнере | ответ CLI вернулся ✅ |
|
||||
| Статус в SQLite (WAL) | ✅ |
|
||||
| `/healthz` видит контейнер | `container_running: true` ✅ |
|
||||
|
||||
Ответ `/healthz`:
|
||||
```json
|
||||
{"status":"ok","container":"balda-executor","container_running":true,
|
||||
"provider":"claude","secret_configured":true,"active_tasks":0,"max_concurrent":1}
|
||||
```
|
||||
|
||||
Результат сквозной пробы (до логина CLI): `status=failed, exit_code=1,
|
||||
stdout='Not logged in · Please run /login'` — **это ожидаемо** и доказывает, что
|
||||
путь Kraken → Mac → контейнер → CLI проходит целиком.
|
||||
|
||||
**Запуск (вручную, до launchd):**
|
||||
```bash
|
||||
cd ~/Developer/agents-bridge
|
||||
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790
|
||||
```
|
||||
|
||||
### ✅ Шаг 3 ВЫПОЛНЕН (2026-09-26) — мост под launchd
|
||||
|
||||
Плейт `com.agents.bridge`: `~/Library/LaunchAgents/com.agents.bridge.plist`,
|
||||
`RunAtLoad` + `KeepAlive`, `WorkingDirectory=/Users/admin/Developer/agents-bridge`,
|
||||
логи в `~/Library/Logs/agents-bridge.{log,err.log}`.
|
||||
|
||||
Блокер шага 3 (туннель вёл бы в закрытый порт) снят: мост поднимается при входе и
|
||||
перезапускается при падении.
|
||||
|
||||
```bash
|
||||
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.agents.bridge.plist
|
||||
launchctl list | grep agents.bridge # → 3063 0 com.agents.bridge
|
||||
```
|
||||
|
||||
📌 **launchd не читает shell `PATH`** — в плейт добавлен `EnvironmentVariables.PATH`
|
||||
с `/opt/homebrew/bin` (иначе `executor.py` не найдёт `docker`).
|
||||
|
||||
⚠️ Тест `KeepAlive` (убить pid → убедиться, что поднялся) **не выполнен**: требовал
|
||||
`kill -9` и был отклонён. Штатная проверка — `launchctl kickstart -k`.
|
||||
|
||||
#### 🔴 Питфолл: `BRIDGE_SECRET` и redaction Hermes
|
||||
|
||||
Команда, создающая `.env` через `python -c` с f-строкой `BRIDGE_SECRET={...}`, в логе выглядит
|
||||
обрезанной (`E...cret written`) — **это редакция секретов Hermes, файл на диске цел**.
|
||||
Проверять содержимое не чтением файла, а через awk с маскировкой:
|
||||
```bash
|
||||
/usr/bin/awk -F= '{ if ($1 ~ /SECRET|TOKEN|KEY|PASSWORD/) print $1"=<redacted len "length($2)">"; else print }' .env
|
||||
```
|
||||
`read_file` на `.env` отказывает по определению (secret-bearing file).
|
||||
|
||||
**3. Сеть (~1 ч)** — Cloudflare Tunnel
|
||||
Добавить published route `agents-bridge.qentra.top` → HTTP `localhost:8790` +
|
||||
Access Application. Токен и процесс cloudflared не трогаются. Проверка: `curl` извне на `/healthz`.
|
||||
⚠️ hostname — **одноуровневый** (двухуровневые дают TLS handshake failure).
|
||||
📌 Alex делает это сам в панели Cloudflare Zero Trust (API-доступа к CF у агента нет).
|
||||
Блокер снят: мост теперь под launchd (шаг 3), URL не будет вести в закрытый порт.
|
||||
|
||||
**4. Секция «Agents» — код ГОТОВ, не активирован (2026-09-26)**
|
||||
`~/Developer/eagle-dash/agents.py` (роутер, 8 эндпоинтов) + `main.py`
|
||||
(`app.include_router`) + `services.yaml` (запись `agents_bridge`).
|
||||
Aктивация:
|
||||
```bash
|
||||
rm -rf ~/Developer/eagle-dash/__pycache__ && \
|
||||
launchctl kickstart -k gui/$(id -u)/com.eagle.dashboard
|
||||
```
|
||||
Детали, питфоллы и спорный момент с `type: daemon` (конфликт с launchd моста за порт) —
|
||||
[[personal/tech/eagle-dashboard-agents-tab]]. Вкладка `?tab=agents` в шаблоне **ещё не сделана**.
|
||||
|
||||
**5. Связка Балда → мост (~2 ч)**
|
||||
MCP-инструмент в Балде → `POST /task`; проба `echo ok > /agents/test/probe.txt`.
|
||||
|
||||
**6. Приёмка (~1 день)**
|
||||
Сквозной тест **от аккаунта Alex** в MM: `@balda` → задача → diff в ответе.
|
||||
Замер расходов (DeepSeek + подписка), документация отката.
|
||||
| создание агента | 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 в логе дашборда | ошибок нет ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 7. Риски
|
||||
## 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-2 параллельных задачи, официальный бинарь, без прокси токена |
|
||||
| Hardlink через ФС → `EXDEV` | высокий | проверка `st_dev` в UI |
|
||||
| Бан подписки за «unusual traffic» | средний | 1 параллельная задача, официальный бинарь, без прокси токена |
|
||||
| Hardlink через ФС → `EXDEV` | высокий | проверка `st_dev` в API |
|
||||
| Mac спит → туннель и мост недоступны | средний | задачи в durable-очереди Балды, идемпотентность |
|
||||
|
||||
---
|
||||
|
||||
## 8. Открыто
|
||||
## 9. Открыто
|
||||
|
||||
1. ~~Провайдер исполнителя~~ → **claude** (оба в образе, переключение `EXECUTOR_PROVIDER`)
|
||||
2. ~~Нужен ли `ask`-апрув~~ → при `allow_all` не нужен; при потребности начинать с B (апрув в мосте, ~0.5 дня)
|
||||
3. 🔴 **Логин CLI — делает Alex сам.** Копирование auth-файлов отклонено; до логина задачи
|
||||
возвращают `Not logged in`
|
||||
4. ~~Мост не в launchd~~ → ✅ **закрыто**: `com.agents.bridge` (`RunAtLoad` + `KeepAlive`)
|
||||
5. 🔴 **Активация секции «Agents»** — код готов, нужен `__pycache__` + `kickstart -k` (апрув не получен)
|
||||
6. ⚠️ **Конфликт за порт 8790** — `agents_bridge` в `services.yaml` объявлен `type: daemon`,
|
||||
но мост уже управляется своим launchd. Ревизия при активации: `type: system` +
|
||||
`health.type: launchctl` либо `type: none`. См. [[personal/tech/eagle-dashboard-agents-tab]] §4
|
||||
7. ⚠️ Вкладка `?tab=agents` в `templates/index.html` не сделана — API готов, UI нет
|
||||
8. Hostname моста — `agents-bridge.qentra.top` (шаг 3, public route добавляет Alex в CF)
|
||||
9. Порт `8790` — свободен до старта моста, занят после; иных слушателей нет
|
||||
10. ⚠️ Под launchd `agents.py` не найдёт `docker`/`docker-compose` (PATH) — если mount вернёт 500,
|
||||
заменить литералы на `/opt/homebrew/bin/...`. Живьём не проверено (дашборд не перезапущен)
|
||||
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/agents-bridge]] — мост :8790, API, питфоллы (шаг 2 ✅)
|
||||
- [[personal/tech/eagle-dashboard-agents-tab]] — секция «Agents»: код, API, питфоллы (шаг 4, не активирован)
|
||||
- [[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` — архитектура дашборда
|
||||
|
||||
## История
|
||||
- **2026-09-26**: план создан; шаги 1-2 выполнены (образ, контейнер, мост на :8790).
|
||||
- **2026-09-26** (продолжение): мост переведён под launchd (шаг 3 ✅, `com.agents.bridge`);
|
||||
написан код секции «Agents» (шаг 4, не активирован); логин CLI оставлен за Alex.
|
||||
Туннель (шаг 3 сети) — за Alex, в панели Cloudflare Zero Trust.
|
||||
@@ -208,6 +208,39 @@ docker buildx build --builder arm64builder --platform linux/arm64 \
|
||||
`docker-compose up` (это формат swarm). Лимит создаёт ложное ощущение защиты —
|
||||
что-то ограничивать нужно иначе (cgroups напрямую или `mem_limit`, зависят от версии).
|
||||
|
||||
### Проверено 2026-09-26 (сборка `balda-executor`)
|
||||
|
||||
Дополнения по факту сборки Фазы 4:
|
||||
|
||||
| Факт | Значение |
|
||||
|---|---|
|
||||
| Сборка образа | `docker buildx build --builder arm64builder --platform linux/arm64 --provenance=false --load -t balda-executor:0.1.0 .` — прошла, ~1 мин |
|
||||
| `compose.override.yaml` при `mounts: []` | должен существовать обязательно: compose подхватывает его автоматически, а без файла `up` падает на монтировании |
|
||||
| Имя каталога в выводе | `docker-compose` пишет путь через **нижний регистр** (`/Users/admin/docker/...`) — macOS ФС регистронезависима, для человека разницы нет |
|
||||
| Пересоздание контейнера | `docker-compose up -d` после правки манифеста: `Recreate` → `Started`, 2-3 сек, `--privileged` не нужен |
|
||||
| Отвязка проекта | убрать запись из манифеста → `render-mounts.py` → `up -d`. Файлы на хосте целы (проверено: `ls` на хосте после отвязки) |
|
||||
| Hardlink в тот же том | `ln -F` даёт `links=2` и **одинаковый inode** — проверено через `stat -f "%l %i"` |
|
||||
| Hardlink на каталог | API возвращает 400 до вызова `ln` (проверка `is_dir()`), ядро тоже откажет |
|
||||
|
||||
### Питфолл: `psutil` отсутствует в venv Eagle Dashboard
|
||||
|
||||
`process/terminate`-операции процесса Hermes (`process kill`) падают с
|
||||
`No module named 'psutil'`. Из-за этого supervisor **не может трекать мост по PID**,
|
||||
и для сервиса моста выбран health `type: http`, а не `type: process` —
|
||||
иначе карточка в дашборде всегда показывает «down», хотя сервис жив.
|
||||
|
||||
### Проверка, что сервис под launchd действительно автозапускается
|
||||
|
||||
```bash
|
||||
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.agents.bridge.plist
|
||||
launchctl list | grep agents.bridge # pid + last exit code
|
||||
curl -s http://127.0.0.1:8790/healthz # сервис отвечает
|
||||
```
|
||||
|
||||
`KeepAlive: true` + `RunAtLoad: true` в plist → сервис переживает падение и перезагрузку.
|
||||
Требовать `PATH` в `EnvironmentVariables` обязательно: под launchd PATH минимальный,
|
||||
и `/opt/homebrew/bin/docker` иначе не находится.
|
||||
|
||||
---
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
Reference in new issue
Block a user