Commit Graph

21611 Commits

Author SHA1 Message Date
kshitijk4poor
b2483b89af fix(mcp): reload OAuth provider on first sight when no tokens in memory (#39551)
Seeding the disk-watch baseline on first observation also swallowed the
case where the process started before login: file absent, then an
external `hermes mcp login` writes it, and the provider never reloaded.
Only skip the reload when the provider already holds tokens in memory.
2026-09-25 14:27:51 +05:30
kshitijk4poor
3a14bb3506 test(mcp): first-observation 401 still refreshes in place (#39551) 2026-09-25 14:27:51 +05:30
LeonSGP43
374ca05387 fix(mcp): seed OAuth disk-watch baseline before reload
(cherry picked from commit 3106b7ad3f8f56e24bd247f50393b5114ac55f24)
2026-09-25 14:27:51 +05:30
kshitijk4poor
976782c646 test(api): seed run-stream fixtures with _RunStream (#25583)
_handle_run_events now calls stream.attach(); bare asyncio.Queue fixtures
500'd the idle keepalive test and kept a dead sweep fallback alive.
2026-09-25 14:27:23 +05:30
kshitijk4poor
017443e8d6 test(api): trim run SSE fan-out tests to two invariants (#25583)
Keep the concurrent-subscribers and reconnect-replay contracts from #69817;
drop the three tests that pin _RunStream internals (overflow bounds, sweep
bookkeeping, response-boundary transport cleanup).

Co-authored-by: LeonSGP43 <cine.dreamer.one@gmail.com>
2026-09-25 14:27:23 +05:30
kshitijk4poor
eea4419ddc fix(api_server): honour platforms.api_server.tool_progress_events for Chat Completions SSE (#12020)
Strict OpenAI clients reject the named hermes.tool.progress SSE frames. Setting
tool_progress_events: false under platforms.api_server (loaded into
PlatformConfig.extra by from_dict) now drops them; default stays on.
Reimplements the intent of #42640 against the adapter config actually read in
production. Overlaps #49069 (erikerosev).

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-09-25 14:27:23 +05:30
doniocode
10963689dc fix(api): sweep overflowed run SSE transports
(cherry picked from commit 6ac7f627265745db816163a4bc26fdbb7aca44e6)
2026-09-25 14:27:23 +05:30
doniocode
4937863e4d fix(api): bound and harden run SSE transports
(cherry picked from commit 11c1d792bbab68bf9f99b2080b6095d58082c0a7)
2026-09-25 14:27:23 +05:30
doniocode
52a2835136 fix(api): fan out and replay run SSE events
(cherry picked from commit 2cb4751f571c3985fac3ddb4199c48833fde940c)
2026-09-25 14:27:23 +05:30
kshitijk4poor
e42b61be43 fix(api_server): emit final_response on chat-completions SSE when no deltas streamed (#31449)
Recovery paths (guardrail halt, partial_stream_recovery) can return a
final_response without firing any content delta; /v1/chat/completions
streaming then closed with an empty body. Mirror _ResponsesStream.collect_result.

Co-authored-by: fmercurio <15571697+fmercurio@users.noreply.github.com>
2026-09-25 14:27:23 +05:30
kshitijk4poor
e62a47ab68 fix(curator): floor interval_hours at 1 and warn once per bad value
interval_hours <= 0 made should_run_now() true on every idle tick,
re-running the review pass each time. Route it through the same
floor-with-default helper as the day counts (renamed _bounded_count),
and log the fallback warning once per (key, value) since the dashboard
status endpoint polls these getters.
2026-09-25 14:08:03 +05:30
AhmetArif0
342c29250d fix(curator): bound automatic transition days like curator prune
get_stale_after_days()/get_archive_after_days() accepted any int from
curator.stale_after_days/archive_after_days. archive_after_days: 0 sets
archive_cutoff to now, so apply_automatic_transitions() (runs unconfirmed
on an idle tick, curator on by default) archives every skill with any past
activity on the next pass; a negative value builds a future cutoff. The
manual path already refuses the same value (_cmd_prune: "--days must be
>= 1"), and "0 disables" is the repo convention elsewhere.

Fix: a value < 1 falls back to the default with one warning naming the
key, the same bound _cmd_prune enforces. Same class of fix as b01b1c8b
(bound kanban gc retention so -N/0 cannot mass-delete).

(cherry picked from commit 6685565a8c5147c8d7c23602f371f60571c44074)
2026-09-25 14:08:03 +05:30
Hermes Agent
658e6c885d revert(agent): drop the no-op side-channel length-stop change
The reasoning half only touched a docstring, and its tests pinned main's
existing continuation (reasoning-off retry, then the 'No visible answer'
ceiling). Nothing changed, so the PR stays on the Windows close/stop fix.
2026-09-25 01:08:23 -05:00
brooklyn!
2c42fcb507 fix(agent): continue a length stop that only has side-channel reasoning
That stop is unfinished thought, not a finished thinking-budget abort.
Continuation still owns it, and the side channel is not the answer.
2026-09-25 01:08:23 -05:00
brooklyn!
8b17394cc6 fix: abort reasoning-field length stops and surface close taskkill failures
finish_reason=length with empty visible content and a non-empty reasoning
or reasoning_content field uses the existing thinking-budget abort. No
model id is consulted. Empty content with no side channel still continues.

Desktop close/stop no longer discards Windows taskkill failures. After the
same tree-kill, owned PIDs are inventoried and only unheld gateway locks
are cleared.
2026-09-25 01:08:23 -05:00
ethernet
f9f235ed1a fix(pm): never adopt another install's venv for a checkout
project_venv_dir() fell back to the running interpreter's venv whenever
hermes_constants was loaded from the checkout. Where the code was loaded
from says nothing about who owns the interpreter: with
`PYTHONPATH=<dev checkout> <app install>/venv/bin/python -m hermes_cli.main`
(a shell wrapper around a dev tree), PROJECT_ROOT is the dev checkout but
the venv is the Desktop install's. base_venv() then returned the app's
venv, `hermes update` synced the dev tree into it, and the Desktop
install's venv became an editable install of the dev checkout. From then
on the Desktop shell tracked ~/.hermes/hermes-agent while its backend and
its handed-off `hermes update` ran and pulled the dev tree, so every
in-app update "succeeded" without moving the install. The same misread
let running_from_selected_environment() accept lazy extras into that
venv.

Only fall back to the running venv when its own hermes-agent install
records this checkout in direct_url.json, which every install of a
checkout into a venv writes (installers, uv sync). Otherwise the
checkout gets its own environment, as it did before 4f6c04cd07. The
#116148 out-of-tree layout (a venv installed from the checkout) still
resolves to the running interpreter.
2026-09-25 01:17:12 -04:00
ethernet
138e33d51f Merge pull request #122244 from NousResearch/fix/restore-pm-merge-drops-current
Restore behavior lost during PM integration
2026-09-25 00:36:50 -04:00
ethernet
067934a106 Merge pull request #122093 from benbarclay/fix/pm-agent-browser-exec-bit
fix(pm): make the staged agent-browser binary executable
2026-09-25 00:35:56 -04:00
ethernet
af3299a22c fix(gateway): don't ask the Windows login question when stdout is captured
#122234 gave Desktop update steps NUL stdin, but the hand-off script
that runs is the one from the checkout being updated FROM. Every update
that starts on an older commit still runs the old script, which gives
steps the hand-off console as stdin and captures their stdout until
they exit. Only the steps after `hermes update` run new code.

So `gateway start --all` from the new checkout still saw an interactive
console, asked "Install it now so the gateway starts on login?" into
the captured stdout, and waited forever. The update never relaunched.

start() now asks only when stdout is a terminal too. Nobody can answer
a question they cannot see.
2026-09-25 00:33:42 -04:00
ethernet
2ef41d2b58 Merge pull request #122103 from NousResearch/ethie/pm-evict-incompatible-plugins
fix(pm): hermes update disables plugins that no longer fit instead of failing
2026-09-25 00:08:10 -04:00
ethernet
e71f4dd4ac Merge pull request #122221 from NousResearch/fix/anthropic-lazy-install
Opt-in provider SDKs (anthropic, bedrock) install and swap into the running process on first use
2026-09-25 00:05:51 -04:00
ethernet
a75d8b420e Merge pull request #122234 from NousResearch/fix/handoff-noninteractive-steps
fix(desktop-update): Windows update steps get NUL stdin; installer asks gateway questions once
2026-09-25 00:05:41 -04:00
ethernet
6834b4c634 Merge pull request #122208 from NousResearch/fix/source-version-release-tags
fix(update): source installs report the current release after updating
2026-09-25 00:04:38 -04:00
ethernet
e39ba502db test(desktop-update): read the console self-test report as UTF-16
Windows PowerShell 5.1 `*>` writes UTF-16LE with a BOM. Decoding it as
UTF-8 hid the PASS line even though the self-test exited 0.
2026-09-25 00:02:54 -04:00
ethernet
0978aca962 fix(pm): retry a plugin fetch failure once, then disable it
Sitting a plugin out after a fetch failure left the recorded stamp stale on
purpose, so every launch resynced, and the spawned-process guard in
prepare_launch raised "dependency sync left this install out of date".
One retry covers a blip; after that the plugin is disabled with the reason,
and hermes plugins enable restores it. Only requires_hermes still sits out,
which boot skips the same way.
2026-09-25 00:00:09 -04:00
ethernet
10d066ffd8 fix(pm): keep the manifest import per plugin; pin the version in the sit-out test
enabled_member_dirs imported hermes_cli.plugins_manifest before its loop, so a
PM closure with no plugins selected needed the application's utils module
(tests/scripts/test_source_driver.py builds exactly that tree).

The requires_hermes sit-out test relied on the host's version identity. A
tagless CI checkout has no parseable version, which makes the gate permissive.
2026-09-25 00:00:09 -04:00
ethernet
c500ee8771 fix(pm): disable a plugin only on evidence about the plugin
An update disabled any plugin that failed a trial build or its
requires_hermes check. Both can be about us, not the plugin: an untagged
source checkout reads as an older release (#122054), so requires_hermes
misjudges a fine plugin, and a download failure says nothing about the
plugin's code. Those now sit the plugin out of the build: config stays
untouched and it rejoins once the cause clears.

Disabling still happens on evidence about the plugin: requires-python vs
the pinned interpreter, manifest_version, an invalid declaration, a uv
resolution conflict, or its own build backend failing (new BuildFailure,
keyed on uv's 'The build backend returned an error').

enabled_member_dirs now skips a requires_hermes misfit instead of
raising. Boot's currency check raised on it before any sync could run, so
the launch path never reached the update sync. The loader skips such a
plugin anyway; admission still refuses enabling one.
2026-09-25 00:00:09 -04:00
ethernet
cae75f0ead fix(pm): an unreadable secondary profile config cannot fail an update
Venv.apply refused the whole graph when any secondary profile's
config.yaml was unreadable, so one broken sibling config failed every
update. Update syncs now leave that profile's plugins out of the union,
report it (stderr + receipt warning), and build the rest; the profile's
plugins rejoin on the next sync once its config is fixed. Boot currency
already skips broken secondaries, so the result reads as current.
Ordinary syncs keep refusing to shrink the recorded graph.
2026-09-25 00:00:09 -04:00
ethernet
ccf631701a fix(pm): disable plugins that no longer fit instead of failing the update
An update resolves the enabled plugin union against the NEW core. A plugin
admitted against the old core can stop fitting when core moves (managed
Python 3.13 -> 3.14 vs a member's requires-python <3.14, a requires_hermes
upper bound, a bumped pin), and the whole update then died after the
source swap with a non-resolver InstallError whose 'retry' hint failed the
same way every time.

Update syncs now pass evict_incompatible_plugins=True (update completion,
historical takeover, launch-time completion, venv_sync, post-update
drift). PM screens statically first (requires-python vs the target
interpreter, manifest/requires_hermes), then, if the rest still fails,
builds core alone to prove the plugins are the cause and re-adds members
in config order, disabling each one that breaks the build. Misfits land in
plugins.disabled (memory.provider cleared) in every home that enables
them, published through the existing journaled change hook (the journal
now carries several configs), and are reported on stderr + receipt
warnings. Admission and ordinary syncs still refuse; only a core that
cannot build on its own fails an update.
2026-09-25 00:00:09 -04:00
ethernet
abe76b5176 fix: restore upstream behavior lost in PM conflict resolutions 2026-09-24 23:59:55 -04:00
ethernet
2ab7d9bfe3 chore(ci): remove publish-e2e-evidence pipeline
gh v2.99 gained a native --attach flag for issues, PRs and comments, so
the custom trusted-publisher chain (gh-image extension + GH_IMAGE_SESSION_TOKEN
workflow_run job + attachment-upload script) is superseded.

Remove:
- .github/workflows/publish-e2e-evidence.yml (workflow_run publisher)
- scripts/ci/publish_e2e_evidence.py + its tests
- e2e-evidence-* artifact upload + evidence staging in e2e-desktop.yml /
  e2e_screenshot_status.py, incl. the 'inline evidence is publishing...'
  marker placeholder in the CI review comment status

Keep: the review-comment screenshot/diff counts and artifact links produced
by e2e_screenshot_status.py.
2026-09-24 23:53:17 -04:00
ethernet
97c4fa022c fix(update): refresh release tags before stamping source versions 2026-09-24 23:52:31 -04:00
ethernet
746d861504 fix(install): don't ask the gateway install questions twice
The setup stage installs the gateway service through
ensure_gateway_service. On Windows that asks the start-now, Scheduled
Task and UAC questions. The gateway stage then ran `hermes gateway
install`, which asked them all again.

`gateway install --if-missing` does nothing when a service is already
installed. Both installers' gateway stages use it, so they ask only
when setup did not install the service.
2026-09-24 23:49:18 -04:00
ethernet
a3456765ed fix(desktop-update): give Windows update steps NUL stdin, not the hand-off console
Steps inherited the hand-off console's stdin, so any step that asks a
question blocked forever. Its prompt went to the captured stdout, which
is shown only after the step exits. `gateway start --all` did exactly
this: it saw an interactive console and asked "Install it now so the
gateway starts on login?". The update stopped after `hermes update exit
code: 0` and never relaunched the app.

Steps now read NUL. Prompts see a non-interactive stdin and take their
defaults. The working-directory self-test also checks that a step's
stdin is not a console, and a new test runs it under a real console.
2026-09-24 23:49:18 -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
e3d43c3c24 test: stop relying on the in-tree venv after #122161
PR #122161 stopped boot from activating the in-tree venv or .venv when
PM has committed no environment. Six Linux tests and four Windows tests
still used that tree to supply their probe modules.

- Launcher tests put the probe in a committed generation. A custom
  HERMES_HOME is its own dependency root, so it gets its own commit.
- The legacy row of test_pre_pm_base_dependencies_activate_only_at_boot
  asserted the removed behavior. test_boot_never_activates_the_pre_pm_venv
  now covers the inverse. The payload row stays.
- The mint payload fixture writes manifest.json as real payloads do, so
  boot selects the payload venv.
- The PowerShell activate test expects PYTHONPATH to be the checkout
  alone. The bootstrap .venv packages do not leak in.
2026-09-24 23:40:03 -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
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
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
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
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
Ben Barclay
28edf7c75a fix(pm): make the staged agent-browser binary executable
The npm tarball ships every bin/agent-browser-* as 0644; agent-browser's
own postinstall sets the exec bit, and pm runs no postinstall. The first
browser_navigate auto-installs agent-browser and then fails with
PermissionError.
2026-09-25 11:19:18 +10: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
Jony
f588166691 fix(google-chat): avoid guessing auth failure cause 2026-09-24 19:27:41 -05:00
ethernet
4b7229d612 fix(icons): commit generated icons; installs and regular builds never render
User installs failed with 'resvg-py is missing' because the web/desktop
source builds rendered icons on whatever python was on PATH. The default
brand outputs are now committed; source_build, apps/desktop build.mjs and
the npm/docusaurus pre-hooks consume them directly. Flavored release
bundles (canary/commit) still render into their own product dir.

icons-freshness-check now regenerates and fails on any byte diff.
2026-09-24 19:17:34 -04:00