Commit graph

2823 commits

Author SHA1 Message Date
nesquena-hermes
d8b82e274a docs: stamp PR #2773 in CHANGELOG entry 2026-05-22 23:17:47 +00:00
nesquena-hermes
12becd1f4b fix(chat): rename _inflightStateLimits() to _getInflightStateLimits() to fix v0.51.117 collision
Closes #2771.

v0.51.117 (PR #2766) introduced a top-level function _inflightStateLimits()
in static/ui.js that collided with the window._inflightStateLimits config
object set in static/boot.js. Because top-level function declarations in
classic (non-module) scripts attach to window, boot.js's assignment
overwrote the function reference, and every later _inflightStateLimits()
call threw TypeError. _compactInflightState() runs on every send(), so
no new chat session could be created — v0.51.117 is effectively unusable.

Reported by @jahilldev, with multiple users (@isma3iloiso, @theDanielJLewis,
@JHVenn) confirming the bug or reverting to v0.51.116.

Fix: rename the function to _getInflightStateLimits() — the window-attached
config key stays under its original name (unchanged for any downstream
code that reads it). Updates all 4 call sites in static/ui.js.

Tests:

  - Update tests/test_inflight_storage_quota.py — the existing test
    asserted 'function _inflightStateLimits()' in UI_JS as a positive
    presence check, which certified the bug. Now asserts the renamed
    function name is present AND the old colliding name is absent AND
    no stale call sites remain.
  - Add tests/test_window_function_collision.py — generalized regression
    that scans every static JS file for top-level function declarations
    whose name also appears as the target of 'window.X = {...}' or
    'window.X = <number>'. This is the exact shape that broke #2715
    (_pinnedSessionsLimit in v0.51.106) and #2771. Test fails with a
    precise diagnostic naming the file and symbol if the bug class
    returns. Confirmed test FAILS on current master (unfixed) and PASSES
    on this branch.

Verified end-to-end against the live browser before commit:
  - typeof window._inflightStateLimits === 'object' (config preserved)
  - typeof window._getInflightStateLimits === 'function'
  - _getInflightStateLimits() returns the limits object
  - saveInflightState() persists to localStorage without throwing

Full pytest suite: 6308 passed, 6 skipped, 3 xpassed, 8 subtests passed.
Opus advisor: SHIP.
2026-05-22 23:17:00 +00:00
nesquena-hermes
f930260157
Merge pull request #2770 from nesquena/release/stage-pr2766
Release CO: v0.51.117 (stage-pr2766 — 1-PR — in-flight recovery storage quota-safe)
2026-05-22 12:56:45 -07:00
nesquena-hermes
aa0c7b7144 Stamp CHANGELOG for v0.51.117 (Release CO / stage-pr2766 / 1-PR) 2026-05-22 19:53:05 +00:00
george-andraws
b2477974c5 fix: make in-flight recovery storage quota-safe 2026-05-22 19:49:20 +00:00
nesquena-hermes
a9acafb918
Merge pull request #2769 from nesquena/release/stage-pr2676
Release CN: v0.51.116 (stage-pr2676 — 1-PR — per-skill enable/disable toggle in Skills panel, CLI-parity, Telegram-approved)
2026-05-22 12:47:44 -07:00
nesquena-hermes
e42e3e59b2 Stamp CHANGELOG for v0.51.116 (Release CN / stage-pr2676 / 1-PR) 2026-05-22 19:43:27 +00:00
lucasrc
35adc3a473 feat: add per-skill enable/disable toggle in Skills panel
Original PR: #2676 by @lucasrc

Adds POST /api/skills/toggle endpoint that flips skills.disabled in
config.yaml, and a UI toggle in the Skills panel that shows all skills
(including disabled ones) with a per-skill on/off control.

- Backend: new endpoint validates skill exists in filesystem before
  toggling. Read-modify-write wrapped in _cfg_lock for thread safety.
  Writes through to platform_disabled.webui when present.
- Frontend: each skill-item now has a toggle switch; disabled skills
  appear muted but still listed (previously they were filtered out).
- i18n: new toggle keys translated across all 9 non-English locales.
- Tests: round-trip test for disabled list normalization + toggle
  endpoint behavior.

Squash-merged from contributor's branch (19 commits + 1 merge commit)
onto current master via the cherry-pick-stale-contributor-prs procedure.
2026-05-22 19:43:00 +00:00
nesquena-hermes
d833e11fda
Merge pull request #2765 from nesquena/release/stage-pr2731
Release CM: v0.51.115 (stage-pr2731 — 1-PR — clarify prompt collapse/expand with chevron-icon polish, Telegram-approved)
2026-05-22 11:21:51 -07:00
nesquena-hermes
56b2f58259 Stamp CHANGELOG for v0.51.115 (Release CM / stage-pr2731 / 1-PR) 2026-05-22 18:15:39 +00:00
nesquena-hermes
057ae7da53 Polish: chevron icon toggle + fix collapsed-card edge clip
- Replace text 'Collapse'/'Expand' button labels with Lucide chevron SVG
  icons (chevron-down expanded → click to collapse, chevron-up collapsed
  → click to expand). Matches the iconographic design language of the
  rest of the chrome (composer buttons, sidebar controls).
  ARIA label + title attributes carry the same semantics for assistive
  tech, so no accessibility regression vs. the text labels.

- Fix collapsed-card edge clipping at viewport bottom. Original
  .clarify-card { bottom: -24px } was sized for the expanded card
  (300-420px tall); adding a 72px collapsed variant pushed the header
  below the parent's visible region. Override bottom to 8px and reduce
  inner padding for the collapsed state so the entire header sits cleanly
  inside the viewport at both desktop and mobile sizes (verified card
  fits with ~115px margin desktop / ~125px margin mobile).

Per Nathan's 2026-05-22 UX feedback on the screenshot package.
2026-05-22 18:14:48 +00:00
Michael Lam
581da3da2b feat: make clarify prompt collapsible 2026-05-22 17:45:40 +00:00
nesquena-hermes
e08eecefb1
Merge pull request #2764 from nesquena/release/stage-407
Release CL: v0.51.114 (stage-407 — 1-PR — update-check recovery from remote re-tags)
2026-05-22 10:38:18 -07:00
nesquena-hermes
60f146afe6 Stamp CHANGELOG for v0.51.114 (Release CL / stage-407 / 1-PR) 2026-05-22 17:32:26 +00:00
nesquena-hermes
15afabf849 docs: stamp PR #2758 in CHANGELOG entry 2026-05-22 17:25:54 +00:00
nesquena-hermes
0703a07654 fix(updates): pass --force to git fetch --tags to recover from remote re-tags
Without --force, git fetch origin --tags refuses to overwrite divergent
local tags and returns 'would clobber existing tag', jamming the entire
WebUI update path indefinitely. The WebUI is a release-tracking consumer
that never pushes tags, so it should always defer to whatever the remote
says a release tag points to. Add --force to all three fetch-tag call
sites:

  - _check_repo (the 'Check now' button + periodic check)
  - apply_force_update (force-reset to remote HEAD)
  - apply_update (stash + pull --ff-only)

Tests:

  - Updated 3 existing tests in test_updates.py whose fake_git mocks
    asserted the exact ['fetch', 'origin', '--tags'] args list.
  - Updated 1 existing test in test_update_banner_fixes.py that asserted
    the same shape for apply_update.
  - Added 4 new regression tests:
      - test_check_repo_fetches_tags_with_force
      - test_apply_force_update_fetches_tags_with_force
      - test_apply_update_fetches_tags_with_force
      - test_check_repo_recovers_from_remote_retag (end-to-end,
        proves the bare --tags fetch shape is no longer used)

Closes #2756.
2026-05-22 17:25:54 +00:00
nesquena-hermes
92d144ba00
Merge pull request #2763 from nesquena/release/stage-406
Release CK: v0.51.113 (stage-406 — 1-PR — composer model picker lag fix + hard-refresh recovery)
2026-05-22 10:18:17 -07:00
nesquena-hermes
aaa06c1574 Stamp CHANGELOG for v0.51.113 (Release CK / stage-406 / 1-PR) 2026-05-22 17:13:44 +00:00
Frank Song
53f294dc8d Fix composer model picker opening lag 2026-05-22 16:58:55 +00:00
nesquena-hermes
73fe8f24c9
Merge pull request #2760 from nesquena/release/stage-405
Release CJ: v0.51.112 (stage-405 — 1-PR — session model authoritative across restore)
2026-05-22 09:57:40 -07:00
nesquena-hermes
d56cd6a312 Stamp CHANGELOG for v0.51.112 (Release CJ / stage-405 / 1-PR) 2026-05-22 16:54:23 +00:00
ai-ag2026
ef9fe1dfdb fix(chat): keep model fallback guard before session option repair 2026-05-22 16:50:17 +00:00
ai-ag2026
ebcf0dabb5 fix(chat): keep restored session model visible on hard refresh 2026-05-22 16:50:17 +00:00
ai-ag2026
765e5aa091 fix(chat): hydrate restored session model on boot 2026-05-22 16:50:17 +00:00
ai-ag2026
6bcc9689aa fix(chat): keep new session model authoritative 2026-05-22 16:50:17 +00:00
ai-ag2026
af1ee81f06 fix(chat): resolve session model before activating 2026-05-22 16:50:17 +00:00
nesquena-hermes
a323f49f14
Merge pull request #2759 from nesquena/release/stage-404
Release CI: v0.51.111 (stage-404 — 1-PR — keep state.db replays out of sidecar tail)
2026-05-22 09:49:28 -07:00
nesquena-hermes
9f4c7adbb1 Stamp CHANGELOG for v0.51.111 (Release CI / stage-404 / 1-PR) 2026-05-22 16:46:12 +00:00
ai-ag2026
dd07334d6c fix(session): keep state db replays out of sidecar tail 2026-05-22 16:25:10 +00:00
nesquena-hermes
0de3eccdc3
Merge pull request #2757 from nesquena/release/stage-403
Release CH: v0.51.110 (stage-403 — 2-PR batch — default personality from config + sort configured providers to top)
2026-05-22 09:24:28 -07:00
nesquena-hermes
4da2a8e21a Stamp CHANGELOG for v0.51.110 (Release CH / stage-403 / 2-PR batch) 2026-05-22 16:20:41 +00:00
fxd-jason
84ef8a63a6 fix: remove xai from has_key test (CI env has no XAI_API_KEY) 2026-05-22 16:13:46 +00:00
fxd-jason
7e556614f9 test: add sort order tests for providers and model picker 2026-05-22 16:13:46 +00:00
fxd-jason
56575bd393 feat: sort configured/custom providers to top in model picker and settings 2026-05-22 16:13:46 +00:00
s010mn
4153a47d0f feat: new_session() reads display.personality from config as default
When display.personality is set in config.yaml (e.g. personality: taleb),
new sessions now inherit it automatically instead of starting with
personality=None and requiring an explicit /personality command.

This makes the selected personality sticky across new conversations rather
than requiring per-session activation.

Behavior:
- display.personality values 'none', 'default', 'neutral', '' are treated
  as no personality (personality=None), matching TUI gateway semantics.
- Config read is wrapped in try/except — if it fails, personality falls
  back to None (no crash, no regression).
- Case-insensitive: 'Taleb' normalizes to 'taleb'.

The /personality slash command still works for per-session overrides as
before; this change only affects the initial default.
2026-05-22 16:13:33 +00:00
nesquena-hermes
6498043b5e
Merge pull request #2755 from nesquena/release/stage-402
Release CG: v0.51.109 (stage-402 — 2-PR batch — sidebar action menu click stability + chat panel sidebar resync after navigation)
2026-05-22 09:07:30 -07:00
nesquena-hermes
0a1e22d0a2 Stamp CHANGELOG for v0.51.109 (Release CG / stage-402 / 2-PR batch) 2026-05-22 15:55:12 +00:00
nesquena-hermes
cab60bcd3b Stage 402: PR #2736 2026-05-22 15:51:12 +00:00
nesquena-hermes
78233e91dc Stage 402: PR #2741
# Conflicts:
#	CHANGELOG.md
2026-05-22 15:51:09 +00:00
nesquena-hermes
05c63b5611
Merge pull request #2754 from nesquena/release/stage-401
Release CF: v0.51.108 (stage-401 — 4-PR batch — session-index dedup + update-check redaction + handoff-summary sqlite leak + RuntimeAdapter Slice 4d gate docs)
2026-05-22 08:39:18 -07:00
nesquena-hermes
46661ede93 Stamp CHANGELOG for v0.51.108 (Release CF / stage-401 / 4-PR batch) 2026-05-22 15:29:02 +00:00
nesquena-hermes
bbaa4f83e3 Stage 401: PR #2744
# Conflicts:
#	CHANGELOG.md
2026-05-22 15:22:30 +00:00
nesquena-hermes
d71b8977d6 Stage 401: PR #2742 2026-05-22 15:22:01 +00:00
nesquena-hermes
742c9cbd9b Stage 401: PR #2730 2026-05-22 15:21:32 +00:00
nesquena-hermes
cc2d41d7c7 Stage 401: PR #2738 2026-05-22 15:21:32 +00:00
Michael Lam
bda91a6edd docs(runtime): define runner route gate 2026-05-22 04:08:59 -07:00
Isla-Liu
5b41f03a92 fix(webui): close sqlite3 connections in handoff-summary path (#2233)
Two functions on the /api/session/handoff-summary hot path were opening
sqlite3.connect(...) inside a bare `with` statement, which commits the
transaction at scope exit but does NOT close the connection. Per-turn
invocations accumulated state.db / state.db-wal file descriptors and
CPython heap pages on long-lived worker threads, surfacing as the
multi-GB VmRSS and 6x duplicated state.db fds observed on the live
instance (D0 pre-restart baseline: VmRSS 1,334,248 kB, 55 fds; cold
baseline after restart: VmRSS 136,668 kB, 10 fds).

Wrap both call sites with contextlib.closing(...) (already imported and
used at seven other sites in the same files) so the connection is
closed deterministically:

  - api/models.py :: count_conversation_rounds
  - api/routes.py :: _persist_handoff_summary_to_state_db

Regression test:
  tests/test_issue2233_sqlite_connection_leak.py loops both functions
  20 times against a tmp state.db and asserts /proc/<pid>/fd count
  does not grow more than 2. Linux-only via sys.platform skip.

D1 live soak against a freshly-built worktree server (port 8799,
isolated HERMES_HOME / HERMES_WEBUI_STATE_DIR) hitting
/api/session/handoff-summary 20 times:

  fd_before      = 5
  fd_after       = 5     (growth 0, threshold < 5)
  vmrss_before   = 52636 kB
  vmrss_after    = 52636 kB  (growth 0 kB, threshold < 30 MB)

The patched fix curve trends below the leak curve.

Rollback: single git revert <this-sha> reverts both file edits.

Refs #2233.
2026-05-22 18:34:06 +08:00
ai-ag2026
dabe7b0ea5 fix(session): keep conversation actions menu clickable 2026-05-22 12:21:10 +02:00
wdzhou
a4e6ffccd9 fix(session): deduplicate _write_session_index full rebuild entries by session_id
The full rebuild path scans SESSION_DIR via glob('*.json') and appends every loaded session to a plain list without deduplicating by session_id. When old-format session_*.json files coexist alongside WebUI-format xxx.json files (both sharing session_id), the index gets duplicate entries, causing frontend Vue key crashes.

Fix: use dict[session_id -> compact_entry] to naturally deduplicate.
2026-05-22 18:02:49 +08:00
wdzhou
16f9887846 fix(session): deduplicate _write_session_index full rebuild by session_id
The full rebuild path of _write_session_index scans SESSION_DIR via
glob('*.json') and appends every loaded session to a plain list without
deduplicating by session_id. When old-format session_*.json files coexist
alongside WebUI-format xxx.json files (both sharing the same session_id),
the same session appears multiple times in the index, causing frontend
Vue key collisions and a blank page.

Fix: use dict[session_id -> compact_entry] to naturally deduplicate.
Prefer the entry with the larger message_count when conflicts arise.
2026-05-22 16:13:42 +08:00