Commit Graph

8021 Commits

Author SHA1 Message Date
Teknium
9d05e7ff92 test(desktop): model settings tests clear their toast timers before jsdom tears down
The main-model apply test raises a success toast, which arms a 5 s
auto-dismiss timer. Nothing cleared it, so when it fired after the file's
jsdom window was gone, vitest reported "window is not defined" from
dismissNotification as an unhandled error and failed JS & TS checks with
every test green. afterEach now calls clearNotifications().
2026-10-08 15:20:58 -07:00
kshitijk4poor
d2cbfcf7db test(desktop): the React Compiler cache is its own module with focused tests
The cache lived inline in vite.config.ts, and the renderer builder tests use a bare fixture
config, so nothing exercised it: a broken warm path would still pass every test.

Move it to scripts/compiler-cache.mjs (behaviour unchanged; toolchain files and cache root are
now parameters, and an existing closeBundle hook is chained instead of replaced) and test hit
vs fresh output, toolchain/NODE_ENV generations, corrupt entries, pruning, incremental (watch)
rebuilds and the dev-server bypass. A cold and warm production build of the real app stay
byte-identical. Reported by Greptile on #135092.
2026-10-08 15:20:58 -07:00
kshitijk4poor
1f6d8b6ba8 fix(desktop): a node-pty layout without its macOS spawn-helper is an incomplete native tree
nativeTreeComplete decides whether a later build may skip restaging, but never looked at
node-pty. A partially extracted package with pty.node and no spawn-helper was recorded
complete; repairing the installed package without a lockfile change then left the staged
copy broken, and every later build shipped a terminal that fails on its first spawn.

Check the layout node-pty itself loads (build/Release, build/Debug, then prebuilds, as in
lib/utils.js) for a binding, and on macOS for the spawn-helper beside it (lib/unixTerminal.js).
Reported by Greptile on #135092.
2026-10-08 15:20:58 -07:00
kshitijk4poor
27e0f189ac fix(desktop): the React Compiler cache serves production builds only, keyed by NODE_ENV
The compiler emits HMR cache-reset code when NODE_ENV is development, and the cache key ignored it, so a dev-server session could seed entries a later production build reused. A dev server also wrote one entry per save with no build to prune them. The cache now wraps only 'vite build', NODE_ENV joins the toolchain key, and entries drop the map's sourcesContent (the module text the key already hashes; restored on a hit): 22 MB -> 15 MB. Verified: warm == cold byte-identical without sourcemaps; with --sourcemap two cold builds already differ in one map's __VITE_ASSET__ placeholder order, so that diff is vite's, not the cache's.
2026-10-08 15:20:58 -07:00
kshitijk4poor
3fc37481eb refactor(desktop): one receipt reader, one output list, no native double copy on renderer reuse
rendererCurrent and productCurrent share hostReceipt() for the schema/product/host checks. The stamped outputs come from bundleElectronMain's own electronOutputs list, so a renamed or added bundle can never be copied stale from the previous dist. The reuse copy skips node_modules/ and native/, which copyNativeTree recopies from nativeDeps right after, and a --typecheck build never reuses a renderer that skipped tsc.
2026-10-08 15:20:58 -07:00
kshitijk4poor
136a4282bb fix(desktop): an incomplete native tree restages on every build
Skipping stage-native-deps on an admitted receipt kept a tree staged without a piece forever:
get-windows (or its win32 x64 binding, which only the native installer inside staging can fetch),
the macOS get-windows helper, or the host HUD helper (no X11 toolchain on Linux). The receipt
identity cannot see the host fix that would complete it.

The receipt now records `degraded` from the staged tree itself (nativeTreeComplete: every piece
this host can produce for the target is present), and build.mjs reuses only a complete one
(nativeInputsComplete). Gaps no host fix closes — win32-arm64 has no get-windows prebuild, a
cross-target pack never builds the helpers — do not count. Judging the tree rather than each
builder's return value covers soft-fail paths that still return a path (the win32 binding).
2026-10-08 15:20:58 -07:00
kshitijk4poor
97260490e1 perf(desktop): skip restaging native inputs whose receipt still matches 2026-10-08 15:20:58 -07:00
kshitijk4poor
6232135eca perf(desktop): cache the React Compiler pass by content, so an update recompiles only the files it changed 2026-10-08 15:20:58 -07:00
kshitijk4poor
1d41d9f610 fix(desktop): native helpers build byte-identical, so the prepared native input stays comparable 2026-10-08 15:20:58 -07:00
ethernet
aac3440bac fix(desktop): build-bundle cleans under the lock, restores the shell, never signs
Review findings on the local build scripts:

- Cleanup ran before desktop.py took the checkout lock, so a second run deleted
  the outputs of a build that was still going. desktop.py gains --clean, which
  removes this checkout's outputs after build_lock is held. Both scripts pass it
  instead of deleting files themselves.
- build-bundle.ps1 left RELEASE_CLAIM_* and HERMES_PAYLOAD_* in the caller's
  session. It now saves those variables and restores them in the finally block.
- The scripts said "unsigned" but inherited signing credentials. The ps1 removes
  AZURE_* for the build. The sh removes CSC_* and APPLE_* and sets
  CSC_IDENTITY_AUTO_DISCOVERY=false, the switch the desktop rebuild command uses.
  The wording now says what is ignored.
- The Store bundle step needs the claim timestamp, and the script deletes the
  claim. The ps1 prints it as HERMES_RELEASE_EPOCH, and BUILDING.md documents it.
2026-10-08 18:02:26 -04:00
ethernet
57949d6630 feat(desktop): build-bundle scripts for local test bundles
scripts/build-bundle.sh (macOS, Linux) and scripts/build-bundle.ps1 (Windows)
build the checked-out commit through scripts/bundles/desktop.py --commit. That
path needs no tag and no push.

Both scripts refuse a dirty tree before any work starts. Both delete the
previous build output first. The staged Windows payload is read-only, so a
plain delete in a reused checkout fails with WinError 5.

build-bundle.ps1 -Store builds the Microsoft Store MSIX. The driver admits
Store packaging only for a stable tag. The script makes a local claim tag
rc.<N>-vX.Y.Z for the next patch after the newest published stable tag. It
counts local and remote attempt refs and starts at 900, so it cannot collide
with a real attempt. A finally block deletes the tag. The script never pushes it.
2026-10-08 18:02:26 -04:00
Maciej Lisiewski
d76cce4f11 ci: apply all automatic linter fixes available 2026-10-08 17:22:54 +02:00
teknium1
8f463316fd feat(plugins): one name resolver for every verb; one switch turns a unified package on
- hermes_cli: _find_plugin_entry is the single resolver (key / manifest name / unique leaf /
  catalog name via the installer-owned record). enable/disable/show/capabilities/update/remove,
  the dashboard and plugins.manage settings all go through it, so the catalog name you
  installed with keeps working (17 catalog entries install under another manifest name).
  _catalog_install_on_disk reuses the same catalog lookup.
- Desktop hub: join agent rows to desktop halves by package folder (what Electron stamps),
  not manifest name, so a self-cloned package is one row instead of two.
- Desktop: turning a unified package on (install dialog 'Enable after install' or the Agent
  switch) also turns its opt-in desktop half on, unless the user switched it off. Off stays
  per half. Capability consent is untouched.
- docs: plugin-catalog page.
2026-10-08 04:12:02 -07:00
teknium1
dacb06ea93 feat(plugins): show on_session_finalize return messages to the user
A plugin's on_session_finalize hook can now return a str or {"message": str}
and Hermes shows it at the session boundary without starting a turn or
rewriting the model reply:

- CLI /new: printed before the new session; CLI quit: printed above the
  exit summary; one-shot -q/-z: printed to stderr (stdout stays the answer)
- TUI: session.close returns the messages; /new shows them as system lines
- Gateway /new and shutdown: sent to the owning chat via the adapter, while
  adapters are still connected

on_session_end stays observer-only: it fires after every turn, not at a
session boundary. Gateway finalize dispatch moves to
gateway/run_shutdown_session_end.py.
2026-10-08 04:11:24 -07:00
teknium1
cc1500a071 fix(desktop): a linked plugin checkout drives its desktop half; doctor flags a stale copy
The Desktop loads a unified package's desktop half from the copy in
desktop-plugins/<name>/, not from the package. Editing the source shipped
nothing and nothing said so:

- a package symlinked into plugins/ (the in-place dev layout the Python
  loader already follows) was never copied at all — Dirent.isDirectory()
  is false for a symlink;
- an installed package edited in place stayed stale until Rescan/restart.

Electron now follows symlinked package dirs, marks their copy linked, and
re-syncs it on any byte difference. The renderer watches a linked copy's
source plugin.js and, on save, runs the reconcile and hot-reloads. Catalog
and git installs keep the mtime rule. `hermes plugins doctor` warns when
the copy differs from the source and says how to refresh it.
2026-10-08 04:07:30 -07:00
Teknium
0240fa4a84 fix(release): read the Hermes version line in the Nix check and accept rc.N attempt refs
Two stable-release identity checks broke on the first pipeline cuts of v0.21.6:

- nix.yml "Verify release package runtime identity" took the LAST token of the
  whole `hermes --version` output. That output ends with the OpenAI SDK line, so
  the check compared 0.21.6 against 2.24.0 and failed every release. Parse the
  first line ("Hermes Agent v<version> ...") instead.
- msix-shared.mjs releaseEpoch() still required the retired `vX.Y.Z-rc` claim
  name. Attempt refs are `rc.<N>-vX.Y.Z` (versioning.parse_attempt_ref), so the
  Windows universal bundle threw "Stable claim tag differs from its payload tag".
  Accept exactly `rc.<N>-<tag>`; a different version (rc.3-v0.27.10 for v0.27.1)
  still refuses.
2026-10-08 01:58:48 -07:00
Teknium
167e9fdb84 fix(desktop): never idle-reap a local profile backend (#134942)
* fix(desktop): never idle-reap a local profile backend

Support f502bc6f: with a remote primary, a "This device" profile's local
`hermes serve` child was SIGTERM'd by the pool idle reaper after 60 min with
nothing streamed (the #105239 pinned-tier TTL), leaving "This device ·
Backend offline" while the user still had the chat open.

A local backend runs its profile's cron jobs and bot chats with nobody
watching, so idleness is not a reason to kill it. The idle reaper now only
reclaims connection descriptors (remote routes: no child, no slot). Local
children are still bounded by the maxBackends slot cap and foreground
reclaim (activeTurn vetoed), unchanged. Removes POOL_PINNED_IDLE_MS.

The Backend Idle Timeout setting description now says it applies to remote
connections only.

E2E no-idle-reap.spec.ts: a pooled local profile backend survives two reaper
ticks past both idle windows. Fails on main (backend reaped), passes here.

* chore(desktop): drop the dead pinned-tier idle policy (retireIdle, lastStreamedAt)

Greptile P2 on #134942: with local children no longer idle-reaped, retireIdle
has no production caller and lastStreamedAt no reader. Removes both, the stamp
write in touchPoolBackend, and the pinned-tier test. The shared retirement
safeguards stay covered through evictTo (identity, admission permit, activeTurn
veto).
2026-10-08 00:54:37 -07:00
ethernet
05d5d7e537 fix(desktop): tag every line of a multi-line waiter script error
PowerShell errors span several lines (message, location, details). The
script tagged only the first physical line with the attempt, so the
parent's attempt filter kept that line and dropped the rest. Write-WaiterLog
now tags each non-empty line.
2026-10-08 00:41:22 -04:00
ethernet
ddde33122d fix(desktop): make the relaunch waiter's script log per-attempt, UTF-8 and bounded
Review follow-up to #134911, which merged before these landed.

- The script log is shared by every attempt, so a failure report could
  quote another attempt's lines. Each line now carries the stage dir
  name and waiterLogTail keeps only the current attempt's.
- Add-Content writes the Windows code page by default while the reader
  decodes UTF-8, which mangles localized errors and non-ASCII paths. The
  script now appends explicit UTF-8 without a BOM.
- waiterLogTail read the whole append-only file before slicing. It now
  reads at most the last 16KB, and the script restarts the log once it
  passes 64KB so it cannot grow without bound.
2026-10-08 00:33:33 -04:00
ethernet
bdb8f92f3b fix(desktop): log why the MSIX relaunch waiter failed to start
startRelaunchWaiter returned undefined for four different failures
(staging dir, script copy, PowerShell spawn, missed handshake), and the
detached script runs with stdio ignored. The Store updater then showed
only "Could not register automatic relaunch" and desktop.log had no
reason, so a user report could not be told apart.

Each failure path now writes its cause to desktop.log (error text,
paths, exit code or signal, elapsed ms). The script appends its own
steps and exit reason to %TEMP%\hermes-relaunch-waiter.log, which
survives the stage dir it deletes on exit, and a failed handshake
includes that file's tail in desktop.log.

The two waiter call sites shared the same options block; they now go
through one startUpdateRelaunchWaiter so the log hook cannot be
forgotten at one of them.
2026-10-08 00:22:46 -04:00
Brooklyn Nicholson
08165d5893 feat(desktop): the dimmed composer comes back after 5s without scrolling 2026-10-07 21:08:42 -05:00
Brooklyn Nicholson
436a6e130e fix(desktop): the scrolled-up composer comes back on hover or focus anywhere in the dock 2026-10-07 21:08:42 -05:00
hermes-seaeye[bot]
e675225af2 fmt(js): npm run fix on merge (#134819)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-07 23:35:01 +00:00
ethernet
bc2e4d3773 Merge pull request #134209 from NousResearch/sid/onboarding
feat(onboarding): first-run setup chat
2026-10-07 19:17:15 -04:00
Austin Pickett
6869d009c9 fix(desktop): the link-title window cannot become visible
On Windows Desktop the hidden tier-2 link-title BrowserWindow has still
painted a blank top-level frame over the chat during browser-tool turns
(#64867). Encode the invisibility contract in the launch options —
off-screen bounds, skipTaskbar, focusable: false — and belt-and-suspenders
it with setOpacity(0) + hide() after creation, best-effort like the audio
mute.

Salvaged from #66523 (thanks @stantheman0128).
2026-10-07 17:13:33 -04:00
Austin Pickett
649d6c0391 test(desktop): fold the MEDIA pdf card case into the existing media suite 2026-10-07 17:05:12 -04:00
Austin Pickett
dbbc8d7947 test(desktop): pin the preview height clamp's viewport growth and ceiling 2026-10-07 17:05:12 -04:00
Austin Pickett
7b9ce36a21 fix(desktop): widen the inline image preview envelope so stacked image sets stay viewable 2026-10-07 17:05:12 -04:00
Austin Pickett
39a01d069d fix(model-picker): keep custom provider rows and the current model honest across switches
Three defects in one class, backend + renderer:

- _lap_bare_custom_row only built the bare `model.provider: custom` row
  while the session was ON it; switching away made it vanish and the
  unconfigured-canonical fallback substituted a 0-model placeholder
  (#59702). Recover the endpoint from the config.yaml `model:` block when
  the caller's current-provider slice points elsewhere; is_current is then
  true only when that slice points back at the URL, and the live probe
  stays gated on refresh / probe-current.

- The Desktop Settings model page kept the previous provider's model
  selected after a provider switch (withActive() painted it as still
  selectable, and Apply would pin the new provider to the old model),
  and a named custom_providers entry the user just switched to showed 0
  models because the normal non-refresh options load probes only the
  CURRENT custom provider (#59063). A user-driven switch now clears the
  model and fires one refresh-scoped options fetch for an empty
  configured endpoint.

- groupModels derived model families solely from the provider catalog;
  an OpenRouter current model the catalog omits left the picker with no
  active-model row (#57534). Prepend the current model when the catalog
  lacks it.

Tests: red on base for the bare-custom regression, the settings switch,
and the missing-current-model row; green with the fix.
2026-10-07 17:04:44 -04:00
hermes-seaeye[bot]
3090ef7731 fmt(js): npm run fix on merge (#134737)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-07 20:26:07 +00:00
Austin Pickett
3adc457de5 Merge remote-tracking branch 'origin/main' into sid/onboarding
Conflicts, all in apps/desktop/src/i18n:
- de/en/es/fr/zh.ts, types.ts: import blocks only. Main added the
  *_projects section split, this branch the *_onboarding one; kept both.
- en_shared_metrics, zh_shared_metrics, ar_common, zh-hant (usage-stats
  consent copy): kept this branch's rewritten copy. Main's only change on
  its side since the merge base was a prettier reflow of
  collectedReliability (2943ee6f19), which the branch's rewrite replaces;
  types_shared_metrics on HEAD already matches the branch's keys.

Follow-through: main's Undo-hide toast (sidebar.projects.hiddenFromSidebar /
undoHide) arrived without ar or zh-hant strings, which this branch's
overlay-completeness test rejects. Added both to ar_chrome / zh-hant_chrome
and regenerated locales/_keys.desktop.json.
2026-10-07 16:16:23 -04:00
Austin Pickett
354724fb08 style(desktop): sort the projects-sibling imports per perfectionist 2026-10-07 16:08:56 -04:00
Austin Pickett
bf8cf34c50 refactor(desktop): move sidebar projects copy into per-locale siblings for #73091
The undo-hide toast strings pushed 8 locale catalog files past their
main-pinned FILE_LINES caps (they may only go down). Offset the growth
the AGENTS.md way: extract the existing sidebar.projects block from each
over-cap file (en/de/es/fr/ja/ru/zh/types) into a topical sibling
<lang>_projects.ts following the <lang>_boot / <lang>_model_menu
convention. No key changes; locales/_keys.desktop.json verified unchanged.
No behaviour change — pure code move.
2026-10-07 16:08:56 -04:00
Austin Pickett
ec60e9a6d8 test(desktop): store-level dismiss/restore round-trip for auto projects
Adds a layout-store test beside the existing menu test: dismiss hides
the row, restore brings it back, restore is idempotent. Also drops an
unused helper the salvaged test file left behind.
2026-10-07 16:08:56 -04:00
Austin Pickett
734c35bbc7 fix(desktop): translate the undo-hide toast strings (de/es/fr/ru) for #73091
The salvaged fix only shipped en/ja/zh; the other bundled locales fell
back to English. Adds hiddenFromSidebar/undoHide for de, es, fr, ru.
2026-10-07 16:08:56 -04:00
PRATHAMESH75
3b2a6b6e1a fix(desktop): make auto-project Hide from sidebar reversible via an Undo toast (#73091)
Hiding an auto-derived project only appended its id to the persisted
$dismissedAutoProjectIds with no restore path, so an accidental Hide from
sidebar was permanent: the toast carried no Undo, ⌘Z was unwired, and there
was no other control to bring the row back.

Add restoreAutoProject(id) mirroring restoreWorktree, and fire a success
toast with an 8s Undo action from removeAuto. The id is captured before the
dismiss so Undo still restores after the row unmounts / scope exits.
2026-10-07 16:08:56 -04:00
unsupportedpastels
a63ffb3648 test(desktop): name the default-zoom test for what it asserts 2026-10-07 14:18:18 -05:00
unsupportedpastels
75dfe26cea fix(desktop): default UI Scale to 100% and keep chat text at 110%
Fresh installs opened at 90% window zoom, and View > Actual Size (Ctrl/Cmd+0) reset to 90% rather than actual size. Ship 100% as the default UI Scale; Chat Text Size keeps its 110% default.
2026-10-07 14:18:18 -05:00
Brooklyn Nicholson
db48af245a fix(desktop): splice fresh-thread room context verbatim
String.replace with a string replacement expands $&, $' and $` from
another member's message against the delta header, so a crafted sibling
line could drop its own tail and re-insert the instruction boundary inside
the background block. Use a replacer function, name the delta header and
the 20-line cap, and say that room scoping is the caller's job.
2026-10-07 14:15:49 -05:00
Paul Larche
d8dc96784b fix(desktop): give fresh group threads room context 2026-10-07 14:15:49 -05:00
Brooklyn Nicholson
6fa88c19ac fix(desktop): lint missing-profile error and move its tests to a sibling file
Keeps remote-lifecycle.test.ts under its code-health line cap and clears
the curly/no-useless-escape errors from check:lint.
2026-10-07 13:31:31 -05:00
Yuan Li
071842a869 fix(desktop): explain missing remote profiles 2026-10-07 13:31:31 -05:00
ethernet
2943ee6f19 fix(ts): npm run fix 2026-10-07 13:42:42 -04:00
tank321
489c1ac298 test(desktop): cover empty cloud agent status 2026-10-07 11:19:11 -04:00
tank321
1fac4e4d63 fix(desktop): hide unknown cloud agent status
Omit the gateway status description when cloud discovery cannot provide a meaningful state, while preserving valid status labels. Add renderer coverage for both unknown and known states.
2026-10-07 11:19:11 -04:00
alt-glitch
c4ba8b1e8e i18n(desktop): translate cron.queuedRun in every overlay
Main added the Run History 'Queued run' label (44c1ad7f84) in English only;
this branch's overlay-completeness ratchet fails on a new untranslated key.
2026-10-07 20:38:07 +05:30
alt-glitch
db02737cf3 Merge remote-tracking branch 'origin/main' into sid/onboarding 2026-10-07 20:37:24 +05:30
alt-glitch
e4a4f1b4f7 fix(desktop): a setup card's typed-pick callback keeps a stable identity
The name card crashed the setup chat on a real Windows bundle (React #185,
maximum update depth): the card wrote its stage callback into
$setupChooseStages, which it also reads, and the callback depended on
rowLabels, a new object every render. 40512011b6 stopped the rows from
changing identity; this removes the dependency itself, so the callback
changes only with kind, mode, multi-select or the request. The labels
effect already stages the row names the callback used to rewrite.

The new test renders the real card: red on 17646902e1 (the crashing
build), green here and with either guard alone.
2026-10-07 19:53:27 +05:30
Austin Pickett
f7b6b4ec89 refactor(desktop-i18n): move billingBlock copy into en_billing sibling
Offset the cron queuedRun string growth in the over-cap en.ts/types.ts
facades per the code health ratchet: moved code keeps its cap.
2026-10-07 10:18:52 -04:00
Austin Pickett
44c1ad7f84 fix(desktop): show a queued cron run row immediately after trigger
Run History only rendered sessions the backend had already materialized,
so an accepted Trigger now was invisible for the tens of seconds the
scheduler took to create the run session (#70826). The queued row is
painted from the click, settles when Run History observes a run that
started after it (any load — a fast 1s poll while queued, the cron.changed
broadcast on event-capable backends, or the visibility poll), and drops
at a bounded 90s timeout so a never-materializing run cannot wedge the
button. A failed request drops the row immediately. Pause deliberately
does not settle it: the pause response only persists enabled=false and
never cancels an already-claimed execution.

Queued design (queued-run row + polling + settle semantics) originates in
#70840 by @nv-cho; this ports it onto current main's
createCronTriggerController wiring.

Fixes #70826
2026-10-07 10:18:52 -04:00