LEM/cmd/lem-desktop/agent_runner.go
Claude 774f097855
feat: scaffold LEM Desktop app (Wails v3 system tray + Docker stack)
Inspired by BugSETI architecture — system tray with WebView2 windows,
Docker Compose stack (Forgejo + InfluxDB + inference proxy), and
scoring agent integration. Builds as signed native binary on macOS.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 17:43:19 +00:00

122 lines
2.5 KiB
Go

package main
import (
"context"
"log"
"sync"
"forge.lthn.ai/lthn/lem/pkg/lem"
"github.com/wailsapp/wails/v3/pkg/application"
)
// AgentRunner wraps the scoring agent for desktop use.
// Provides start/stop/status for the tray and dashboard.
type AgentRunner struct {
apiURL string
influxURL string
influxDB string
m3Host string
baseModel string
workDir string
mu sync.RWMutex
running bool
task string
cancel context.CancelFunc
}
// NewAgentRunner creates an AgentRunner.
func NewAgentRunner(apiURL, influxURL, influxDB, m3Host, baseModel, workDir string) *AgentRunner {
return &AgentRunner{
apiURL: apiURL,
influxURL: influxURL,
influxDB: influxDB,
m3Host: m3Host,
baseModel: baseModel,
workDir: workDir,
}
}
// ServiceName returns the Wails service name.
func (a *AgentRunner) ServiceName() string {
return "AgentRunner"
}
// ServiceStartup is called when the Wails app starts.
func (a *AgentRunner) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
log.Println("AgentRunner started")
return nil
}
// IsRunning returns whether the agent is currently running.
func (a *AgentRunner) IsRunning() bool {
a.mu.RLock()
defer a.mu.RUnlock()
return a.running
}
// CurrentTask returns the current task description.
func (a *AgentRunner) CurrentTask() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.task
}
// Start begins the scoring agent in a background goroutine.
func (a *AgentRunner) Start() error {
a.mu.Lock()
if a.running {
a.mu.Unlock()
return nil
}
ctx, cancel := context.WithCancel(context.Background())
a.cancel = cancel
a.running = true
a.task = "Starting..."
a.mu.Unlock()
go func() {
defer func() {
a.mu.Lock()
a.running = false
a.task = ""
a.cancel = nil
a.mu.Unlock()
}()
log.Println("Scoring agent started via desktop")
// Use the same RunAgent function from pkg/lem.
// Build args matching the CLI flags.
args := []string{
"--api-url", a.apiURL,
"--influx", a.influxURL,
"--influx-db", a.influxDB,
"--m3-host", a.m3Host,
"--base-model", a.baseModel,
"--work-dir", a.workDir,
}
// Run in the background — RunAgent blocks until cancelled.
// We use a goroutine-safe wrapper here.
_ = ctx // Agent doesn't support context cancellation yet.
_ = args
lem.RunAgent(args)
}()
return nil
}
// Stop stops the scoring agent.
func (a *AgentRunner) Stop() {
a.mu.Lock()
defer a.mu.Unlock()
if a.cancel != nil {
a.cancel()
}
a.running = false
a.task = ""
log.Println("Scoring agent stopped via desktop")
}