Commit Graph

19952 Commits

Author SHA1 Message Date
kshitij 321cbcc2e2 test(redact): add YAML ReDoS test, strengthen existing test with keyword
- Relax timing bound from 1.0s to 2.0s (CI machine robustness).
- Add test_long_dotted_run_with_keyword_completes_fast: includes a secret
  keyword so the pre-gate does NOT skip _CFG_DOTTED_RE — directly exercises
  the possessive-quantifier regex, not just the pre-gate.
- Add test_yaml_assign_redos_resistance: _YAML_ASSIGN_RE was modified but
  had no ReDoS test — add 100-line stress input.
- Add test_yaml_assign_secret_still_redacted: verify YAML matching behavior
  preserved with possessive quantifiers.
2026-08-01 15:58:25 +05:30
carlotestor 13ad903a3c perf(redact): eliminate exponential backtracking in config-key patterns
_CFG_DOTTED_RE's nested quantifier (?:[A-Za-z0-9_\-]+\.)+ backtracks
exponentially on long non-matching dotted runs (doubles every ~4
segments). Flatten it and use possessive quantifiers (py3.11+) in
_CFG_DOTTED_RE and _YAML_ASSIGN_RE wherever the successor is disjoint.

Zero behavior change: equivalence fuzz-verified over 120k structured
and random inputs comparing full sub() output including groups. Adds a
ReDoS regression test.
2026-08-01 15:58:25 +05:30
kshitijk4poor 84952e89f9 chore: map tron@chriswykel.com -> Wpnx330 (PR #68334 salvage) 2026-08-01 15:43:13 +05:30
kshitij 536754919d fix(auxiliary): replan cache sections on the async fallback path too
_call_fallback_candidate_sync replans messages/tools for each resolved
destination, but its async mirror still shipped the caller's decorated
sections verbatim — the primary destination's markers (including a
direct-native tool marker) leaked to fallback candidates with different
cache contracts, and the relay saw the display label instead of the
resolved provider/api_mode. Mirror the sync path: resolve the
destination, replan both sections, thread provider/api_mode through
_relay_async_completion, and replan again for the auth-refresh retry
client. Mutation-checked: the new parity test fails on the verbatim
pass-through shape.

Follow-up to #76032 (#20880).
2026-08-01 15:39:58 +05:30
kshitij e7340ea281 perf(prompt-caching): make PromptCachePlan.marker_count lazy
The count walked every message part and tool schema on every request but
is consumed only by tests. Compute it on demand via a property instead.

Follow-up to #76032 (#20880).
2026-08-01 15:39:58 +05:30
kshitij af06308425 refactor(prompt-caching): collapse triplicated destination-plan and label parsing
Three copies of the same logic landed with #76032:
- MoA's _call_prepared_aggregator and auxiliary_client's
  _replan_synchronous_cache_sections both implemented stub → policy →
  strip → plan for a resolved destination. Extract
  plan_cache_sections_for_destination() into agent_runtime_helpers (which
  already owns the policy functions) and route both through it. Also
  removes a redundant full-transcript deepcopy+strip per request (the
  caller pre-stripped what build_prompt_cache_plan strips again).
- The fallback_chain[N] label regex + chain-entry lookup lived in
  _fallback_entry_timeout AND _fallback_destination. Extract
  _fallback_chain_entry() and reuse.

MoA's cache-plan failure log is promoted debug → warning: the call-block
site skips MoA, so this block is the aggregator's only decoration path —
a silent failure ships an undecorated request (the 0%-cache MoA bug class).

Behavior-preserving; 195 targeted tests green.

Follow-up to #76032 (#20880).
2026-08-01 15:39:58 +05:30
kshitij 7ae4a5efba fix(prompt-caching): consolidate static-prefix split, guard empty volatile suffix
_apply_static_prefix_marker duplicated _apply_system_cache_markers' split
logic minus its empty-suffix guard: when the stored system prompt equals
the static prefix exactly, the tool-cache plan emitted a two-part split
with a trailing empty text block — HTTP 400 on native Anthropic. Fold the
tool-cache layout into the existing helper via mark_suffix /
fallback_to_whole flags; the empty-suffix case now marks the prompt as
one whole block. Behavior-parity verified against the merged planner for
every non-empty-suffix shape.

Follow-up to #76032 (#20880).
2026-08-01 15:39:58 +05:30
kshitijk4poor e078c8c6ef fix: widen fallback warning to the sibling custom-endpoint 256K path
Review pass 2 (reuse reviewer HIGH): the step-3b probe-down fallback for
custom/local endpoints returns the same silent 256K default but only
logged at INFO - invisible by default, and it is the MORE common path
for small local models (the exact users the warning exists for).

Extract _warn_context_length_fallback() (deduped per model+base_url)
and call it from both fallback sites, per the fix-the-whole-bug-class
rule. Regression test drives the custom-endpoint path and fails without
the widening (mutation-checked).
2026-08-01 15:05:05 +05:30
kshitijk4poor 4c2d0c7fd8 refactor: dedupe fallback warning per model, drive pool-cleanup tests through real run()
Review follow-up:
- Warn once per (model, base_url) at the step-9 fallback via a module-level
  dedup set (established _WARNED_* idiom). The fallback result is
  deliberately never cached, so the un-deduped warning fired on every
  resolution - e.g. once per gateway message via the session-hygiene path.
- Replace the three inline-mock pool-cleanup tests (which reproduced the
  try/except block against a MagicMock and passed even with the production
  code reverted) with a parametrized test that drives the real
  BatchRunner.run() with a patched Pool; drop the CPython stdlib
  signature change-detector test.
- Add a once-per-model warning regression test; clean up dead imports.

All tests verified to fail against pre-PR batch_runner.py/model_metadata.py
and pass with the fix (mutation check).
2026-08-01 15:05:05 +05:30
kshitijk4poor a1ff62a139 fix: context-length fallback logging, batch trajectory durability, pool cleanup
Salvage of #6629 by aaronlab (kshitijk4poor reworked against current main).

Three concerns from the original PR, reworked to address review feedback:

1. Context-length fallback diagnostic (agent/model_metadata.py):
   get_model_context_length() silently returned 256K when all 9 detection
   methods failed. Users with small-context models (8K, 32K) would get 256K
   silently, causing hard-to-debug API context-length errors. Added a
   warning log at the step 9 fallback with model name, base_url, and the
   correct config override hint (model.context_length, not context_length).
   The token-estimation ceiling-division fix from the original PR already
   landed on main (5c2ecdec) with CJK handling — not duplicated here.

2. Fsync for batch trajectory writes (batch_runner.py):
   Trajectory entries were written without flush/fsync, but the checkpoint
   immediately marked them as completed. A crash between write and disk
   sync would leave the checkpoint claiming completion with no trajectory
   data on disk. Added flush() + os.fsync() before checkpoint update.

3. Pool cleanup on interruption (batch_runner.py):
   Ctrl+C during pool.imap_unordered() relied on context manager cleanup
   which can hang. Added explicit pool.terminate() + pool.join() for both
   KeyboardInterrupt and Exception paths. The original PR used
   pool.join(timeout=10) which is invalid — CPython's Pool.join() takes
   no timeout parameter. Fixed to use pool.join() without arguments.

Tests:
  - test_warning_emitted_on_fallback: verifies warning fires at step 9
  - test_no_warning_when_cached: verifies no false warning when cache hits
  - test_trajectory_entry_is_synced_to_disk: verifies os.fsync is called
  - test_pool_terminate_called_on_exception: verifies cleanup on RuntimeError
  - test_pool_terminate_called_on_keyboard_interrupt: verifies cleanup on Ctrl+C
  - test_pool_join_called_without_timeout: verifies no timeout arg to join()
  - test_real_pool_join_accepts_no_timeout: integration check on CPython API

Co-authored-by: Aaron Lab <aaronlab@users.noreply.github.com>
2026-08-01 15:05:05 +05:30
ajzrva-sys 34c11fa689 fix(terminal): honor explicit config keys over stale env
Let terminal keys explicitly present in config.yaml override matching stale TERMINAL_* values while preserving environment values for omitted keys. Merged defaults remain backfill-only.

Exercise the real config.yaml to _get_env_config path for backend selection, partial terminal sections, matching-key overrides, environment fallback, one-shot bridging, and config read failures.

Closes #71137
2026-08-01 15:03:42 +05:30
Rod Boev 9fc12bf7a4 perf(prompt-caching): preserve tool-loop cache boundaries (#20880) 2026-08-01 14:27:12 +05:30
hans f09c56c003 fix(gateway): dedup model-switch markers so they don't accumulate in history
Each mid-session `/model` switch appended a `[System: The active model for
this chat has changed to …]` user-role marker to the live conversation history
and never removed the prior one. N switches left N stale markers, all re-sent
to the provider on every subsequent turn — the issue measured ~80-120 tokens
each (Chinese+English), so 5 MoA-preset switches burned ~400-600 context
tokens per API call, permanently. Only the newest marker is meaningful (it
names the currently-active model); the rest are pure waste. Fixes #65891.

`_append_model_switch_marker` now strips any earlier markers from
`session["history"]` (in place, under the existing history_lock) before
appending the new one, so the live payload carries exactly one marker. This is
the payload re-sent each turn, and it's self-healing across resumes: whatever
markers a history reload brings back, the next switch collapses to one.

A stable `_MODEL_SWITCH_MARKER_PREFIX` constant is shared by the builder and
the `_is_model_switch_marker` predicate so the two can't drift; the marker
string itself is byte-for-byte unchanged. Existing behavior (role='user' per
the single db.append_message persistence) is preserved.

Scope note: this dedups the in-memory history (the per-turn cost the issue
quantifies). The DB still persists one row per switch for audit/resume; I did
NOT soft-archive prior marker rows because `active=0, compacted=0` is the
repo's rewind/undo sentinel (hermes_state.py get_messages docs), so
deactivating a marker naively would make it look rewound — a follow-up that
needs its own archive semantics.

Tests (tests/tui_gateway/test_model_switch_marker_role.py, +5): second switch
replaces (not stacks) the marker; the issue's exact 5-MoA-switch sequence
leaves one marker naming the last model; real conversation turns are preserved
in order; a stale marker sitting between turns is stripped; history_version
increments once per switch. `pytest test_model_switch_marker_role.py` → 13
passed (8 existing unchanged). Authored on Windows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013b1XyXitAxV7phGmKWigJX
2026-08-01 14:20:38 +05:30
kshitij c4e8613bba Merge pull request #76031 from kshitijk4poor/chore/contributor-email-rodboev
chore: add contributor email mapping for @rodboev
2026-08-01 14:20:17 +05:30
BB-light e9d52d2bda fix(caching): honor prompt_caching.cache_ttl disable in config
Setting prompt_caching.cache_ttl to a falsy value (false, null, off,
disabled, no, none) now fully disables prompt caching instead of
being silently ignored.

The disable propagates through anthropic_prompt_cache_policy() (early
return when _cache_disabled flag is set) and restore_primary_runtime()
(override after snapshot restore), so it survives /model switches and
fallback re-derivation — the gap that caused #56105 to be reverted in
#56126.

Salvage of #33555 by @BB-light, with model-switch/fallback survival
gap fixed on top.

Co-authored-by: BB-light <BB-light@users.noreply.github.com>
2026-08-01 14:14:15 +05:30
kshitijk4poor 7d54cb2672 chore: add contributor email mapping for @rodboev 2026-08-01 14:05:29 +05:30
kshitijk4poor 40e0e7ad56 docs(acp): correct late-refresh docstring — post-first-turn tools land via between-turns refresh
The claim 'late tools then require an explicit /reload-mcp' was false on
current main: the between-turns prologue refresh (agent/turn_context.py)
picks up late-connecting servers cache-safely at every turn boundary,
and ACP has no /reload-mcp. The daemon's real marginal value is tool-list
freshness in the [session created -> first message] window.
2026-08-01 13:27:58 +05:30
kshitijk4poor 23c13589dd fix(acp): serialize late MCP refresh with turn start + self-sufficient agent-build wait
Review follow-ups on the #32811 salvage:
- Hold state.runtime_lock and bail on is_running so the pre-first-turn
  guard can't race the first prompt dispatch (a refresh publishing
  mid-turn would swap tools= and break the just-created cache prefix).
  Regression test mutation-checked (guard removed -> test fails).
- In-memory-only session lookup in the daemon: get_session() falls
  through to a DB restore that builds a whole new AIAgent just to
  decide no-op (TUI equivalent also checks its in-memory dict only).
- Pass quiet_mode=True explicitly, matching the TUI/gateway callers.
- Use ensure_mcp_discovery_before_agent_build() (landed on main after
  the PR) instead of bare wait_for_mcp_discovery() so the ACP agent
  build is self-sufficient and gets the retry-after-zero-connected
  allowance, matching CLI/one-shot construction sites.
2026-08-01 13:27:58 +05:30
Sergey Prontsevich 640de6562e fix(acp): add bounded wait + late-refresh for configured MCP servers
ACP entry.py fires MCP discovery in a background daemon thread, but
_make_agent snapshots tools once at build and never re-reads the registry.
Unlike CLI/TUI, ACP had no bounded wait before the snapshot and no
late-refresh for configured (config.yaml) MCP servers — a reachable-but-
slow server that finished after agent build was invisible for the whole
session.

Changes:
- acp_adapter/session.py (_make_agent): call wait_for_mcp_discovery()
  before AIAgent construction, bounded by mcp_discovery_timeout (default
  ~1.5s). A dead server can't block; servers that miss the bound are
  picked up by the late-refresh below.
- acp_adapter/server.py (_schedule_mcp_late_refresh): new method on
  HermesACPAgent — if discovery is still in flight after session creation,
  spawns an off-critical-path daemon that joins it (bounded 30s), then
  rebuilds the tool snapshot via the shared refresh_agent_mcp_tools helper.
  Cache-safe: only runs pre-first-turn (_user_turn_count/_api_call_count
  both 0); once the user has sent a message the snapshot is frozen, exactly
  as TUI PR #48403 does.
- Called from new_session, load_session, resume_session.
- Mirrors the TUI pattern (tui_gateway _schedule_mcp_late_refresh, PR
  #48403) and the CLI pattern (get_tool_definitions → wait_for_mcp_discovery).

Tests:
- Replace the AST-based test (source-text inspection) with three
  behavioral regression tests in tests/acp_adapter/test_acp_mcp_discovery.py:
  1. Blocked discovery does not block startup (non-blocking contract)
  2. Delayed discovery lands tools via late-refresh (pre-first-turn)
  3. Late-refresh is cache-safe: skips rebuild after first turn

Addresses teknium1 review on PR #32811.
2026-08-01 13:27:58 +05:30
Sergey Prontsevich 89f0b63da4 perf(mcp): non-blocking startup via background MCP discovery + TUI fast path
Fire-and-forget MCP server connections on a daemon thread so the
gateway / CLI / ACP process becomes interactive immediately instead
of blocking on slow remote MCP servers (HTTP timeouts, sluggish
stdio boot).  Previously `hermes --tui` waited 2-5 s after the splash
screen before rendering the UI while `discover_mcp_tools()` ran
synchronously on the critical path.

Changes:
- tools/mcp_tool.py: add `discover_mcp_tools_background()` — thin
  wrapper that spawns `discover_mcp_tools()` on a named daemon thread
- tui_gateway/entry.py: call `discover_mcp_tools_background()` before
  sending gateway.ready (replaces inline call that blocked the JSON-RPC
  pipe for the TUI Ink app)
- hermes_cli/main.py:
  - skip `\_prepare_agent_startup()` for TUI path — plugins, MCP, and
    shell hooks are only needed by the CLI agent loop; the TUI's
    gateway subprocess discovers them independently (~370 ms saved)
  - fast-path in `\_make_tui_argv()`: when `dist/entry.js` exists and
    is fresh, skip npm install / rebuild checks entirely (~350 ms saved)
- cli.py (`\_prepare_deferred_agent_startup`): same background pattern
  for deferred startup (Termux interactive CLI)
- acp_adapter/entry.py: same pattern so ACP server launches asyncio
  immediately while MCP connects in parallel

Result:
- TUI Python wrapper: ~730 ms → ~80 ms (9× faster)
- gateway.ready: ~2700 ms → ~400 ms (7× faster)
- Total TUI cold start: ~3400 ms → ~480 ms

Related: #29726, #29184, #19326 (closed stale)

Closes #29726
2026-08-01 13:27:58 +05:30
brooklyn! 9b1d8341b2 Merge pull request #75988 from NousResearch/bb/cwd-focus-and-display
Session workspace tracks focus — and paints as ~/…
2026-08-01 02:53:44 -05:00
brooklyn! 1a25214364 Merge pull request #75998 from NousResearch/bb/pane-size-remember
fix(desktop): keep ⌘G/files rails at their default size
2026-08-01 02:52:43 -05:00
kshitij 3d5f3967d5 Merge pull request #75984 from kshitijk4poor/chore/contributor-emails-prontsevich
chore: add contributor email mappings for @Prontsevich
2026-08-01 13:20:30 +05:30
Brooklyn Nicholson 5657c4a541 fix(desktop): sort display-path import for eslint 2026-08-01 02:46:40 -05:00
Brooklyn Nicholson 47d7b7fb1d fix(desktop): keep review/files rails at their declared size
All-fixed splits used to promote the last track to flex-grow and drop its
max clamp. Review and files both declare maxWidth, so ⌘G/⌘J ballooned them
and sash overrides only set a basis that grow still expanded past.
2026-08-01 02:45:47 -05:00
HeLLGURD d109138ef5 fix(mcp): avoid replaying historical events on startup (#13414)
EventBridge initialized each session's last_seen timestamp to 0.0, so
the first poll after 'hermes mcp serve' starts treated every saved
user/assistant message in state.db as a fresh events_poll event.

The fix establishes a per-session timestamp baseline on startup via
_establish_baseline(), recording the latest existing message timestamp
without emitting events. Only messages written after the baseline are
delivered on subsequent polls.

Also hoists _ts_float to module-level (needed by _establish_baseline)
and adds ImportError fallbacks for hermes_constants imports so the
bridge works in environments where the module isn't on the path.

Salvage of #13414 by @afurm, re-applied by @HeLLGURD in #41239.

Co-authored-by: afurm <afurm@users.noreply.github.com>
2026-08-01 13:13:30 +05:30
Brooklyn Nicholson 883076ccd7 feat(desktop): ⌘N/⌘T keep the focused session's project
resolveNewSessionCwd now inherits the focused chat's workspace when you
aren't drilled into a sidebar project, so a new tab or draft stays in
the same repo as the chat you were looking at.
2026-08-01 02:32:28 -05:00
Brooklyn Nicholson 3707741d9f fix(desktop): statusbar cwd follows the focused session
Workspace indicator was stuck on the primary $currentCwd while timers
and context already tracked focus. Resolve from the focused runtime
slice, then the stored session row, with a mid-switch ownership gate.
Path tips across chrome use displayPath (~/).
2026-08-01 02:32:28 -05:00
Brooklyn Nicholson 5b4c57a7ff feat(desktop): shared path display collapses home to ~
One paint helper for UI chrome: /Users/x/y → ~/y (also /home and
C:\Users). Copy/reveal still use the real absolute path.
2026-08-01 02:32:28 -05:00
brooklyn! 57b1eb8c4d Merge pull request #75975 from NousResearch/bb/terminal-session-link
Link terminal tabs to the session you're working in
2026-08-01 02:22:30 -05:00
brooklyn! bfc014e3a8 Merge pull request #75966 from NousResearch/bb/dither-tail-only
fix(desktop): thinking indicator can no longer appear mid-transcript
2026-08-01 02:18:12 -05:00
Brooklyn Nicholson c450fb931d feat(desktop): switching sessions re-selects the terminal tab in its cwd
A $currentCwd listener in the terminal store picks the user tab whose
live shell cwd (restoreCwd, falling back to launch dir) matches the
session's workspace. Selection only: no tab is created, closed, or
revealed; detached sessions and unmatched cwds leave the rail alone,
and an already-matching active tab keeps focus.
2026-08-01 02:15:02 -05:00
Brooklyn Nicholson eb545ddea9 feat(desktop): ⌘-click closes a terminal rail tab
Same gesture the pane tabs already carry — isMetaClose hoists from
pane-tab.tsx into lib/middle-click.ts beside its sibling middle-click
gesture, so the two surfaces share one predicate instead of drifting.
2026-08-01 02:15:02 -05:00
Brooklyn Nicholson ba75633334 fix(desktop): never show the thinking indicator anywhere but the thread tail
A turn that ended without its message.complete (turn crash, reconnect gap,
steer race) left its streaming bubble pending:true forever. The next user
message then landed after it, stranding a live dither indicator
mid-transcript.

Three layers:
- session.info running=false (the agent loop's finally-block signal, the
  only settle edge those paths still emit) now finalizes the streaming
  bubble via the same math as Stop.
- A fresh submit settles any leftover pending bubble before appending the
  new user message, and drops a stale streamId so the new turn seeds fresh.
- AssistantMessage renders the loading/stall indicator only on the thread's
  last message, so no upstream state bug can ever paint one mid-transcript.
2026-08-01 02:10:56 -05:00
kshitij 151e72a5fc refactor: simplify pairing check return and drop over-defensive getattr
Follow-up cleanup from /simplify-code review:
- Replace 'if X: return True / return False' with 'return X'
- Replace 'getattr(source, "chat_type", None) or ""' with 'source.chat_type'
  (SessionSource.chat_type is a non-optional str field)
2026-08-01 12:38:38 +05:30
xxxigm 29b3adf902 test(telegram): cover allowlist + unauthorized_dm_behavior pair pass-through
Guard the early-auth pairing gap: unknown DMs must reach the gateway when
pairing is the effective unauthorized-DM behavior, while ignore mode and
unauthorized group senders stay rejected.
2026-08-01 12:38:38 +05:30
xxxigm dae4cf6bb6 fix(telegram): let pairing-bound DMs past early auth with allowlist
The #40863 intake prefilter rejected unauthorized DMs whenever an allowlist
existed, so gateway pairing never ran even when the operator set
telegram.unauthorized_dm_behavior: pair (which must win over the #9337
allowlist silence default). Pass those DMs through; groups stay blocked.
2026-08-01 12:38:38 +05:30
kshitijk4poor e5ba319a5c chore: add contributor email mappings for @Prontsevich 2026-08-01 12:37:17 +05:30
brooklyn! 18627ff009 Merge pull request #75931 from NousResearch/bb/tui-slash-priority
The TUI slash menu leads with the skills you actually use
2026-08-01 02:01:44 -05:00
kshitij 3572d4bca1 fix(mcp): ensure MCP discovery completes before agent build in non-interactive sessions
Non-interactive sessions (hermes chat -q, hermes -z) snapshot the tool
registry at AIAgent construction time. If background MCP discovery hasn't
finished, MCP tools are invisible for the entire session — and unlike
interactive mode, there is no between-turns late-binding refresh to recover.

Root cause: wait_for_mcp_discovery() only joins an already-created discovery
thread, so it no-ops if a direct/single-query path reaches agent construction
before MCP startup created that thread. Oneshot._run_agent() didn't call it
at all.

Fix:
- Add ensure_mcp_discovery_before_agent_build() helper to mcp_startup.py:
  idempotently starts discovery if needed + bounded wait. Fail-open on errors.
- Add single_query parameter to _resolve_discovery_timeout/wait_for_mcp_discovery:
  uses mcp_single_query_discovery_timeout (default 15s) instead of the
  interactive mcp_discovery_timeout (1.5s) because one-shot sessions have no
  second turn to recover.
- Wire into CLI _init_agent (single_query from _single_query_mode flag set
  in cli.py's single-query path) and oneshot._run_agent (single_query=True).
- Interactive sessions unchanged: keep 1.5s bound (between-turns refresh covers).

Closes #38448, #51316, #37013, #68137
Composite salvage of #60017 (chrishart0), #51322 (Bartok9), #38620 (buptwz),
#43544 (halonke), #36882 (vanhoof).
2026-08-01 12:27:31 +05:30
brooklyn! d1c40a731d Merge pull request #75949 from NousResearch/bb/fix-copy-with-reactions
fix(desktop): copy selected chat text again
2026-08-01 01:56:43 -05:00
Brooklyn Nicholson 90e6fe4f55 fix(desktop): terminal selection mirror yields to chat copy
mirrorSelection called textarea.select() whenever xterm had a scrap,
which replaced any chat highlight so ⌘C copied the wrong thing. Only
claim the document selection while the terminal is focused and nothing
outside it is highlighted.
2026-08-01 01:49:38 -05:00
Brooklyn Nicholson 1d97c035ef fix(desktop): drag-select and ⌘C work on user bubbles again
User bubbles are buttons, so the global user-select:none rule killed
text selection. Right-click-to-react and click-to-edit also ate a live
highlight. Prefer selection when one exists.
2026-08-01 01:49:38 -05:00
brooklyn! 85148f79f7 Merge pull request #75937 from NousResearch/bb/win-icon-size
fix(desktop): make the Windows app icon match native icon size
2026-08-01 01:41:24 -05:00
webtecnica 4be138eb03 fix(config): skip URL alias without extra_headers instead of returning early (#74465)
get_custom_provider_extra_headers() was returning the result of
normalize_extra_headers() on the first matching base_url, even when
that entry had no extra_headers configured. A later providers.<name>
entry sharing the same URL but with headers set was therefore ignored.

Fix: store the normalized headers and only return when non-empty,
otherwise continue searching the remaining entries.

Fixes #74465
2026-07-31 23:33:12 -07:00
Baophan00 595be544c0 test(config): add regression tests for broken-YAML config preservation
Verify that set_config_value and unset_config_value refuse to write
when config.yaml contains YAML syntax errors, and the original file
is left intact.
2026-07-31 23:33:12 -07:00
Baophan00 df09a90cd6 fix(config): refuse to write when config.yaml has YAML syntax errors
set_config_value() and unset_config_value() silently replaced the
entire config with an empty dict when config.yaml could not be
parsed. A single YAML syntax error would cause 'hermes config set'
to wipe all settings and write only the new key. Now exits with
error and preserves the existing file.
2026-07-31 23:33:12 -07:00
Brooklyn Nicholson e524310118 fix(desktop): render the ico truly full-bleed
The first regeneration kept a ~5% transparent margin around the icon
plate (94.9% coverage). Windows expects the plate itself to be the icon
edge — scale the artwork's rounded plate to span the canvas exactly.
2026-08-01 01:33:05 -05:00
brooklyn! 41e55679ee Merge pull request #75848 from NousResearch/bb/toggle-terminal-persist
Toggle any pane wherever you put it, and keep the header hidden
2026-08-01 01:32:36 -05:00
brooklyn! d41d9e4faa Merge pull request #75935 from NousResearch/bb/card-retire
The changed-files card stops at five rows
2026-08-01 01:31:30 -05:00