Drive daemon config from settings file via injected applier
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+46
-20
@@ -4,8 +4,8 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"antidrift/internal/evidence"
|
||||
"antidrift/internal/knowledge"
|
||||
"antidrift/internal/session"
|
||||
"antidrift/internal/settings"
|
||||
"antidrift/internal/statusfile"
|
||||
"antidrift/internal/store"
|
||||
"antidrift/internal/tasks"
|
||||
@@ -36,33 +37,58 @@ func main() {
|
||||
srv := web.NewServer(ctrl)
|
||||
srv.Init() // re-arm or expire a restored Active session
|
||||
|
||||
// Wire the AI planning coach. Backend selectable via ANTIDRIFT_AI_BACKEND
|
||||
// (claude default, or codex). Misconfiguration degrades to no coach rather
|
||||
// than failing startup — manual planning still works.
|
||||
if backend, err := ai.NewBackend(os.Getenv("ANTIDRIFT_AI_BACKEND")); err != nil {
|
||||
log.Printf("ai disabled: %v", err)
|
||||
// Resolve and load settings, seeding from the legacy ANTIDRIFT_* env vars on
|
||||
// first run. After first run the file is the sole source of truth; env is
|
||||
// ignored. A missing file is the first-run signal.
|
||||
settingsPath, err := settings.DefaultPath()
|
||||
if err != nil {
|
||||
log.Fatalf("resolve settings path: %v", err)
|
||||
}
|
||||
cfg, err := settings.Load(settingsPath)
|
||||
if err != nil {
|
||||
cfg = settings.SeedFromEnv()
|
||||
if err := settings.Save(settingsPath, cfg); err != nil {
|
||||
log.Printf("settings: could not write %s (continuing): %v", settingsPath, err)
|
||||
} else {
|
||||
log.Printf("settings: seeded %s from environment", settingsPath)
|
||||
}
|
||||
}
|
||||
|
||||
// The knowledge source is a single file adapter whose path is driven entirely
|
||||
// by SetKnowledgePath, so startup and live settings edits share one code path.
|
||||
ctrl.SetKnowledge(knowledge.NewFileSource(""))
|
||||
|
||||
// applyFn re-wires the running daemon from a Settings value: AI backend +
|
||||
// service (coach/drift/nudge/reviewer), tasks command, and knowledge path. It
|
||||
// validates the backend FIRST and mutates nothing on failure, so POST /settings
|
||||
// can reject an invalid backend atomically.
|
||||
applyFn := func(s settings.Settings) error {
|
||||
backend, err := ai.NewBackend(s.AIBackend)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", settings.ErrInvalidBackend, err)
|
||||
}
|
||||
svc := ai.NewService(backend)
|
||||
ctrl.SetCoach(svc)
|
||||
ctrl.SetDriftJudge(svc)
|
||||
ctrl.SetNudge(svc)
|
||||
ctrl.SetReviewer(svc)
|
||||
log.Printf("ai: %s backend (coach + drift judge + nudge + reviewer)", backend.Name())
|
||||
ctrl.SetTasks(tasks.NewMarvin(s.MarvinCmd))
|
||||
ctrl.SetKnowledgePath(s.KnowledgePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Wire the Tasks port: Amazing Marvin via ampy's `am` CLI. The command is
|
||||
// overridable with ANTIDRIFT_MARVIN_CMD (e.g. "uv run am" or an absolute
|
||||
// path). A missing or failing CLI degrades to no tasks panel at fetch time —
|
||||
// planning still works.
|
||||
ctrl.SetTasks(tasks.NewMarvin(os.Getenv("ANTIDRIFT_MARVIN_CMD")))
|
||||
log.Printf("tasks: marvin adapter (am)")
|
||||
|
||||
// Wire the Knowledge port: a single profile file grounding the coach. The
|
||||
// default path is overridable with ANTIDRIFT_KNOWLEDGE_FILE; unset falls back
|
||||
// to ~/.antidrift/knowledge.md. A missing/unreadable file degrades to an
|
||||
// ungrounded coach at load time — planning still works.
|
||||
ctrl.SetKnowledge(knowledge.NewFileSource(os.Getenv("ANTIDRIFT_KNOWLEDGE_FILE")))
|
||||
log.Printf("knowledge: file adapter")
|
||||
// Apply once at startup. A bad persisted backend should not be fatal: fall back
|
||||
// to claude (always valid), persist the correction, and re-apply.
|
||||
if err := applyFn(cfg); err != nil {
|
||||
log.Printf("settings: %v; falling back to claude backend", err)
|
||||
cfg.AIBackend = "claude"
|
||||
_ = settings.Save(settingsPath, cfg)
|
||||
if err := applyFn(cfg); err != nil {
|
||||
log.Fatalf("settings: apply failed even with claude: %v", err)
|
||||
}
|
||||
}
|
||||
srv.SetSettings(settingsPath, cfg, applyFn)
|
||||
log.Printf("settings: ai=%s, marvin=%q, knowledge=%q", cfg.AIBackend, cfg.MarvinCmd, cfg.KnowledgePath)
|
||||
|
||||
// Mirror runtime status to ~/.antidrift_status for a window-manager bar.
|
||||
// A misconfigured home dir degrades to no status file, not a failed start.
|
||||
|
||||
Reference in New Issue
Block a user