Files
ratatoskr-go/internal/config/types.go
Hermes 733e63339a
Some checks failed
CI / test (push) Successful in 40s
CI / build-and-package (amd64, linux) (push) Successful in 35s
CI / build-and-package (amd64, windows) (push) Failing after 26s
feat: opencode через HTTP API — пул serve-серверов вместо spawn/NDJSON
Runner теперь ходит к постоянным serve по HTTP API (v1.17+, /api):
- клиент Client (create/send/wait/abort/messages/verdict)
- Pool: по одному serve на каталог, ленивый подъём, root-сервер в worktree,
  выделение портов, ReleaseTask при завершении задачи
- Run: CreateSession('ratatoskr-<агент>') -> Send -> поллинг Verdict из
  text-частей assistant-сообщений; idle/hard таймауты дают RC=-1
- вердикт извлекается из последнего assistant text-парта (плоский text)
- тесты: unit на фейковом HTTP-сервере; e2e эмулирует serve через httptest,
  агент определяется по title сессии
2026-08-18 13:36:54 +05:00

120 lines
5.1 KiB
Go
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.
// Package config — загрузка конфигурации Ratatoskr-go.
//
// Источники (каждый следующий имеет приоритет над предыдущим):
// 1. Файл config.yaml (с подстановкой ${VAR:-default})
// 2. Переменные окружения (env-теги в структуре)
//
// Порядок: Load("config.yaml") → YAML → os.Expand → env override → Validate.
package config
import (
"errors"
"fmt"
"time"
)
// Duration — time.Duration с YAML-сериализацией (строка "5s", "10m").
type Duration time.Duration
func (d Duration) Duration() time.Duration { return time.Duration(d) }
func (d Duration) String() string { return time.Duration(d).String() }
func (d *Duration) UnmarshalYAML(unmarshal func(any) error) error {
var s string
if err := unmarshal(&s); err != nil {
return fmt.Errorf("%w: %v", ErrInvalidFormat, err)
}
td, err := time.ParseDuration(s)
if err != nil {
return fmt.Errorf("%w: parse duration %q: %v", ErrInvalidFormat, s, err)
}
*d = Duration(td)
return nil
}
// Config — полная конфигурация бинаря.
type Config struct {
Telegram TelegramCfg `yaml:"telegram"`
OpenCode OpenCodeCfg `yaml:"opencode"`
Git GitCfg `yaml:"git"`
Chat ChatCfg `yaml:"chat"`
Paths PathsCfg `yaml:"paths"`
Update UpdateCfg `yaml:"update"`
}
// GitCfg — источник репозиториев (для git clone).
type GitCfg struct {
BaseURL string `yaml:"base_url" env:"GIT_BASE_URL"`
Token string `yaml:"token" env:"GIT_TOKEN"`
}
// UpdateCfg — автообновление.
// Enabled=false отключает и авто-проверку, и /update.
// BaseURL — хост Gitea для обновления (обязателен: без него update отключён,
// фоллбэк на git.base_url НЕ используется).
// Token — токен read:package. Приоритет: вшитый -X main.updateToken > этот.
// Package — имя пакета-хранилища (по умолчанию ratatoskr). Владелец пакета —
// жёсткая константа (packageOwner в app).
// CheckInterval — как часто авто-проверять наличие обновления
// (по умолчанию 24h). Авто-режим ТОЛЬКО уведомляет; замена — по /update.
type UpdateCfg struct {
Enabled bool `yaml:"enabled" default:"false"`
BaseURL string `yaml:"base_url" env:"UPDATE_BASE_URL"`
Token string `yaml:"token" env:"UPDATE_TOKEN"`
Package string `yaml:"package" default:"ratatoskr"`
CheckInterval Duration `yaml:"check_interval" default:"24h"`
}
type TelegramCfg struct {
Token string `yaml:"token" env:"TG_TOKEN"`
ChatID string `yaml:"chat_id" env:"TG_CHAT_ID"`
}
type OpenCodeCfg struct {
Bin string `yaml:"bin" default:"opencode"`
DBPath string `yaml:"db_path" default:""`
Config string `yaml:"config" env:"OPENCODE_CONFIG"`
ConfigDir string `yaml:"config_dir" env:"OPENCODE_CONFIG_DIR"`
HardTimeout Duration `yaml:"hard_timeout" default:"20m"`
IdleTimeout Duration `yaml:"idle_timeout" default:"5m"`
PollMs Duration `yaml:"poll_ms" default:"2s"`
Serve ServeCfg `yaml:"serve"`
}
// ServeCfg — настройки постоянных opencode serve-процессов (API-режим).
// Runner ходит к serve по HTTP API (v1.17+, /api). ratatoskr сам поднимает
// по одному serve на каталог через Pool (Hash: hostname/port; служебный
// root-сервер в worktree живёт всё время app, остальные — лениво).
//
// Поля enabled/url оставлены для обратной совместимости конфига и сейчас не
// меняют поведение: serve обязателен для API-режима, сервер всегда
// спавнится пулом (hostname/port задают базовые значения).
type ServeCfg struct {
Enabled bool `yaml:"enabled" default:"false"`
URL string `yaml:"url" env:"OPENCODE_SERVE_URL"`
Hostname string `yaml:"hostname" default:"127.0.0.1"`
Port int `yaml:"port" default:"4096"`
Password string `yaml:"password" env:"OPENCODE_SERVE_PASSWORD"`
}
type ChatCfg struct {
PollInterval Duration `yaml:"poll_interval" default:"30s"`
}
type PathsCfg struct {
Worktree string `yaml:"worktree" default:"./worktrees"`
DB string `yaml:"db" default:"./ratatoskr.db" env:"RATATOSKR_DB"`
}
// Validate проверяет обязательные поля. Возвращает C1 MissingField
// (склеенную, все ошибки сразу) или C2 InvalidFormat.
func (c *Config) Validate() error {
var errs []error
if c.Telegram.Token == "" {
errs = append(errs, fmt.Errorf("%w: telegram.token", ErrMissingField))
}
if c.Telegram.ChatID == "" {
errs = append(errs, fmt.Errorf("%w: telegram.chat_id", ErrMissingField))
}
return errors.Join(errs...)
}