settings

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package settings owns the settings a user changes while the daemon runs: the dry-run switch, the decision models and thresholds, retention, where trashed mail goes, whether the mailbox's own mail is left alone, and the model provider keys. They live in the settings table and lie over the environment (internal/config), which supplies the defaults. Provider keys are stored encrypted under the master key and are never handed back out of this package except inside the config the model adapters are built from.

Index

Constants

View Source
const (
	KeyStored      = "stored"      // saved through the API; it wins over the environment
	KeyEnvironment = "environment" // set in the daemon's environment only
	KeyNone        = "none"
)

Where a provider key in force comes from. Never the key.

View Source
const (
	WorkspaceNoneNeeded = "none_needed" // the key was made for one workspace: nothing to name
	WorkspaceOne        = "one"         // one workspace, which MailRules stored as the setting
	WorkspaceSeveral    = "several"     // several: the user picks one
	WorkspaceFailed     = "failed"      // Anthropic could not say (no permission, network): the user types it
)

What a lookup found out about the Claude key in force.

View Source
const DefaultRetentionDays = 30

DefaultRetentionDays is how long message snippets are kept until the user says otherwise.

Variables

View Source
var Deciders = []string{"jev", "clef", "anthropic", "openai", "ollama"}

Deciders are the values MAILRULES_DECIDER accepts.

View Source
var ErrNoComposer = errors.New("no generative model is configured for the rule composer")

ErrNoComposer means no generative model is configured for the rule composer.

Functions

func CheckModel

func CheckModel(spec string) string

CheckModel says, in a sentence, what is wrong with a rule's model override, or "" when it is a decider this daemon knows: empty (the default), a decider name, or name:model. openai and ollama have no default model.

func KeyNames

func KeyNames() []string

KeyNames lists the provider keys, sorted.

Types

type ComposerMissing

type ComposerMissing struct {
	Model string // composer_model as set, e.g. openai:gpt-4o-mini
	Path  string // the setting to fill in, as the API spells it
	What  string // what to call it, e.g. "an OpenAI API key"
}

ComposerMissing is ErrNoComposer with what the chosen composer model lacks: the one place that decides what the user is told to add.

func (*ComposerMissing) Error

func (e *ComposerMissing) Error() string

func (*ComposerMissing) Is

func (e *ComposerMissing) Is(target error) bool

Is makes errors.Is(err, ErrNoComposer) true.

func (*ComposerMissing) Message

func (e *ComposerMissing) Message() string

Message is the sentence for a person: what the chosen model needs and where to add it.

type Invalid

type Invalid struct{ Path, Message string }

Invalid is a settings change the user has to correct. Path names the field; Message is a sentence for a person, complete without the path.

func (*Invalid) Error

func (e *Invalid) Error() string

type Patch

type Patch struct {
	Reset         []string // setting names, as the API spells them
	DryRun        *bool
	Decider       *string
	DeciderModel  *string
	FallbackModel *string
	ComposerModel *string
	EscalateBelow *float64
	MinConfidence *float64
	RetentionDays *int
	TrashToFolder *bool
	LeaveOwnMail  *bool
	OpenAIBaseURL *string // empty = api.openai.com
	OllamaURL     *string // empty = none
	// AnthropicWorkspaceID is empty or a wrkspc_ ID (config.ValidWorkspaceID); empty = none.
	AnthropicWorkspaceID *string
	Keys                 map[string]string
}

Patch is a change: nil fields stay as they are. An empty string is a value like any other, stored as it is: no fallback, the provider's default model, no URL. Where an empty value cannot work (the composer's model, the model of a decider that has no default) it is refused. Reset names the settings to forget, which puts the environment's default back in force. In Keys, an empty value removes the stored key: a key cannot be "set to nothing".

type Settings

type Settings struct {
	Store  *store.Store
	Master []byte
	Env    *config.Config // the defaults
	Deps   models.Deps    // shared by every router built here
	// Workspaces looks up the workspace a Claude key needs; nil = never looked up, the
	// user types it.
	Workspaces WorkspaceFinder
	// contains filtered or unexported fields
}

Settings reads and changes the stored settings.

func (*Settings) Apply

func (s *Settings) Apply(ctx context.Context, p Patch) error

Apply validates a change against the settings it would produce and stores it in one transaction. A problem the user can fix comes back as *Invalid. Nothing needs a restart: the executor reads dry-run and trash_to_folder before every action, the pipeline reads leave_own_mail for every email, and Live rebuilds the router. A Claude key saved or replaced forgets what was learnt about the old one's workspace, and a workspace MailRules found for it; then, with no workspace set, it is looked up.

func (*Settings) Composer

func (s *Settings) Composer(ctx context.Context) (models.Generator, error)

Composer returns the generative model the rule composer writes rules with: composer_model as config.SplitComposerModel reads it, on Anthropic, an OpenAI-compatible endpoint or Ollama. Until that provider has its key or URL it is a *ComposerMissing, which is ErrNoComposer.

func (*Settings) Effective

func (s *Settings) Effective(ctx context.Context) (config.Config, error)

Effective returns the configuration in force: the environment with the stored settings laid over it. It holds decrypted provider keys, so it is for building model adapters, never for showing.

func (*Settings) Live

func (s *Settings) Live(ctx context.Context) (router *models.Router, minConfidence float64)

Live returns the decision router and the act threshold for the settings as stored right now; pipeline.Pipeline.Live is this method. The router is rebuilt only when a stored setting has changed since the last call, whoever changed it, so a save in the browser reaches the next email without a restart. router is nil while the decider lacks its key.

func (*Settings) LookupWorkspaces

func (s *Settings) LookupWorkspaces(ctx context.Context) (WorkspaceLookup, error)

LookupWorkspaces says which workspace the Claude key in force needs, as the Settings screen asks. A known answer (none needed, one, several) is given again without asking Anthropic; otherwise Anthropic is asked, and one workspace found while none is set is stored as the setting.

func (*Settings) RetentionDays

func (s *Settings) RetentionDays(ctx context.Context) int

RetentionDays returns the retention_days setting in force: how long message snippets are kept. When the settings cannot be read it is the default.

func (*Settings) RouterFor

func (s *Settings) RouterFor(ctx context.Context, spec string) *models.Router

RouterFor returns the router for a rule's model override (a decider spec, see CheckModel), built from the settings in force and kept until they change. It is nil when that decider cannot be used, for example because its key is not set; the caller then decides with the default router. pipeline.Pipeline.Override is this method.

func (*Settings) View

func (s *Settings) View(ctx context.Context) (View, error)

View returns the settings in force.

type View

type View struct {
	DryRun        bool
	Decider       string
	DeciderModel  string
	FallbackModel string // empty = never escalate
	ComposerModel string
	EscalateBelow float64
	MinConfidence float64
	RetentionDays int
	TrashToFolder bool              // trash goes to actions.TrashFolder, not the server's Trash
	LeaveOwnMail  bool              // mail from the mailbox's own address is left alone (pipeline.OwnMail)
	OpenAIBaseURL string            // an OpenAI-compatible endpoint; empty = api.openai.com
	OllamaURL     string            // a local Ollama server
	Keys          map[string]string // KeyStored | KeyEnvironment | KeyNone
	Warnings      []Warning         // never nil
	// The Claude workspace every Claude request names; empty = none. Its name is known when
	// a lookup listed it; Found says MailRules looked it up and stored it itself.
	AnthropicWorkspaceID    string
	AnthropicWorkspaceName  string
	AnthropicWorkspaceFound bool
}

View is what may be shown: every setting in force, and for each provider key only where it comes from.

type Warning

type Warning struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Path    string `json:"path"`
}

Warning is something about the settings in force that will not work as it stands, which the user may still be on the way to fixing (the first-run wizard saves in steps), so it is reported and not refused. Path names the setting to fill in.

type WorkspaceFinder

type WorkspaceFinder interface {
	Find(ctx context.Context, apiKey string) (models.WorkspaceAnswer, error)
}

WorkspaceFinder asks Anthropic which workspace a Claude key needs. models.AnthropicWorkspaces is the one the daemon uses.

type WorkspaceLookup

type WorkspaceLookup struct {
	Status     string
	Workspaces []models.Workspace
}

WorkspaceLookup is what a lookup answers: its status and, for one or several, the organisation's workspaces (never nil).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL