Files
ratatoskr-go/.serena/memories/core.md
ki.sagidullin d731a7429d
Some checks failed
CI / test (push) Failing after 1m20s
CI / build-and-package (amd64, linux) (push) Failing after 1m5s
CI / build-and-package (amd64, windows) (push) Successful in 31s
feat(config): отключаемый Telegram-канал (telegram.enabled)
- TelegramCfg.Enabled (дефолт true); при false канал не создаётся и не крепится в Router, long-poll не стартует — нет сетевых вызовов к api.telegram.org
- Validate требует token/chat_id только при enabled
- Load пресетит enabled=true до unmarshal (applyDefaults для bool не различает явный false)
- тесты, README и config.yaml.example обновлены
2026-08-23 17:36:05 +05:00

60 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# core
Ratatoskr-go — оркестратор конвейера Ratatoskr (порт с Python на Go) в единый
бинарь. Субагенты (analyst/dev/reviewer) запускаются через внешний процесс
[opencode](https://opencode.ai) (v2 HTTP API). Интерфейсы: Telegram-бот + Fyne UI.
Linux — статический headless-бинарь (CGO_ENABLED=0); Windows — с Fyne-окном (cgo).
## Структура (модули internal/)
```
cmd/ratatoskr/ точка входа; флаги: -config, -version, -noui (headless). main.version/updateToken вшиваются ldflag'ом
internal/
app/ composition root/DI: App.New -> config.Load+Validate, ResolveExePaths, storage, Runner, Analyst, Core, Worker, Updater, UI. packageOwner="kamelion", Version="0.2.2"
config/ YAML+env загрузка (${VAR:-default}), defaults, validate C1-C4
model/ доменные типы/контракты: Status + validTransitions (IsValidTransition/IsTerminal), TraceStatus. Нижний слой — не зависит от storage/events
chat/ мультиканальный Router; telegram — long-poll канал. Коды M1-M5
core/ state-machine задач + Decider/analyst интерфейс. Коды D3/D4
analyst/ аналитик: промпт + разбор JSON-вердикта (opencode agent). Коды A1-A4
events/ шина событий UI: Bus (buffered Sub/Pub), типизированный Hub, события TaskCreated/TaskStatusChanged/HistoryAppended/TraceAppended/AgentActivity, LogBus (панель «Логи»)
worker/ polling-планировщик + dev/reviewer-конвейер + gitops. Коды W*, E*, R*
agents/ встроенные opencode-агенты (analyst.md, dev.md, reviewer.md) через go:embed
opencode/ HTTP-клиент v2 API opencode serve, поллинг вердикта, LiveRegistry. Коды O1-O5
storage/ SQLite (modernc.org/sqlite, без CGO): tasks, traces, task_history. Коды S1-S5
update/ автообновление из Gitea Packages. Коды U1-U6
ui/ desktop-оболочка: Store-абстракция (копии), Commands (UI->Core), Controller (шины->View), View/Window интерфейсы
ui/desktop/ Fyne-реализация окна (build-tag cgo, --noui / нет cgo → headless)
scripts/ build-publish-ui.ps1 — локальная Windows-сборка Fyne-бинаря + публикация в Gitea
docs/ ui-spec.md — спека Fyne UI (слои, event-bus, fyne.Do/снапшоты)
```
## Ключевые инварианты
- **App.New-сигнатура:** `App.New(configPath, version, updateToken string, noUI bool)` (4-й параметр — headless; cgo-вариант собирается только при наличии C-компилятора).
- **chat.Router:** асинхронная обработка входящих — **ограниченный пул `chatWorkers=4` воркеров + FIFO-очереди per-user** (`userState`, `workerLoop`/`runUser`). Порядок сообщений одного UserID сохраняется (флаг `scheduled` → один активный воркер на пользователя); разные пользователи обрабатываются параллельно (до 4 одновременных LLM-вызовов). Backpressure по `jobs` блокирует только перегруженного пользователя, не весь long-poll. `Processed()`/`WaitProcessed()` — синхронизация тестов.
- **FreeChat** (app): `sessions map[uid]sessionID` защищён `sync.Mutex` (пишется из разных воркеров роутера).
- **UI:** окно — ещё одна реализация `chat.Channel` (присоединяется в Router). Core не трогает UI; обмен — событийная шина (events). Кнопка «Завершить» = полный выход (SetOnQuit→cancel→UI.Run возвращается); закрытие крестиком = сворачивание, Core живёт. UI собирается с `--noui`/без cgo.
- **Фазы аналитика (Decision.Phase):** `ask`, `propose`, `ready` (два последних обрабатываются одинаково в core), `abort`. Требования валидатора: ask — chat_reply/questions; propose — хотя бы одно изменённое поле; ready — без изменённых полей.
- **Статусы задач (internal/model):** draft→collecting→ready→approved→running→success/failed/timeout + cancelled/aborted/closed (терминальные). UserID — chat.ID (одна активная задача на чат).
- **Decider/Worker/Analyst/Reviewer:** Decider=analyst интерфейс; Worker — polling-планировщик; Reviewer проверяет diff dev-ветки (R1-R6), вердикт JSON {passed, critical_issues, solid_violations, comments}.
- **gitops (worker):** worktree-режим; feature-ветка `feat/<taskTag>` от origin/main; push через http.extraHeader, токен Bearer.
- **Пути «всё рядом с .exe»:** db/worktree резолвятся от ExeDir; config.yaml — рядом с бинарём, фоллбэк cwd.
- **Автообновление:** авто = только Check+уведомление; замена — по /update; версии в `commit-<sha7>/` (не `latest/`); Verify сверяет контрольную сумму бинаря против `.sha256` той же версии. **Perf:** `ResolveLatest` проверяет наличие бинаря версии **HEAD-пробой без скачивания тела** (405/501 → fallback `GET Range: bytes=0-0`), сортировка версий по id убыв.; один общий `http.Client` (keep-alive). Ошибка U4 — только при несовпадении суммы (пустой/отсутствующий `.sha256` пропускает проверку — M1-известное замечание).
- **Telegram toggle:** `telegram.enabled` (дефолт true, preset в `Load` до unmarshal, т.к. `applyDefaults` для bool не различает явный false). При `false` канал не создаётся/не крепится в Router и long-poll не стартует — сетевых вызовов к api.telegram.org нет; token/chat_id не валидируются.
## opencode (v2 HTTP API, >= 1.18.18)
- Интеграция с субагентами — через headless `opencode serve`, **v2 API** (`/api/*`). Версия opencode >= 1.18.18.
- **Модель — только глобальный конфиг opencode.** Ratatoskr модель не выбирает и про неё не знает: opencode сам берёт модель по умолчанию из своего глобального конфига. Код opencode-конфиг не читает (config.go удалён).
- **Свой агент:** при создании сессии в `POST /api/session` передаётся `agent` (analyst/dev/reviewer/chat/postmortem) из встроенных определений (`internal/agents`, распаковка в `OPENCODE_CONFIG_DIR`).
- **Поллинг вердикта:** `POST /api/session/:id/prompt` (durable admit) → `GET /api/session/:id/message?order=desc&limit=200` (новые assistant-сообщения, текст в `content[].type=="text"`) → завершение = `GET /api/session/active` без сессии + финальное assistant-сообщение, стабильное `settlePolls=2` опроса. `POST .../interrupt` вместо abort.
## Контракты (не ломать)
- `App.New(configPath, version, updateToken string, noUI bool)` — сигнатура.
- `packageOwner` — константа "kamelion" (не плодить vars/ldflag/конфиг).
- `update.Updater` — создаётся структурой `&update.Updater{...}`, конструктора нет.
- `ui.Store` — чтение-модель для UI: ListTasks/GetTask/GetHistory/GetTraces (копии, без ссылок на storage).
- `events.Bus`/`events.LogBus` — шина UI (см. internal/events). publisher-интерфейсы подключаются к Core/Worker.
см. также `mem:tech_stack`, `mem:conventions`, `mem:task_completion`, `mem:suggested_commands`.