// Package agent provides shared types and utilities for Gas Town agents // (witness, refinery, deacon, etc.). package agent import ( "encoding/json" "os" "path/filepath" "github.com/steveyegge/gastown/internal/util" ) // State represents an agent's running state. type State string const ( // StateStopped means the agent is not running. StateStopped State = "stopped" // StateRunning means the agent is actively operating. StateRunning State = "running" // StatePaused means the agent is paused (not operating but not stopped). StatePaused State = "paused" ) // StateManager handles loading and saving agent state to disk. // It uses generics to work with any state type. type StateManager[T any] struct { stateFilePath string defaultFactory func() *T } // NewStateManager creates a new StateManager for the given state file path. // The defaultFactory function is called when the state file doesn't exist // to create a new state with default values. func NewStateManager[T any](rigPath, stateFileName string, defaultFactory func() *T) *StateManager[T] { return &StateManager[T]{ stateFilePath: filepath.Join(rigPath, ".runtime", stateFileName), defaultFactory: defaultFactory, } } // StateFile returns the path to the state file. func (m *StateManager[T]) StateFile() string { return m.stateFilePath } // Load loads agent state from disk. // If the file doesn't exist, returns a new state created by the default factory. func (m *StateManager[T]) Load() (*T, error) { data, err := os.ReadFile(m.stateFilePath) if err != nil { if os.IsNotExist(err) { return m.defaultFactory(), nil } return nil, err } var state T if err := json.Unmarshal(data, &state); err != nil { return nil, err } return &state, nil } // Save persists agent state to disk using atomic write. func (m *StateManager[T]) Save(state *T) error { dir := filepath.Dir(m.stateFilePath) if err := os.MkdirAll(dir, 0755); err != nil { return err } return util.AtomicWriteJSON(m.stateFilePath, state) }