Commit Graph
601 Commits
Author SHA1 Message Date
Teknium 95aed4f8de fix(telemetry): smoke waits for the gateway's turn rows instead of assuming the stop settles them
Review finding "Exit can drop queued metrics" (Greptile P2 + independent review W2): the smoke's
docstring claimed stopping the auto-started gateway settles its asynchronous rows, but the gateway's
graceful exit ends in os._exit, which skips the shared-metrics flush. Poll the probe home's store
(read-only, so polling never creates it) with a 30 s deadline until the gateway turn rows validate,
then stop the gateway; on expiry the store validation reports what is missing.
2026-10-25 22:27:40 -07:00
Teknium 7191be2fc5 fix(telemetry): smoke stops only the probe home's verified live gateway
Review finding "Cleanup repeats weaker process checks" (Greptile P2 + independent review W1): the
smoke's gateway stop trusted the probe home's gateway.pid record (kind + home) and signalled whatever
process held that PID, so a live non-gateway process or a recycled PID named by a stale record was
terminated. Resolve the PID with gateway.status.live_gateway_pid_for_home (start-time reuse guard,
gateway command line, home ownership) and do nothing on a miss; psutil binds the create time so
terminate() refuses a PID reused in between. The driver puts --hermes-repo first on sys.path so the
check comes from the tree under test.
2026-10-25 22:27:40 -07:00
Teknium 142dc8bbbd fix(telemetry): relay shared-metrics smoke passes on main again
The smoke's expected counters predate three merges, so on origin/main it stopped at the first
assertion:

- #106742 (unified gateway runtime): `hermes chat -q` is now served by a gateway it auto-starts
  for the home, which adds hermes.gateway.reply_latency and hermes.platform.delivery (platform
  local) and a second hermes.startup.latency row (surface gateway_boot).
- eb2edce53a: the turn's read_file call now also records hermes.tool_batch.count.
- e76fb951a1: a failed update counts the stage it died in, so the fetch-failed receipt adds an
  (apply, failed) hermes.update.stage row.

Expectations now live in one place: a single EXPECTED_METRIC_NAMES set for the SQLite and the
package checks, exact dimension dicts for the new rows, the update stages derived from the receipt
the probe feeds (UPDATE_RECEIPT, also used to build the probe), and both startup surfaces.

The smoke also stops the gateway the chat auto-started (pid record of its own home, then wait),
so a run no longer leaves a detached `gateway run --idle-exit` behind for 10 minutes, and the
gateway's asynchronous rows land before the package export.

The smoke is not run by CI, so the test file now pins its expectations to the contract: every
expected name is a contract counter, every exact dimension dict has the metric's current field set
and passes counter_dimensions_are_valid, the startup surfaces are contract surfaces, and the
expected update rows equal update_receipt_fields(UPDATE_RECEIPT).
2026-10-25 22:27:40 -07:00
kshitijk4poor 015f47a12b fix(config): keep auth's globals()-dispatched plugin status import; ship the reader gate to hook fixtures
Two regressions from earlier commits on this branch, caught by CI's Python tests:

- 8d7bff7615 dropped `get_plugin_oauth_auth_status` from hermes_cli.auth's imports as unused, but
  `_STATUS_BY_AUTH_TYPE` dispatches oauth_device_code / oauth_external plugin providers through
  `globals()[name]`, so every OAuth plugin provider's status raised KeyError
  (test_plugin_provider_picker_residue). Restored with a noqa naming the dynamic lookup.
- 7131eadb77 registered scripts/check_config_yaml_readers.py as the `config-readers` guard, but the
  code-health CLI tests copy only the listed guard scripts into their temp repos, so the installed
  hooks failed on the missing script. Added it to the fixture's _GUARD_SCRIPTS.
2026-10-25 18:54:09 -07:00
Bear Huddlestonandemozilla b5eb5ee7a8 feat(webapp): carry the browser-hosted Desktop's server pieces from #93508
Carried from BearHuddleston's #93508 onto main. The Webapp relay and the
profile-incarnation fence stay out: carried code follows main's
named-profile rule and refuses a missing or deleted profile home.

- Webapp routes: one-use launch tickets, window handoff, and gateway
  WebSocket tickets minted for the signed-in browser
- Browser surface policy and the SPA document policy
- A host terminal for the browser that survives reconnects
- Staged browser uploads and file tickets; file.attach takes a staged upload
- hermes sessions adopt
- Webapp bundle build helpers and the renderer freshness check
- Regenerated gateway contracts

Co-authored-by: emozilla <emozilla@nousresearch.com>
2026-10-25 03:22:14 -07:00
Teknium 97253cc182 fix(run_tests): count import/collection failures and missing named paths
A test file that raised at import (e.g. a decorator using pytest without
`import pytest`) exited 2 with "collected 0 items / 1 error" and no
failed-test count, so the Summary line read "2 tests passed, 0 failed"
while the run exited 1, and the file was filed under a generic "no tests
ran" bucket. A positional test path that did not exist was dropped by
discovery and the run exited 0 without testing it.

- mark a failing file whose output shows a collection error and give it
  its own failure bucket, "FAILED AT IMPORT/COLLECTION", with pytest's
  one-line cause (the last traceback E line)
- the Summary line names failing files: "; FAILED: 1 of 2 files
  (1 at import/collection)"; error/crash/skip notes move into one helper
- the failure buckets become a cause -> (header, detail) table
- a positional path that does not exist exits 2 with
  "error: no such test path: ..."; --paths defaults keep skipping missing
  roots
- per-file pytest exit 5 (nothing collected, e.g. platform-gated) stays a
  pass, as deliberately added with the run-level NO TESTS RAN guard
2026-10-24 17:10:39 -07:00
Byrd-IT cd424473d1 fix(run_tests_parallel): name pytest ERRORs on the Summary line
A file whose tests pass but whose fixtures error at setup ("41 passed,
7 errors") exits 1, yet the Summary read "41 tests passed, 0 failed" and
the file was filed under "all tests passed but pytest exited non-zero".

- accumulate summary["errors"] into tests_errored and print ", N errors"
  on the Summary line when non-zero (same pattern as crashed_note)
- dedicated failure bucket "with pytest ERRORs" listing passed/error
  counts per file; such files no longer land in the passed-but-nonzero
  or no-tests-ran buckets
- tests: parser returns errors for "41 passed, 7 errors"; end-to-end
  probe with a failing fixture asserts the Summary shows "3 errors";
  clean run has no errors note
2026-10-24 17:10:39 -07:00
Teknium 7152bf009a fix(install): browser opt-out uses the name every PM release accepts
install.sh --skip-browser / --no-playwright and install.ps1 -SkipBrowser
now hand PM `--without agent-browser` again instead of `--without chromium`.

The downloaded installer is the newest script, but by default it installs
the stable tag, whose PM (v0.22.5) rejects `--without chromium` with
"--without accepts only optional default packages (agent-browser,
cua-driver)" and exits 1, failing the whole install. The new PM already
reads `agent-browser` as `chromium` (pm.defaults.RETIRED_DEFAULTS), so the
legacy spelling works against both the stable tag and main. Direct
`hermes pm install --without chromium` stays valid on the new PM.
2026-10-23 01:43:17 -07:00
Teknium 1ac18d2b12 refactor(pm): drop the agent-browser package; Chromium is a default package in its own right
- pm: AgentBrowser class + lock entry gone; Chromium default=True and its env
  exports only PLAYWRIGHT_BROWSERS_PATH (Playwright launches without an
  executable path still need it). A recorded or passed --without agent-browser
  maps to chromium (pm.defaults.RETIRED_DEFAULTS), and pm gc forgets the stale
  agent-browser fact so its store entry is reclaimed.
- install.sh --skip-browser/--no-playwright and install.ps1 -SkipBrowser pass
  --without chromium; undo hint is hermes pm install chromium.
- Docker bakes /etc/hermes/chromium-executable-path; stage2-hook validates it
  and exports nothing; the sandbox-desktop image drops the agent-browser npm
  install and keeps its Playwright Chromium.
2026-10-23 01:41:25 -07:00
Teknium a4072ce7e6 fix(desktop): the short uv cache junction creates its parent
Review follow-up: short_alias now creates the junction's parent directory,
so preparation never depends on the caller having made the work dir first
(the existing prepare_tools test failed on both Windows lanes with WinError 3).
That test now checks the invariant the Windows path keeps: UV_CACHE_DIR
reaches the cache directory, through the short work-local junction on Windows.
2026-10-22 17:47:42 -07:00
Teknium 1aaf75e6f2 fix(desktop): build Windows sdists under a short uv cache path
With a cold desktop-inputs cache, Windows desktop preparation builds sdists
that have no cp314 Windows wheel (obstore on win32-x64, dependency-injector on
win32-arm64). uv builds each one in
<cache>\python\runtime\<toolchain id>\sdists-v10\pypi\<pkg>\<ver>\<id>\src,
about 200 characters below the deep .cache\desktop-inputs root, so MSVC
link.exe outputs passed MAX_PATH and the build failed with LNK1104.

On Windows, prepare_tools now hands uv a short job-local junction
(<work>\uv) that points at the same transported cache directory. uv keeps the
spelling it is given, so builds run under the short path while the bytes are
still restored and saved by the desktop-inputs cache. The native toolchain id
is read through the junction, and prepare_native keeps the absolute spelling
instead of resolving the junction back to the long path.

CARGO_TARGET_DIR is also owned by the preparation environment and points at
<work>\cargo, so Rust sdists link under the work root on every target even
if a tool resolves the junction.
2026-10-22 17:47:42 -07:00
Maciej Lisiewski 2b31d12ade fix(pm): apply lock overrides to requirements builds; read pip check uncoloured
Requirements builds (Termux deb assembly and wheelhouse gate, PM runtime
wheelhouse install) install the lock's export with uv pip install, which
re-resolves and rejects the overridden graph. Pass the lock: its manifest
overrides become --overrides and the closing check accepts them.

FORCE_COLOR/CLICOLOR_FORCE made uv pip check emit ANSI the report parser
cannot read, failing every install with an expected override mismatch.
2026-10-22 17:36:39 +02:00
Teknium 99cc712bf3 fix(update): a damaged .git no longer strands paused gateways
When a checkout's .git exists but git rejects it (empty HEAD, a gitdir:
file pointing nowhere), every `hermes update` failed at the fetch with a
generic "Failed to fetch updates" and, on Windows, left the paused
gateways stopped with "the next `hermes` command restarts them". No
later command could: the whole-tree gate held the set while HEAD was
unreadable, so bots stayed down until someone repaired the checkout by
hand.

- tree gate: a set whose every pause already found HEAD unreadable and
  that never bound a tree move resumes. The gateways were serving that
  exact tree and no update step wrote it. A recorded move, or a HEAD
  that was readable at the pause, still holds the set.
- fetch failure: "not a git repository" now says the checkout's git data
  is damaged and prints the installer one-liner that repairs it.
- install.sh: a dangling `.git` file is moved aside and re-cloned like a
  commit-less one, matching install.ps1 (Test-Path already matched it);
  both installers' warning names a damaged .git, not only an
  interrupted clone.
2026-10-21 16:57:37 -07:00
Teknium be64dc5b18 test: Termux assembly test skips when the Docker daemon hangs instead of failing main
The bind-mount ownership test probes `docker info` / `docker image inspect`
with a 15 s timeout but only skipped on a non-zero exit, so a CI runner
daemon that hangs past the timeout raised TimeoutExpired and failed the
test (main run 39560831204 on 9ec17f7f4f, both parametrizations). A daemon
that does not answer is as unavailable as one that refuses: skip, the same
way tests/docker/conftest.py::_docker_available already treats a timeout.
Only the assembly run itself may fail this test.
2026-10-21 00:46:50 -07:00
ethernet d6d77c3649 Merge pull request #141725 from NousResearch/feat/store-auto-publish
feat(release): Store certifies before the gate; publication switches it live
2026-10-20 17:43:36 -04:00
ethernet 01af57a06c Merge pull request #141716 from NousResearch/perf/release-metadata-only-receipts
perf(release): stage candidates from metadata; verify package bytes at publish
2026-10-20 17:31:33 -04:00
ethernet e8a8a9b65a Merge pull request #141718 from NousResearch/fix/https-origin-opener-reset
test(release): https_origin resets urllib's cached default opener
2026-10-20 17:19:22 -04:00
ethernet ee8f05a48a Merge pull request #141651 from NousResearch/fix/desktop-prepare-caches
fix(desktop): Windows prepare reuses its restored wheel, vcpkg and npm caches
2026-10-20 16:03:44 -04:00
ethernet 4148f6d1f3 feat(release): Store certifies before the gate; publication switches it live
stable-store commits the Store submission held (targetPublishMode Manual)
instead of leaving an uncommitted draft, so certification runs before the
release gate and a run whose stable-publish fails still shows customers
nothing. The publication pass then PUTs the held submission with
Immediate and commits it again, so it goes live without a Partner Center
click.

Microsoft documents no call that releases a held submission and does not
say whether update accepts a committed one (409 is documented only as
"the current state of the app"). A 409 from the switch leaves the
submission held and prints the Publish now instruction; the release
stays green. The first real release shows which answer the API gives.

Reconcile receipt-bound Store work independently of channel advancement, including
already-current published releases, so an interruption cannot strand a held
submission. Record each upload's claim in certification notes and replace a held
Manual predecessor only after its exact remote abandon marker matches the claim
commit. Unknown owners and publishing submissions remain protected.
2026-10-20 15:50:30 -04:00
ethernet fa6a127ed6 perf(release): stage candidates from metadata; verify package bytes at publish
candidate-manifest spent ~4 of its 5.2 min downloading 8.7 GB (stable run
38491090442: both MSIX bundles, including the 4.3 GB Store bundle) only to
re-hash them, and each stage-receipt job re-downloaded its group's ZIPs and
bundle (60-256 s). The same bytes are hashed by the build job that uploaded
them, bound by digest in its handoff receipt, and downloaded and digest-
checked again by release_artifacts.publish (materialize) before anything
goes live.

Candidate staging and receipts now download only the metadata JSONs; package
rows still carry the receipt digests. The one check that read package
contents, the universal bundle identity against its per-arch rows, moves to
publish, after materialize has verified the bytes. A Store-only claim has no
metadata to fetch, so candidate-manifest reads its receipts alone.
2026-10-20 15:50:30 -04:00
ethernet 61c6721057 test(release): https_origin resets urllib's cached default opener
urlopen() builds its default opener once per process, capturing the SSL
context from SSL_CERT_FILE at that moment. Every https_origin fixture mints
its own self-signed CA, so whichever test first called urlopen() pinned its
CA for every later test in the same file: later origins failed with
CERTIFICATE_VERIFY_FAILED. Today's tests happen to avoid the order that
trips it; a new publish test in the release stack hit it. Reset the cached
opener per fixture so each origin's CA is the one in use.
2026-10-20 15:50:30 -04:00
ethernet c8d272e860 test(desktop): give the vswhere identity test the OpenSSL release builds pass
On win32-arm64, native_cache_path deliberately treats identity without the
provider's OpenSSL (OPENSSL_DIR) as incomplete, so the test got a fresh
partition on the arm64 Windows lane while x64 passed. Release builds always
hand vcpkg's static OpenSSL in; the test now does too.
2026-10-20 15:50:30 -04:00
ethernet 550e2a891f feat(release): publication commits the Store draft with auto-publish on
The Store build used to be committed at green with targetPublishMode
Manual, and the publication pass could only print "click Publish now":
the submission API has no call that releases a held submission (update
and commit return 409 once it is committed). Every stable release needed a
person in Partner Center.

stable-store now uploads the verified bundle as an uncommitted draft
(msstore publish --noCommit) and stops. The publication pass, which runs
only when the release really goes out, reads the receipt-bound candidate
manifest, checks the pending draft carries that release's Store bundle,
sets targetPublishMode Immediate and commits it. Certification starts
then, and the build goes live when it passes.

- A draft from a newer green run is left for its own release; a committed
  or live submission is a no-op, so reruns are safe.
- submit refuses to replace a committed submission still on its way live
  (an earlier release); msstore publish would delete it.
- complete's Reconcile step now receives the MS_STORE_* credentials; it had
  none, so the success path silently skipped the Store.
- A failed or unreadable Store step stays a warning: the release publishes.
2026-10-20 15:50:30 -04:00
ethernet 84b1f05313 fix(desktop): read VCToolsVersion BOM-tolerantly; match vswhere as a path
Windows CI on #141651:
- footguns lint: Microsoft.VCToolsVersion.default.txt is read with
  'utf-8', which keeps a BOM in the version string. Use 'utf-8-sig'
  (and write a BOM in the test fixture, as Visual Studio does).
- Windows-only test: locate_command joins the known dir with mixed
  separators (".../Installer\vswhere.exe"), so the stub's string
  comparison never matched, vswhere looked absent, and both calls fell
  back to a random partition. Compare as paths.
2026-10-20 15:50:30 -04:00
ethernet ec64d1440e refactor(desktop): find vswhere through hermes_platform
Review (#141651): _msvc_identity hand-wrote the vswhere.exe install path,
which is the known-path table the hermes_platform rule keeps in one place.
Add visual_studio_installer_dirs() to resolver/known_dirs.py and locate
vswhere with locate_command (PATH first, then the installer directory).

Return None explicitly when vswhere reports no installations, before selecting
the newest instance.

Warn on stderr with the reason whenever MSVC identity detection returns None:
missing vswhere, no matching installations, or a failed probe/version read.
2026-10-20 15:50:30 -04:00
Maciej Lisiewski ba78364593 fix: update tag selection to work without local refs 2026-10-20 20:36:30 +02:00
Teknium 781f8bbd78 test: await the never-resumed update child's death on a process handle
test_script_killed_before_publishing_the_delegate_runs_no_update killed the
hand-off script, slept a fixed 3 s and then asked tasklist whether the
suspended update child was gone. The child dies through its job: the script
held the only handle to a KILL_ON_JOB_CLOSE job, and the kernel terminates
the members when the dying process's handle table is torn down. That
termination is asynchronous to the script's own exit, so on a loaded
Windows runner the 3 s guess loses and tasklist still lists the child.

Open a SYNCHRONIZE handle on the child while it lives (which also pins the
pid against reuse) and wait for it to be signalled, bounded at 60 s. The
'ran' check moves after the wait: once the child has exited it can never
write the marker file, so the assertion is final instead of a snapshot.
2026-10-20 05:15:06 -07:00
ethernet 58801c565b perf(ci): stop transporting node_modules in the desktop dependency snapshot
node-deps runs npm ci on every prepare (the receipt never matched across
runs in any sampled job), and npm ci deletes node_modules first. The
restored trees were GBs of tar/untar for nothing; npm's content cache
(npm/_cacache) is what lets npm ci skip downloads. The workspace path
validation only existed to guard those paths, so it goes too.
2026-10-19 12:30:42 -04:00
ethernet e97ce2a457 fix(desktop): Windows native wheels and vcpkg OpenSSL reuse the restored cache
Two caches were restored every run and then never read.

uv's native-wheel partition (native_cache_path) needs a toolchain identity
to be stable. win32-x64 never runs VsDevCmd, so VCToolsVersion is unset and
the identity fell back to a random uuid: every run compiled cryptography,
obstore, davey and friends from source into a fresh partition (all 6
sampled x64 runs had a new partition). Identify MSVC the way rustc's cc
crate finds it, through vswhere and the default VCToolsVersion file.

vcpkg's default binary cache lives under LOCALAPPDATA, which the desktop
preparation moves into the job-local home, so OpenSSL rebuilt every arm64
run ("Restored 0 package(s)", ~3 min). Point VCPKG_DEFAULT_BINARY_CACHE at
native/vcpkg-archives, inside the transported native domain.
2026-10-19 12:30:42 -04:00
kshitijk4poor 3c23e52ae3 fix(desktop-update): add the gateway hint only when the follow-up blames gateways
posix.sh and windows.ps1 appended "Run hermes gateway restart" whenever
the gateway_restart step was owed. Since this PR, that step can be owed
for a serve/dashboard unit alone (a system hermes-dashboard the updater had
no root for), and the hint then sends the user to a command that never
touches that unit. Both hand-offs now drop the hint only when the owed
reason is serve/dashboard-only: it starts with "serve/dashboard" and does
not mention `hermes gateway restart`. Every other gateway_restart reason
keeps the hint, including the raw exception text update_finish owes when
the restart phase itself crashes and reasons from older CLIs. A
dashboard-only owed restart points to the update log, which names the
unit's own command. The step id and owed-step parsing are unchanged.

Fixes #141071
Refs #141021
2026-10-19 02:48:18 +05:30
Teknium 2b64406cb2 test(pm): carry hermes_dir_acl.py into the minimal source snapshots
hermes_constants.mkdir_under_hermes_home and update_host_obligation import
hermes_dir_acl, so the takeover preparation and the fresh-install acceptance
snapshots (hand-picked root modules, not the whole tree) died with
"No module named 'hermes_dir_acl'". The product ships every root *.py
(setup.py _root_py_modules), so only these fixtures needed the file.

Refs #134889 (W8)
Refs #140337
2026-10-17 08:03:52 -07:00
Teknium 620f3b3b45 feat(plugin-catalog): add a Nous-granted verified tier
Verified entries come from the company that makes what the plugin integrates,
confirmed by the Nous team. Only maintainers set official/verified (CODEOWNERS
review; no CI check inspects the value). Tier order everywhere: official,
verified, community. Desktop's installed-plugin pill now reads the same
catalogInstall.tier map as the install card, dropping the duplicate
skills.plugins.tierOfficial/tierCommunity keys.
2026-10-16 14:31:11 -07:00
unsupportedpastels b65e078cdc fix(install): keep "git is required" on macOS when git is not on PATH
stage_prerequisites sent every Mac whose git_runs failed into
install_macos_git, and git_runs is also false when git is missing from
PATH. So a Mac with no git got the Command Line Tools message instead of
"git is required", contradicting the "no git on PATH: unchanged" row and
failing test_real_install_sh_ladder_leaves_only_closed_tokens on macOS.
Gate the macOS path on git being on PATH.

That ladder test was gated with a bare skipif, so the macOS lane never
imported it; give it platforms("posix") per tests/AGENTS.md.

Also from review:
- Check the developer tools' git before Homebrew, so a git that fails
  with the tools present (unaccepted Xcode license, broken ~/.gitconfig)
  reports git's own error instead of running brew install git first.
- Log why xcode-select --install failed (e.g. no GUI session over SSH)
  before the wait, instead of discarding its output.
- Parametrize the dialog-then-continue and fail-fast cases.
2026-10-16 09:05:04 -04:00
unsupportedpastels e6bfe7a5f1 fix(install): treat macOS developer tools as present only when they hold git
A macOS upgrade can leave /Library/Developer/CommandLineTools behind with its contents gone; xcode-select -p still names it, so the folder-exists check skipped the install dialog for the most common broken-git Mac. Require $dev/usr/bin/git instead, which both the CLT folder and Xcode's Contents/Developer hold.

When xcode-select --install refuses over such a leftover folder, no dialog opens: fail at once with the reinstall remedy instead of waiting 15 minutes.
2026-10-16 09:05:04 -04:00
unsupportedpastels 9b3d61732a fix(install): report git errors when macOS developer tools are already present; wait under Hermes-Setup
When xcode-select -p names an existing developer directory (e.g. Xcode installed, license never accepted), no install dialog will open: skip the dialog and the wait and fail with git's own error instead of waiting 15 minutes and suggesting the wrong fix.

Hermes-Setup drives stages with --non-interactive --json; it has no stage timeout, a Cancel button, and streams log lines as progress, so wait for the Command Line Tools there too. Only a plain headless --non-interactive run fails fast.

Run Homebrew with HOMEBREW_NO_AUTO_UPDATE=1 (auto-update runs the broken git and can take minutes) through run_logged instead of discarding its output.
2026-10-16 09:05:04 -04:00
unsupportedpastels 24204fd196 test(install): run the macOS git tests on macOS, beside the installer tests
Move the prerequisite tests to tests/scripts/install/ and drop the uname stub: macOS cases are platforms("macos"), the Linux case platforms("linux"). The probe PATH holds only system dirs so a host Homebrew never runs.
2026-10-16 09:05:04 -04:00
Teknium 6d63140782 fix(install): a rerun moves a checkout only forward onto the release
Review (unsupportedpastels) on #139311: a rerun over a main checkout moved
it to v0.22.2, which was cut on release/0.22.1 and diverged from main, so
the move crossed the gateway runtime cutover backwards without the downgrade
guard `hermes update` runs before the same move (update_downgrade_guard
refuses when a state.db holds session_admissions rows). The linear test
history could not show it: there, "does not contain the release" always
meant "behind it".

A rerun now moves only when the release descends from HEAD. A checkout that
contains it, has diverged from it, or cannot tell (shallow) stays, and
`hermes update` decides that move with its guard; an explicit
--channel stable is saved and left to `hermes update` the same way. The
stable-default tests gain the release-branch shape (v0.22.2's): a rerun
from main's tip keeps HEAD, a rerun from before the fork still moves.
2026-10-15 12:09:14 -07:00
Teknium 4cfa3ecd97 fix(install): an unreachable release fails a fresh install instead of publishing an empty one
Review (jquesnelle, Greptile) on #139311: when the release lookup succeeded
but its tag could not be fetched, a fresh install had already cloned with
--no-checkout, the fallback cleared the release, and no step wrote the files:
the installer published an empty checkout and exited 0. A rerun in the same
state jumped to main's tip, against the PR's own keep-the-checkout rule.

Stable that applies but cannot be reached now fails a fresh install with a
retryable message (naming --channel main), and keeps a rerun's checkout,
unless stable was asked for by name, which fails. Lookup gains a third
source, origin's highest vX.Y.Z tag over the same transport the clone uses
(the publication run pushes final tags), so a lookup failure with git
working is no longer a separate case. The pytest upgrade harness, whose
origin carries no release tags, pins its fresh installs to main the way the
install E2E harnesses already do.
2026-10-15 12:09:14 -07:00
Teknium 3857c7b8ee fix(install): new installs land on the latest stable release
`hermes update` has followed the stable channel for official source checkouts
since #135847, but install.sh and install.ps1 still cloned main's tip, so every
new user (CLI one-liners and Hermes-Setup, which bakes `-Branch main`) started
on unreleased code and only reached stable at the next release.

For the official repository's main line, with no explicit --commit:
- a fresh install resolves the latest published vX.Y.Z release (GitHub API,
  then the github.com /releases/latest redirect, which needs no API quota),
  fetches its tag (releases can sit on release/* branches) and checks it out
  detached, where `hermes update` puts a stable checkout;
- a rerun with no recorded channel moves forward to that release and keeps a
  checkout that already contains it (forward-only, like the update default);
  if the lookup fails a rerun keeps its checkout instead of jumping to main;
- `--channel main` / `-Channel main` / HERMES_INSTALL_CHANNEL=main installs
  main's tip and saves the choice with `hermes update --set-channel`, so later
  updates keep following main. Forks, mirrors and other branches are unchanged.

The install E2E harnesses that serve unreleased commits from a tagged local
mirror pin themselves to main through the same variable.
2026-10-15 12:09:14 -07:00
Teknium 730680d8ff test(scripts): give the PowerShell build probe a load-proof bound
The probe runs a cold Windows PowerShell 5.1 that spawns git and Python
children, twice, each capped at 30 s. On a loaded Windows arm64 runner
that cap expired with the probe healthy (TimeoutExpired, no assertion
reached). The cap is a liveness bound, not a speed assertion: 180 s.
2026-10-15 09:12:44 -07:00
Maciej Lisiewski 19f6f1482c ci: fix home path issue specific to blacksmith runners 2026-10-15 05:23:51 -07:00
Teknium fe45cf6684 ci(release): start the Windows bundle when both arches are built, not after their smokes
Stable builds each Windows arch in its own desktop-bundled-release call and
assembles the universal bundle in a third call that needs both. Each per-arch
call also ran that arch's MSIX install-and-chat smoke, so assembly waited for
the slower arch's smoke after its build: on rc.2-v0.22.2 the bundle sat behind
the arm64 smoke from 07:16 to 07:28.

A new windows-smokes-with-bundle input moves both MSIX smokes into the
win32-bundle call. The smokes already fetch the staged package from the R2
handoff, so they run there beside assembly. The per-arch calls then only
build, the bundle call's phase result requires both smokes, and the candidate
manifest records the Windows smoke results from the bundle call, so a failed
smoke still blocks the release. Callers that build everything in one call
(canary, channel, commit builds) leave the input off and are unchanged.
2026-10-15 02:06:01 -07:00
Teknium eb75627faf Merge pull request #106742 from NousResearch/feat/unified-gateway-runtime
One gateway owns every local session: CLI, TUI, Desktop (local), API, ACP, bots and cron attach to the same live conversation
2026-10-14 13:20:01 -07:00
ethernet 9686d55ca8 ci(release): admit stable releases from release/* branches, not just main
The release entrypoint and the stable gate both required the claimed commit
to be an ancestor of main. Both now accept an ancestor of main or of any
release/* branch, via one shared git query (stable.release_line_contains).
The entrypoint prunes its mirror of release/* so a deleted branch stops
admitting commits. The termux previous-package ancestry check follows the
same rule. Workflows still dispatch from main.
2026-10-14 16:04:20 -04:00
Teknium 2c01eb5958 Merge origin/main into feat/unified-gateway-runtime
48 commits from main (e8c8e46471). One conflict: hermes_cli/runtime_provider_custom.py, where both sides
added the `config=` keyword to _get_named_custom_provider; main's signature (config + resolve_credentials)
is taken whole.
2026-10-14 12:41:58 -07:00
ethernet a8cc19ebfd fix(release): a Store-only Windows build gets no signing vars; Store-plus-macOS smokes record only macOS
- The Windows build step passes AZURE_* empty when skip-signed is set.
  after-pack and electron-builder sign whenever the vars exist, and the
  Azure login is skipped with the signed lane, so a Store-only build with
  the vars configured would sign without credentials.
- candidate_manifest records a smoke result only for calls whose package is
  a target. `--without windows` runs the Windows candidate calls for the
  Store bundle; their success is not a smoke, and counting it made assemble
  refuse the manifest.
2026-10-14 14:59:28 -04:00
ethernet f9d8a74035 feat(release): split the Microsoft Store bundle into its own windows-store lane
`windows` used to build the signed MSIX, the universal bundle and the Store
bundle together, so skipping the signed pipeline meant skipping the Store too.
Now `windows` is the signed pipeline and `windows-store` the unsigned Store
bundle; both default on and the shared per-arch builds only make what the
lanes ask for.

- lanes: new `windows-store` lane and derived `windows-build` (either lane),
  which gates the shared candidates-win32-* jobs and handoffs.
- desktop-bundled-release: `skip-signed` builds only the Store variant and
  needs no Azure login, signing, metadata, smoke or receipt; `skip-store`
  builds and submits no Store bundle (replaces the old `store` input).
- manifest: a Store-only release has no package rows, only the Store bundle
  file; assemble() now takes the claim's lanes and checks each Windows bundle
  against its lane.
- claims record `lanesRevision: 2`. A lanes claim without it was cut when
  `windows` also meant the Store bundle (rc.2-v0.22.1), so it reads as
  windows + windows-store.
- release_artifacts: drop the unused `assemble` CLI command, which could not
  name the lanes.
2026-10-14 14:39:03 -04:00
ethernet 6743ab711a fix(release): termux-only claims finish and partial releases keep the shared downloads page
Two review findings on the lanes change:

- candidate-manifest judged only the desktop calls, and when a claim built
  none (--only termux) it called require_success with an empty list and could
  never finish. It now requires every candidate call the claim built, Termux
  included, and judges smoke results only for the desktop calls that have one.
- `complete` rendered the shared releases/stable/index.html for any release
  with bundles, so a windows-only release replaced the previous full release's
  macOS links. admit now emits a derived `desktop-channel` flag and the
  renderer takes --no-channel-page, leaving the tag page and release table
  alone while the shared page waits for a release that serves the channel.
2026-10-14 11:24:03 -04:00
ethernet 36a8ee18cd feat(release): choose what a stable release builds and tests with lanes
The skip and opt-in flags (--skip-bundles, --skip-tests, --include-macos-intel,
--include-termux) were four booleans in the claim, and every consumer
re-derived what they meant. A release is now a set of lanes, defined once in
scripts/releases/lanes.py: five build lanes (docker, windows, macos-arm64,
macos-intel, termux) and ten test lanes (ci, nix, docker-tests, pm-bundle,
windows-live, install-e2e, bootstrap-version, termux-checks, smoke, upgrade).
The default is every lane except macos-intel and termux.

  release.py release --only windows,docker      # the Store bundle + image, untested
  release.py release --with macos-intel --without install-e2e

- The claim records `lanes`. A claim cut before lanes existed still parses:
  skipBundles/skipTests/include* translate to the lane set that gated the
  same jobs, because the sequencer reads every claim tag ever cut.
- `admit` emits `lanes` and `jobs` (a run flag per lane-controlled job). Each
  job's `if` reads its own `jobs` entry and the gates derive their expected
  skips from the same table, so the workflow and the gates cannot drift.
  tests/ci/test_stable_release_graph.py evaluates every `if` against 37 lane
  sets.
- A release may build any subset: the candidate manifest carries exactly the
  claim's targets, and the final tag records null for the docker digest or the
  manifest digest when the claim built neither.
- The protected desktop channel, feeds, APT index and upgrade baseline move
  only when the claim built windows and macos-arm64. A partial set is staged,
  verified and (with windows) submitted to the Store, and leaves the channels
  on the previous release. A release that moves no channel is finished once
  public.
- publish-bundles passes `store` so a claim without windows submits nothing
  to the Microsoft Store.

Not exercised end to end: no real release was cut with a partial set.
2026-10-14 11:03:26 -04:00
Teknium 0715474bfd Merge origin/main into feat/unified-gateway-runtime
375 commits from main (0e427d788d). 24 files conflicted; the resolutions that carry a decision:

- /initiate-setup: main removed the command (25bf722063). Its gateway rewrite, CLI handler and the
  Desktop kickoff go with main; this branch's command-layer admission of it is dropped too
  (gateway/session_commands.py _PROMPT_COMMANDS, its slash.exec test row, the parity doc row), and
  the Desktop slash dispatcher loses the `hidden` option that only the kickoff used.
- Code this branch moved out of its old file, carrying main's edits to the moved copy:
  first-contact notes (gateway/run_turn.py -> run_turn_prepare.py: no message/setup_command),
  the TUI agent factory (tui_gateway/server.py -> agent_factory.py: onboarding toolsets at build,
  setup_beat/set_theme lifecycle cards, no cli platform extras on a coding selection), the serve
  lifespan (web_server.py -> web_server_app.py standalone_lifespan: first-install probe before the
  state.db reconcile, onboarding due answer after the setup bootstrap), and the compression child
  publish (the shared _publish_compression_child_on_conn reads the parent's model_config).
- agent/auxiliary_client.py: both import changes (_route_key from main; this branch's move of
  _without_structured_output_format).
- Desktop: main's onboarding run (decideOnboarding, OnboardingEnd, card-waiting Send) replaces the
  kickoff; this branch's prepared-submission slash path, request_id on session.create and
  PreparedImageRecovery stay. Pool-limits imports are dropped (this branch removed the pool).
  createBackendSessionForSend takes main's (preview, overrides) signature.
- ci.yaml detect job: main's `ref: inputs.commit` plus this branch's sparse checkout.
- package.json / lock: main's vitest 4.1.11 plus this branch's ws 8.21.1.
- Generated: gateway-contract.openrpc.json regenerated; parity doc regenerated.
2026-10-14 05:52:49 -07:00