[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/plans/music-tags-review/music-tags-review.md personal/projects/balda/balda-mm-delegation-plan.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 15:22:34 +06:00
1 parent ca471c21af
commit 269f2b189a
4 files changed
+437 -15

No files matched your search

+247
View File
@@ -0,0 +1,247 @@
# Фаза 4 — исполнитель на Mac + `~/agents` + секция «Agents» в Eagle Dashboard
> Создан 2026-09-26. Статус: **ПЛАН — ожидает апрува Alex, работа не начата.**
> Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фаза 4 + Фаза 5).
> Предыдущее: [[personal/projects/balda/balda-kraken-mm-deploy]] (Фаза 3 ✅ — бот отвечает Alex).
---
## 0. Решения Alex (2026-09-26, этот заход)
| # | Вопрос | Решение |
|---|---|---|
| 1 | Папка для проектов исполнителя | **`~/agents`** |
| 2 | Подтверждения | **Full access** → `permissions.mode: allow_all` |
| 3 | Управление hardlink/symlink в `~/agents` | **Новая секция в Eagle Dashboard** |
---
## 1. 🔴 ПРОВЕРЕНО ЖИВЫМ ТЕСТОМ: симлинк в Docker НЕ работает
Alex спросил: «Если в папку симлинк кинуть вовне — докер это прочтет?»
**Ответ: НЕТ.** Проверено на живом Docker (alpine:3.20, linux/arm64), не по памяти.
```
host: /tmp/symtest/outside/file.txt = "REAL CONTENT ON HOST"
bind: -v /tmp/symtest/bind:/mnt (внутри /mnt/target -> /tmp/symtest/outside)
контейнер: cat /mnt/target/file.txt
→ cat: can't open '/mnt/target/file.txt': No such file or directory
```
Сам симлинк внутри виден (`lrwxrwxrwx link -> /outside`), но **цель резолвится
относительно корня контейнера**, где этого пути нет. Docker монтирует только реальный
путь — симлинки не разворачивает и не «протаскивает».
### Следствие для архитектуры
```
Mac Контейнер-исполнитель
~/agents/ /agents (bind-mount, rw)
project-x/ ← РЕАЛЬНАЯ папка → видна
src -> ~/Developer/proj-x ← СИМЛИНК → ✗ БИТЫЙ
```
**Правило:** внутрь `~/agents` — только **реальные каталоги** или **hardlink**.
Симлинки допустимы **только если цель лежит внутри того же смонтированного корня**.
---
## 2. Symlink / hardlink / bind-mount — что выбрать
| Способ | В Docker | Может пересечь ФС | Может указать на файл вне mount | Итог |
|---|---|---|---|---|
| **symlink** | ✗ битый | да | ✗ нет | ❌ не использовать для внешних целей |
| **hardlink** | ✓ работает | **✗ нет** | ✗ нет | ✓ для файлов внутри `~/agents` |
| **bind-mount** | ✓ работает | да | да | ✓ для внешних каталогов |
| **APFS clonefile** | ✓ (реальная копия) | да | да | ✓ для «копии проекта» без расхода диска |
### Ключевое ограничение hardlink
Hardlink **не может пересечь границу файловой системы** и **не может ссылаться на
каталог** (только на файл, и только на macOS — с `-F`). То есть замена симлинка
«проект целиком» через hardlink технически невозможна, если проект лежит
на другом томе или это директория.
**Вывод:** «линкование проекта извне» = **bind-mount**, а не линк. Линки — для
внутренней организации одного тома.
📌 **Прямо отвечая на исходный замысел Alex:** hardlink'ом **нельзя** подключить
проект-каталог извне — физически невозможно (каталоги не линкуются, ФС не пересекается).
Поэтому в целевой архитектуре внешние проекты висят на **bind-mount'ах**
(`~/Developer/<proj>:/agents/<name>`), а секция Dashboard управляет именно манифестом
`agents-mounts.yaml`. Hardlink-операции остаются как вспомогательный инструмент
для файлов внутри одного тома.
---
## 3. Целевая архитектура
```
Mattermost (Alex)
│ «почини баг X в project-y»
▼
Балда-оркестратор (Kraken, DeepSeek, mode=allow_all)
│ 1. читает Obsidian, формирует ТЗ
│ 2. /goalkeeper → work → validate
▼
Мост (Mac, FastAPI, :8790) ← новый сервис, регистрируется в Eagle Dashboard
│ HTTP + shared secret (WireGuard-хаб уже есть)
│ идемпотентность + таймаут + лимит 1-2 параллельных задач
▼
Контейнер-исполнитель (Mac, Docker)
│ -v ~/agents:/agents:rw
│ -v ~/Developer/<proj>:/agents/<name> ← для внешних проектов (bind, не линк)
│ Node 24 + codex + claude + git
│ CLAUDE_CODE_OAUTH_TOKEN из env
▼
возврат: diff + вывод тестов + коммит → Балда → MM
```
---
## 4. Секция «Agents» в Eagle Dashboard
### Где живёт код (проверено 2026-09-26)
| Файл | Строк | Что править |
|---|---|---|
| `~/Developer/eagle-dash/main.py` | 889 | новые `@app.get/post/delete` роуты |
| `~/Developer/eagle-dash/templates/index.html` | 1248 | новая вкладка + Alpine.js стейт |
| `~/Developer/eagle-dash/health.py` | 181 | health-чек моста (`type: http`) |
| `~/Developer/eagle-dash/services.yaml` | 6.5 КБ | регистрация сервиса `agents-bridge` |
**Существующие вкладки:** `services` · `crons` · `pages` · `files`
(роутинг через `?tab=`, `setUrlTab()`, `activeTab` в Alpine).
### Что делает новая секция
1. **Список содержимого `~/agents`** — по каждому проекту: имя, тип (реальная папка /
symlink / hardlink), размер, дата изменения, **статус линка** (валиден / битый).
2. **Создать проект** — `mkdir ~/agents/<name>`.
3. **Подключить внешний проект** — **bind-mount-запись**: сохраняется в
`~/Developer/agents-mounts.yaml`, мост перезапускает контейнер с новым `-v`.
⚠️ **Не symlink** — см. §1.
4. **Hardlink-операции** — `ln -F <file> ~/agents/<proj>/<name>` (только файлы,
только внутри тома; в UI показывать `st_dev` и блокировать при разных ФС).
5. **Удалить из контейнера** — убрать из `agents-mounts.yaml` + `docker compose up -d`
⚠️ **Файлы на диске не трогать** — только отвязка от контейнера (см. Core Rules).
6. **Открыть терминал** — ссылка на существующую вкладку `files`.
### API-контракт (черновик)
```
GET /api/agents → [{name, kind, size, mtime, link_valid, target}]
POST /api/agents {name} → mkdir ~/agents/<name>
POST /api/agents/{name}/mount {host_path} → добавить bind-mount + recreate
DELETE /api/agents/{name}/mount → убрать bind-mount + recreate (файлы целы)
POST /api/agents/{name}/hardlink {file, name} → ln -F (с проверкой st_dev)
GET /api/agents/{name}/usage → размер, место на томе
```
### Чего секция НЕ делает
- **Не удаляет** содержимое `~/agents` — только отвязывает от контейнера.
- **Не убивает** процессы — lifecycle контейнера через существующий supervisor.
- **Не создаёт symlink** на внешние цели — вместо них bind-mount.
---
## 5. Система подтверждений — итог разведки
### Как устроено в Balda (апстрим, `docs/architecture/interactive-questions.md`)
Три режима (`permissioncmd.Mode`):
| Режим | Поведение |
|---|---|
| `allow_all` | никого не спрашивает — **выбран Alex** |
| `ask` | ADK-callback **блокирует турн**, вопрос уходит в чат, ждёт ответа, таймаут fail-closed, продолжает через `ResumeTarget` |
| `deny_all` | всё запрещено |
🔴 **`ask` сейчас работает только для Telegram и Slackagent** — прямая цитата
`interactive-questions.md`: *«`ask` is supported for Telegram and Slackagent and
fails closed elsewhere»*. Для Mattermost `ask` **фейлится закрыто**.
### Что это значит для сценария «MCP просит линк → Alex подтверждает из чата»
Прямой путь **сейчас невозможен** поверх MM. Варианты:
| Вариант | Объём | Ложится куда |
|---|---|---|
| A. Дописать `ask` в MM | ~1-2 дня | ветка `feat/mattermost-channel` → **расширение PR #8** |
| B. Апрув в мосте (свой мини-эндпоинт + очередь) | ~0.5 дня | FastAPI-мост Фазы 4 |
**С Alex выбран `allow_all`** — этим заходом подтверждения не нужны. Пункт
оставлен как задел: когда `ask` понадобится, начинать с варианта **B** (дешевле,
не зависит от ревью апстрима).
---
## 6. План работ (по шагам)
### Шаг 1 — `~/agents` + контейнер-исполнитель (~2 ч)
1. `mkdir ~/agents` + `.gitkeep`, права `admin:staff`.
2. `~/Docker/balda-executor/Dockerfile` — Node 24 + `codex` + `claude` + `git`.
3. `docker-compose.yml` — `-v ~/agents:/agents:rw`, `CLAUDE_CODE_OAUTH_TOKEN` из `.env`.
4. Авторизация: `claude setup-token` на Mac → токен в `~/Docker/balda-executor/.env` (0600).
5. Проверка: `docker exec ... claude --version` + `codex --version`.
### Шаг 2 — Мост на Mac (~3-4 ч)
1. `~/Developer/agents-bridge/` — FastAPI, порт **8790**.
2. Эндпоинты: `POST /task` (idempotency key), `GET /task/{id}`, `GET /healthz`.
3. Запуск CLI headless в контейнере, возврат diff + тестов.
4. Лимит параллельных задач: **1-2** (риск «unusual traffic» — см. §Риски плана Фаз).
5. Shared secret в заголовке + проверка source IP (WireGuard-подсеть).
### Шаг 3 — Сеть Kraken → Mac (~1 ч)
1. WireGuard-туннель (хаб на VPS уже есть — см. скилл `wireguard-vps-hub`).
2. Проверка: `curl` с Kraken на Mac `:8790/healthz`.
### Шаг 4 — Секция «Agents» в Eagle Dashboard (~3-4 ч)
1. `main.py` — 6 роутов из §4.
2. `templates/index.html` — вкладка `agents` + `?tab=agents`.
3. `services.yaml` — запись `agents-bridge` (`health.type: http`, url `:8790/healthz`).
4. `__pycache__` + `launchctl kickstart -k gui/$(id -u)/com.eagle.dashboard`.
### Шаг 5 — Связка Балда → мост (~2 ч)
1. MCP-инструмент в Балде (или webhook) → `POST /task`.
2. Проверка на задаче-пустышке: `echo ok > /agents/test/probe.txt`.
### Шаг 6 — Приёмка Фазы 5 (~1 день)
1. Сквозной тест **от аккаунта Alex**: `@balda почини X` → diff в MM.
2. Расходы: DeepSeek-токены + подписочные лимиты.
3. Задокументировать: как работает, где логи, как откатить.
---
## 7. Риски
| Риск | Оценка | Митигация |
|---|---|---|
| Симлинк в `~/agents` на внешний путь → битый | **подтверждён** | bind-mount вместо линка; UI помечает битые линки |
| Hardlink через границу ФС → `EXDEV` | высокий | проверять `st_dev` перед `ln`; UI блокирует |
| Бан подписочного аккаунта за «unusual traffic» | средний | 1-2 параллельных задачи, официальный бинарь, без прокси токена |
| Полный доступ (`allow_all`) + MCP → агент может снести `~/agents` | **средний** | бэкапы + `git rm`-семантика вместо `rm`; правки только через bind-mount-манифест |
| Мост открыт наружу | высокий | только WireGuard-подсеть + shared secret; не публиковать порт в LAN |
---
## 8. Открыто (требует ответа Alex при старте)
1. Порт моста — **8790** (взят свободный; проверить `lsof -i :8790` перед стартом).
2. Провайдер исполнителя — **claude** (подписка уже залогинена) или **codex**?
3. Нужен ли `ask`-апрув в будущем, или `allow_all` достаточно постоянно?
## История
- **2026-09-26**: создан. Решения Alex: папка `~/agents`, full access, управление
линками — секция в Eagle Dashboard. Живым тестом доказано, что симлинк в Docker
не читается → внешние проекты подключать bind-mount'ом, не линком.
## Связанные заметки
- [[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, 8/8 CI, ждёт ревью)
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы, дельта-апплаер
- скилл `eagle-dashboard` — архитектура дашборда, health-типы, launchd-нюансы
- скилл `wireguard-vps-hub` — туннель Kraken → Mac
@@ -1,7 +1,14 @@
---
title: Music Tags — Review
type: task
namespace: personal
tags: [music, jellyfin, tags, review, truenas]
status: awaiting-approval
---
# Music Tags — проверка тегов
**К правке:** 4777 файлов · **без изменений:** 3129
**К правке:** 6773 файлов · **без изменений:** 1133
> [!warning] Ничего не изменено
> Это план. Ставь галочки, что одобряешь. После «go» — бэкап в `Music/backup/` и применение.
@@ -11,8 +18,37 @@
| Блок | Файлов | Что это значит |
|---|---:|---|
| [[1-needs-decision\|Требует решения]] | 28 | Нет ни исполнителя, ни альбома. Взять неоткуда — только имя файла. |
| [[2-medium-confidence\|Средняя уверенность]] | 2144 | Восстановлено из имён файлов и структуры папок. |
| [[3-high-confidence\|Высокая уверенность]] | 2605 | Точная перекодировка CP1251 → UTF-8, обратимая без потерь. |
| **Средняя уверенность** (разбита по категориям) | 5787 | Восстановлено из имён файлов и структуры папок. |
| [[3-high-confidence\|Высокая уверенность]] | 958 | Точная перекодировка CP1251 → UTF-8, обратимая без потерь. |
### Средняя уверенность — файлы по категориям
| Категория | Файлов |
|---|---:|
| [[2-medium-confidence-Pop_Club_Любэ\|Pop, Club, Любэ]] | 1141 |
| [[2-medium-confidence-RockFolk_Пилот\|Rock&Folk / Пилот]] | 281 |
| [[2-medium-confidence-RockFolk_Ногу_Свело\|Rock&Folk / Ногу Свело!]] | 153 |
| [[2-medium-confidence-RockFolk_Ночные_Снайперы\|Rock&Folk / Ночные Снайперы]] | 95 |
| [[2-medium-confidence-z_Другое_Enigma_Ленинград\|z Другое, Enigma, Ленинград]] | 903 |
| [[2-medium-confidence-Детские\|!Детские]] | 510 |
| [[2-medium-confidence-Рэп\|!Рэп]] | 469 |
| [[2-medium-confidence-RockFolk_Муммий_Тролль\|Rock&Folk / Муммий Тролль]] | 80 |
| [[2-medium-confidence-RockFolk_Пикник\|Rock&Folk / Пикник]] | 83 |
| _(остальные — в списке файлов блока)_ | |
> 📂 Раньше это был один файл на 1.1 МБ — Obsidian тормозил. Теперь **56 нот**,
> разбитых по категориям; Rock&Folk разложен по группам (глубина 2).
## Откуда взялось поле `track`
В v5 добавлена правка **номера трека**. Раньше номер оставался в названии:
```
БЫЛО: title = "01 Hit The Road Jack (Kub vs Ray Charles)"
СТАЛО: title = "Hit The Road Jack (Kub vs Ray Charles)" track = 1
```
Затронуто 5158 файлов. В нотах строка `track: → **N**`.
## Важное про блок 1
@@ -23,6 +59,13 @@
Если подставить туда имя папки, получится мусор вида `artist = Сказки`
(238 треков одним «исполнителем») — от этого отказались.
## Что НЕ трогаем (осознанно)
- **Рукописный транслит** — `EMINЭM`, `КняZz`, `Flёur`, `изBESTняк`, `Скаzки` (126 файлов).
Это оригинальное написание / реальные псевдонимы, а не порча кодировки.
- **`album == artist`** (101) — одноимённые альбомы: `Рубль/Рубль`, `Би-2/2001 Би-2`.
- **Числовые названия** (15) — `007` у Ленинграда, `48` у Пилота — это реальные треки.
## Формат
Каждый файл — чекбокс `- [ ]`. Под ним: поле, старое значение → **новое**, и источник