agentmod

package
v1.0.80 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ActionSyncMods = "syncmods"
	ActionStop     = "stopsfserver"
	ActionStart    = "startsfserver"
)
View Source
const (
	OpAdd        = "add"
	OpRemove     = "remove"
	OpSetVersion = "setVersion"
	OpUpdateAll  = "updateAll"
	// OpApplyPending carries no modReference and no version: it escalates the
	// change that is ALREADY persisted and pending, it does not describe a new one.
	OpApplyPending = "applyPending"
)

Variables

This section is empty.

Functions

func Apply

func Apply(agentID, accountID bson.ObjectID, ch ModChange, applyNow bool, trigger v2.TaskTrigger) ([]string, error)

Apply resolves the change, persists it, and enqueues the work.

applyNow is only consulted when the server is running: a stopped server needs no chain and no gate.

func ApplyConfigOnly

func ApplyConfigOnly(agentID, accountID bson.ObjectID, modReference, config string, applyNow bool, trigger v2.TaskTrigger) ([]string, error)

ApplyConfigOnly persists an edit to a mod's .cfg text and enqueues a sync.

A config-text edit moves no versions, so Diff on the resulting lockfile is empty and Apply's diff-empty short circuit would silently drop it — but the text only reaches the agent's disk inside ModLock.Config during a sync, so skipping the enqueue here would leave the running server on the old config indefinitely. The diff check is therefore skipped deliberately, not an oversight: for a config-only change, "the lockfile didn't change" says nothing about whether the agent needs to re-sync.

func ApplyPendingNow

func ApplyPendingNow(agentID, accountID bson.ObjectID, trigger v2.TaskTrigger) ([]string, error)

ApplyPendingNow escalates an already-deferred change to run immediately.

The selection was persisted when the change was deferred, so Diff is empty and Apply's short circuit would silently drop this — but the pending sync is real and is only waiting for the server to stop. The diff check is skipped deliberately, exactly as in ApplyConfigOnly: "the lockfile didn't change" says nothing about whether a sync is still owed.

Nothing is persisted here either; there is nothing new to persist. planFor sees the pending sync id plus applyNow and RE-POINTS that same sync onto a fresh stop -> sync -> start chain rather than adding a second one.

func Backfill

func Backfill() error

Backfill migrates the old embedded modConfig.selectedMods array into agentmods.

It is idempotent: an agent whose array has already been unset does not match the query below, so this is safe to run on every boot until it is deleted.

A mod that is permanently absent from the catalogue (pruned, or a lookup that keeps erroring) will block that agent's migration on EVERY boot, and will say so in the error log every time. That is deliberate: a loud, recoverable stall is preferable to a silent, permanent loss of the agent's mod selection, and only an operator can tell the two failure modes apart.

func CountDirectByAgent

func CountDirectByAgent(agentIDs []bson.ObjectID) (map[bson.ObjectID]int32, error)

CountDirectByAgent counts each agent's directly-selected mods - the user's own selection, not the resolved dependency closure - in a single aggregation over the whole batch. The dashboard lists every agent on an account and needs a count per card; a count query per agent would be an N+1 on the most-loaded page in the product.

Callers must pass only agent IDs they have already authorised: this scopes to the IDs given and nothing else, so it neither widens nor enforces account access.

Agents with no mods are absent from the aggregation's output entirely, so the map simply has no entry for them and a lookup yields the zero value - which is the count you want.

func DeleteAbsent

func DeleteAbsent(agentID bson.ObjectID, keep []string) error

DeleteAbsent removes the agent's rows for mods no longer in the lockfile. It is how a removed mod, and a dependency nothing needs any more, leave the selection.

func EnsureIndexes

func EnsureIndexes() error

EnsureIndexes creates the indexes the collection's correctness depends on. uniq_agent_mod is the upsert key and the guarantee that one agent cannot hold two rows for the same mod.

func Get

func Get(agentID bson.ObjectID, modReference string) (*v2.AgentModSchema, error)

Get returns the agent's row for one mod, or (nil, nil) if it has none.

func Init

func Init() error

Init prepares the collection at boot: indexes first, then the migration. Call this once during boot, next to agenttask.EnsureIndexes() and before agenttask.StartDispatcher() - the migration must finish before any task can be dispatched against a lockfile that assumes agentmods already holds the agent's selection.

func ListForAgent

func ListForAgent(agentID bson.ObjectID) ([]v2.AgentModSchema, error)

func RefreshNeedsUpdate

func RefreshNeedsUpdate() error

RefreshNeedsUpdate flags every agent mod whose catalogue version has moved past its pin. It never touches desiredVersion and never enqueues a task: a version bump is always a user action, so a bad mod release cannot take down a live server unattended.

This replaces CheckAgentModsConfigs, which loaded every agent document in the database and wrote every mod config back to compute this boolean. Here it is one updateMany per catalogue mod, keyed on modReference, never touching an agent document at all.

func ReportInstalled

func ReportInstalled(agentID bson.ObjectID, mods []v2.InstalledMod) error

ReportInstalled records what the agent actually has on disk. Mods the agent reports but which are not in its selection are ignored: the next sync removes them from the disk, and creating rows for them here would resurrect them.

func Resolve

func Resolve(agentID bson.ObjectID) (v2.Lockfile, error)

Resolve pins the agent's current direct selection.

func ResolveSelection

func ResolveSelection(agentID bson.ObjectID, direct map[string]string) (v2.Lockfile, error)

ResolveSelection pins a hypothetical direct selection: modReference -> the version constraint the user pinned, or "" for "latest compatible".

This is the one place the dependency graph is computed. The agent never resolves anything, so two agents cannot disagree, and an impossible selection is an error the user sees at click time rather than a task that dies later.

func SetConfig

func SetConfig(agentID bson.ObjectID, modReference, config string) error

func SetDesiredVersion

func SetDesiredVersion(agentID bson.ObjectID, modReference, version string) error

func UpsertMany

func UpsertMany(agentID, accountID bson.ObjectID, locks []v2.ModLock, modIDs map[string]bson.ObjectID) error

UpsertMany writes a resolved lockfile back as the agent's selection: one upsert per mod, keyed on uniq_agent_mod. It sets desiredVersion, direct, and config, and leaves installed/installedVersion alone — those belong to the agent's report, and clobbering them here would make every sync look necessary.

Types

type Change

type Change struct {
	Added   []ChangedMod `json:"added"`
	Removed []ChangedMod `json:"removed"`
	Changed []ChangedMod `json:"changed"`
}

func Diff

func Diff(current []v2.AgentModSchema, next v2.Lockfile) Change

Diff compares the agent's current selection against a freshly resolved lockfile. It is pure, and it is what both Preview (which renders it) and Apply (which acts on it) are built from.

func Preview

func Preview(agentID bson.ObjectID, ch ModChange) (Change, error)

Preview resolves a hypothetical change and returns what it would do. Nothing is written. A resolution failure is returned as an error, which is what puts "cannot add X: requires SF >= 1.1" in the dialog instead of in a dead task.

func (Change) IsEmpty

func (c Change) IsEmpty() bool

type ChangedMod

type ChangedMod struct {
	ModReference string `json:"modReference"`
	From         string `json:"from,omitempty"`
	To           string `json:"to,omitempty"`
	// Dependency is true when the resolver pulled this mod in rather than the user
	// choosing it. "Also installs: A, B, C".
	Dependency bool `json:"dependency"`
}

ChangedMod is one line of a change, as the confirmation dialog renders it.

type ModChange

type ModChange struct {
	Op           string
	ModReference string
	Version      string
}

ModChange is one user action on the mod selection.

Jump to

Keyboard shortcuts

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