package agent import ( "fmt" "regexp" "strconv" ) // MinVersions defines the minimum required CLI version for each agent type. // Versions below these will be rejected during daemon registration. var MinVersions = map[string]string{ "claude": "2.0.0", "codex": "0.100.0", // app-server --listen stdio:// added in 0.100.0 } // semver holds a parsed semantic version (major.minor.patch). type semver struct { Major, Minor, Patch int } // versionRe matches version strings like "2.1.100", "v2.0.0", or // "2.1.100 (Claude Code)" — it extracts the first three numeric components. var versionRe = regexp.MustCompile(`v?(\d+)\.(\d+)\.(\d+)`) // parseSemver extracts a semver from a version string. func parseSemver(raw string) (semver, error) { m := versionRe.FindStringSubmatch(raw) if m == nil { return semver{}, fmt.Errorf("cannot parse version %q", raw) } major, _ := strconv.Atoi(m[1]) minor, _ := strconv.Atoi(m[2]) patch, _ := strconv.Atoi(m[3]) return semver{Major: major, Minor: minor, Patch: patch}, nil } // lessThan returns true if v < other. func (v semver) lessThan(other semver) bool { if v.Major != other.Major { return v.Major < other.Major } if v.Minor != other.Minor { return v.Minor < other.Minor } return v.Patch < other.Patch } // CheckMinVersion validates that detectedVersion meets the minimum for agentType. // Returns nil if the version is acceptable or no minimum is defined. func CheckMinVersion(agentType, detectedVersion string) error { minRaw, ok := MinVersions[agentType] if !ok { return nil } min, err := parseSemver(minRaw) if err != nil { return fmt.Errorf("invalid minimum version %q for %s: %w", minRaw, agentType, err) } detected, err := parseSemver(detectedVersion) if err != nil { return fmt.Errorf("cannot parse detected %s version %q: %w", agentType, detectedVersion, err) } if detected.lessThan(min) { return fmt.Errorf("%s version %s is below minimum required %s — please upgrade", agentType, detectedVersion, minRaw) } return nil }