update

package
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Oct 14, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

Package update finds, installs, and restarts onto codeaf releases.

Index

Constants

View Source
const (
	// CurlCommand is the independent installation road shown at launch beside
	// /update and after a failed or unavailable in-place update. IT IS THE
	// SUPPORTED LINE, NOT THE SCRIPT UNDER IT: agentfield.ai/get/codeaf serves
	// scripts/install.sh from main, and the address is the one a person can
	// remember and the one every other place codeaf documents spells, so a
	// launch line and the README can never hand out two different roads.
	CurlCommand = "curl -fsSL https://agentfield.ai/get/codeaf | bash"

	NoUpdateCheckEnv  = "CODEAF_NO_UPDATE_CHECK"
	GitHubAPIEnv      = "CODEAF_GITHUB_API"
	GitHubDownloadEnv = "CODEAF_GITHUB_DOWNLOAD"
)
View Source
const (
	// AutoGrace is how long a launch offer waits before the automatic answer.
	// Nothing is blocked while it runs.
	AutoGrace = 10 * time.Second
)

The auto updater's durable memory: one small file in the profile holding the release a person dismissed, the release that failed and how often, and the backoff after that failure. An unreadable file reads as empty state, because the worst case of forgetting is one repeated offer.

View Source
const (
	// CheckTimeout is the whole-exchange budget for a launch check and for
	// `codeaf update --check`. THE CHECK CLOCK NEVER COVERS AN INSTALL: a
	// release asset is allowed to make steady progress for much longer.
	CheckTimeout = 3 * time.Second
)

Variables

View Source
var ErrInstallInFlight = errors.New("another codeaf is already installing an update")

ErrInstallInFlight means another process holds the target's lock.

Functions

func AutoStatePath

func AutoStatePath(profileDir string) string

AutoStatePath names the coordinator's file in the profile.

func ChannelTag

func ChannelTag(channel, date, sha string) (string, error)

ChannelTag forms a dev or staging tag from a date and revision.

func CleanupOld

func CleanupOld(string)

CleanupOld removes nothing on systems where the running executable can be replaced directly.

func CleanupOldRunning

func CleanupOldRunning(func() (string, error))

CleanupOldRunning does nothing where replacing a running executable leaves no backup. In particular, it does not resolve the executable during launch.

func CompareChannelBuilds added in v0.4.0

func CompareChannelBuilds(running string, runningPublished time.Time, selected string, selectedPublished time.Time) (int, bool)

CompareChannelBuilds compares a running build with the release selected by the API from the same channel. A negative result means the selected release is newer; a positive result means the running build is ahead.

func CompareSemverTags

func CompareSemverTags(left, right string) (int, bool)

CompareSemverTags orders stable and release-candidate tags. A stable tag follows every release candidate on its own version line.

func CurlLine added in v0.4.0

func CurlLine(executable, channel string) string

CurlLine returns the independent installation road for the running file and release channel. The product remains codeaf; only the destination file name changes when a differently named executable asks for its own road back.

func DismissRelease

func DismissRelease(profileDir, tag string) error

DismissRelease records "not now" for one exact release. A lock or write that failed is returned, because the surface may not report a skip it did not save.

func ExecutableTarget

func ExecutableTarget(executable func() (string, error)) (string, error)

ExecutableTarget resolves the running binary before an installer replaces it.

func FollowedChannel added in v0.4.0

func FollowedChannel(running string) string

FollowedChannel names the release channel a build takes its updates from. IT IS NOT ALWAYS THE CHANNEL THE TAG WAS CUT ON: a release candidate follows STABLE, because its launch line, its bare /update and its bare `codeaf update` all speak about the stable release its line is heading for. A build from source follows stable too, so the road it is offered is the ordinary one. Only dev and staging follow themselves. Every place that has to answer "which channel is this build on" asks here, so the launch notice and the curl line under it can never name two different roads.

func InstallRefusal

func InstallRefusal(target, curl string) string

InstallRefusal names why this target may not be replaced in place — a package manager owns it, or its directory is not writable — or "" when an ordinary install may proceed. The curl line is the road the refusal offers instead.

THE TARGET IS CANONICALIZED FIRST, exactly as Install canonicalizes it: a launcher that hands in a symlink (or a folder reached through one) must answer about the file the replacement would land on, or a managed install answers "ordinary" while the installer refuses it a moment later.

func Kind

func Kind(tag string) string

Kind names the release channel encoded by a tag.

func NextSemverTag

func NextSemverTag(tags []string, channel, component string) (string, error)

NextSemverTag returns the next stable or release-candidate tag.

func PackageManaged

func PackageManaged(target string) string

PackageManaged names the package manager that owns a path, or "" when the path is an ordinary install this program may replace. A brew, distro or nix install must go through its manager, not through an in-place replacement that the next upgrade would quietly undo.

func ParseChannel added in v0.4.0

func ParseChannel(tag string) (channel, date string, ok bool)

ParseChannel returns the channel and calendar date carried by a dev or staging tag. The date is kept as YYYYMMDD because that spelling sorts in the same order as the days it names.

func Restart

func Restart(plan Plan) error

Restart replaces this process after the terminal has been restored.

func RestartArgs

func RestartArgs(original []string, transcript string) []string

RestartArgs keeps the original launch shape and points it at this transcript.

func SaveAutoState

func SaveAutoState(profileDir string, state AutoState) error

SaveAutoState writes the coordinator's file atomically. It is the raw write and does NOT take the profile lock: a caller with a read-modify-write to make goes through [mutateAutoState], which holds both locks across the read and the write.

func ShouldOffer

func ShouldOffer(state AutoState, tag string, now time.Time) bool

ShouldOffer reports whether the automatic updater may offer this release now: not dismissed, attempts not exhausted, and any backoff passed. A manual door ignores all three.

func ValidDate

func ValidDate(value string) bool

ValidDate reports whether a date has the channel tag's YYYYMMDD shape.

func ValidSHA

func ValidSHA(value string) bool

ValidSHA reports whether a revision can form a channel tag.

Types

type AutoState

type AutoState struct {
	// DismissedRelease is the tag a person said no to, by chord or by command.
	DismissedRelease string    `json:"dismissed_release,omitempty"`
	DismissedAt      time.Time `json:"dismissed_at,omitempty"`
	// FailedRelease, Failures and LastAttemptAt bound automatic retries.
	FailedRelease string    `json:"failed_release,omitempty"`
	Failures      int       `json:"failures,omitempty"`
	LastAttemptAt time.Time `json:"last_attempt_at,omitempty"`
	LastError     string    `json:"last_error,omitempty"`
}

AutoState is what the background updater remembers between launches.

func AutoFailure

func AutoFailure(profileDir, tag string, err error) AutoState

AutoFailure records one failed automatic install of a release. Its signature is unchanged — the caller only wants the note on the surface — but a write that did not land returns what the file really holds rather than a fabricated count, and a failure recorded by another window is not clobbered: the whole state is read and rewritten under the lock.

func AutoSuccess

func AutoSuccess(profileDir string) AutoState

AutoSuccess clears the failure belonging to a release now installed.

IT DOES NOT CLEAR A DISMISSAL. The person's "not now" for another tag is a separate decision and survives — the read-modify-write reads the whole state and empties only the failure fields — and doing so under the profile lock is what keeps a concurrent skip from being erased by a success that read the file a moment too early.

func LoadAutoState

func LoadAutoState(profileDir string) AutoState

LoadAutoState reads the coordinator's file; a missing or corrupt one is empty.

func (AutoState) Blocked

func (s AutoState) Blocked(tag string) bool

Blocked reports whether automatic installation of this release has been tried too many times already.

func (AutoState) Dismissed

func (s AutoState) Dismissed(tag string) bool

Dismissed reports whether this exact release was answered "not now".

func (AutoState) RetryDue

func (s AutoState) RetryDue(tag string, now time.Time) bool

RetryDue reports whether a failed release may be tried again now.

type Available

type Available struct {
	Latest           string
	Running          string
	LatestPublished  time.Time
	RunningPublished time.Time
	Curl             string
}

Available is the release comparison shown at launch or by --check.

func CheckLaunch

func CheckLaunch(ctx context.Context, options CheckOptions) (Available, bool)

CheckLaunch silently checks the release channel this build follows. Every failure is absence at this surface.

func (Available) Ahead added in v0.4.0

func (a Available) Ahead() bool

Ahead reports whether this build is newer than the release selected from its own channel. Source builds and incomparable channels are never called ahead.

func (Available) Newer

func (a Available) Newer() bool

Newer reports whether the selected release supersedes this build.

func (Available) Notice

func (a Available) Notice() string

Notice is the one launch line carrying both update roads.

type CheckOptions

type CheckOptions struct {
	Running    string
	ProfileDir string
	Client     *Client
	Now        func() time.Time
	Disabled   bool
	Executable string
}

CheckOptions supplies the launch policy's disk, clock, and release client.

type Choice

type Choice struct {
	Channel string
	Version string
	Running string
	// Published is THE MOMENT THE CALLER ALREADY KNOWS this exact release was
	// published, and it means something only beside Version.
	//
	// A PINNED TAG IS RESOLVED WITHOUT THE API, so the release it answers would
	// carry no publish moment at all — and the install record written from it
	// could not be ordered against the file on disk, which is how a stale
	// same-day dev or staging build gets written over a newer one
	// ([recordSupersedes]). The road that pins has the fact already: the launch
	// check read it from the API and the offer kept it, so it rides here rather
	// than being guessed at under the lock. A caller resolving a CHANNEL leaves
	// it zero, because the API's own answer is the fact on that road.
	Published time.Time
}

Choice selects one release channel or one exact tag.

type Client

type Client struct {
	HTTP            *http.Client
	APIBase         string
	DownloadBase    string
	Token           string
	Revision        string
	CheckWindow     time.Duration
	APIWindow       time.Duration
	StallWindow     time.Duration
	DownloadCeiling time.Duration
}

Client talks to the release API and release asset host.

func NewClient

func NewClient(revision string, timeout time.Duration) *Client

NewClient builds the release client from the same mirror and token inputs as the shell installer. The timeout belongs only to the small release check; every install request carries the clock for the particular thing it reads.

func (*Client) Check

func (c *Client) Check(ctx context.Context, choice Choice) (Release, error)

Check selects one release inside the short whole-exchange window shared by the launch notice and the terminal's explicit check. Installation calls Select directly, where each metadata request receives its own API window.

func (*Client) Select

func (c *Client) Select(ctx context.Context, choice Choice) (Release, error)

Select resolves a channel with the same timestamp law as the shell installer.

func (*Client) TagPublished

func (c *Client) TagPublished(ctx context.Context, tag string) (time.Time, bool)

TagPublished asks the release API for ONE exact tag's publish moment, and it is BEST-EFFORT BY CONTRACT. The moment is what the install record carries and what orders two same-day dev or staging builds ([recordSupersedes]), so a NAMED tag — which [Select] answers without the API — is worth one bounded lookup before its install writes that record. A tag the API cannot answer for — offline, rate-limited, deleted, or a release the API has not indexed — leaves the moment UNKNOWN, which is never guessed at and never fails the install: the file is still there to fetch, and a person naming it means it.

type InstallLock

type InstallLock struct {
	// contains filtered or unexported fields
}

InstallLock is a held lock on one executable target.

func TryTargetLock

func TryTargetLock(target string) (*InstallLock, error)

TryTargetLock claims the lock for one executable target, returning ErrInstallInFlight when another process holds it. The target is canonicalized first, so an alias of a file already held answers in flight rather than opening a second lock file beside a path nothing else names.

func (*InstallLock) Record

func (l *InstallLock) Record() (installRecord, bool)

Record reads the last install this target recorded, and reports false unless the record still matches the file on disk. A tag written by bytes that are no longer there is not a fact about the file.

THE BYTES COME THROUGH THE HELD HANDLE, and the read does not move its cursor (io.NewSectionReader). A second handle would be a second look-up of a name another process may be replacing, and on Windows it would be unreadable to the process that locked it: LockFileEx's own documentation says that a process which locks a region with one handle — which is what [lockFileExclusive] does — cannot access that region through a second handle until it unlocks it (learn.microsoft.com/windows/win32/api/fileapi/nf-fileapi-lockfileex). A record written by the lock holder would then be the one thing it could not read, so the record is read from the handle that holds the lock.

func (*InstallLock) RecordInstalled

func (l *InstallLock) RecordInstalled(tag, digest string, published time.Time)

RecordInstalled writes what this target now holds. The caller holds the lock and has just written those bytes.

func (*InstallLock) Release

func (l *InstallLock) Release()

Release drops the lock. It is safe on a nil lock and safe to call twice.

type InstallOptions

type InstallOptions struct {
	Client  *Client
	Release Release
	Target  string
	Curl    string
	GOOS    string
	GOARCH  string
	// AllowDowngrade says the PERSON chose this exact release by name — `/update
	// <tag>` or `update --version <tag>` — and a rollback is what they asked
	// for. An automatic or channel-following update never sets it, so a stale
	// caller cannot silently downgrade a file another window advanced.
	AllowDowngrade bool
}

InstallOptions describes one in-place replacement.

type InstallResult

type InstallResult struct {
	Release Release
	Path    string
	// Already is true when the target was already the requested release: a
	// successful no-op, not a failure, and not something to alarm anyone about.
	Already bool
}

InstallResult is the release and executable path that are ready to restart.

func Install

func Install(ctx context.Context, options InstallOptions) (InstallResult, error)

Install downloads, checks, and atomically replaces one executable.

type Plan

type Plan struct {
	Path string
	Args []string
}

Plan is the executable and arguments a restored terminal restarts with.

type RCTag

type RCTag struct {
	Base    Version
	Counter int
}

RCTag is a release candidate and the stable line it belongs to.

func ParseRC

func ParseRC(tag string) (RCTag, bool)

ParseRC parses a release-candidate tag.

type RefusalError

type RefusalError struct {
	Reason string
}

RefusalError is a typed refusal: this target will not be replaced in place, and Reason is the sentence to show. It is not a download or write failure, so it is never retried and never counted against a release's automatic attempts.

func (*RefusalError) Error

func (e *RefusalError) Error() string

type Release

type Release struct {
	Tag                string
	Repository         string
	PublishedAt        time.Time
	RunningPublishedAt time.Time
}

Release is one selected GitHub release and the repository that answered.

type Version

type Version struct {
	Major int
	Minor int
	Patch int
}

Version is the stable semantic part of a release tag.

func ParseStable

func ParseStable(tag string) (Version, bool)

ParseStable parses a stable release tag.

func (Version) Bump

func (v Version) Bump(component string) (Version, error)

Bump advances one semantic component.

func (Version) Compare

func (v Version) Compare(other Version) int

Compare orders stable semantic versions.

func (Version) String

func (v Version) String() string

Jump to

Keyboard shortcuts

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