Обновление и удаление

Обновление

Способ обновления зависит от того, как установлен работающий Hermes:

УстановкаКак обновлять
Управляемый checkout исходниковhermes update или передача обновления из десктопа, собранного из исходников.
MSIX для Windows (sideload)Кнопка Update в десктопе и Windows App Installer.
Пакет из Microsoft StoreОбновления Microsoft Store.
Бандл-приложение для macOSКнопка Update в десктопе, через electron-updater.
Docker-образСкачайте нужный образ и пересоздайте контейнер с тем же томом данных.
NixОбновите flake/профиль и пересоберите.
Termux APTpkg update, затем pkg upgrade hermes-agent.

hermes update не переписывает файлы приложения, которыми владеет пакет. PM управляет зависимостями, а не распространением приложения: hermes pm update — команда мейнтейнера для обновления пинов, а не альтернативный способ обновить приложение.

Для управляемой установки из исходников:

hermes update

Checkout исходников официального репозитория следит за каналом stable: берёт последний опубликованный релиз GitHub вида vX.Y.Z (черновики, пререлизы и теги canary не в счёт) и переключается ровно на его коммит. hermes update и кнопка обновления в десктопе переходят только от релиза к релизу, а не от коммита к коммиту. Форки и зеркала по-прежнему следят за веткой main. Обновление готовит зависимости через PM и сообщает об изменениях конфигурации и о результатах перезапуска процессов.

hermes update --set-channel main     # follow every commit on main instead
hermes update --set-channel stable   # back to releases only
hermes update --channel main         # one run against main; nothing is saved

Выбор хранится по установкам в update.installs файла config.yaml. Установка, которая канал не выбирала, двигается только вперёд: checkout, уже опередивший последний релиз (например, следивший за main до появления этого умолчания), остаётся на месте, пока не выйдет более новый релиз. Команда --set-channel stable закрепляет checkout на точном коммите релиза, даже если тот старше. Установки из Windows Store, MSIX и бандлы десктопа это не затрагивает: их канал зашит в пакет (см. ниже).

Обновление десктопных бандлов

В Windows sideload-приложение проверяет зарегистрированный источник App Installer. При применении обновления оно сначала скачивает дескриптор .appinstaller, потом останавливает свои бэкенды, открывает этот локальный файл и выходит, чтобы Windows заменила пакет. Отсоединённый процесс-ожидатель пытается перезапустить приложение, когда версия пакета сменится. Если зарегистрировать ожидатель не удалось, появится предупреждение: открыть приложение придётся вручную.

В macOS применение обновления скачивает ZIP и ждёт, пока Squirrel.Mac примет подписанное приложение, и только потом останавливает бэкенды. Установку с перезапуском запрашивает лишь этот сценарий применения. Обычный выход из приложения скачанное обновление не ставит. Если загрузка или проверка сорвалась, бэкенды продолжают работать.

Пакеты из Store обновляются через Store, а не через какой-либо из sideload-фидов. Сломанную или недоступную проверку обновлений нельзя трактовать как «уже последняя версия». Приложение и встроенная в него базовая среда выполнения обновляются вместе; пользовательские данные лежат вне пакета. См. Установку десктопа.

Десктопные бандлы stable и canary — это разные приложения. Установка canary не заменяет stable, в том числе в Windows с MSIX. У каждого приложения своё состояние десктопа, и обновляется оно в пределах канала, вшитого в его сборку: сменить канал — значит поставить другое приложение, а не поменять настройку. У иконки canary жёлтый фон (тёмно-жёлтый в тёмной теме), а команда CLI у неё — hermes-canary.

Разовые бандлы под конкретный коммит отделены и от обоих каналов релизов, и от других таких же бандлов. На их красных иконках — короткий SHA сборки, он же стоит в команде CLI hermes-<short-sha>. Такие сборки не проверяют и не ставят обновления, в том числе через hermes update и hermes update --check. Вместо этого они пишут:

This build doesn’t get updates. Ask the developer who gave it to you for a new build.

При этом разные приложения всё равно делят профили, конфигурацию и сессии Hermes в одном домашнем каталоге Hermes. Запуск разных сборок на одном профиле не даёт изоляции схемы: новая сборка может изменить сохранённые данные так, что старая их уже не прочитает. Перед тестами сделайте бэкап общих данных. Десктоп и отдельный CLI предупреждают, когда тот же профиль использует другая живая установка, — но это лишь совет, а не блокировка. Уведомления десктопа после обновления привязаны к приложению, так что запуск canary не «съест» отложенное уведомление stable. Схема URL hermes:// остаётся общей: ссылки обрабатывает то приложение, которое зарегистрировало её последним.

совет

hermes update сам находит новые параметры конфигурации и предлагает их добавить. Пропустили приглашение? Запустите hermes config check, чтобы увидеть недостающие опции, а потом hermes config migrate — он добавит их в интерактивном режиме.

Пассивные уведомления об обновлении

На закреплённых и неинтерактивных установках пассивную проверку версии в CLI и в баннере можно отключить:

hermes config set updates.check false

Это убирает и закэшированные уведомления об обновлении, и сетевые запросы пассивной проверки. По умолчанию — true. Явные hermes update --check и hermes update продолжают работать; на апдейтер десктопного приложения эта настройка не влияет.

Что происходит при обновлении

Для checkout исходников, которому разрешено обновляться, hermes update проходит такие фазы:

  1. Снимок состояния до обновления — Hermes сохраняет выбранные файлы состояния каждого профиля в его каталог state-snapshots/. Среди них данные сопряжения, cron-задачи, config.yaml, .env и auth.json. Автоматические быстрые снимки пропускают отдельные файлы больше 1 ГиБ. Параметр updates.pre_update_backup выбирает quick, full или off. Полные архивы учитывают исключения бэкапа. Восстановление описано в разделе Снимки и откат. Быстрые снимки возвращают файлы состояния, но не код приложения. Снимок делается по принципу best-effort: если он не удался, обновление печатает предупреждение ⚠ Pre-update snapshot FAILED и идёт дальше, а квитанция записывает pre_update_backup как сорвавшийся шаг (намеренный off/--no-backup попадает в пропуски квитанции вместе с причиной).
  2. Обновление кода — применяет настроенную ветку исходников или тег стабильного релиза и обновляет сабмодули. В Linux и macOS каждый шлюз, запущенный из этой установки, приостанавливается непосредственно перед первым изменением checkout (см. Простой шлюза во время обновления); если применять нечего или обновление упало до изменения checkout, ничего не останавливается.
  3. Проверка синтаксиса после pull + автооткат — закончив pull, Hermes компилирует девять критичных файлов, которые импортируются при каждом запуске hermes. Если хоть один не парсится (скажем, осиротевший маркер конфликта слияния или случайно обрезанный файл), Hermes откатывает установку, чтобы ваша оболочка осталась рабочей: сбрасывает checkout на коммит, с которого он работал до pull (git reset --hard <sha-до-pull>), а если обновление ушло с detached-checkout или с припаркованной фича-ветки, возвращается туда командой git checkout --detach <sha> / git checkout <branch> (ветку, которую не удаётся снова открыть, восстанавливает в detached-состоянии на том же коммите). Повторите hermes update, когда апстрим починит проблему.
  4. Подготовка зависимостей — PM подтягивает нужные инструменты и готовит полное окружение Python из нового lock-файла, уже установленных extras и требований включённых плагинов. Прежде чем опубликовать свой выбор, он проверяет это окружение. Из-за плагина обновление не падает никогда. Плагин, который больше не подходит новому ядру, добавляется в plugins.disabled во всех профилях, где он включён (у провайдера памяти очищается memory.provider). Сюда относятся requires-python, исключающий Python самого Hermes, manifest_version новее, чем поддерживает этот Hermes, и зависимости, которые резолвер не может совместить с ядром и плагинами, стоящими раньше в порядке конфига, либо которые не собираются сами. При сбое загрузки или сети делается одна повторная попытка; если и она не удалась, плагин отключается. Обновление печатает ⚠ Disabled plugin '<name>' in <home>: <reason>, записывает это в предупреждения квитанции и продолжает работу. Включить плагин снова можно через hermes plugins enable <name>, когда выйдет совместимый релиз или вернётся сеть. Диапазон requires_hermes, в который не попадает текущая версия, плагин не отключает никогда: checkout исходников без тегов релизов может выглядеть как более старый релиз. Такой плагин просто пропускает обновление (⚠ Left plugin '<name>' … out of this update), остаётся включённым и возвращается, как только Hermes сообщит подходящую ему версию. Вторичный профиль с нечитаемым конфигом так же пропускает обновление, пока конфиг не починят. Этот шаг проваливается только тогда, когда не собирается само ядро.
  5. Миграция конфигурации — находит опции, добавленные после вашей версии, и предлагает их задать.
  6. Пересборка десктопа (сборка рядом с подменой) — если десктопное приложение Hermes собиралось из этого же checkout, оно пересобирается, чтобы интерфейс соответствовал новому коду. Сборка складывается во временный каталог рядом с apps/desktop/release/, проверяется и только после этого переименованием встаёт на место прежней (на Windows антивирус реального времени, ненадолго удерживающий release/win-unpacked, пережидается несколькими короткими повторами). Если пересборка сорвалась на любом шаге — битая загрузка Electron, нехватка зависимости, кончилось место на диске, — прежнее приложение остаётся нетронутым и запускается по-старому; обновление печатает строку с ⚠, всё равно доделывает остальные шаги (миграцию конфига, перезапуск шлюзов) и завершается с кодом 0, оставив в квитанции последующее действие build, а пересборку повторят следующий запуск, hermes update или hermes desktop --build-only --force-build. На macOS пересобранный бандл затем копируется (через ditto, с сохранением подписи) поверх устаревшего /Applications/Hermes.app или ~/Applications/Hermes.app, чтобы копия, которую запускают Finder и Dock, соответствовала бэкенду; установленную копию, которая сейчас запущена, не трогают — обновление попросит выйти из неё и снова выполнить hermes update. Если копию, установленную прежним обновлением, с тех пор удалили (в том числе перетащили в Корзину), обновление при необходимости пересоберёт её, вернёт на место и сообщит об этом, чтобы Finder, Dock и Spotlight снова находили Hermes; hermes uninstall удаляет приложение насовсем. В Windows, если обновление заканчивается внутри десктопного приложения, которое пришлось бы пересобрать (приложение доводит до конца прерванное обновление при запуске), пересборку пропускают с уведомлением: Windows блокирует файлы работающего приложения, а остановка приложения оборвала бы и само обновление. Остальная часть обновления завершается; закройте Hermes Desktop и запустите hermes desktop из терминала либо нажмите Update now в Settings → About, чтобы пересобрать и снова открыть приложение.
  7. Автоперезапуск шлюзов: в Linux и macOS шлюзы, приостановленные на время обновления, запускаются снова, как только подготовлены зависимости и обновлены лаунчеры, ещё до сборки продуктов; в Windows они перезапускаются после сборок. Любой другой работающий шлюз обновляется после завершения апдейта. Шлюзы под управлением сервиса (systemd на Linux, launchd на macOS) перезапускаются через менеджер сервисов. Ручной шлюз перезапускается, если Hermes удаётся сопоставить его PID с профилем. С бэкендами hermes serve / hermes dashboard, запущенными вручную, всё иначе: апдейтер оставляет их работать и просит владельца перезапустить их. См. Напоминания о ручном перезапуске бэкендов. За бэкенды, которыми управляет запущенное Desktop-приложение, по-прежнему отвечает само приложение.
  8. Миграция на мультиплексирование (установки с несколькими профилями) — когда парк проверен на новом коде, установка с двумя и более профилями, у которых всё ещё по шлюзу на профиль, сворачивается в один мультиплексирующий шлюз по умолчанию, если ничто этому не мешает (то же, что hermes gateway migrate --multiplex --yes); если препятствие есть (токен бота, общий для двух профилей; вторичный профиль, занимающий порт без входа /p/<profile>/), обновление печатает препятствия со способами устранения и ничего не меняет. Установки с одним профилем не трогаются никогда. См. Миграция со шлюзов на каждый профиль.

Простой шлюза во время обновления

В Linux и macOS hermes update останавливает каждый шлюз, запущенный из этой установки, прямо перед первым изменением checkout и запускает ровно этот набор снова уже на новом коде, когда зависимости подготовлены (до сборки веб-интерфейса, TUI и Desktop, которая может идти минуты). Всё это время боты не в сети: обычно это git-обновление плюс синхронизация зависимостей. Раньше шлюз продолжал работать на старом коде, пока git переписывал файлы под ним, и любой модуль, который он успевал импортировать лениво в этом окне, брался уже из нового кода.

  • Обновление печатает одну строку с тем, что оно приостановило (⏸ Paused for the update: hermes-gateway.service), и ещё одну, когда всё вернулось (▶ Restarted paused gateway(s): …).
  • Юниты systemd и агенты launchd останавливаются и запускаются через systemd/launchd, поэтому менеджер сервисов не может перезапустить их на старом коде посреди обновления. Шлюз, который вы запустили вручную (hermes gateway run), сначала сливается (доделывает текущую работу), а затем снова стартует в фоне с той же командной строкой и тем же домашним каталогом Hermes; его вывод идёт в logs/gateway-update-resume.log в этом каталоге, а не в ваш терминал.
  • /update из чата переживает паузу: когда обновление выполняется внутри systemd-юнита самого шлюза, оно сначала переносит себя в отдельный scope. Вопрос, заданный, пока шлюзы приостановлены, сразу получает ответ по умолчанию, потому что передать его некому.
  • Приостановленный набор записывается в домашний каталог Hermes до первой остановки. Если обновление прервали, следующая команда hermes (или hermes update) снова запустит ровно эти шлюзы. Неудавшееся обновление, которое не успело изменить checkout, перезапускает их на старом коде. Если синхронизация зависимостей упала после смещения checkout, шлюзы перезапустятся, когда очередной запуск синхронизирует зависимости.
  • Шлюз, который апдейтер не может остановить через его супервизор (юнит s6 или кастомный юнит systemd, --external-supervisor, задание launchd, из которого запущено это обновление), остаётся работать и, как и раньше, обновляется после апдейта.
  • --no-gateway-restart ничего не останавливает; шлюзами вы управляете сами.
  • Пауза никогда не блокирует обновление. Если не удаётся перечислить, записать или остановить шлюзы, апдейтер перезапускает всё, что остановил, сообщает об этом, и обновление идёт так, как шло до появления паузы: шлюзы работают через подмену и перезапускаются потом.

В Windows шлюзы приостанавливали и раньше: они блокируют файлы, которые заменяет синхронизация зависимостей. Перезапускаются они после сборок, когда обновление заканчивается.

Почему перезапуск шлюза может занять время

Перезапуск идёт по принципу «сначала слить»: работающий шлюз отказывается брать новые ходы, а затем ждёт, пока текущая работа (ходы чата, задачи cron, запуски через API) завершится, и только потом выходит; ожидание ограничено agent.restart_after_turn_timeout (по умолчанию 30 минут), чтобы долгую задачу не обрывать на середине. Пока идёт это ожидание, апдейтер каждые 30 секунд печатает, чего шлюз ещё ждёт, — например:

  → hermes-gateway: draining (up to 1875s)...
  ⏳ still draining — 1560s left before the forced restart
     waiting on 1 active work unit(s):
       • cron job 6ba19dab68df (nightly-scout) in external worker pid 573597, running 6m40s
     finish or kill the work above to release the drain now; agent.restart_after_turn_timeout in config.yaml caps this wait

Для ходов чата показываются ключ сессии, модель и текущий инструмент; для задач cron — id задачи, имя и процесс, который её выполняет (на установках с systemd это внешний воркер, переживающий перезапуск, иначе сам шлюз). hermes gateway status перечисляет те же единицы работы, пока шлюз сливается. Чтобы не ждать, завершите или убейте перечисленную работу либо уменьшите agent.restart_after_turn_timeout в config.yaml (0 сразу переводит в принудительный слив).

Зависшая работа перезапуск не держит: ход чата, простаивающий дольше agent.gateway_timeout, или запуск cron старше допуска планировщика на выполняющиеся задачи (max(2 × интервал задачи, cron.inflight_max_minutes), по умолчанию 30 минут) исключается из ожидания и прерывается перезапуском.

Работа, переживающая шлюз, перезапуск тоже не держит: запуск cron, уже переданный воркеру в его собственном scope systemd (systemd-run --user --scope, обычный случай на установке с systemd), продолжает работать, когда шлюз останавливается, а его результат из надёжной очереди доставит тот шлюз, который поднимется следующим. Поэтому перезапуск идёт дальше, а не ждёт до полного предела. Воркер, которому не удалось получить собственный scope (нет доступной пользовательской D-Bus-сессии, см. cron.require_restart_safe_scope), остаётся в cgroup шлюза и по-прежнему ожидается, потому что перезапуск убил бы его посреди работы.

Пропавшие файлы апдейтера в Windows

Если поддерживаемый скрипт апдейтера пропал — скажем, его забрал в карантин антивирус, — старый пересыльщик обновления падает, а не докладывает об успешной передаче. Почините установку и посмотрите отчёт о карантине в системе безопасности, прежде чем повторять; отключать антивирусную защиту не нужно. Перед тем как отчитаться об успехе, поддерживаемый апдейтер проверяет импорт CLI, заголовок исполняемого файла Windows, заголовок ASAR и упакованную точку входа, читаемый HTML рендерера с локальной точкой входа модуля, начальные файлы модулей и текущую отметку сборки. Это минимальные проверки артефактов, а не полный аудит зависимостей и не тестовый запуск приложения или бэкенда. О пропавшем Python сообщают до ожидания завершения десктопа; починка зависимостей при этом всё равно может пройти в рамках обновления. Electron проверяет предварительные условия поддерживаемой передачи до остановки бэкендов, если такая раскладка на месте; настоящие старые «плоские» раскладки апдейтера тоже поддерживаются, поэтому не всякий пропавший файл апдейтера удаётся заметить до остановки бэкендов.

В Windows десктоп, который снова открыли во время упаковки, останавливают ещё раз — прямо перед тем, как подготовленная сборка встанет на место. Эта уборка ограничена исполняемыми файлами внутри дерева релиза десктопа этого же checkout; посторонние установки не трогают. Оставшаяся блокировка по-прежнему приводит к провалу подмены, а не к обходу ошибки переименования.

Fetch падает с should_include_obj should only be called on existing objects

Git 2.53 и новее может упасть при fetch в partial clone, если у некоторых его pack-файлов нет маркера .promisor. Так бывает, когда фильтрованный fetch превратил полный или shallow-клон в partial, а также когда маркеры потерялись. hermes update помечает такие паки и один раз повторяет fetch, а повторный запуск установщика помечает их до fetch. Установка, у которой собственный апдейтер старше этого исправления, исправление скачать не может: перезапустите установщик либо пометьте паки вручную (при закрытом Hermes) и обновитесь снова.

Checkout лежит в hermes-agent внутри домашнего каталога Hermes (~/.hermes или HERMES_HOME, если он задан; в Windows %LOCALAPPDATA%\hermes, пока не задан HERMES_HOME).

# macOS / Linux
repo="${HERMES_HOME:-$HOME/.hermes}/hermes-agent"
for p in "$repo"/.git/objects/pack/pack-*.pack; do [ -e "${p%.pack}.promisor" ] || : > "${p%.pack}.promisor"; done
# Windows
$repo = Join-Path ($(if ($env:HERMES_HOME) { $env:HERMES_HOME } else { "$env:LOCALAPPDATA\hermes" })) 'hermes-agent'
Get-ChildItem "$repo\.git\objects\pack\pack-*.pack" | ForEach-Object {
  $m = [IO.Path]::ChangeExtension($_.FullName, '.promisor')
  if (-not (Test-Path -LiteralPath $m)) { New-Item -ItemType File -Path $m | Out-Null }
}

Checkout остаётся partial clone. Не убирайте remote.origin.promisor / partialclonefilter, чтобы обойти падение: объекты, до которых дотягиваются только теги релизов или бэкапы обновлений, никогда не скачивались, поэтому у не-partial checkout git gc упадёт с bad tree object. Если вы это уже сделали, догрузите недостающие объекты и убедитесь, что ничего не осталось, прежде чем снова включать автоматическую очистку:

git -C "$repo" rev-list --objects --missing=print --all | grep '^?' | cut -c2- | git -C "$repo" fetch -q --no-tags --stdin origin
git -C "$repo" rev-list --objects --missing=error --all >/dev/null && echo complete
git -C $repo rev-list --objects --missing=print --all | Where-Object { $_.StartsWith('?') } | ForEach-Object { $_.Substring(1) } | git -C $repo fetch -q --no-tags --stdin origin
git -C $repo rev-list --objects --missing=error --all | Out-Null; $LASTEXITCODE   # 0 = complete

.git продолжает расти в partial clone

Checkout установщика — blobless partial clone: каждый коммит и каждый листинг каталога лежат локально, а содержимое файлов git скачивает по требованию, и каждая такая загрузка записывается в отдельный маленький пак. Установщики конца сентября 2026 года делали вместо этого treeless-клоны (--filter=tree:0). Git запрашивает недостающее дерево, не говоря, какие деревья у него уже есть, поэтому treeless-checkout при каждом checkout и каждом обходе истории с фильтром по пути заново скачивал полные снимки каталогов. hermes update один раз конвертирует такой checkout первым шагом после pull нового кода, до работы с зависимостями: один git fetch --refetch --filter=blob:none приносит все коммиты и деревья (около 120 МБ), и дальше обновления перестают качать их заново. Повторный запуск установщика поверх такого checkout конвертирует его так же. Если этот fetch не удался, обновление печатает предупреждение, продолжает работу и повторяет конвертацию в следующий раз.

Hermes Desktop, собранный до появления конвертации, каждые несколько минут обходит историю с фильтром по пути, поэтому на treeless-checkout он мог забить диск pack-файлами (в одном отчёте дошло до 434 ГБ) и продолжать после неудавшегося обновления. Если это уже случилось, восстановление такое:

  1. Закройте Hermes Desktop, затем завершите все оставшиеся процессы git rev-list, git maintenance или git commit-graph: выход из приложения их не останавливает.
  2. Если диск забит полностью, удалите брошенные файлы передачи, чтобы освободить место: rm -f "$repo"/.git/objects/pack/tmp_pack_* (Windows: удалите tmp_pack_* в .git\objects\pack). Каждое обновление и само удаляет их, когда не запущен ни один процесс git.
  3. Запустите hermes update. Он сначала конвертирует checkout, а затем за это и следующие обновления разгребает груду паков, как описано ниже.

Если сам hermes update не запускается, конвертация вручную выглядит так:

git -C "$repo" fetch --refetch --filter=blob:none origin
git -C "$repo" config remote.origin.partialclonefilter blob:none

hermes update и hermes update --check выставляют maintenance.commit-graph.enabled, gc.writeCommitGraph и fetch.writeCommitGraph в false в этом checkout, потому что запись commit-graph по коммитам, которых граф ещё не видел, скачивает все их деревья. Не трогайте эти настройки, а gc.auto оставьте по умолчанию, чтобы собственный автоматический gc git по-прежнему мог сливать паки.

Каждый hermes update тратит на уборку этих паков не более 60 секунд и продолжает с того места, где остановилось предыдущее обновление. Он удаляет паки, все объекты которых хранятся и в другом паке, а затем сливает самые маленькие из оставшихся, пока их больше 50. Ничего из хранящегося локально не теряется, и ничего не зависит от того, отдаёт ли это GitHub до сих пор. Паки, которые убитый git fetch оставил закреплёнными файлом .keep, тоже учитываются; собственный repack git их не трогает. Чтобы вручную свернуть всё разом (при закрытом Hermes; на большом checkout это полный repack, который может идти много минут):

git -C "$repo" -c gc.writeCommitGraph=false gc --auto

Обновление с нестандартной ветки: --branch

--branch NAME — разовое переопределение: обновление идёт с вершины указанной ветки, а не с выбранного канала:

hermes update --branch release-candidate
hermes update --check --branch experimental   # только посмотреть отставание

Если ваш локальный чекаут на другой ветке, Hermes сам прячет (stash) незакоммиченную работу, переключает HEAD на нужную ветку и делает pull. Ветки, которых нет локально, автоматически отслеживаются от origin/<имя> (git checkout -B <имя> origin/<имя>). Если ветки нет нигде, обновление падает аккуратно: спрятанные изменения восстанавливаются перед выходом, так что застрять в странном состоянии не получится. Логика синхронизации форка с апстримом (только для main) на остальных ветках пропускается.

Чекаут завис на фича-ветке

Если исходный чекаут остался на фича-ветке (по вине тулинга, эксперимента с worktree или ручного переключения), hermes update сам вернёт его на целевую ветку обновления — при условии, что рабочее дерево чистое:

  • Ветка полностью влита (каждый её коммит уже есть в origin/main — git cherry не находит невлитых): обновление так и сообщает — Checkout was parked on '<branch>' (fully merged) — switched back to main — и после этого остаётся на main.
  • У ветки есть невлитые коммиты, но дерево чистое: обновление всё равно переключится на main, чтобы оно могло состояться. На это рассчитывают неинтерактивные вызовы — кнопка обновления в десктопе, /update у шлюза, cron: разрулить пропуск им нечем. Ваши коммиты при этом целы: git checkout никогда не выбрасывает закоммиченную работу, а обновление печатает заметное уведомление с именем ветки и числом коммитов, плюс команду git checkout <branch>, чтобы позже вернуться к этой работе.

Держите свою ветку осознанно — скажем, локальные патчи поверх main? Задайте updates.parked_branch_strategy: update_in_place в config.yaml. Тогда обновление вливает origin/main в вашу ветку, а не уходит с неё: чекаут остаётся на месте, коммиты живы, работающий код при этом обновляется. Где возможно — fast-forward; при расхождении — настоящий merge под защитой тега pre-update-<stamp>, а на конфликте всё останавливается чисто, ничего не изменив. Разовое исключение делает hermes update --switch-branch: он возвращает поведение с переключением, что удобно на глубокой фича-ветке, которой ни к чему копить merge-коммиты от обновлений.

Когда у зависшей ветки есть незакоммиченные изменения (грязное дерево), Hermes её не трогает. Обновление кода помечается как SKIPPED с явным предупреждением, которое называет ветку, показывает, насколько она отстала от origin/main, и даёт точные команды для решения — вместо того чтобы делать вид, что обновление прошло успешно. Итоговая строка всегда показывает реальную ветку и HEAD (✓ Update complete! [main @ 30fcf9580]), так что расхождение сразу заметно. Чтобы полностью отключить автопереключение, задайте updates.auto_switch_parked_branch: false в config.yaml (предупреждение о пропуске при этом всё равно сработает).

Локальные коммиты в целевой ветке

Коммиты, сделанные прямо в целевой ветке обновления (main), перестают давать fast-forward, как только апстрим уходит вперёд, а checkout не может отличить их от force-push в апстриме. Поэтому обновление сбрасывает main на origin/main. Перед сбросом оно сохраняет старый HEAD как refs/hermes-update-backups/diverged-main-<stamp>-<sha> и печатает эту ссылку вместе с числом коммитов, которые уходят из ветки. git log origin/main..<ref> покажет их список; вернуть их можно через git branch <name> <ref> или git cherry-pick. Повторный запуск установщика поверх существующего checkout (install.sh / install.ps1 — так делает бутстрап десктопа) пишет такие же ссылки. Всякий раз, когда hermes update пишет такую ссылку, он оставляет по десять самых свежих каждого вида и удаляет те, что старше 30 дней. Чтобы патчи переживали обновления, держите их в собственной ветке с updates.parked_branch_strategy: update_in_place.

Локальные изменения при неинтерактивных обновлениях

Запускаете hermes update в терминале — Hermes прячет незакоммиченные правки дерева исходников, делает pull, а после спрашивает, восстанавливать ли их. Ровно так, как было всегда. Для интерактивных обновлений ничего не меняется.

Автостэш касается только правок дерева исходников. На плоской установке — где корень git-чекаута совпадает с $HERMES_HOME (например, установка с HERMES_INSTALL_DIR=$HERMES_HOME или созданная старым установщиком) — рабочее состояние профиля (state.db с его файлами WAL/SHM, state-snapshots/, backups/, sessions/, cron/jobs.json, хранилища cron/*.db, config.yaml, auth.json, memories/, lock/pid-файлы, …) лежит внутри чекаута как неотслеживаемые файлы. Эти пути игнорируются git, так что автостэш их не трогает, и работающий шлюз сохраняет свою базу на протяжении обновления. Если в корне плоской установки вы держите другие неотслеживаемые файлы, вынесите их из чекаута или добавьте в .git/info/exclude: всё неотслеживаемое и не игнорируемое сгребается в автостэш, как правка исходников.

Другое дело, когда обновление идёт без терминала — от кнопки «Update» в десктопном или чат-приложении, либо по триггеру шлюза. Спрашивать некого, и судьбу спрятанных изменений решает настройка updates.non_interactive_local_changes:

# ~/.hermes/config.yaml
updates:
  non_interactive_local_changes: stash   # по умолчанию: сохранить + автовосстановить
  # non_interactive_local_changes: discard  # выбросить локальные правки исходников
  • stash (по умолчанию) — спрятать автоматически, сделать pull, затем автовосстановить ваши изменения поверх обновлённого кода. Ничего не теряется; конфликты при восстановлении сохранятся в git stash для ручного разбора.
  • discard — спрятать и удалить stash после pull, чтобы обновление всегда ложилось на чистое дерево. Включайте это лишь на машинах, где локальные правки исходников Hermes вам точно не нужны. Здесь именно stash-drop, а не git reset --hard + git clean -fd, поэтому игнорируемые пути вроде node_modules, venv и артефактов сборки остаются нетронутыми.

В десктопном приложении это Settings → Advanced → In-App Update Local Changes.

Десктопные обновления никогда не восстанавливают правки сами. Десктопный апдейтер вызывает hermes update --keep-stash: локальные правки исходников по-прежнему прячутся, чтобы обновление прошло, но обратно не накатываются — они остаются в git stash, а лог обновления печатает точную команду git stash apply <ref> для их возврата. Так локальные правки не проезжают молча через десктопные обновления и не ломают свежеобновлённую установку. (non_interactive_local_changes: discard всё равно перевешивает, если вы сами выбрали выбрасывать их.) Вернуть отложенные изменения вручную:

cd ~/.hermes/hermes-agent   # или корень вашей установки
git stash list --format='%gd %H %s'   # найдите запись hermes-update-autostash
git stash apply stash@{0}

Флаг --keep-stash можно передать и обычному терминальному hermes update — если такое же поведение без автовосстановления нужно вам в интерактивном режиме.

Только проверка: hermes update --check

hermes update --check сравнивает checkout с целью выбранного канала (по умолчанию это последний стабильный релиз, на канале main — origin/main), не применяя код, не ставя зависимости и не перезапуская шлюзы. Для сравнения может понадобиться fetch метаданных Git, так что обещания «ни одной записи на диск» здесь нет. Установки, которыми владеет пакет, сообщают свой внешний способ обновления.

Предпросмотр парка: hermes update --plan

Перед обновлением машины, где крутится несколько профилей или сервисов, запустите hermes update --plan. Он печатает тип установки, работающие сервисы Hermes по всем профилям, их супервизоры и версии запущенного кода, а также способ перезапуска для каждого сервиса. Вручную запущенные бэкенды hermes serve / hermes dashboard тоже попадают в план — с записанным адресом привязки, но их перезапуск отдаётся владельцу: апдейтер их не останавливает и не поднимает заново. Установки, управляемые образом или пакетом, вместо плана называют внешнюю команду обновления. Режим «только чтение», на живом парке машин безопасен.

Ту же опись апдейтер вкладывает в квитанцию каждого настоящего обновления (~/.hermes/logs/update_receipts/), так что после апдейта можно сверить, что он видел, с тем, что он сделал.

Квитанции обновления и проверка версий по парку

Каждый запуск hermes update пишет машиночитаемую квитанцию в ~/.hermes/logs/update_receipts/ (хранятся последние 20, latest.json всегда указывает на самую свежую): план парка до обновления, каждый выполненный шаг, всё пропущенное и почему, итог перезапуска шлюза и финальная матрица версий по парку. Один из таких шагов — починка рантайма SQLite (sqlite_runtime_repair): неудавшаяся починка записывает настоящую причину (например, ошибку uv sync) и пару версий SQLite, а отложенная или неприменимая попадает в пропуски со своей причиной. После фазы перезапуска апдейтер сверяет запущенный код каждого живого шлюза со свежеобновлённым чекаутом и печатает матрицу по профилям. Шлюз, который всё ещё отдаёт старый код, отмечается заметно и с точной командой перезапуска и записывается в квитанции как последующее действие gateway_restart; код уже на месте, поэтому обновление всё равно завершается с кодом 0, а каждая следующая команда предупреждает, пока парк не перейдёт на новый код, так что разноверсийный парк молча за здоровый не сойдёт. И --plan, и проверка версий спрашивают каждый работающий шлюз напрямую через его локальный управляющий сокет (gateway.sock в каталоге данных профиля, именованный канал в Windows), когда тот доступен, поэтому сведения о версии и супервизоре приходят от самого шлюза; шлюзы старых версий, как и раньше, обнаруживаются по файлам состояния.

Мультиплексированный шлюз по умолчанию — это один процесс, обслуживающий несколько профилей, поэтому в матрице он появляется один раз и отвечает за каждый профиль из своей записи served_profiles. Это же покрытие снимает подсказку «A previous hermes update pulled new code but did not restart running gateways»: как только этот шлюз (или, после ручного git pull, каждый шлюз, перезапущенный обновлением) работает на текущем коде, достаточно hermes gateway restart — подсказка больше не ждёт, пока следующий hermes update запишет свежую квитанцию. То же верно и для обязательства перезапуска, оставленного обновлением, которое умерло, не успев записать, какие шлюзы оно должно перезапустить (или старым апдейтером, который их вообще не записывал): как только каждый живой шлюз работает на текущем checkout, обязательство снимается, и подсказка пропадает. На хосте, где шлюзов нет вовсе (только Desktop-приложение), оно снимается, когда ни у одного профиля нет шлюза, ушедшего без чистой остановки, а каждый работающий бэкенд либо перезапускается своим супервизором, либо имеет собственное напоминание. Такое обязательство записывается одно на ХОСТ — в общий для профилей каталог-точку встречи ($HERMES_GATEWAY_LOCK_DIR, иначе $XDG_STATE_HOME/hermes/gateway-locks) как host-update-restart-<install key>.json (по одному на установку, так что второй checkout Hermes у того же пользователя хранит свой; неключевой host-update-restart.json от прежнего релиза учитывается и снимается только установкой, у которой нет собственной записи, поэтому другой checkout на том релизе сохраняет свой долг), — поэтому CLI каждого профиля видит одно и то же: hermes -p coder update и hermes -p writer update перезапускают общий мультиплексированный шлюз один раз на двоих, а не каждый по разу. Обязательство, оставленное старым апдейтером, работавшим по профилям (fleet_restart_pending в домашнем каталоге Hermes одного профиля), по-прежнему читается и снимается. Обновление, чей план до апдейта не нашёл ни одного шлюза, ничего не должно и следов не оставляет. Бэкенд под супервизором Desktop, systemd или launchd перезапускается своим супервизором и снятию обязательства никогда не мешает; открытым его держит только ручной бэкенд, напоминание для которого сохранить не удалось.

Напоминания о ручном перезапуске бэкендов

После обновления попросите владельца каждого вручную запущенного бэкенда перезапустить hermes serve или hermes dashboard; для SSH-бэкенда переподключите Desktop. Перезапуск одних только шлюзов эти процессы не обновляет.

Для проверенного живого бэкенда апдейтер сохраняет напоминание, прежде чем записать его перезапуск как deferred. Благодаря этому обновление может завершиться, если остальные проверки пройдены. Процесс с неизвестной идентичностью на новую отсрочку претендовать не может.

Напоминания лежат в serve_restart_pending/ внутри активного домашнего каталога Hermes, отдельно от ротируемых квитанций обновления. Каждое напоминание опознаёт процесс по PID и времени создания. Предупреждения при старте держат его, пока этот процесс жив или его состояние неизвестно, и удаляют только тогда, когда подтверждено, что именно этого процесса больше нет. Ни следующее обновление, ни здоровый шлюз его не удаляют.

Если сохранить напоминание не удалось, Hermes предупреждает и переносит несохранённое обязательство в следующие квитанции, чтобы повторить попытку. Проверьте права на хранилище и свободное место и перезапустите бэкенд, как указано в инструкции. Если не пишутся ни напоминания, ни квитанции, надёжное хранение обязательства гарантировать нельзя.

Автоматические обновления изнутри шлюза: --no-gateway-restart

Обновление, запущенное самим шлюзом (задача cron, апдейтер десктопа, любая автоматика, живущая дочерним процессом шлюза), не переживёт перезапуск собственного парка: шлюз сливается по SIGUSR1, а KillMode=mixed в systemd затем убивает всё, что осталось в cgroup, включая апдейтер. hermes update --no-gateway-restart прогоняет весь конвейер (pull, зависимости, workspace’ы Node, веб-интерфейс, обслуживание) и пропускает только перезапуск и проверку парка; в Linux и macOS он пропускает и паузу перед изменением checkout, так что ничего не останавливается. Маркер ожидающего перезапуска сохраняется, поэтому следующий старт CLI предупредит, а следующий обычный hermes update (или hermes gateway restart) подтянет парк. Сочетайте его с отдельным шагом перезапуска — например, таймером через 10–15 минут после задачи обновления. Квитанция фиксирует отсрочку; устаревший парк, вызванный только этой отсрочкой, не делает обновление partial.

Прерванные перезапуски шлюзов

Если прошлое обновление код подтянуло, но перезапуск парка не довело до конца, следующий hermes update повторит попытку — даже когда checkout уже актуален. Пустой обход процессов восстановлением не считается: у сорвавшихся юнитов systemd и установленных задач launchd живого PID может и не быть. Если не удалось обнаружить сервисы или перезапустить их либо запрошенный сервис не подтвердился активным, маркер остаётся отложенным: обновление всё равно завершается с кодом 0 (новый код уже на месте), печатает строку с ⚠ и записывает в квитанцию последующее действие gateway_restart, каждый старт CLI предупреждает о невыполненном перезапуске, а следующий hermes update повторяет его. Поднимите затронутые сервисы напечатанными командами либо повторно запустите hermes update.

Сорвавшаяся квитанция из прошлого сама по себе ещё не доказывает, что шлюзы до сих пор устарели. Предупреждения при старте и в статусе шлюза, как и догоняющее обновление, проверяют, есть ли на текущем checkout живой преемник у каждого профиля шлюза, записанного в этой квитанции. Ручной перезапуск шлюза может снять совет, известный только из квитанции, не переписывая исторический итог. Обязательства по ручным бэкендам сначала должны перейти в их отдельные напоминания.

Маркер отложенного перезапуска хранит собственную опись среды выполнения до обновления и целевой коммит. Восстановление сверяется именно с этой описью, а не с описью более старой квитанции. Чтобы маркер сняли, нужны непустой проверенный текущий парк, преемник на целевом коммите для каждого записанного профиля шлюза и успешная обработка всех найденных обязательств по ручным бэкендам. Ручному бэкенду, который ещё жив или чьё состояние неизвестно, нужно сохранённое напоминание; процессу, про который подтверждено, что он завершился, напоминание не нужно. Пропавшие шлюзы, неподдерживаемые среды выполнения или сорвавшаяся запись напоминания оставляют маркер отложенным.

Старые маркеры без описи, а также повреждённые или неподдерживаемые описи не снимаются автоматически ни при старте, ни при догоняющей сверке. Перезапустить все видимые шлюзы недостаточно, чтобы установить, что именно было должно то обновление. Предупреждение остаётся, пока одно из следующих обновлений не завершится с новым маркером, содержащим собственную опись, и не отчитается успешно.

Полный бэкап перед обновлением: --backup

Для ценных профилей — продакшен-шлюзов, общих командных установок — можно включить полный бэкап HERMES_HOME (конфиг, авторизация, сессии, навыки, сопряжение) до pull:

hermes update --backup

Или сделать это поведением по умолчанию для каждого запуска:

# ~/.hermes/config.yaml
updates:
  pre_update_backup: full

updates.pre_update_backup работает в трёх режимах:

  • quick сохраняет выбранные файлы состояния, описанные выше. Это режим по умолчанию.
  • full добавляет zip-архив с учётом исключений бэкапа. На больших каталогах данных это может занять несколько минут.
  • off отключает бэкапы перед обновлением. --no-backup включает этот режим на один запуск.

Старые булевы значения по-прежнему поддерживаются: true означает full, а false — off.

совет
Переезжаете на новую машину?

Бэкапы перед обновлением защищают обновление на месте. Если вы переносите всю установку на другое железо, используйте вместо этого hermes backup + hermes import — см. Перенос Hermes на другую машину и hermes backup против hermes profile export.

Процессы и изменения зависимостей в Windows

Пока процессы работают, Windows может держать открытыми исполняемые файлы и файлы нативных расширений. Поэтому PM готовит поколения зависимостей отдельно, а не заменяет импортированные библиотеки на месте. Работающий процесс сохраняет текущие импорты, пока не перезапустится.

Следуйте диагностике блокировки для конкретного процесса и установки. Не убивайте все процессы с именем Python или Hermes подряд. У обновления исходников и у замены пакета MSIX разные владельцы и разные требования к остановке.

Парсер сохраняет --force и --force-venv ради совместимости с Windows. Это не обычные инструкции для обновления и не гарантия, что блокировку файла ОС удастся обойти. Прежде чем повторять сорвавшееся обновление, посмотрите hermes update --plan, лог обновления и hermes pm status.

Рекомендуемая проверка после обновления

После обновления из исходников запустите диагностику ниже. Для бандл-приложения вместо команд Git внутри пакета смотрите его страницу About и состояние бэкенда.

  1. git status --short — если дерево неожиданно «грязное», разберитесь, прежде чем продолжать.
  2. hermes doctor — проверяет конфиг, зависимости и состояние сервисов.
  3. hermes --version — убедитесь, что версия поднялась как ожидалось.
  4. Используете шлюз? hermes gateway status.
  5. hermes pm status — посмотрите, как прошла подготовка зависимостей и какие шаги сорвались.
внимание
«Грязное» рабочее дерево после обновления

Если git status --short показывает неожиданные изменения после hermes update, остановитесь и изучите их, прежде чем продолжать. Обычно так бывает, когда локальные правки переналожились поверх обновлённого кода или шаг с зависимостями обновил lock-файлы.

Если терминал отключился посреди обновления

От случайной потери терминала hermes update защищён:

  • Обновление игнорирует SIGHUP, поэтому закрытие SSH-сессии или окна терминала больше не убивает его на полпути. Дочерние процессы pip и git наследуют эту защиту, так что оборванное соединение не оставит окружение Python наполовину установленным.
  • Весь вывод во время работы дублируется в ~/.hermes/logs/update.log. Терминал пропал — переподключитесь и загляните в лог, чтобы понять, завершилось ли обновление и удался ли перезапуск шлюза:
tail -f ~/.hermes/logs/update.log
  • Ctrl-C (SIGINT) и завершение работы системы (SIGTERM) по-прежнему учитываются: это намеренные отмены, а не случайности. Ctrl-C после того, как новый код уже на месте, завершается с кодом 130 и сообщает об этом: оставшиеся шаги (зависимости, сборки, перезапуск шлюзов) ещё предстоят, и их доведёт до конца следующий запуск или hermes update.

Оборачивать hermes update в screen или tmux ради того, чтобы пережить обрыв терминала, больше не нужно.

Когда обновление или запуск отказывается гадать

В нескольких ситуациях всё останавливается с сообщением, а не идёт дальше: продолжение могло бы оставить наполовину обновлённый checkout или шлюз на старом коде.

  • Другой запуск чинит прерванное обновление. Если обновление убили, пока git двигал checkout, следующий запуск hermes сначала возвращает checkout на место. Запуски, стартующие одновременно (перезапускающийся шлюз или бэкенд Desktop рядом с вашим CLI), ходят по очереди. Запуск, который ждёт чужого восстановления дольше 10 секунд, завершается со статусом 1: hermes: another Hermes launch is finishing an interrupted 'hermes update'; launch again in a moment. Запустите снова, когда другой закончит. Бэкенд, который в этот момент стартует Hermes Desktop, выглядит как неудачный запуск; достаточно открыть приложение заново.
  • Сбой git в Windows → запасной вариант с ZIP. В Windows ошибка git во время hermes update переводит на скачивание ZIP релиза, только если git ещё не тронул checkout: тот же коммит и та же ветка, что при старте обновления, stash не делался, перемещение дерева не начиналось. Когда git уже спрятал изменения, сменил ветку или начал двигать файлы, обновление сообщает об ошибке git и завершается с кодом 1, потому что ZIP-наложение похоронило бы это состояние. Повторите hermes update, исправив проблему с git.
  • Сборке Desktop нужен доступный для записи .git. Сборка Hermes Desktop из checkout командой hermes desktop берёт ту же блокировку checkout, что и hermes update (ей она тоже нужна), и держит её в каталоге git этого checkout. Если этот каталог недоступен вам для записи (например, checkout принадлежит другой учётной записи), сборка отказывается с сообщением .../.git/hermes-update.lock is not writable, а не собирается параллельно с возможным обновлением. Исправьте владельца или права на .git. В macOS/Linux файл блокировки, оставленный только для чтения командой sudo hermes update, всё равно можно использовать.
  • Запись о перезапуске не удаётся сохранить. hermes update записывает перезапуск шлюзов, который ему предстоит выполнить, в каталог состояния хоста ($HERMES_GATEWAY_LOCK_DIR, иначе $XDG_STATE_HOME/hermes/gateway-locks) до перемещения checkout. Если в этот каталог писать нельзя, запись попадает в домашний каталог Hermes текущего профиля. Читает её только этот профиль, поэтому обновление идёт дальше лишь на установке без других профилей. Если другие профили есть или список профилей прочитать не удаётся, обновление отказывается до того, как что-либо будет сдвинуто. Сделайте каталог состояния доступным для записи и запустите снова.

Проверка текущей версии

hermes --version

Сравните её с последним релизом на странице релизов GitHub.

Обновление из мессенджеров

Обновиться можно и прямо из Telegram, Discord, Slack, WhatsApp или Teams — отправьте:

/update

Команда подтянет свежий код, обновит зависимости и перезапустит работающие шлюзы. На время перезапуска бот ненадолго уйдёт офлайн (обычно 5–15 секунд), а затем продолжит работу.

Ручное обслуживание исходников и откат

Для независимого checkout под разработку пользуйтесь руководством по установке из исходников. Прежде чем менять ревизию Git, закоммитьте или иначе сохраните локальную работу. Сервисы, которыми вы управляете, остановите до того, как заменять код, на котором они работают.

Сменив ревизию исходников, выполните процедуру установки зависимостей и бутстрапа для этой ревизии. Checkout под управлением PM используют python -m pm.cli install из выбранного окружения исходников. Голый pip install — не то же самое: он не готовит управляемое хранилище инструментов и выбор среды выполнения PM.

Откат кода в checkout ещё не откатывает данные. Новые релизы могут мигрировать конфигурацию или базы данных так, что старый код их уже не прочитает. Если старому релизу нужен прежний формат данных, возьмите согласованный бэкап, сделанный до обновления. Перед восстановлением сохраните текущие данные.

Установки, которыми владеет пакет, откатываются или переустанавливаются средствами своего менеджера пакетов. Не запускайте Git или pip внутри подписанного приложения или неизменяемого образа.

Python 3.14 и старые интерпретаторы

Hermes работает только на Python 3.14. В pyproject.toml по-прежнему объявлено requires-python = ">=3.11,<3.15", но у каждой runtime-зависимости стоит маркер python_version >= '3.14'. Широкий диапазон нужен ровно для одного: установка из исходников, чей venv старше миграции на PM (Python 3.11–3.13), должна суметь переключиться на новый код и ещё раз выполнить hermes update. Это обновление передаёт работу свежему завершающему процессу, PM ставит закреплённый интерпретатор 3.14 из pm/lock.json, собирает на нём окружение зависимостей и перенаправляет лаунчеры. После этого старый интерпретатор больше не используется.

Что это значит за пределами hermes update:

  • pip install ., uv pip install ., pip install -e . или пакет Homebrew/PyPI, собранный на Python 3.11–3.13, ставят hermes-agent без зависимостей, и пакет не импортируется. Эти способы установки не поддерживаются; используйте установщик из исходников или упакованную сборку.
  • Сборка Nix вне flake репозитория видит тот же набор зависимостей под маркером и требует интерпретатор 3.14.
  • Лаунчер hermes, который всё ещё указывает на venv до миграции, чините через hermes update (или python -m pm.cli install из checkout), а не переустановкой в старый venv.

Установки, управляемые образом (Docker): маркер происхождения

В публикуемые Docker-образы вшит небольшой маркер только для чтения (/etc/hermes/image-provenance.json), который авторитетно подтверждает: файловая система управляется образом. hermes update, hermes update --check и кнопка «Update» в дашборде сверяются с ним прежде, чем что-либо трогать: на установке, управляемой образом, они аккуратно отказывают (код выхода 2), печатают настоящую команду обновления (docker pull nousresearch/hermes-agent:latest) и записывают квитанцию со статусом refused, чтобы инструменты управления парком видели саму попытку. Маркер побеждает, даже если исходный чекаут примонтирован в контейнер через bind-mount — отказ строится на реальном состоянии работающей файловой системы, а не на том, как она выглядит. Повреждённый маркер тоже приводит к отказу — так и задумано (fail-closed). Установки под управлением Nix и apt отказывают через тот же механизм, используя уже существующее определение.

Заметка для пользователей Nix

Nix больше не входит в число официально поддерживаемых способов установки (best-effort) — см. Настройку Nix. Если вы ставили через Nix flake, обновления идут через менеджер пакетов Nix:

# Обновить вход flake
nix flake update hermes-agent

# Или пересобрать со свежей версией
nix profile upgrade hermes-agent

Установки Nix неизменяемы — за откат отвечает система поколений Nix:

nix profile rollback

Подробнее — в Настройке Nix.


Удаление

hermes uninstall

Для установок из исходников перед удалением посмотрите вывод hermes uninstall --dry-run. Удаление по умолчанию может сохранить конфигурацию и пользовательские данные. --full удаляет и данные. --data удаляет пользовательские данные, не трогая код, которым владеет пакет. Все эти режимы необратимы — сначала сделайте бэкап.

Для MSIX/Store используйте «Параметры Windows» → «Приложения» → «Установленные приложения». В macOS выйдите из Hermes и перенесите бандл приложения в Корзину. Docker, Nix и Termux удаляются тем же менеджером, которым их ставили. Их файлы пакета деинсталлятор для исходников не удаляет. Удаление данных — отдельный шаг от удаления пакета.

совет
Переезжаете на новую машину, а не уходите насовсем?

Прежде чем удалять установку, выполните hermes backup. Полный архив включает учётные данные, но исключает скачанные среды выполнения, окружения зависимостей, кэши и профили браузера. Перед удалением исходных данных просмотрите его отчёт о пропущенных файлах. hermes profile export упаковывает один профиль без учётных данных. См. hermes backup против hermes profile export.

Ручное удаление

rm -f ~/.local/bin/hermes
rm -rf /path/to/hermes-agent
rm -rf ~/.hermes            # По желанию — оставьте, если планируете переустановку
инфо

Если шлюз стоял как системный сервис, сначала остановите и отключите его:

hermes gateway stop
# Linux: systemctl --user disable hermes-gateway
# macOS: launchctl remove ai.hermes.gateway
перевод и редактура:

обновлено

⌕ ESC