mirror of
https://github.com/ollama/ollama.git
synced 2026-07-03 03:38:52 +00:00
launch: check for min version for hermes desktop (#16912)
This commit is contained in:
parent
1c5ebbf5f4
commit
ada1eb5163
2 changed files with 184 additions and 1 deletions
|
|
@ -14,6 +14,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/ollama/ollama/api"
|
||||
|
|
@ -23,6 +24,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// https://github.com/NousResearch/hermes-agent/releases/tag/v2026.6.5
|
||||
hermesDesktopMinVersion = "v0.16.0"
|
||||
hermesInstallScript = "curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- --skip-setup"
|
||||
hermesWindowsInstallURL = "https://hermes-agent.nousresearch.com/install.ps1"
|
||||
hermesWindowsInstallCmd = "& ([scriptblock]::Create((irm " + hermesWindowsInstallURL + "))) -SkipSetup"
|
||||
|
|
@ -94,9 +97,48 @@ func (h *HermesDesktop) Run(_ string, _ []LaunchModel, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.ensureHermesDesktopMinVersion(bin); err != nil {
|
||||
return err
|
||||
}
|
||||
return hermesAttachedCommand(bin, h.launchArgs(args)...).Run()
|
||||
}
|
||||
|
||||
func (h *HermesDesktop) ensureHermesDesktopMinVersion(bin string) error {
|
||||
if hermesGOOS == "windows" {
|
||||
return nil
|
||||
}
|
||||
version := hermesVersionOf(bin)
|
||||
if version == "" {
|
||||
return nil
|
||||
}
|
||||
if semver.Compare(version, hermesDesktopMinVersion) >= 0 {
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "%sHermes %s is older than the minimum version (%s) for `hermes desktop`; updating...%s\n", ansiGray, version, hermesDesktopMinVersion, ansiReset)
|
||||
if err := hermesAttachedCommand(bin, "update").Run(); err != nil {
|
||||
return fmt.Errorf("failed to update hermes to %s or newer: %w", hermesDesktopMinVersion, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hermesVersionOf(bin string) string {
|
||||
out, err := hermesCommand(bin, "--version").Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
firstLine := strings.SplitN(strings.TrimSpace(string(out)), "\n", 2)[0]
|
||||
return parseHermesVersion(firstLine)
|
||||
}
|
||||
|
||||
func parseHermesVersion(firstLine string) string {
|
||||
for _, field := range strings.Fields(firstLine) {
|
||||
if semver.IsValid(field) {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (h *HermesDesktop) Onboard() error {
|
||||
return config.MarkIntegrationOnboarded("hermes-desktop")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -630,7 +630,7 @@ func hermesDesktopTestExecutableRelativePath(goos string) string {
|
|||
func writeHermesDesktopTestBinary(t *testing.T, dir string) {
|
||||
t.Helper()
|
||||
bin := filepath.Join(dir, "hermes")
|
||||
if err := os.WriteFile(bin, []byte("#!/bin/sh\nprintf '[%s]\\n' \"$*\" >> \"$HOME/hermes-invocations.log\"\n"), 0o755); err != nil {
|
||||
if err := os.WriteFile(bin, []byte("#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then\n printf 'Hermes Agent v0.16.0 (2026.6.5)\\n'\n exit 0\nfi\nprintf '[%s]\\n' \"$*\" >> \"$HOME/hermes-invocations.log\"\n"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -732,6 +732,147 @@ func TestHermesDesktopRun(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
func writeHermesVersionedTestBinary(t *testing.T, dir, version string) {
|
||||
t.Helper()
|
||||
script := "#!/bin/sh\n" +
|
||||
"case \"$1\" in\n" +
|
||||
" --version)\n" +
|
||||
" printf 'Hermes Agent " + version + " (test)\\n'\n" +
|
||||
" ;;\n" +
|
||||
" update)\n" +
|
||||
" printf 'update\\n' >> \"$HOME/hermes-update.log\"\n" +
|
||||
" ;;\n" +
|
||||
" *)\n" +
|
||||
" printf '[%s]\\n' \"$*\" >> \"$HOME/hermes-invocations.log\"\n" +
|
||||
" ;;\n" +
|
||||
"esac\n"
|
||||
bin := filepath.Join(dir, "hermes")
|
||||
if err := os.WriteFile(bin, []byte(script), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHermesVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{"standard release", "Hermes Agent v0.16.0 (2026.6.5)", "v0.16.0"},
|
||||
{"newer release", "Hermes Agent v0.17.0 (2026.6.19)", "v0.17.0"},
|
||||
{"older release", "Hermes Agent v0.15.1 (2026.5.29)", "v0.15.1"},
|
||||
{"prerelease", "Hermes Agent v0.16.0-rc1 (2026.6.5)", "v0.16.0-rc1"},
|
||||
{"no version token", "Hermes Agent", ""},
|
||||
{"empty", "", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := parseHermesVersion(tt.input); got != tt.want {
|
||||
t.Fatalf("parseHermesVersion(%q) = %q, want %q", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHermesDesktopRun_UpdatesCliOlderThanMinVersion(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("uses a POSIX shell test binary")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
withLauncherHooks(t)
|
||||
withInteractiveSession(t, true)
|
||||
withHermesPlatform(t, runtime.GOOS)
|
||||
clearHermesMessagingEnvVars(t)
|
||||
clearHermesDesktopPackageEnvVars(t)
|
||||
t.Setenv("PATH", tmpDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
writeHermesVersionedTestBinary(t, tmpDir, "v0.15.1")
|
||||
|
||||
DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) {
|
||||
t.Fatalf("did not expect messaging prompt during desktop launch: %s", prompt)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := (&HermesDesktop{}).Run("", nil, []string{"--foreground"}); err != nil {
|
||||
t.Fatalf("Run returned error: %v", err)
|
||||
}
|
||||
|
||||
updateLog, err := os.ReadFile(filepath.Join(tmpDir, "hermes-update.log"))
|
||||
if err != nil {
|
||||
t.Fatalf("expected hermes update to run for an older CLI: %v", err)
|
||||
}
|
||||
if strings.TrimSpace(string(updateLog)) != "update" {
|
||||
t.Fatalf("expected update log 'update', got %q", updateLog)
|
||||
}
|
||||
if got := readHermesDesktopInvocations(t, tmpDir); got != "[desktop --foreground]" {
|
||||
t.Fatalf("expected desktop launch after update, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHermesDesktopRun_SkipsMinVersionCheckOnWindows(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("uses a POSIX shell test binary")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
withLauncherHooks(t)
|
||||
withInteractiveSession(t, true)
|
||||
withHermesPlatform(t, "windows")
|
||||
clearHermesMessagingEnvVars(t)
|
||||
clearHermesDesktopPackageEnvVars(t)
|
||||
t.Setenv("PATH", tmpDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
writeHermesVersionedTestBinary(t, tmpDir, "v0.15.1")
|
||||
|
||||
DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) {
|
||||
t.Fatalf("did not expect messaging prompt during desktop launch: %s", prompt)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := (&HermesDesktop{}).Run("", nil, []string{"--foreground"}); err != nil {
|
||||
t.Fatalf("Run returned error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(tmpDir, "hermes-update.log")); err == nil {
|
||||
t.Fatal("expected hermes update NOT to run on Windows, but hermes-update.log exists")
|
||||
}
|
||||
if got := readHermesDesktopInvocations(t, tmpDir); got != "[desktop --foreground]" {
|
||||
t.Fatalf("expected desktop launch without update, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHermesDesktopRun_DoesNotUpdateCliAtOrAboveMinVersion(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("uses a POSIX shell test binary")
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
setTestHome(t, tmpDir)
|
||||
withLauncherHooks(t)
|
||||
withInteractiveSession(t, true)
|
||||
withHermesPlatform(t, runtime.GOOS)
|
||||
clearHermesMessagingEnvVars(t)
|
||||
clearHermesDesktopPackageEnvVars(t)
|
||||
t.Setenv("PATH", tmpDir+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||||
writeHermesVersionedTestBinary(t, tmpDir, "v0.17.0")
|
||||
|
||||
DefaultConfirmPrompt = func(prompt string, options ConfirmOptions) (bool, error) {
|
||||
t.Fatalf("did not expect messaging prompt during desktop launch: %s", prompt)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := (&HermesDesktop{}).Run("", nil, []string{"--foreground"}); err != nil {
|
||||
t.Fatalf("Run returned error: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(tmpDir, "hermes-update.log")); err == nil {
|
||||
t.Fatal("expected hermes update NOT to run for a current CLI, but hermes-update.log exists")
|
||||
}
|
||||
if got := readHermesDesktopInvocations(t, tmpDir); got != "[desktop --foreground]" {
|
||||
t.Fatalf("expected desktop launch without update, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHermesDesktopRunUsesWindowsLocalAppDataPackage(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue