Commit Graph

22834 Commits

Author SHA1 Message Date
Vladimir Rogozhin Assistant 101dfd782d Add PageUp and PageDown keybind support 2026-08-15 01:04:06 -07:00
BAS Lam 13acf7759e fix(desktop): remove session.new keybind hijacking typed N keys
The session.new default keybindings included 'mod+n' and 'shift+n',
which fired when users typed uppercase N (Shift+N) or accidentally
pressed Ctrl+N while using an IME to type Chinese — silently creating
new sessions mid-conversation.

Remove both combos so New Session is only reachable via the sidebar
button. Ctrl+Shift+N (session.newWindow) is unaffected.
2026-08-15 01:04:06 -07:00
Teknium 1c9de87ffb fix: drop duplicate composingRef declaration from rebase weave 2026-08-15 01:03:57 -07:00
Teknium 32efae941f chore: map contributor emails for salvaged commits 2026-08-15 01:03:57 -07:00
Teknium 89df391580 fix(desktop): keybinds skip IME composition keys; pin draft survival across Settings navigation
- use-keybinds: bail out of the global keydown dispatcher while an IME
  composition is active. Windows Chinese IMEs use Ctrl+, as their
  punctuation toggle, which also matched nav.settings and navigated away
  mid-word — destroying the unsent composer draft (#41079).
- use-composer-draft.test: regression-pin the unmount-stash/remount-restore
  contract so route navigation (Settings) can never again drop an unsent
  draft with the React tree.
- use-composer-actions.test: the bounded-preview pipeline keys attachments
  to the durable path and resolves thumbnails asynchronously; the dropped-
  screenshot test now asserts the durable-path contract instead of the
  retired full-res previewUrl field.
2026-08-15 01:03:57 -07:00
Jakub Wolniewicz d0642e61e5 fix(desktop): preserve legacy attachment replacements 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz e2a2fba4aa fix(desktop): preserve in-flight attachment replacements 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 462e7d8e6b fix(desktop): preserve tile attachment ownership 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 193c199b23 fix(desktop): make image attachment updates occurrence-safe 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 7d5e9757ec fix(desktop): preserve image preview ownership across drafts 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 6dfe63e1e2 fix(desktop): preserve image attachment occurrence ownership 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz 5e09b4008b fix(desktop): serialize composer image previews 2026-08-15 01:03:57 -07:00
Jakub Wolniewicz d345f0831f fix(desktop): bound multi-image thumbnail raster cost 2026-08-15 01:03:57 -07:00
David Metcalfe 485e1e8f69 fix(desktop): keep full-res previewUrl separate from downscaled thumbnail
Addresses review feedback on #68744: downscaling inside
attachmentPreviewDataUrl made previewUrl the 2048px PNG, but current main
(d8cb73b4ab) feeds that field to ImageLightbox and useImageDownload, so
large attachments would open and download at reduced resolution.

- attachmentPreviewDataUrl returns the full-resolution data URL again
- ComposerAttachment gains thumbnailUrl?: string (store/composer.ts)
- attachImagePath stores previewUrl (full-res) + thumbnailUrl (downscaled)
- Attachment pill renders thumbnailUrl ?? previewUrl; lightbox/download
  keep the full-res previewUrl
- optimisticAttachmentRef prefers thumbnailUrl for the in-flight bubble
  display ref (same main-thread decode freeze at send time)
- Attachment-level regression test in use-composer-actions.test.ts:
  full-res previewUrl preserved while thumbnailUrl is a separate
  downscaled value (4000x3000 -> 2048x1536)
2026-08-15 01:03:57 -07:00
David Metcalfe 24fc613318 fix(desktop): downscale large images in composer preview to prevent UI freeze 2026-08-15 01:03:57 -07:00
Johnny 0d447712a0 fix(desktop): keep composer stable across compression 2026-08-15 01:03:57 -07:00
Gille 224530211e fix(desktop): let composer status titles use row width 2026-08-15 01:03:57 -07:00
xrwang8 47970a9e4c fix(desktop): clear edit-composer submitting latch after 200ms cooldown
- Add submitting state + IME composition guard to prevent double-submit
- submitEdit() sets latch, then clears after 200ms timeout
- handleKeyDown() guards on composing so IME Enter doesn't submit
- Shift+Enter inserts newline without submitting
- Test validates Enter calls onEdit, latch clears for second session

Fixes #70771

Signed-off-by: xrwang8 <xrwang8@gmail.com>
2026-08-15 01:03:57 -07:00
mollusk 652ae4877a fix(desktop): let the edit composer scroll long prompts
The inline edit composer caps height at max-h-48 but had no overflow
rule, so long prompts were clipped with no way to reach the tail.
Add overflow-y-auto to match the main composer editor.
2026-08-15 01:03:57 -07:00
Lester Liang 49fbca1880 fix(desktop): prevent accidental composer popout 2026-08-15 01:03:57 -07:00
Denis 354abd7c57 fix(desktop): keep empty composer from collapsing
Fixes #68134\nFixes #68095
2026-08-15 01:03:57 -07:00
infinitycrew39 2a82c6ffbc style(desktop): align empty-state width with full chat column 2026-08-15 01:03:57 -07:00
infinitycrew39 006a4a8873 fix(desktop): restore full-width chat composer column 2026-08-15 01:03:57 -07:00
Teknium f2a30fa400 test: derive lost-and-found synthetic width from the live schema
The mapper test pinned the sessions column count (54, then 55, then 56
within one week as git_metadata_generation and the hidden flag landed).
Every ordinary column addition broke it. Derive max_fields from
PRAGMA table_info at runtime with a >= floor so the test keeps asserting
the rebuild contract without change-detecting the schema width.
2026-08-15 00:55:20 -07:00
hermes-seaeye[bot] af585de28e fmt(js): npm run fix on merge (#86801)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-15 07:46:57 +00:00
Teknium 0930133dbf chore: map contributor email for PaulBlackSwan 2026-08-15 00:37:00 -07:00
Teknium 03c85a1c7c test(desktop): port gateway-file-download tests to vitest electron project
The salvaged branch predates the electron test project's node:test -> vitest
migration (test:desktop:platforms is now `vitest run --project electron`).
Import `test` from vitest so the suites are collected; assertions stay on
node:assert/strict per the existing electron test convention.
2026-08-15 00:37:00 -07:00
Paul Armbruster 27b003996a fix(desktop): stream native gateway downloads + 404 data-url fallback
Addresses both review findings on the remote-gateway download PR:

1. Unbounded buffering (finding #1). fetchBuffer / fetchBufferViaOauthSession
   accumulated the entire response (then copied it again via Buffer.concat)
   before saveGatewayFile even opened the save dialog, so a large gateway file
   could exhaust the native process. Both auth paths now stream: once response
   headers arrive the connect timeout is cleared, the filename is derived, the
   save dialog is shown, and the body is piped to the chosen destination with
   backpressure. A read/write error tears down the stream and unlinks the
   partial file. The byte-moving, data-URL decoding, and filename/path helpers
   are extracted into gateway-file-download.ts so they're unit-testable without
   Electron.

2. No fallback for older gateways (finding #2). saveGatewayFile required the new
   /api/fs/download route. Desktop and the remote gateway update independently,
   so a gateway predating this PR 404s. Added a 404-only compatibility fallback
   to the existing capped /api/fs/read-data-url route (bounded, so it only
   serves smaller files — enough to keep older backends working).

Tests: gateway-file-download.test.ts covers streaming, backpressure,
error-cleanup (unlink on write/response error), data-URL decoding, filename
derivation (incl. traversal reduction), and 404 detection;
gateway-file-download-transport.test.ts asserts both transports stream (no
whole-body Buffer.concat) and that the 404 fallback is wired. Both registered
in the desktop platform test list. Server-side /api/fs/download tests
(streaming + sensitive-file reject) already pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 00:37:00 -07:00
Paul BlackSwan d5a865882a fix(desktop): save remote gateway files natively 2026-08-15 00:37:00 -07:00
Dean Chen 0bc7070cf8 fix(desktop): gate image copy on decoded contents 2026-08-15 00:36:15 -07:00
Dean Chen 3c570c61ff fix(desktop): preserve copy image for large remote media 2026-08-15 00:36:15 -07:00
Teknium aa27edd971 chore(contributors): map jackoconner55@icloud.com 2026-08-15 00:36:03 -07:00
Teknium 46fe44fa8d test(cli): stub portable-MCP lookup in completer read test; bound resolve_toolset memo
- The readonly-loader completer test now stubs
  get_portable_mcp_server_names_nowait — real plugin discovery runs
  load_config() during one-time process init, which is not the
  per-keystroke read the test guards against.
- Cap _resolve_toolset_memo at 256 entries: generation-keyed entries
  from stale generations are never hit again, so clear on overflow to
  keep long sessions bounded.
2026-08-15 00:36:03 -07:00
jackoconner55 2f54ad4023 perf(cli): skip launcher-side plugin discovery for TUI handoff
(cherry picked from commit e2e0edd6b8ec10d02e867ff11dd1f9b4961a1ba7)
2026-08-15 00:36:03 -07:00
spfcraze b9f7525a1b test(agent): pin measured-work regression for display-flag config cache
(cherry picked from commit 57a7044c20965150e74b0ecadb2550f0ed81ee8e)
2026-08-15 00:36:03 -07:00
spfcraze e25cafc83e perf(agent): cache per-turn display-flag config reads
_file_mutation_verifier_enabled and _turn_completion_explainer_enabled
re-read config.yaml on every call via load_config() (~1ms deepcopy per
call). finalize_turn runs these gates at the end of every turn, so each
turn paid two redundant config deepcopies. The sibling
_credits_notices_enabled already caches on self; mirror that pattern.

The env-var override stays authoritative and uncached, so runtime flips
still work. Config flips now apply on the next session, matching the
documented sibling semantics.

(cherry picked from commit f2d0e00d71ba35fa3a53cd41076261e3c5feb45d)
2026-08-15 00:36:03 -07:00
spfcraze 7b45d1d049 perf(toolsets): memoise resolve_toolset keyed on registry generation
resolve_toolset() recursively walks toolset includes and, with
include_registry=True, merges registry-registered tools on every call —
each external call re-runs the includes walk and takes a fresh registry
snapshot under the registry lock. It is called dozens of times per
_get_platform_tools() (every /tools completion keystroke, per picker
render) and at 19 call sites across the CLI/gateway.

Memoise the external-entry result keyed on (name, include_registry,
registry id, registry generation). tools.registry exposes a monotonic
_generation counter bumped on every register/deregister/alias/MCP
refresh (its docstring explicitly invites generation-keyed memoisation),
so a cache entry is valid until the registry changes. External callers
never pass visited, so the memo engages exactly at the public entry
and the internal cycle-detection recursion is untouched.

Measured: _get_platform_tools drops 165us -> 59us per call (3x) with the
xAI credential fix simulated; /tools completion ~2ms -> ~77us/keystroke
combined. Regression tests: repeat resolution is a memo hit (get_toolset
called once), a generation bump forces a fresh resolve, and the memoised
result is identical to a fresh resolution.

(cherry picked from commit 3d36ecb273f6652d00556a7b9d846c08047801aa)
2026-08-15 00:36:03 -07:00
spfcraze 2a5093eeff docs(auth): note get_default_hermes_root in the global-store memo docstring
(cherry picked from commit 64de1a13f967c5735d861b972fe6c65e89f5af17)
2026-08-15 00:36:03 -07:00
spfcraze 47400fe2af test: make memo pins pre-fix-safe (raising=False resets)
(cherry picked from commit 4822daed5d9238348c50bfbdf8c3c4795adc4986)
2026-08-15 00:36:03 -07:00
spfcraze 4bd746c6e9 perf(cli): memoise default-hermes-root resolution and global auth-store read
get_default_hermes_root() resolves HERMES_HOME against the platform
native home (~80us of path resolution) on EVERY call and is called at
31+ sites — every _load_global_auth_store() (per provider row in the
/model picker), kanban, backup, gateway, update. Its result depends
only on (HERMES_HOME, native home), so memoise it keyed on those two
inputs, compared for free on each call (freshness-correct even if a
test or plugin mutates HERMES_HOME mid-process).

_load_global_auth_store() re-read + re-parsed the global auth.json on
every call; read_credential_pool() -> load_pool() runs it once per
provider row in the /model picker even when the profile has entries and
the global fallback never fires. Memoise keyed on the global auth
file's path+mtime (same pattern as _nous_auth_status_cache); the store
only changes when a global-scope auth write touches the file.

Measured (profile mode, 30-provider global store): get_default_hermes_root
81us -> 10us; _load_global_auth_store 128us -> 66us; load_pool 165us ->
137us per call — ~2ms saved per /model picker render (20 provider rows).

Regression tests: hermes_constants memo pin (no path resolution on
repeat calls, HERMES_HOME change forces a fresh resolution); global-store
memo pins (store read once across repeats, mtime bump re-reads once,
absent store stays cheap).

(cherry picked from commit be348f32e5bd7479c26fabb652de549fb9c8a1e1)
2026-08-15 00:36:03 -07:00
spfcraze 473490a2e9 perf(cli): stop per-keystroke config re-reads in slash completers
The /tools and /personality completers run on every keystroke while the
user types those commands (complete_while_typing), and both re-read +
re-parse the full config on each keypress:

- _tools_completions called load_config() — the defensive deepcopy
  (~340us/call on cache hit) even though it only reads toolset enable
  state + MCP server names. Switched to load_config_readonly() (the
  perf(agent) #74322 pattern; this per-keystroke site was missed).
- _personality_completions called load_cli_config() — a full YAML parse
  + deep merge of the built-in defaults (~110us) — on every keystroke.
  Memoised keyed on the config file path+mtime (same pattern as load_env
  / _nous_auth_status_cache), so the parse runs once per config state.

Measured: /tools 357us -> 18us per keystroke; /personality parse drops
from 1-per-keystroke to 1-per-config-change (500 keystrokes -> 1 parse).

Regression tests: _tools_completions uses the readonly loader (deepcopy
loader never called); personality memo parses once across repeated
completions and re-parses once after a config mtime change.

(cherry picked from commit 2b3f897171f93dc6b099848ec5ab3763b7294870)
2026-08-15 00:36:03 -07:00
Adolanium ee1731b13c perf(cli): re-export decomposed command modules lazily, ~60ms off every CLI start
The main.py decomposition re-exported the sessions/update/dashboard command
surface with eager from-imports, so every hermes invocation (including
hermes --version) paid for update_cmd's dependency chain (jwt, click,
cryptography). Resolve the re-exports through the existing PEP 562 module
__getattr__ (same pattern as _PROVIDER_MODELS) so each module loads on
first actual use. Internal call sites go through a _self() helper because
bare-name lookups do not trigger __getattr__; _self() imports sys locally
since update tests patch hermes_cli.main.sys. The
_warn_stale_dashboard_processes back-compat alias moves into the lazy
surface, and the sessions argparse dispatch defers sessions_cmd to call
time. Monkeypatching hermes_cli.main.<name> keeps working: a patch sets a
real module attribute, which shadows __getattr__.

Measured (Windows 11, Python 3.11, median of 7 warm runs):
import hermes_cli.main 253ms -> 196ms (-22%).

(cherry picked from commit cad1083b71635f98815b698d69a18f7f58e15517)
2026-08-15 00:36:03 -07:00
Teknium 8a22220ca8 chore: map contributor email (audit_pr_attribution) 2026-08-15 00:35:40 -07:00
DannyFengTianYu 07b9090259 fix(desktop): preserve complete history when branching
Read the durable display transcript when creating a branch instead of copying the compacted model projection. Hydrate the Desktop branch boundary from persisted history, avoid stale whole-chat counts, and seed the new tile from the backend snapshot. Add regression coverage for compacted histories, visible-message counts, selected prefixes, and hydration races.

(cherry picked from commit c3d2d759ae104395adde215b2e293ccf8e895684)
2026-08-15 00:35:40 -07:00
DannyFengTianYu b066f2b373 fix(history): isolate branch transcripts from parent updates
(cherry picked from commit 57c51cc401e867e0b315e051ba138d0b8f7a5f27)
2026-08-15 00:35:40 -07:00
Teknium 101740ed36 chore: map contributor email (audit_pr_attribution) 2026-08-15 00:35:28 -07:00
mj 73e9b1a124 docs(desktop): align getGateway docstring with $gateway atom subscription
Residual hunk from PR #84832's picker-rebind commit; the functional
surfaces.tsx change landed on main via #86250.

(cherry picked from commit 990936d6f6e752ddb474787c1f7fdc3ad4f4dc60, docstring hunk only)
2026-08-15 00:35:28 -07:00
mj 20e5d78f54 fix(desktop): recover failed gateway catalogs
(cherry picked from commit ef1d2cc5e541f37fbd12b41f686a4c18a9b85320)
2026-08-15 00:35:28 -07:00
mj 1ef7a0bdda fix(desktop): recover empty model catalogs
(cherry picked from commit 72a06b9e0b392c494be011dc74ccf948ca693e3f)
2026-08-15 00:35:28 -07:00
Teknium a654c52419 chore: map contributor email (audit_pr_attribution) 2026-08-15 00:35:16 -07:00