Files
ki.sagidullin 8c91c83024
Some checks failed
CI / test (push) Failing after 1m20s
CI / build-and-package (amd64, linux) (push) Failing after 1m4s
CI / build-and-package (amd64, windows) (push) Successful in 37s
fix(opencode): агенты — в <worktree>/.opencode/agent, OPENCODE_CONFIG_DIR больше не выставляем
OPENCODE_CONFIG_DIR в opencode v1.18.18 перенаправляет Global.Path.config
(global.ts: config = OPENCODE_CONFIG_DIR ?? ~/.config/opencode), из-за чего
глобальный конфиг (модель/провайдеры, напр. tokentool) не загружался и
opencode уходил в fallback-модель. Агенты открывались, т.к. OPENCODE_CONFIG_DIR
дополнительно сканируется как каталог для agent/*.md.

Теперь агенты распаковываются в <worktree>/.opencode/agent/*.md, где opencode
находит их через project-каталог .opencode (paths.ts, cwd=worktree). Глобальный
конфиг не трогаем вовсе.

- internal/opencode: удалены Server.ConfigDir/Pool.ConfigDir и env OPENCODE_CONFIG_DIR
- internal/config: удалено поле OpenCodeCfg.ConfigDir (config_dir)
- internal/app: ensureAgentsDir пишет в <worktree>/.opencode/agent
- internal/agents: WriteTo(dir) → dir/agent/*.md
- README/config.yaml.example/memory обновлены
2026-08-23 17:54:35 +05:00

8.8 KiB
Raw Permalink Blame History

core

Ratatoskr-go — оркестратор конвейера Ratatoskr (порт с Python на Go) в единый бинарь. Субагенты (analyst/dev/reviewer) запускаются через внешний процесс opencode (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) из встроенных определений. Агенты распаковываются в <worktree>/.opencode/agent/*.md (opencode находит через project-каталог .opencode); OPENCODE_CONFIG_DIR НЕ выставлять — он перенаправляет Global.Path.config и ломает глобальный конфиг (модель/провайдеры) → падение в fallback.
  • Поллинг вердикта: 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.