Commit Graph

43575 Commits

Author SHA1 Message Date
ethernet
86e01ba3ed fix(pm): a lazily installed extra swaps into the running process
ensure_import synced the extra into a new generation and then always
raised "restart Hermes to activate", so choosing a provider whose SDK is an
opt-in extra (anthropic, bedrock, ...) failed the first message in every
fresh install or worktree even though the install had succeeded.

adopt_selected() already knows when swapping a live process onto a new
generation is safe (it was on the generation selected before the sync, and
nothing it imported changed version). Call it after the sync; raise only
when adoption was refused, naming restart_needed()'s reason.
2026-09-24 23:44:25 -04:00
ethernet
5d39ddd28b feat(pm): say when this process must restart to load the selected generation
A process that could not adopt a newly published dependency generation keeps
importing the old one. restart_needed() names that case (and stays silent for
dev venvs, Nix and anything not booted from a PM generation, so no false
restart prompts); adopt_selected() lets callers move onto the selection before
loading new code. The test helpers publish real generations and make the test
process run from one.

(cherry picked from commit 978abe8ec4b852778b8c63bcefdf141db51bc703)
(cherry picked from commit 28be27334adc531db59bf37a02a64acaaf47a2ee)
2026-09-24 23:40:17 -04:00
ethernet
5e11975e2d feat(pm): adopt a newly selected generation inside the running process
After a plugin's runtime request is published, the process that asked should be
able to import the result without a restart, but only when nothing it already
loaded would change underneath it. Adopt when this process was on the
generation that was selected when the build started, and no loaded module's
file appears in the RECORD of a distribution whose version changed or
disappeared. Adopting swaps the site-packages entry, processes only the new
generation's new .pth files, rewrites PYTHONPATH/PATH for child processes, and
leases the new generation while keeping the old lease, since loaded packages
still resolve submodules from it.

(cherry picked from commit 478f945d9f36e94229f58b251642b7c9f3b9e781)
2026-09-24 23:40:16 -04:00
ethernet
39c0a39100 fix(providers): report why a lazy SDK install did not land
_get_anthropic_sdk() swallowed every ensure_import("anthropic") failure and
_require_sdk() then told the user to "Install it with: hermes pm install
--extra anthropic". PM often HAS installed it: sync_venv succeeds into a new
dependency environment that only activates at process boot, and
ensure_import raises "installed; restart Hermes". A lazy-install guard
("this process is not running from the install's dependency environment")
was flattened the same way. Users were told to install something that was
installed, or given a command that doesn't address the actual refusal.

Keep the import as the decider, but remember the InstallError and put its
text in the ImportError. bedrock_adapter._require_boto3 had the identical
shape; azure_identity_adapter already propagates str(exc) and is the model.
2026-09-24 23:37:27 -04:00
ethernet
61c06ca3ce Merge pull request #122161 from NousResearch/fix/never-activate-legacy-venv
PM never activates the pre-PM in-tree venv (gateway lost pydantic_core after update)
2026-09-24 23:07:03 -04:00
ethernet
6ce9ed4223 fix(update): key the early-spawn sync on currency, not on a commit
Under the updater's claim, sync whenever dependencies are not current
rather than only when nothing is committed. The tail's own children
and post-sync verification children are current and stay no-ops. A
stale generation still committed from the previous Python pin is the
same ABI trap as the pre-PM venv, and it now syncs too. If the sync
still leaves the tree out of date, raise instead of relaunching into
another sync.
2026-09-24 22:56:59 -04:00
ethernet
3a42f0fe10 fix(update): commit dependencies for processes the update spawns early
prepare_launch returned early for any process running under the
updater's own claim, so it would not re-run the completion tail. That
also covered processes the updater spawns before PM commits a
generation (a restarted gateway), which then booted with no
environment: previously on the pre-PM venv, now refused.

Under the updater's claim with nothing committed, sync the dependency
generation (carrying the legacy venv's extras, as the first sync
always has), skip the tail since that belongs to the updater, and
relaunch on the store Python. The relaunched process sees the commit
and returns early as before, so the no-recursion guard still holds.
2026-09-24 22:53:06 -04:00
ethernet
c821ecdd8f fix(pm): never activate the pre-PM in-tree venv
With nothing committed, activate_dependencies fell back to the in-tree
venv/.venv. After an update that venv was built for the old interpreter
(uv CPython 3.11) while the process ran PM's store Python 3.14, so every
compiled module in it was unloadable: the messaging gateway's Group Chat
worker died on `No module named 'pydantic_core._pydantic_core'` until PM
committed a generation ~40 minutes later and deleted the old venv.

committed_venv() returns the committed generation or a sealed payload's
environment, never the in-tree venv. Boot activation and child
activation environments use it. With nothing committed, a venv/Nix
interpreter keeps its own packages; PM's bare store Python refuses with
the repair remedy instead of running on inherited paths. selected_venv
keeps its contract because pre-PM updaters import it after the swap.
2026-09-24 22:48:35 -04:00
Gille
8623cd4a84 fix(desktop): keep slow plugin installs from reporting false failure (#121856) 2026-09-24 22:34:38 -04:00
ethernet
0b724b178e Merge pull request #122136 from NousResearch/fix/desktop-post-update-backend-start
Desktop no longer fails to start after an update (dead backend record wedged boot)
2026-09-24 22:23:14 -04:00
ethernet
c2b7fa19db fix(desktop): stop waiting 45s on a dead backend record after an update
A hard-killed backend leaves its spawn-ledger record and its published
session token behind. Since the attach path started adopting published
tokens (f1247d2e01), such a record passed the token rung and then had its
refused port polled for the whole 45s readiness budget. The renderer's boot
timeout is the same 45s and starts first, so it gave up just before main
fell through to spawning, and every Retry/Repair killed the fresh backend
and produced another dead record: the app never started after an update.

A ledger record is written only after its backend binds, so a refused
connection on one means the process is gone. Readiness gains an opt-in
`alreadyBound` that fails at once on ECONNREFUSED; the attach probe and the
attached-backend liveness monitor set it. Remote/SSH readiness keeps
polling, since a tunnel still coming up refuses legitimately.
2026-09-24 22:21:30 -04:00
ethernet
2cf676b37c Merge pull request #122127 from NousResearch/ethie/fix-web-ui-build-icons
test(web): drop the icons step from web build expectations
2026-09-24 22:20:04 -04:00
ethernet
d74470744b Merge pull request #122125 from NousResearch/fix/drop-stale-hindsight-extra
fix(pm): hermes update no longer fails on the stale hindsight extra (#122071, salvage #122092)
2026-09-24 22:16:49 -04:00
ethernet
3aa215fdc9 build: remove leftover references to the dropped hindsight extra
The extra removal left CI, the Docker image and the nix package still
requesting `hindsight`. Once the extra is gone, `--extra hindsight` and
extraDependencyGroups = [ "hindsight" ] ask for something that no longer
exists. Drop them the same way 73c598e319 originally did: remove it from
the CI extras lists, the Docker sealed-venv build and the nix default
groups, and point the nix examples/check at honcho. Also remove the
stray blank line left in the exclude-newer table.
2026-09-24 22:15:15 -04:00
fangliquan
425c5c1167 test(pm): exclude catalog Hindsight from legacy extra selection 2026-09-24 22:12:39 -04:00
fangliquan
285768fdfb fix(pm): drop stale Hindsight extra
Merge 27df3b8847 brought back the hermes-agent[hindsight] extra
(hindsight-client==0.6.1) that 73c598e319 removed. The catalog
Hindsight plugin now requires hindsight-client>=0.10.1,<1, so any
workspace containing it fails `uv lock`. Remove the extra, its
exclude-newer entry and its legacy-takeover mapping, and regenerate
uv.lock.

The workspace-member rename from the original PR is dropped here;
#122098 landed that half.

Salvaged from #122092.
2026-09-24 22:12:34 -04:00
ethernet
10907a9e17 test(web): drop the icons step from web build expectations
4b7229d612 commits the default-brand icons, so build_source_web no
longer runs generate-icons.mjs. It updated test_source_build.py but
not test_web_ui_build.py, which uses the same source_products fixture.
The web build tests on main now expect a step that never runs.
2026-09-24 22:11:12 -04:00
ethernet
996c252914 Merge pull request #122098 from liuhao1024/liuhao/cron-bugfix-122071
fix(pm): name virtual workspace members by their unique key
2026-09-24 22:11:02 -04:00
ethernet
f3fdee8788 Merge pull request #122101 from NousResearch/fix/record-msix-executable
fix(release): Windows release builds no longer fail recording the executable version
2026-09-24 22:10:00 -04:00
Austin Pickett
6ee0ae4d5e fix(desktop): open markdown preview links inside the app
Electron denies every target=_blank window, so a web link in a previewed
.md file looked clickable and did nothing; a #fragment link rode the
HashRouter and changed the app route instead of scrolling the note; a
relative link to a sibling note never opened it.

Links now take the same doors chat links take: web links render through
ExternalLink (in-app browser, Cmd/Ctrl for native), file links are wrapped
in the existing #preview/ hash before Streamdown's hardener sees them and
resolved against the note's directory by normalizeOrLocalPreviewTarget,
and headings get GitHub-style ids from a rehype plugin so a TOC click
scrolls within the pane. Slugs and lookups are NFC-normalized so a TOC
written on another editor still finds its heading.

Fixes #81055
Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
2026-09-24 22:00:20 -04:00
Austin Pickett
7fd7f33cc2 fix(desktop): collapse ../ in the renderer preview path fallback
A note linking ../other.md resolved to /notes/../other.md when the Electron
normalizer is unavailable, a path the fs bridge then rejects.
2026-09-24 22:00:20 -04:00
liuhao1024
574c5d940a docs(pm): note why tool.uv.package = true makes a member buildable
Review suggestion from #122098: spell out on the condition line that
uv package mode produces real build metadata, so such members keep
their declared name.
2026-09-25 09:37:18 +08:00
ethernet
3d7fc8fc03 Merge pull request #122095 from NousResearch/fix/desktop-ismain-eval
fix(desktop): isMain returns false when there is no entry script
2026-09-24 21:35:02 -04:00
ethernet
051febedfd fix(release): read the Windows executable name from the MSIX manifest
record() looked up stamp['identity']['windowsExecutableName'], a key no
stamp writer emits, so every Windows record on a runner resolved no
executable and failed. The built package's Application/@Executable
names the exe; match it in the unpacked dir only, which also skips
before-pack's .bak rollback copy.
2026-09-24 21:28:32 -04:00
liuhao1024
0fc90369a5 fix(pm): name virtual workspace members by their unique key
uv identifies a workspace member by its declared project name, so the
same plugin enabled in two profiles declares one name twice and the
dependency sync fails with 'Two workspace members are both named ...'.
Metadata-only members (no build backend) now carry the unique member
key in their name, exactly like manifest-only members already do. A
buildable member keeps the name it declares, since uv verifies it
against the package metadata its backend produces.
2026-09-25 09:24:18 +08:00
brooklyn!
71239b7003 fix(desktop): treat a missing plugin-decision door as bot mode on
Other plugin tests mock host without that door. A missing read is the
default, not a crash.
2026-09-24 20:23:18 -05:00
brooklyn!
7bde1dfe20 fix(desktop): read bot-mode state through the plugin SDK
Plugins cannot import the contrib store. host.pluginDecisions is the
read-only door, and absence still means the plugin default.
2026-09-24 20:23:18 -05:00
brooklyn!
ea3478d520 fix(desktop): do not dial the bot relay drain with nothing to deliver
The 30s drain opened a throwaway gateway socket even when Bot Mode was off or the route had no outbox work. With the push door, only a route that signaled bot_relay.outbox.pending is drained. Older shells without that door still poll. The background-scope reset from #119836 is unchanged.
2026-09-24 20:23:18 -05:00
ethernet
4920ca4160 fix(desktop): isMain returns false when there is no entry script
Importing a script from `node -e` leaves process.argv[1] undefined, and
pathToFileURL(undefined) throws on import. The bootstrap installer's
signing step imports batch-sign-binaries.mjs this way, and it crashed
when that pulled in sanitize-pe-signatures.mjs.
2026-09-24 21:22:18 -04:00
Hermes Agent
6c4a983e8d style(desktop): format session action hook 2026-09-24 20:22:11 -05:00
brooklyn!
f0d6d2151c fix(desktop): publish pending clarify before transcript hydration
An authoritative still-pending clarify was answerable only after
getLatestSessionMessages returned. Publish the snapshot tool-call row
in the same needsInput view update, and keep provenance checks from
hiding that row.
2026-09-24 20:22:11 -05:00
Hukla
f7a861379f fix(desktop): decode fetched link metadata by charset 2026-09-24 20:13:12 -05:00
Hermes Agent
f1247d2e01 fix(desktop): adopt a published session token on the post-update relaunch
The post-update relaunch refused a backend that had published a session token
and spawned another. Adopt the host rendezvous token when GET / withholds it.

The stale app.asar half is dropped: main now judges desktop freshness from the
compiler receipt written inside the packaged output (26c4e8b160), so a skipped
or failed rebuild no longer looks current.
2026-09-24 20:06:38 -05:00
Hermes Agent
f26825eb2d test(desktop): keep rebased checks green 2026-09-24 20:04:24 -05:00
brooklyn!
6391c3c7e4 fix(desktop): route omitted-profile messaging approvals to the list server
A Telegram row that omits profile is owned by the backend that served
the messaging list. Keep a non-primary connection id, send a primary-pool
list through that profile door, and do not guess primary or ambient when
the list server was never recorded.
2026-09-24 20:04:24 -05:00
brooklyn!
c4ff1d89f4 fix(desktop): settle a silent live turn and offer retry
A dead or interrupted turn left the thinking spinner up, including after
a partial assistant payload. When that session stops producing events,
force-settle it and stamp the existing retryable error card instead of
leaving the spinner spinning.
2026-09-24 20:04:19 -05:00
brooklyn!
d371e21d07 fix(desktop): Enter on a focused clarify choice reaches activateActive
Clicking a single-select choice leaves focus on the option button, and the
window keydown handler bailed for every button, so Enter never submitted the
staged answer. Exempt button[data-choice] and let activateActive handle Enter:
a staged single-select answer submits, a multi-select row toggles, and Continue
still confirms the set. Choice buttons do not call submitAnswer.

Fixes #92816

Co-authored-by: echohn <echohn@gmail.com>
2026-09-24 20:02:48 -05:00
brooklyn!
b609c745df fix(desktop): scope the config-record cache to the active gateway
The shared ['hermes-config-record'] slot survived a gateway switch, so a
settings save painted the previous machine's record and PUT it onto the
other config.yaml. Key that existing slot by $activeConnectionId.

Fixes #101640
2026-09-24 20:02:37 -05:00
brooklyn!
da77a7e2e8 fix(desktop): defer tray hide and log main-process stalls
Hiding inside the minimize handler wedges the Windows minimized flag, and
showInactive() restores a painted window that never takes input. Defer hide
until after that dispatch, skip a stale hide if the user already restored,
and activate with show()+focus() on Windows. Log main-process event-loop
stalls in desktop.log; renderer unresponsive handlers cannot see AppHangB1.

Reported in #119252.

Co-authored-by: finn763 <165816600+finn763@users.noreply.github.com>
Co-authored-by: KoNit-K <konit.block@protonmail.com>
2026-09-24 19:53:53 -05:00
brooklyn!
8b88c6b6c8 fix(desktop): speak each assistant turn once across id rewrite
Auto-speak keyed already-spoken on the assistant row id and ordinal.
Hydration rewrites the live id and tool-row folds move that ordinal, so
the same turn looked unspoken and was played again. A second
playSpeechText stopped the first clip. markSpoken also ran before a play
that never started, so the turn stayed silent.

Key the anchor on the user turn, which survives both. A later turn that
says the same thing is still spoken. Release the anchor when playback
never starts, and do not let a second start of the same turn stop the first.
2026-09-24 19:52:31 -05:00
Hermes Agent
b50bb77ec5 fix(desktop): preserve implicit default local route 2026-09-24 19:51:33 -05:00
brooklyn!
76460be82d fix(desktop): refuse remote-only profile spawns when the connection id is missing
An empty connection id is no longer rewritten to registry.primary, which dialed
another SSH host. A concrete remote-only profile, including default, is refused
on the forced-local branch instead of spawned. A profile open without an owner
route no longer stamps mode local when the live connection is remote.
2026-09-24 19:51:33 -05:00
brooklyn!
f86db25ef5 fix(desktop): refresh peer windows and refuse a stale multi-window submit (#65047)
A second Desktop window on the same stored session never saw the first
window's completed turn, and submitting from that stale transcript could
fork the session. Notify other windows to re-pull on turn completion, and
refuse composer and session-tile submit when the local transcript is behind
the authoritative latest page.

Co-authored-by: stantheman0128 <stanshih888@gmail.com>
2026-09-24 19:49:23 -05:00
brooklyn!
b81c5811b7 fix(tts): review the speak-stream ffmpeg lookup
ffmpeg is a system decoder, same as the other TTS sites. The new call
belongs on the resolution allowlist.
2026-09-24 19:28:05 -05:00
brooklyn!
20bcc9bd14 fix(tts): stream Edge speak-stream per sentence instead of whole-text fallback
Desktop speak-stream sent type=fallback whenever the provider had no
chunked PCM API. Edge is that case, so the client waited for the full
reply and POSTed it. Cut sentences with the existing sync TTS tool and
stream that PCM. Fallback stays the last resort when synthesis produces
no audio.

Refs #91997
2026-09-24 19:28:05 -05:00
Brooklyn Nicholson
434b1011a9 fix(desktop): aggregate session dot state per profile on the rail (#91710)
A profile that finished work (or blocked on input, or is still running)
while another profile was selected showed no indicator on its rail square
or dropdown row — the session-level unread/attention layers
($sessionDotStateById, the persisted per-profile unread markers, the
backend row.unread watermark) all existed, but the profile rail
subscribed to none of them.

Add $profileDotStateByScope: a per-(connection, profile) rollup of the
shared session dot state, derived in three passes:

  1. loaded chat/messaging rows claim their row-tagged scope
     (stalled/background fold into working, like the sidebar's buckets;
     cron rows stay excluded so a profile square is not a cron counter),
  2. unlisted live runtimes claim the scope their socket proved
     (runtimeSessionOwner; no proven owner claims nothing),
  3. persisted unread markers with no loaded row claim their profile's
     scope only when the owning gateway is unambiguous — two gateways
     sharing a profile name leave the bucket unpainted rather than
     guessing.

Priority: needs-input > working > unread, matching the session rank.

The busy->idle finish edge now passes the socket-proven owner profile
into markSessionUnreadFinished, so a background profile's finish can no
longer land in the ACTIVE profile's marker bucket (which would light
the wrong square). The reconnect parked-set becomes a Map so a later
confirm keeps the runtime id for that lookup.

The rail paints the rollup as a small dot on ProfileSquare, RestSquare,
ProfilePill and both dropdown rows, with every non-zero count in the
accessible name and tooltip ("writer, 1 unread session"). The active
profile's square stays clean — the workspace is homed there and its
rows are on screen in the sidebar below.
2026-09-24 19:27:41 -05:00
rainbowgits
189f051679 fix(desktop): show full-resolution attachments in sent bubble and lightbox
Attached images rendered at a 512px thumbnail in the live sent bubble and
click-to-zoom lightbox, only becoming sharp after a session reload rehydrated
the turn from disk. Route the in-flight bubble through the same DirectiveImage
path as a reloaded turn: a bounded thumbnail is painted inline (preserving the
deliberate anti-freeze cap) while the full-resolution file is handed to the
on-demand lightbox and download.

Fixes #93204
2026-09-24 19:27:41 -05:00
andyst-dev
83c8d1353b docs(artifacts): document shell-output scan false-positive budget + array-index drop (reviewer)
Review points:
1. Noisy shell stdout (curl -v, build logs) is kept from flooding the panel:
   every prose candidate passes through looksLikeArtifact, which requires a
   file/image extension or http(s)/data: scheme, and local file existence is
   resolved via the media ladder (artifactImageSrc -> resolveMediaDisplaySrc
   -> readFileDataUrl). Documented at the shell-output scan site.
2. Bare numeric array indices are dropped from the key path intentionally so
   array-of-results payloads (e.g. outputs.0.output) match via their real
   segments; noted that switching to exact-key matching would silently break
   those shapes.
2026-09-24 19:27:41 -05:00
andyst-dev
276f224742 fix(artifacts): index files produced by the terminal tool
Terminal results were never scanned for artifact references: 'terminal'
is not matched by ARTIFACT_PRODUCER_TOOL_RE, and its stdout lives under
the bare 'output' key which STRONG_TOOL_ARTIFACT_KEY_RE does not list.
Script-generated figures (matplotlib, ffmpeg, pandoc, ...) never showed
up in the Artifacts panel unless the assistant remembered to re-emit a
MEDIA: tag in prose.

Treat terminal as an artifact producer: scan its free-text result
(MEDIA tags, markdown refs, URLs, absolute paths) and accept
'output'/'stdout' as scannable keys for that tool only. Non-terminal
tools keep their existing strict key gate.

Fixes #92220
2026-09-24 19:27:41 -05:00
Jony
f588166691 fix(google-chat): avoid guessing auth failure cause 2026-09-24 19:27:41 -05:00