240 Commits
Author SHA1 Message Date
Maciej Lisiewski 7062229090 chore(js): switch the workspaces from npm to pnpm
pnpm 12.15.0 (native executable) installs every JS workspace from one
pnpm-lock.yaml, imported from package-lock.json with the same resolved
versions. website/, the WhatsApp bridge and the photon sidecar stay separate
npm projects.

Configuration moves from .npmrc and the root manifest into
pnpm-workspace.yaml: the 14-day minimumReleaseAge and its excludes,
overrides, allowBuilds (the old allowScripts set, with strictDepBuilds
failing any unlisted build script), engineStrict, sideEffectsCache: false
(in-place rebuilds must not write into the store) and
verifyDepsBeforeRun: false (a `pnpm run` never installs).

The pnpm version is pinned in pm/lock.json and in root devEngines.
pmOnFail: ignore keeps that pin out of pnpm-lock.yaml: a leading pin
document makes osv-scanner read zero packages. scripts/build/node-deps.mjs
enforces the exact version instead, and every lock reader refuses a
multi-document lock. A root preinstall refuses `npm install`; devEngines
onFail is warn so npx (MCP servers, the tsc linter) keeps working inside
the checkout.

User-machine installs run --frozen-lockfile --trust-lockfile (release age is
checked when locking, in CI, not per user). node-deps removes npm-era
node_modules trees before the first pnpm install. Nix builds the store with
fetchPnpmDeps and a hash file that CI checks.
2026-11-06 04:08:24 +01:00
Maciej Lisiewski 0247bae91f test(js): fail the lockfile and workspace contracts when their input is missing
allow-scripts-sync and assistant-ui-tap-compat returned early without a lockfile, and
react-dom-pair-compat checked nothing when the workspace list was empty, so all three would pass
vacuously once their input moved. They now fail instead.
2026-11-05 14:08:26 -08:00
Maciej Lisiewski c2a0ecc734 fix: resolve electron, vite and playwright from the workspaces that declare them
Scripts outside the declaring workspace found these through the hoisted root node_modules:
mock-server and connector-rehearsal (whose desktop-local Electron path did not exist under npm),
ui-tui's visual harness, the eval harnesses, and the tests/install/e2e-assets drivers, which import
tests-js's @playwright/test and zod. They now resolve from apps/desktop or tests-js; the typed
e2e-assets files go through a tests-js re-export module so their types resolve the same way.
2026-11-05 14:08:26 -08:00
Maciej Lisiewski 99306b4b01 fix(desktop): resolve app-builder-lib and node_modules through module resolution
Packaging scripts and tests reached app-builder-lib's internals as ../../../node_modules/..., and
test fixtures junctioned only the repo-root node_modules, both of which assume npm hoists every
package to the root. The internals are now joined onto the installed package's resolved entry (the
pattern windows-bundle-tools.mjs already uses), AppInfo comes from the package's public entry, and
fixtures mirror both the root and apps/desktop node_modules, so the copied desktop files resolve
their dependencies exactly as the checkout does. gen-msix-manifest output is byte-identical.
2026-11-05 14:08:26 -08:00
Maciej Lisiewski 48ef546fb2 chore(js): declare dependencies the workspaces only reached through hoisting
Every workspace imported packages it never declared and found them only because npm hoists the
whole tree to the root node_modules: desktop (app-builder-lib, @electron/osx-sign, @electron/asar,
plist, @tailwindcss/node, remark-gfm, remark-parse), bootstrap-installer (@nanostores/react),
shared (vitest), ui-tui (chalk, strip-ansi) and tests-js (@types/node, vite, esbuild). Each is
pinned to the version npm already resolves for it, so the installed tree is unchanged apart from a
tests-js copy of @types/node matching the other workspaces' 22.20.5.
2026-11-05 14:08:26 -08:00
BearHuddleston f0b595d15d test(install-e2e): cut the STT setting's comment to its reason
The commit that added stt.enabled: false carries the mechanism and the
incident. The writer keeps one line saying why the setting exists and
which releases need it; the test assertion needs no comment.
2026-11-03 22:56:41 +00:00
BearHuddleston 9e4b789b4a test(install-e2e): journeys turn STT off so an old release's boot probe cannot stall the send
The Windows installer-script -> hermes-desktop-app-update leg from
v2026.7.30 failed its OLD update-window chat on rc.1-v0.22.7 (run
41707948253, job 126169529713): "The mock must receive this checkpoint
prompt after the send", and the mock never saw a chat request.

v2026.7.30 answers wake.status inline on the WebSocket reader (#85438
moved it to the RPC pool in v2026.8.13), and its STT readiness check
lazy-installs faster-whisper first, which an installer-script venv
lacks. The app calls wake.status when it connects, so every later
request on that socket waits for the install. In the failing job it
took 53 s: session.create outlived the renderer's 30 s timeout
("request timed out after 30s: session.create"), the draft went back
into the composer and no turn started. The same leg passed on
rc.3-v0.22.6, where the install took 3.5 s.

The journey config writer now sets stt.enabled: false beside
onboarding.run: false. With STT off, v2026.7.30's wake and voice probes
skip the install. These journeys test install and update, not voice
input.

Reproduced on Windows 11 with the real v2026.7.30 app and backend
through runInstalledDesktopSmoke. With the faster-whisper and
ctranslate2 index pages held 25 s each, the install took 54 s and the
check failed with the CI message. The new config passed under the same
index, which never received a faster-whisper request.
2026-11-03 22:34:22 +00:00
Maciej Lisiewski 7b55a7c236 Merge pull request #148622 from NousResearch/ci/drop-macos-intel
ci: remove the Intel macOS CI and release legs
2026-11-03 21:56:01 +01:00
Maciej Lisiewski 8f4ac1c1da Merge pull request #148630 from NousResearch/deps/security-batch-2026-11-03
fix(deps): clear release-age-eligible security alerts (undici, zod, fast-uri, qs, pypdf, pyasn1, tornado, filelock, setuptools)
2026-11-03 21:52:27 +01:00
Bear Huddleston 3ab4fb510f Merge pull request #148497 from NousResearch/fix/desktop-notarize-transient-retries
fix(desktop): keep notarizing through dropped connections to Apple
2026-11-03 14:34:28 -06:00
Maciej Lisiewski b9621cd0b2 fix(deps): bump zod to 4.6.7
Moves the tests-js direct pin (which the root lock hoists for
@assistant-ui/react and eslint-plugin-react-hooks) and the photon
sidecar's transitive copy from 4.6.5 to 4.6.7, the newest 4.6 patch.
4.6.7 was published 2026-10-12, past the 14-day npm release-age gate.

Advisory: GHSA-mrhx-c7xc-m3w5
2026-11-03 21:06:29 +01:00
BearHuddleston 9dd1ea34f2 refactor(desktop): one retry schedule and one timeout fixture
The missing-ticket retries used their own delay table, which matched
the first five lost-contact pauses. They now call contactDelay(), with
a limit of five. A ticket that never propagates still gets 6 staples,
the same pauses (15, 30, 60, 120 and 120 s) and the same error.

The LOST_CONTACT comment now says which codes point at the runner's own
network (-1003, -1004, -1006, -1009) rather than at Apple.

The older resume test's local `timeoutOutput` copied `notaryTimeout`,
apart from `_kCFStreamErrorDomainKey` (1 rather than 4, the value in
the canary log). The test now uses `notaryTimeout`.
2026-11-03 20:04:19 +00:00
BearHuddleston 23ca14a28e fix(desktop): give lost contact while stapling a 45-minute window
Stapling starts only after Apple has accepted the app. Until now a
dropped connection there could retry for whatever was left of the
four-hour notarization budget, holding the runner for hours on a
network path that main gave up on within a minute. The host-unreachable
codes (-1003, -1004, -1009) usually mean the runner's own network is
broken, not that Apple is down.

Lost contact while stapling now retries for up to 45 minutes, or for
what is left of the notarization budget when that is shorter. 45
minutes is about twice the outage the 2026-11-03 canary ran into. The
wait for Apple's verdict keeps the full budget.

The staple loop also checks LOST_CONTACT before it counts a retry. A
ticket that is still missing after its five retries now throws inside
its own branch, instead of falling through to the lost-contact check.

The outage test now covers both limits, an acceptance at t=0 and one
10 minutes before the budget ends. It fails on the previous head, which
retried for about 4 h, and the second case fails if the Math.min is
removed.
2026-11-03 20:03:29 +00:00
BearHuddleston 6c7252f9c5 test(desktop): replay stapler's real output once, not eight times
The staple-retry test spawned eight `node -e` children under vitest's
5 s default timeout. On a loaded Mac it timed out in review (5,012 ms).
On a 20-core Linux host it took about 0.2 s idle and about 3 s at load
average 144.

The first dropped connection still goes through a real child process.
That proves the hook reads stapler's exit 68 and its output. The other
five drops and the two missing-ticket replies are thrown directly, as
the outage test already does. Under the same load the test now takes
about 0.5 s, and about 40 ms idle. It still fails against main's hook.
2026-11-03 20:02:12 +00:00
Maciej Lisiewski 06192b894c ci(release): stop building and publishing macOS Intel desktop bundles
Remove the darwin-x64 build, receipt and smoke jobs from Desktop Bundled
Release and the candidates/transitions/packaged-x64 jobs from Stable Release,
with the release scripts that required them: the macos-intel lane, the
darwin-x64 job group, the x64 macOS target in candidate/channel manifests and
the two-leg macOS feed merge. The macOS updater feed is arm64-only.

Pre-lanes claims still read: the published ones all carry
includeMacosIntel:false; a claim with true is now refused.
2026-11-03 20:58:05 +01:00
Teknium 558b54f0c0 test(e2e): room evidence in dark theme with natural messages, reply chip, @ picker, details and row shots
The mock gains groupScripts: E2E tokens keyed on a phrase of the member turn
prompt, so the user message on screen is a natural sentence instead of the
script. The spec shoots in dark theme (THEME=light for the light leg), adds
Reply-in-thread chip, @ picker, details panel and Bots-list row shots, and
opens the Desktop-driven room by answering only the groups.capabilities probe
like a gateway without a room driver (turns stay real), as group-turn-integrity
does.
2026-11-03 10:13:09 -08:00
Teknium 308f317707 test(e2e): live gateway-owned room spec with scripted tool calls and failures
The mock inference server learns E2E_TOOL(<handle>)[<tool>:<command>] (a
member's first completion calls a tool) and E2E_FAIL(<handle>) (HTTP 401),
and group-scripted completions honour holdFirstCompletionContaining.
group-room-native.spec.ts drives a three-bot gateway-owned room through
working (tool held), settled and failed states and screenshots each.
2026-11-03 10:13:09 -08:00
BearHuddleston 2941c7fe66 fix(desktop): keep notarizing through dropped connections to Apple
The 2026-11-03 canary (desktop run 41591721476) signed both macOS
builds, and Apple accepted the arm64 submission, but both builds failed
in this hook on network errors it does not retry:

- arm64: `stapler staple` exited 68 with NSURLErrorDomain Code=-1005
  "The network connection was lost." from api.apple-cloudkit.com. Only
  the exit-65 missing-ticket case was retried.
- x64: `notarytool wait` timed out (Code=-1001) at 07:22, 07:24, 07:27
  and 07:38 UTC. Resuming stopped after three pauses (15, 30 and 60 s),
  although the notarization budget is four hours.

Treat NSURLError timeouts, lost connections and unreachable hosts
(-1001, -1003, -1004, -1005, -1006, -1009) as lost contact in both
loops. Keep resuming the same submission, and retrying the staple, with
pauses capped at 120 s until the next pause would cross the existing
four-hour budget. A ticket that has not propagated yet keeps its own
five retries. Verdicts, authentication failures and other stapler
errors still fail at once.
2026-11-03 13:37:20 +00:00
Teknium f0d94882e8 test(install-e2e): classify the v2026.7.30 macOS DMG app-update refusal as known
The published June Hermes-Setup.dmg updater writes the update marker with its
own PID and runs the v2026.7.30 checkout's `hermes update`, whose update lock
only recognises its orchestrator via HERMES_UPDATE_HANDOFF_PID (which the June
binary predates). The pre-pull CLI refuses with "Another Hermes update is
already running" and both desktop-installer@latest in-app legs from that tag
fail deterministically. The refusal runs in released code, so the update
target cannot fix it.

Extend the known-failures mechanism to macOS:
- rules carry `platforms` (default windows; existing rules unchanged)
- classifyWorkRoot reads the macOS harness layout (shas.env, home/.hermes)
- macos-desktop-e2e.sh rotates bootstrap-installer.log before the update,
  collects HERMES_HOME/logs on failure, and only a full rule match (commit,
  method pair, exact assertion, both fresh log signatures) yields a receipt
- install-e2e-macos-run.yml uploads the receipt as install-e2e-known-<id>--<leg>
- KNOWN_FAILURES.md documents the case with verified failing jobs
2026-11-02 07:57:32 -08:00
Teknium b7de5ae5a5 test(install-e2e): chat smoke focuses the composer without a pointer click
The pointer click at the composer's hit-tested centre passed the topmost check
but did not focus the v0.19.1 editor on 7 of 43 legs (proof run 41418896446:
"The click at the composer must focus it"). Focus it with
focus({ preventScroll: true }) after the same topmost check: no scrolling, and
the hit test still proves the composer is the element a user would reach.
2026-11-02 02:40:10 -08:00
Teknium df745813cd test(install-e2e): chat smoke clicks and types into the composer without scrolling the app
The first fix (non-scrolling elementFromPoint wait before the trial click)
was not enough: in install-e2e run 41414755532 the wait passed on every
failing v2026.7.30 leg (the call log has no overlay intercept at all), and
the trial click then failed on its FIRST attempt with <html> intercepting.
Playwright's click actionability always starts with scrollIntoViewIfNeeded,
and its retries cycle end/center/start alignment. On the v0.19.1 desktop
that scrolls the overflow-hidden chat surface, so the composer is displaced
under the dock/surface/<html> for good; the leg recording shows the layout
cycling between composer-at-bottom, -centre and -top with a still pointer.
The boot overlay was never the trigger.

Drop Playwright pointer and press actions on the composer: the non-scrolling
hit test is the interactability check, the send focuses the composer with a
real mouse click at its hit-tested centre and must observe it took focus,
and keys go through page.keyboard (locator.press/pressSequentially call
element.focus(), which can scroll too). Every check stays as strict for
newer apps: topmost at its centre, focused by the click, holds the typed
prompt, clears on submit.
2026-11-02 02:40:10 -08:00
Teknium c1cf86e88b test(install-e2e): chat smoke waits out the boot overlay without scrolling the app
The v2026.7.30 (v0.19.1) desktop keeps its composer editable behind the
"Starting Hermes…"/connecting overlay. The smoke's trial click started
while the overlay was up, and Playwright retries a blocked pointer action
by calling scrollIntoView with end/center/start alignment. That scrolls
the app's overflow-hidden ancestors (chat surface, composer fade), so once
the overlay cleared the composer's centre hit the dock/surface/<html> on
every retry and the click timed out at 120 s (every Windows leg and one
Linux leg from v2026.7.30 in run 41411274160; the recording shows the
layout cycling with a stationary pointer).

Poll a non-scrolling in-page hit test until the composer is topmost, then
keep the trial click as the final interactability check.
2026-11-02 02:40:10 -08:00
Teknium ddc8c31514 test(install-e2e): HEAD -> NEXT legs accept the reused desktop app's build commit
Every HEAD -> NEXT install/update E2E leg (Linux, macOS, Windows) failed with
"Running commit <HEAD> does not equal expected <NEXT>". The synthetic NEXT
commit adds only a marker file, so since 7a954418ce `hermes update` keeps the
packaged desktop app ("no desktop build input changed"), and that app keeps
naming the commit it was built from by design.

The smoke now accepts the app reporting NEXT's parent only when the installed
commit is a source checkout whose single change from that parent is the
synthetic marker file. Any real commit still requires the app to report it,
so a stale app after a real desktop change stays a failure.
2026-11-02 00:02:44 -08:00
Teknium c7da41dd52 fix(desktop): a session's running highlight ends when its background work ends off screen
A turn that ends with work still going (a background process, a background
subagent) keeps its sidebar row lit. The light comes from per-session stores
fed by events and by a per-pane poll, and the poll pauses as soon as that
session's pane is hidden. A background process that exits without
notify_on_complete sends no event either, so the stored "running" entry never
changed and the row kept spinning until the user clicked back into the session.

The app-wide live-work roster already reads every backend every 10 s, but it
could only ADD rows, never end one. Each roster bucket now records which
sidebar rows its source answers for. A read from that source that is newer than
the event-fed entry and reports no work for the row now ends the highlight.
A row no source covers keeps its event-fed state, and a closed source covering
a lit row is re-read on the existing one-minute busy cadence.
2026-11-01 11:07:22 -08:00
unsupportedpastels 3883e02935 perf(webapp): build the browser bundle without downloading Electron
The Webapp recipe installs Desktop's workspace, and node-deps prepared
Electron's binary for any install that selects apps/desktop. The browser
bundle never runs Electron, so prepareNodeDependencies now takes
`electron: false` and build-webapp.mjs passes it. Desktop builds keep the
default.
2026-10-27 09:42:22 -04:00
unsupportedpastels a2ba95ac01 test(webapp): match injected chunk faults by native path
The copy-fault mock matched forward-slash suffixes, which a Windows
path built with join never ends with, so both injected failures were
bypassed there. Name each fault by the complete path the build copies
from, built with the same join.
2026-10-26 21:30:03 -04:00
unsupportedpastels 148f36ea6c fix(webapp): warn when a previous chunk cannot be kept across a rebuild
keepPreviousGeneration skipped a copy that failed for an unexpected reason (a full disk, a permission error) without a word: the build recorded its receipt and published, and tabs still on the previous generation lost that chunk with nothing in the build log saying why.

The build still never fails here, but a failed copy, an unexpected directory error, or a failed cleanup of the partial copy now warns with the path and error code. The expected cases stay quiet: no previous build, a rejected manifest entry, a new file standing where the name or its directory would go (including EEXIST from COPYFILE_EXCL).
2026-10-26 21:30:03 -04:00
dokterdok f22598136d fix(webapp): keep the previous bundle's lazy chunks across a rebuild
A Webapp tab that was already open crashed the next time it opened a
lazy view such as Settings once `npm run build:webapp`, `hermes webapp`
or `hermes update` had published a new renderer. Publication replaces
apps/desktop/dist-webapp, which the gateway reads per request, so the
old entry's hashed lazy chunks 404 and the rejected import reaches the
app's error boundary until the page is reloaded.

Before the receipt is written, the builder now copies into the new
generation the files the previous generation's Vite manifest names that
the new one lacks: only that generation's own files, so a rebuild keeps
exactly one generation back; only while its bytes still match its
receipt (freshness.mjs gains outputIntact, which ignores the marker a
failed `hermes update` leaves in the bundle: that still forces the
rebuild, but no longer costs open tabs their chunks); only regular
files inside it, never through a symlink and never over a new file. A
bad entry is skipped and never fails the build. The builder names the
manifest itself, so it exists whatever the Vite config says.

Refs #141864, #93508

(cherry picked from commit 096996d1a591917b21c96e104d20e9dc54c5d850)
2026-10-26 21:30:03 -04:00
Maciej Lisiewski 05b83f632f Merge pull request #143719 from NousResearch/bulk-update-js
chore(deps): bump npm dependencies to latest minor/patch releases
2026-10-25 12:54:29 +01:00
Bear Huddlestonandemozilla 7ac4cf552c feat(desktop): carry the browser host renderer from #93508
Carried from BearHuddleston's #93508: the browser bridge and transport,
launch-session handling, touch and phone layout, browser previews, file
staging, the host terminal rail, Webapp settings pages and media ticket
renewal. Relay-only and fence-only code stays out.

Co-authored-by: emozilla <emozilla@nousresearch.com>
2026-10-25 03:22:15 -07:00
Maciej Lisiewski 4d5c6cfb0c chore(node): require Node >=26
Root and desktop engines.node move from '^22.22.0 || ^24.11.0 || >=26.0.0' to '>=26', and the
website's from '>=20.0' to '>=26.0' (PM already pins Node 26.12.1). The lockfile workspace engine
mirrors, the CONTRIBUTING prerequisite, the tests-js engine-policy table and the
bootstrap-installer release workflow's setup-node pin (24.11.1 -> 26.12.1, which engine-strict
would now reject) follow.
2026-10-23 22:41:04 +02:00
Maciej Lisiewski 3fd66bf83e chore(deps): bump zod to 4.6.5
zod: 4.4.3 -> 4.6.5 (pins edited: 1; also: {})
2026-10-23 22:00:12 +02:00
Maciej Lisiewski 11cc7bf3da chore(deps): bump @playwright/test to 1.64.0
@playwright/test: 1.62.1 -> 1.64.0 (pins edited: 2; also: {'playwright': "['1.62.1']->['1.64.0']", 'playwright-core': "['1.62.1']->['1.64.0']"})
2026-10-23 22:00:12 +02: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 3ba2b0787e fix(release): Windows x64 MSIX smoke runs on a GitHub-hosted runner and reports why a launch dies
Every Windows x64 MSIX chat smoke since the Blacksmith migration (91fae5f8cb)
failed: first on the GitHub-hosted guard, and since that guard was removed
(272adc7b6f) with Playwright's "Process failed to launch!", on Electron 40 and
44 alike. The same builds' arm64 MSIX, on a GitHub-hosted runner, chats; the
last green x64 smoke (Oct 14) ran on windows-latest-32-core. Move the x64 leg
back there, as f1b290e773 did for the install/update GUI legs.

The failure itself carried no evidence: when the app exits before its debugger
line, Playwright's Electron.launch leaves sibling stderr-watcher rejections
unhandled, Node dies on them, and the smoke's catch never writes its receipt or
Playwright's call log. The driver now logs those rejections (still failing the
run), so the receipt records the app's exit code and stderr, and the Windows
smoke saves Application-log errors from the run on failure.
2026-10-22 18:59:52 -07:00
ahrazzle a9376424e5 fix(desktop): prepare the locked Electron runtime explicitly before use
Electron 42 dropped the postinstall that unpacked its binary into
node_modules/electron/dist. The binary now appears on the first
require('electron'), so parallel vitest workers race to unpack it
("Text file busy" in #128267's CI), and `hermes desktop --source` finds
no path.txt to launch.

electron-runtime.mjs runs Electron's own install.js once: after npm ci
for the desktop workspace, and in CI before the parallel checks. It first
checks the installed package against the desktop pin and the lockfile,
and does nothing when the runtime is already in place. On the current
40.10.2 pin, whose postinstall still unpacks the binary, it is a no-op.

Salvaged from #137648 (the runtime-preparation subset only).
2026-10-22 16:17:57 -04:00
Maciej Lisiewski c82bdd01ae chore(deps): bump vitest to 4.1.12
Latest 4.1.x patch: mocker hardening for manual and redirect mocks
(vitest-dev/vitest#11580 backport to v4). Also bumps @vitest/mocker.

vitest 4.1.12 is 7 days old, below the min-release-age=14d guard, so
.npmrc gains a dated exclude entry (remove when > 2wks old).
2026-10-22 21:20:10 +02:00
Teknium 48b5c3283b test(install-e2e): the canonical gateway proves its commit where its cwd is unreadable, and the update window looks for it
Builds on #142306 (Maciej, @c2h5oh). That PR taught the desktop smoke that the
listener Desktop dials can be the canonical gateway named in the home's
gateway_state.json, and proves that gateway's tree by the cwd `gateway ensure`
starts it in. Install-e2e run 39567335733 (main + this branch) hit two cases it
does not reach:

- Windows: CIM exposes neither the cwd nor the environment of another process.
  Every desktop checkpoint that reached the (now startable) detached gateway
  failed with "Source backend listener imports a different source tree
  (cwd=(unreadable), HERMES_PYTHON_SRC_ROOT=(unset) ...)". The gateway now
  vouches for itself there. localBackendProcess keeps the code_sha the gateway
  stamped into its own gateway_state.json (gateway/status.py). assertBackendOrigin
  accepts the app's reported root for it only when that code_sha is the
  installed commit, which both callers already read. A readable cwd is still
  the evidence everywhere else, and the child/ledger rules are unchanged.
- All OSes, HEAD -> NEXT app-driven update legs: update-window-chat.mjs called
  localBackendProcess without a home, so it never looked for the canonical
  gateway ("Expected one Desktop-owned, registered host or canonical gateway
  backend listener on port N; found 0"). It now passes the app's own
  hermesHome and the installed commit.
2026-10-21 03:04:36 -07:00
Teknium a4c13ee015 test(install-e2e): a later step's mock rebinds the port the surviving gateway froze
An install/update journey runs its phases in separate CI steps. Each step's
mock provider dies with its step, but the gateway Desktop started is detached
on purpose and survives into the next step with the provider URL it read at
boot. The next step started a mock on a fresh port and rewrote config.yaml/.env,
so the surviving gateway kept dialing the dead port: every HEAD -> NEXT desktop
leg on macOS and Windows failed with "The mock must receive this checkpoint
prompt after the send" (run 39523017025: agent.log shows the gateway calling
127.0.0.1:61022 while the new mock listened on 53982; macOS 49478 vs 49533).
Linux passed only because its mock happened to survive between steps.

mock-provider.mjs now takes the prior step's URL and rebinds that port when it
is free (any free port otherwise), and both callers (mock_start and
Start-DesktopJourneyMock) pass it. The config stays byte-identical across
steps, and the chat smoke's commit check still proves the gateway runs the
updated code.
2026-10-21 03:04:36 -07:00
Maciej Lisiewski 9a6140877b fix(install-e2e): desktop smoke accepts the canonical gateway Desktop attaches to
Since #106742 Desktop reaches its local backend through `hermes gateway ensure`,
which attaches to (or starts, detached) the canonical `gateway run` daemon. That
listener is reparented away from Electron and has no `serve` spawn-ledger record,
so localBackendProcess found 0 listeners and every Linux +desktop install/update
leg failed after an otherwise healthy boot.

Accept a third listener kind, `host-gateway`: the pid gateway_state.json names in
the home under test or its machine root, running, kind hermes-gateway, same
incarnation (start_time in gateway.status units), and the actual OS listener on
the connected port. The installed-tree origin check is unchanged and gets no
app-report waiver for it; the gateway proves its tree by the install-root cwd
`gateway ensure` starts it in.
2026-10-21 02:43:42 +02:00
ethernet 13cb8cca17 ci: pull requests restore the uv and npm caches without saving their own
save-python-cache and save-node-cache now default to false on
pull_request events, like save-tools-cache. Their restore-only steps
read the exact keys the saving steps write on main, so a PR gets main's
uv and npm caches and leaves nothing PR-scoped behind.

Restore-only used to mean an explicit-save flow: run- and job-scoped
keys meant for save-pm-cache or a caller's actions/cache/save to write
later. Nothing calls save-pm-cache, and the last caller restoring from
the npm explicit-save key moved to its own cache, so the flow and its
outputs are deleted.
2026-10-20 15:50:30 -04:00
Teknium 91e27b6c09 fix(desktop-build): npm run builder -- --win --x64 can stage its per-target native deps
`npm run builder` with an arch flag stages native deps into
apps/desktop/build/native-deps-<platform>-<arch>
(scripts/run-electron-builder.mjs). The product guard in
scripts/build/frontend-common.mjs accepted only apps/desktop/build/native-deps
as a generated destination in the tree, so every source build with an
explicit arch died before staging:

  Error: Output must be outside source or in a supported generated
  destination: ...\apps\desktop\build\native-deps-win32-x64

Seen on blacksmith-32vcpu-windows-2025 packaging the Desktop for the W3
attach lane (run 39053936919, `npm run builder -- --dir --publish never
--win --x64`). The guard now also accepts exactly
native-deps-(darwin|linux|win32)-(x64|arm64) under apps/desktop/build.
A nested path below one, or another platform/arch, is still refused.

Red -> green: run-electron-builder.test.mjs "source multiarch ..." now
asserts each per-target --out it hands stage-native-deps.mjs passes
productOutput (red on main: "Output must be outside source ..." for
native-deps-darwin-x64). product-builders.test.mjs gains the accepted and
refused per-target names (10 passed).

Refs #134889 (W3)
Refs #140337
2026-10-17 11:33:15 -07:00
Mikhail Tarasov c1f19acb7f fix(tui): ship runtime Python helpers with the built product
(cherry picked from commit 699555cebc116c50d8fab0d7d0e0dc604840bca6)
2026-10-16 01:54:58 -07:00
ethernet db4a6a9e26 fix(ci): packaged-update acceptance retries a download whose connection resets
bundle-inputs.mjs downloaded the ~100 MB package with one fetch. The CDN
reset the connection mid-body (`TypeError: terminated`, ECONNRESET) and the
Windows arm64 acceptance leg of Stable release rc.1-v0.22.1 died on a
network blip. A reset or timeout now retries the whole download up to 3
times; an HTTP error or a wrong digest is a real defect and still fails
at once. The manifest fetch gets the same treatment.
2026-10-14 11:03:26 -04:00
ethernet cf3f423c3e Merge pull request #138028 , fixes for bundles
fix(pm): `hermes update` no longer aborts on plugin pyprojects with no project version (salvage #122302)
2026-10-14 00:07:01 -04:00
ethernet f57d12ebd4 fix(deps): land source-map-js 1.2.2 and sync the workspace pins to the overrides
source-map-js 1.2.2 clears the last npm-audit finding in web and ui-tui
(GHSA-68fv-2mgg-jv7q). It was published 2026-09-30, inside min-release-age=14,
so .npmrc excludes it by name like the other advisory fixes, with a note to
remove the line once it is older than two weeks.

The override alone does not move an existing lock entry: package.json said
1.2.2 while package-lock.json still resolved 1.2.1 until `npm update
source-map-js --package-lock-only`. The lock now resolves 1.2.2 and a relock
is a no-op.

apps/desktop and tests-js still declared yaml 2.8.1 and vitest 4.1.10, which
the new overrides silently replaced; their declared pins now match the
overrides and the lock's workspace entries.

Doctor audit counts on this tree: root 0, web 0, ui-tui 0.
2026-10-13 22:51:07 -04:00
Teknium 5c0b7024e0 fix(install-e2e): install and update journeys record first-run onboarding as done
Since #137522 and #137807 a fresh desktop install opens the first-run
onboarding chat. The install/update E2E desktop chat smoke then sent its
checkpoint prompt into that chat instead of a normal one, so the mock
never received it ("The mock must receive this checkpoint prompt after
the send"), and on the Windows GUI installer path the installer-launched
app was mid onboarding turn when the journey closed it, so it did not exit
within 30 s. Seen on v0.22.0 attempt rc.1 in three Windows
desktop-installer@latest (HEAD -> NEXT) legs.

The mock provider writer now sets onboarding.run: false in the root
config, and the Windows GUI install phase seeds the same key before the
installer launches the app. The journeys test install and update, not
onboarding (which has its own desktop e2e spec).
2026-10-13 15:07:03 -07:00
alt-glitch 303b70f75b chore(release): retire HERMES_GUEST_ONBOARDING from bundles, build tooling and the updater
The Microsoft Store variant forced HERMES_GUEST_ONBOARDING=1 into its bundle env,
and commit/channel builds could bake it with --bundle-env. Nothing reads it now.

- scripts/bundles/desktop_inputs.py: the Store bundle env is just the request's.
- bundle_env.py / bundle-env.mjs: the key is no longer allowed for new builds.
- channel-protocol.ts: the key moves to RETIRED_BUNDLE_ENV_KEYS, so preview
  manifests already published with it still decode (the HERMES_SKIP_INTRO
  precedent).
- BUILDING.md example uses HERMES_PREVIEW_FULL_CONNECTORS.
- Build tests use another allowed key as their sample; both validators reject the
  retired key and the decoder drops it.
- msix-virtualization.test.mjs: every generated MSIX manifest (store, release,
  canary, channel) keeps filesystem and registry write virtualisation disabled,
  and the builder config declares unvirtualizedResources. The machine marker and
  the shared %LOCALAPPDATA%\hermes root depend on that.
2026-10-14 00:32:21 +05:30
ethernet a174341a89 fix(install-e2e): chat smoke waits for the app to show the mock model before sending
The v2026.7.1 desktop keeps the composer's model as renderer state and ships
it as the session override on session.create. waitForChatReady only proves the
composer is interactable, so a send that beat the configured model's load went
to the app's default (openrouter/claude-opus-4.6), got a 401, and the mock never
saw the prompt: "The mock must receive this checkpoint prompt after the send".
rc.12-v0.21.7 hit it in 2 of the 5 macOS dmg legs on that baseline.

The smoke now waits (30s cap) for the mock model's name to appear in the page
text. Apps prettify the id ("Mock Model"), so case and separators are folded,
and no selector is assumed. An app that never renders the name proceeds and
the mock-received assertion still decides; the renderer log records it.
2026-10-11 14:54:08 -04:00
kshitijk4poor bc0758c393 fix(desktop): a renderer built under other VITE_*/NODE_ENV settings is never reused
Vite inlines every VITE_* variable into the renderer and NODE_ENV selects its mode, but the
desktop receipt recorded only sources and prepared inputs. After `VITE_PERF_PROBE=1 npm run
build`, the next plain build reused (and productCurrent certified) the renderer that still
carries the perf probe; a NODE_ENV change had the same gap.

The desktop build inputs now record the renderer environment (VITE_* plus NODE_ENV, unset
read as vite's production default), so both rendererCurrent and productCurrent require it to
match. Reported by Greptile on #135092.
2026-10-08 15:20:58 -07:00