Commit Graph

28271 Commits

Author SHA1 Message Date
Brooklyn Nicholson 52d9f3006d Merge main for the MoA switch test fix (#90212) 2026-08-19 14:02:51 -05:00
brooklyn! 6c0f354524 Merge pull request #90212 from NousResearch/bb/fix-moa-switch-test-reasoning-echo
test(agent): unbreak main — MoA switch fake needs a reasoning_echo reader
2026-08-19 14:02:44 -05:00
Brooklyn Nicholson df9e2251f8 test(agent): give the MoA switch fake a reasoning_echo reader
663fa68cd4 added an unguarded `agent._read_reasoning_echo_from_config()` call
to switch_model's core field swap. The fake agent here carries only the
attributes switch_model touches, so the new call raises AttributeError inside
the rollback-protected block — every field the test asserts on gets restored
to its pre-swap value and all four cases fail on main with
`assert 'opencode-go' == 'moa'`.

Teach the fake the reader, matching the production AIAgent shape.
2026-08-19 13:56:11 -05:00
Brooklyn Nicholson ae6c973fc7 fix(update): run the Windows update hand-off unattended
The re-exec'd child inherits the console, so sys.stdin.isatty() still reported
a terminal and the update asked its local-changes question. By then the parent
shim had exited and the shell had taken the console back, so the prompt could
not be answered and the update sat there forever — worse than the lock it
replaced, because nothing recovers without closing the window.

Spawn the child with stdin closed. It then takes the same path the gateway and
Desktop updates take: honour updates.non_interactive_local_changes, which
stashes by default so nothing is lost, and keep going without asking.
2026-08-19 13:41:44 -05:00
kshitij kapoor b2057c1685 refactor: extract duplicated load_config_readonly try/except into helper
The identical 6-line try/except block for reading model.reasoning_echo
from config appeared in both agent_init.py (init) and
agent_runtime_helpers.py (switch_model). Extracted into
AIAgent._read_reasoning_echo_from_config() static method — net -1 LOC.
2026-08-20 00:03:50 +05:30
Yingliang Zhang 7e3775de2e chore: map contributor emails 2026-08-20 00:03:50 +05:30
a 8d0e3ab814 chore: map contributor email for yingliang-zhang 2026-08-20 00:03:50 +05:30
Caleb DeLeeuw 4ee8a9a169 test: production-shape resolver + init-read coverage for reasoning_echo
Salvaged from #73811 per the consolidation triage on #76503, adapted to this
PR's per-active-provider model.reasoning_echo design.

Existing reasoning_echo tests hand-set _reasoning_echo_flag; none drives the
real config path init_agent uses:
    load_config_readonly().get("model").get("reasoning_echo")
which is wrapped in `except Exception: False`, so a broken read would silently
disable the feature untested. This adds a temp-HERMES_HOME test that resolves a
named custom provider via the real resolve_runtime_provider (asserting
provider == "custom"), materializes the flag from a real config file, and
checks reasoning_content is preserved with the flag on and stripped with it off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ab7R4NizLNbNyQZShtciKg
2026-08-20 00:03:50 +05:30
Yingliang Zhang 663fa68cd4 fix: add reasoning_echo_flag to init snapshot and switch rollback
Address review feedback on PR #76503:

1. Init-time primary snapshot (agent_init.py:2756) was missing
   reasoning_echo_flag — after fallback recovery the flag was
   restored as False even when model.reasoning_echo: true was set.

2. Switch transaction snapshot (agent_runtime_helpers.py:2284) was
   missing _reasoning_echo_flag — a failed client rebuild during
   switch_model would leave the old provider with the new provider
   echo policy.

Both omissions now fixed. No test regressions (56 passed).

Signed-off-by: Yingliang Zhang <zhangyingliang@outlook.com>
2026-08-20 00:03:50 +05:30
Yingliang Zhang 73243b0d2e feat(config): per-provider reasoning_echo opt-in for custom providers
Add model.reasoning_echo (default false) and per-fallback-entry
reasoning_echo to preserve assistant reasoning_content when
replaying history to custom providers and OpenAI-compatible gateways
that proxy thinking-mode models (Kimi K3, GLM-5.2, DeepSeek, etc.)
but are not matched by the built-in host-based _REASONING_ECHO_RULES.

The flag is per-active-provider, not a global toggle:
- Primary: read from model.reasoning_echo at init and switch_model
- Fallback: set by try_activate_fallback from the fallback entry
- Restore: restore_primary_runtime copies the switch_model snapshot

Unlike PR #76019 global agent.reasoning_echo toggle, the
per-provider flag travels with the active provider — falling back to
a strict provider (Mistral, Groq, Cerebras) correctly strips
reasoning_content even when the primary had the flag enabled,
because the flag is False for the strict fallback.

Complements PR #27361 (dynamic detection) which fires after the first
API response; this PR covers turn-1 and history-replay-on-fresh-session
where dynamic detection has not fired yet.

Closes #76018
Refs: #27297, #27361, #76019

Signed-off-by: Yingliang Zhang <zhangyingliang@outlook.com>
2026-08-20 00:03:50 +05:30
brooklyn! 6284afbaab Merge pull request #90194 from NousResearch/review/88767
fix(update): show the current stage and elapsed time while updating
2026-08-19 13:31:12 -05:00
brooklyn! 73a1d583e3 Merge pull request #90192 from NousResearch/bb/windows-update-shim-self-lock
fix(update): stop Windows updates from locking their own launcher
2026-08-19 13:25:22 -05:00
Brooklyn Nicholson 268615cbb3 test(update): cover the Windows shim self-lock class
Detection across every launch variant (argv[0], the zipapp __main__.py, the
main-module spec origin, the ancestor chain) plus the venv scoping that keeps
an unrelated hermes.exe from triggering a hand-off; the re-exec's argv, env
marker, loop guard and both fall-through paths; the pending-rename filter;
and the venv/.venv layout split.

Retires the reboot-deferred quarantine assertion along with the fallback.

Launch-variant cases from #89970 by @Akloenx123, pending-rename cases from
#88121 by @fangliquanflq.
2026-08-19 13:17:44 -05:00
Brooklyn Nicholson e602225d86 test(update): cover the progress contract without a test hook in the updater
The self-test grew a branch that spawns a Python child so pytest could prove
progress advances during one. It doesn't need to: /progress is answered from
its own runspace, so the existing hold already blocks the main thread, and
the spawn only exercised Invoke-HermesStep, which nothing here changes.

The Windows test now asserts the invariant instead of the self-test's stage
string, and the posix half -- previously untested, and the half that broke --
gets real coverage: serve-ui.py's wire shape, and posix.sh driven end to end
with a stub `hermes` that reports which stage was on screen while it ran.
2026-08-19 13:16:13 -05:00
Brooklyn Nicholson cf2ab8522a fix(update): one elapsed clock, served to the shim by both orchestrators
The shim is a shared page, but only windows.ps1 publishes a stage and an
elapsed count. On mac and Linux posix.sh publishes `running` with an empty
message and no clock, so the running branch rendered the h2 back into the
muted line ("Updating Hermes" twice) and started a clock in the browser,
losing "Hermes will open once done." on both platforms.

A clock started in the page measures when the window painted, not how long
the update has been running -- on posix that is the only clock there is, and
it reads zero after the desktop-exit wait has already burned 30s. That is the
hardcoded-milestone problem #75895 removed, in a new costume.

So: elapsed comes from the orchestrator or is not shown. serve-ui.py stamps
it per request from the hand-off start (a value written into the status file
would freeze between publishes, which are minutes apart -- exactly the stall
the line exists to disprove), matching what Windows' in-process listener
already does. posix.sh gets the stages it was missing, at the four gates it
genuinely waits on. Absent a stage the page keeps the settled copy, and an
old orchestrator that sends no clock simply shows no clock.
2026-08-19 13:16:13 -05:00
Brooklyn Nicholson 3f6d4c6338 fix(gateway): run Windows /update as a module, not through the shim
The Windows branch spawned the updater as `hermes.exe update --gateway`, so
the update held the very shim it had to replace and failed with os error 32.
Invoke it as `python -m hermes_cli.main update --gateway` under the same
interpreter the gateway already runs, which maps no shim.

Salvaged from #89970 by @Akloenx123.
2026-08-19 13:15:02 -05:00
Brooklyn Nicholson a9eb99d172 fix(update): stop deferring shim renames to next boot
MOVEFILE_DELAY_UNTIL_REBOOT was the quarantine's last resort, and it is worse
than doing nothing. It writes to HKLM, so a non-elevated update — every
Desktop-driven one, and most terminal ones — gets ERROR_ACCESS_DENIED and
reports nothing. When it does succeed it frees nothing for the install
running right now, and the queued operation outlives that update: at the next
boot it moves aside whatever sits at the shim path, including a shim a later
repair just wrote.

Drops the fallback and sweeps entries older versions queued, matching only
our own <shim> -> <shim>.old.<stamp> pairs so unrelated installers keep
theirs.

Salvaged from #88121 by @fangliquanflq.
2026-08-19 13:14:40 -05:00
Brooklyn Nicholson 867ab54e20 fix(update): re-run Windows updates off the console shim
`hermes update` launched as venv\Scripts\hermes.exe can never finish on
Windows. The launcher runs the interpreter with the shim as its script and
holds it open without FILE_SHARE_DELETE for the whole command, so the
quarantine rename is refused and uv fails to replace hermes.exe with
os error 32 — every time, with no Desktop, gateway or AV involved. The
concurrent-instance preflight cannot catch it because it excludes this
process and its ancestors by design.

Detect the shim from both the process ancestry and this process's own launch
paths (argv[0], __main__.__file__, the spec origin — the runpy/zipapp launch
puts <shim>\__main__.py there), intersected with the project venv's shims so
an unrelated hermes.exe never matches. When it matches, re-run the same
argv as `venv\Scripts\python.exe -m hermes_cli.main ...` and return, which
releases the shim before the child installs anything.

The hand-off sits ahead of the update lock so the child claims the marker
itself rather than adopting one the parent immediately releases, and any
failure falls through to the previous in-process behaviour with the manual
command printed.

Refs #88838, #89599, #86093
2026-08-19 13:13:09 -05:00
Brooklyn Nicholson 7a94b1fbf7 fix(update): resolve the project venv as venv or .venv
`uv venv` writes `.venv` while our installers write `venv`, and every venv
lookup in the update/repair paths hardcoded `venv`. On a `.venv` install
`_venv_scripts_dir()` returned None, so the Windows shim-lock preflight, the
quarantine, and the console-script verification all silently skipped
themselves — the update walked straight into the failure they exist to catch.

Adds `hermes_constants.project_venv_dir()` as the single resolver and routes
both `_venv_scripts_dir()` implementations plus the two VIRTUAL_ENV call
sites through it.

Refs #79542
2026-08-19 13:11:35 -05:00
Gille 107531549a fix(update): show live Desktop update progress 2026-08-19 13:11:27 -05:00
brooklyn! 87e32b6b30 Merge pull request #90070 from dcdexhome/fix/desktop-pin-sync-reentrant-crash
fix(desktop): stop re-entrant pin-sync reconcile from overflowing nanostores
2026-08-19 12:45:05 -05:00
Brooklyn Nicholson 3a034356a2 ci: run the Python lane for docs and website script changes
llms.txt coverage is asserted in Python, but website/ sat on the Python skip
list, so a PR adding a docs page — or regressing the generator — went green
without ever running the test that checks the page is reachable. That is how
the index drifted to 53% coverage unnoticed.
2026-08-19 12:41:45 -05:00
Brooklyn Nicholson dc8481b78b feat(skill): route hermes-agent's unknown-feature questions to llms.txt
The routing table listed 18 topics and had nothing to say about the rest of
the product, so an agent asked how to get bots to talk to each other answered
that it could not — while user-guide/bot-mode documented four ways to do it.

Point the catch-all at the published index, which is generated from the docs
tree on every build and so cannot fall behind the feature set. website/ is
never packaged, so the URL is the only complete self-knowledge a running
Hermes has; curl covers sessions where the web tools are disabled.
2026-08-19 12:41:45 -05:00
Brooklyn Nicholson a45d854d7d fix(docs): index every docs page in llms.txt, not a hand-picked 98
The section list decided membership as well as order, so it drifted as the
docs grew: 109 of 204 pages were absent from the index every LLM reads to
learn what Hermes does — Bot Mode, the desktop app, computer use, web search,
skins, Mixture of Agents, and 22 messaging platforms among them.

Enumerate the docs tree instead. SECTIONS now curates only which pages lead a
section; anything it does not name is absorbed under its path, and a page
matching no section lands in "More" rather than falling out. This also picks
up the three .mdx pages the .md-only glob never saw, points section landing
pages at the directory URL Docusaurus actually serves, and drops a curated row
still aimed at a guide moved to developer-guide/plugins in #59613.

Tests hold both directions against the filesystem rather than the enumerator,
so a page cannot go missing and a link cannot point at a page that moved.
2026-08-19 12:41:45 -05:00
Teknium bdc5b1f74c chore: map contributor email for noahingh 2026-08-19 10:19:35 -07:00
Noah Lee 9551df1205 test(skills): add CLI regression tests for skills uninstall --yes/-y flag
Covers the new --yes/-y flag on , asserting the
parsed value reaches do_uninstall(skip_confirm=True) via the real
main() -> cmd_skills -> skills_command dispatch path. Mirrors the
install-flag test pattern in test_skills_install_flags.py.
2026-08-19 10:19:35 -07:00
Noah Lee cd3f9b64de feat(skills): add --yes/-y flag to hermes skills uninstall
`do_uninstall` already accepted a `skip_confirm` parameter and the
slash-command handler already passed `skip_confirm=True`, but the CLI
argparse path never exposed a flag to reach it. This adds `--yes`/`-y`
to `hermes skills uninstall`, matching the existing pattern on `install`
and `reset`.
2026-08-19 10:19:35 -07:00
Teknium 00888e80ac test: drop the upscale-defaults-off catalog invariant
Grok Imagine Image 2.0 (ceabb030fb) intentionally ships upscale: True —
its 1k native output is sub-2MP. Per-entry defaults are a catalog
decision now; the blanket all-off invariant no longer reflects policy.
Per-call upscale=true/false override is unchanged.
2026-08-19 10:07:30 -07:00
hermes-seaeye[bot] eab087c06f fmt(js): npm run fix on merge (#90140)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-19 16:56:58 +00:00
brooklyn! d21cd51d86 Merge pull request #88887 from helix4u/fix/desktop-repair-missing-venv
fix(desktop): repair missing Windows runtime
2026-08-19 11:46:26 -05:00
brooklyn! 0a0497060a Merge pull request #90014 from xxxigm/fix/desktop-wrap-toast-titles
fix(desktop): wrap toast titles so long errors stay readable
2026-08-19 11:41:30 -05:00
Bryan Bednarski 0e5499b153 chore: retrigger CI after main image fix
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
2026-08-19 09:30:41 -07:00
brooklyn! 6c6c17e0fc Merge pull request #90112 from NousResearch/bb/tour-unanswered-bridge
fix(tour): an unanswered tour bridge no longer costs 45s per action
2026-08-19 11:28:45 -05:00
Brooklyn Nicholson 84d81d258f fix(tour): an unanswered tour bridge no longer costs 45s per action
The renderer's `tour.request` handler ships in the desktop bundle, but the
tool is offered by the backend, and the two update on different clocks. A
desktop build older than the tour tool receives the event in a renderer with
no branch for it, so `tour.respond` never comes and the agent blocks for the
full 45s deadline — once per action the model tries. A single "give me a
tour" turn (targets, then narrate, then stop) stacked those waits into
minutes of dead air, which is what got reported against #89620.

Hold a session's first action to a deadline a working renderer cannot miss,
and let an unanswered probe mark the bridge unavailable for that session:
later calls return immediately with an error naming the actual fix instead
of stalling again. Once a client has answered, real actions get the full
deadline back, so a preview tour injecting into a live page still works and
one slow action no longer condemns a live client. The verdict lives on the
session record, so it dies with the session and a new one re-probes.

The same five-action sequence goes from ~225s of dead air to a single 10s
probe. Toolset gating is unchanged: removing the tool outright needs a
client capability declared at session.create, which prompt caching means
can only take effect for a new session.
2026-08-19 11:20:28 -05:00
brooklyn! 28803e68b4 Merge pull request #90113 from NousResearch/bb/review-submit-isolation
fix(desktop): isolate review submit to one composer (supersedes #90097, #85911)
2026-08-19 11:15:43 -05:00
Teknium 9b5e7a3226 fix(image_gen): Grok Imagine 2.0 no longer upscales by default — opt-in policy restored
ceabb030f added the Grok Imagine Image 2.0 catalog entry with upscale=True,
violating the Aug 2026 opt-in-only upscaling policy (f06c41522) and breaking
test_upscale_defaults_are_all_off on main, which reddened every PR's slice
12/12.
2026-08-19 12:14:36 -04:00
Bryan Bednarski bc92c875ef test(relay): defer compaction flush expectation
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
2026-08-19 09:14:12 -07:00
Riccardo Roveri b95f3a5202 fix(providers): use copy-on-write instead of deepcopy in NVIDIA prepare_messages
Addresses the unresolved review comment on #30674 — avoid deep-copying
the whole conversation (including large tool outputs and attachments) to
sanitize two top-level keys on a handful of tool-role messages.

Switches to a shallow outer-list copy + shallow per-message dict copy,
applied only to messages that actually carry name/tool_name. Matches the
copy-on-write pattern already used by the shared sanitizer in
agent/transports/chat_completions.py and by QwenProfile.prepare_messages().
The needs_sanitize early-return path is preserved unchanged so the
existing identity check (prepare_messages returns the original list object
when nothing needs sanitizing) continues to hold.
2026-08-19 18:14:03 +02:00
pmos69 fc05cfb58e fix nvidia tool message schema 2026-08-19 18:10:53 +02:00
brooklyn! 00e5a361b6 Merge pull request #89611 from NousResearch/bb/desktop-godfiles
refactor(desktop): decompose god files into atomic modules
2026-08-19 11:05:01 -05:00
Brooklyn Nicholson 6a3d50c6e0 fix(tui): allow the ESC byte in the SGR param matcher
eslint no-control-regex rejects the CSI regex even though ESC is the
sequence we have to parse.
2026-08-19 11:03:01 -05:00
Brooklyn Nicholson 2725d3225b fix(tui): stop the composer placeholder from sticking Terminal.app into dim
The placeholder hint and its synthetic cursor chip hand-rolled truecolor
escapes ([38;2;r;g;b / [48;2;r;g;b]) and wrote them raw past Ink's depth
layer. Legacy Terminal.app has no truecolor parser — it walks compound
params one by one, so the literal 2 in 38;2;… lands as SGR 2: dim ON,
with no 22m ever emitted. Every frame that painted the placeholder left
the terminal's dim attribute stuck, and subsequent cells rendered dimmed
until an unrelated bold span's 22m happened to clear it — text randomly
flipping dim and back, worst right after the composer empties.

Measured on a live resumed session (PTY capture, params interpreted the
legacy way): 1026 glyphs painted with stuck dim on main, 0 with the fix.

Route both helpers through Ink's own colorize, the same repair colorizeEcho
got for the fast-echo path (gray-accent bug) — the escape now downgrades
with the terminal's real color depth, and a 256-color terminal gets 38;5;N
it can actually parse.

Also harden hermes-ink's transitionAnsiCodes for compound SGRs: real tool
output ships [1;31m-style sequences whose endCode is [0m, dodging the
endCode-based weight detection — parse the params instead (skipping 38/48
extended-color arguments) so a compound bold→dim transition passes through
SGR 22 too.
2026-08-19 11:03:01 -05:00
Brooklyn Nicholson 830108c8d9 fix(desktop): send review agent-ship to the composer that opened it
The ship button always targeted `main`, so a tile Review still prompted
the workspace session. Remember the originating composer target with the
pane's cwd, capture the live surface at click, and toast if that chat
isn't on screen instead of dropping the click.

Co-authored-by: unsupportedpastels <theoldwizard123@pm.me>
Co-authored-by: youtiaowei <youtiaowei@users.noreply.github.com>
2026-08-19 11:01:58 -05:00
Brooklyn Nicholson 04956c1a87 fix(desktop): isolate composer submit to one visible surface
Review "Ask Hermes to open PR" was a window-level event that every mounted
composer claimed with `target === 'main'`, so one click shipped every open
session and project with dirty files. Bind the request to the visible
surface captured at click time.

Co-authored-by: unsupportedpastels <theoldwizard123@pm.me>
Co-authored-by: youtiaowei <youtiaowei@users.noreply.github.com>
2026-08-19 11:01:54 -05:00
brooklyn! d697457f38 Merge pull request #89837 from NousResearch/bb/glass-acrylic
feat(desktop): back window glass with Windows 11 materials, and scope its controls per OS
2026-08-19 11:00:57 -05:00
brooklyn! 1087381345 Merge pull request #89817 from NousResearch/bb/sidebar-all-profiles-scope
fix(desktop): keep recents when ALL-profiles scope has one profile (supersedes #84313)
2026-08-19 11:00:49 -05:00
brooklyn! 3f33bb809a Merge pull request #89833 from NousResearch/bb/scope-session-lookup
fix(desktop): scope session lookup to the active profile (supersedes #79522)
2026-08-19 10:57:17 -05:00
Brooklyn Nicholson 8e9342b016 chore: kick CI 2026-08-19 10:53:30 -05:00
Brooklyn Nicholson 70743bc4cb chore: kick CI 2026-08-19 10:53:26 -05:00
Bryan Bednarski 6f7596e9a2 docs(relay): link supported observability exporters
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
2026-08-19 08:52:04 -07:00