Commit graph

603 commits

Author SHA1 Message Date
AJV20
7efb4c5acb fix: scope skill toggles to active profile 2026-05-30 03:36:09 +00:00
AJV20
e68c670bba fix: keep agent cron sessions in project chips 2026-05-30 02:18:51 +00:00
Sanjays2402
ab4de3bb64 Fix #3103: stop emitting Connection: close on long-lived SSE streams
The gateway SSE handler (`_handle_gateway_sse_stream`) and session-events
SSE handler (`_handle_session_events_stream`) both set
`Connection: close` on the response. On long-lived SSE streams, browsers
(Chrome, Firefox, Safari) interpret that header as 'this is a one-shot
response, the EventSource lifecycle is over the moment the body ends'
and trigger an immediate reconnect when the server-side worker rotates.

The result, with EventSource's auto-reconnect on top, is a tight loop of
connect -> sessions_changed snapshot -> reconnect every ~1s that pegs
the worker, thrashes the session list, and prevents the in-app session
list from ever settling.

Removing the header lets the python BaseHTTPServer close the socket
naturally after the stream ends, which is what EventSource expects.

Regression introduced in 598fd4ff.
2026-05-30 02:02:40 +00:00
AJV20
b53bc1a2ea fix: harden webui passive performance paths 2026-05-30 01:39:54 +00:00
AlexeyDsov
da9211bf43 fix(session): copy all critical fields on duplicate and branch to prevent state loss 2026-05-30 01:19:50 +00:00
Harlan Zhou
39f09a5b7a fix: read skills disabled state from active profile 2026-05-29 23:21:48 +00:00
nesquena-hermes
11ba36beb9 Merge PR #3135 into stage-batch41
# Conflicts:
#	CHANGELOG.md
#	api/routes.py
2026-05-29 22:15:46 +00:00
nesquena-hermes
5c96764e3d Merge PR #3099 into stage-batch41
# Conflicts:
#	CHANGELOG.md
2026-05-29 22:13:08 +00:00
nesquena-hermes
3206aec3f3 Merge PR #3138 into stage-batch40
# Conflicts:
#	CHANGELOG.md
2026-05-29 22:01:06 +00:00
AJV20
3c2f98eb23 fix: polish WebUI assistant replies 2026-05-29 13:01:14 -04:00
AJV20
ce82ea3fdf fix: add joplin search auth compatibility 2026-05-29 12:36:27 -04:00
AJV20
35f89c4e5b fix: harden external notes guardrails 2026-05-29 12:30:04 -04:00
ai-ag2026
53f16c4ee6 fix: log WebUI shutdown diagnostics 2026-05-28 23:22:31 +02:00
nesquena-hermes
c1a7668bdf Merge PR #3091 2026-05-28 19:51:29 +00:00
Hermes Agent
b92204a7b7 fix(sidebar): keep newer continuation visible over older snapshot 2026-05-28 13:38:06 -06:00
AJV20
2cb3c9f10b Merge remote-tracking branch 'origin/master' into HEAD
# Conflicts:
#	CHANGELOG.md
#	tests/test_workspace_git.py
2026-05-28 14:50:33 -04:00
nesquena-hermes
88bdc99a49 Merge PR #3085
# Conflicts:
#	CHANGELOG.md
2026-05-28 18:27:03 +00:00
nesquena-hermes
0a2dabc730 stage-batch36: tighten #3064 MEDIA: token gate to non-user-role messages
Per Opus advisor on stage-batch36: skip role='user' messages in
_session_media_token_allows_image_path so a user-injected MEDIA: token
cannot mint an allow-list entry for the user's own request. Preserves
the original use case (assistant/tool emitted artifacts outside the
active workspace) while making the implicit threat model explicit.

Defense-in-depth — the single-user WebUI scope means same-origin user
input already had the same effective access, but multi-user / shared
WebUI deployments would benefit from the restriction.
2026-05-28 18:20:25 +00:00
ai-ag2026
1cd58f6f5a fix(session): preserve sidecar order in display merges 2026-05-28 19:58:17 +02:00
nesquena-hermes
a3fc305aeb Merge PR #3077
# Conflicts:
#	CHANGELOG.md
#	tests/test_webui_gateway_chat_backend.py
2026-05-28 17:47:56 +00:00
nesquena-hermes
1c89c7d327 Merge PR #3064
# Conflicts:
#	CHANGELOG.md
2026-05-28 17:47:35 +00:00
nesquena-hermes
4412aea9e8 Merge PR #3059
# Conflicts:
#	CHANGELOG.md
2026-05-28 17:47:34 +00:00
nesquena-hermes
921b94a287 Merge PR #3046
# Conflicts:
#	CHANGELOG.md
2026-05-28 17:47:34 +00:00
AJV20
60d4b2d990 fix: harden WebUI bugfix sweep 2026-05-28 13:38:50 -04:00
AJV20
8e6ed66815 fix: clarify gateway chat auth errors 2026-05-28 09:59:35 -04:00
AJV20
cbd3704a7f fix: preserve literal prefill script paths 2026-05-28 09:31:07 -04:00
AJV20
1b5e6f6fae fix: mirror WebUI prefill env for AI-recent notes 2026-05-28 07:19:31 -04:00
Frank Song
10573ab8aa Fix session media image rendering 2026-05-28 18:05:01 +08:00
ai-ag2026
ce59e7ca20 fix: defer stale stream repair for active workers 2026-05-28 09:33:40 +02:00
Frank Song
9190ab4449 Fix empty partial activity tail recency 2026-05-28 15:30:49 +08:00
AJV20
571bb1010d Merge remote-tracking branch 'origin/master' into webui-context-prefill-status
# Conflicts:
#	CHANGELOG.md
2026-05-28 00:57:34 -04:00
AJV20
83cc12b0bf feat: add optional gateway-backed WebUI chat (#3021)
Squash-merged pr-3021 into stage-batch34. Default-off bridge to Hermes Gateway API server via HERMES_WEBUI_CHAT_BACKEND=gateway.
2026-05-28 03:27:07 +00:00
AJV20
8757a8fbf6 fix: align AI-recent notes with WebUI prefill hook 2026-05-27 22:44:55 -04:00
nesquena-hermes
c1942a1cd8 fix(sessions): widen #3023 to all 5 session-id validators via shared is_safe_session_id helper
PR #3023 only updated Session.load() and Session.load_metadata_only(), leaving
three sibling validators (Session-internal _repair_stale_pending and the
/api/session/worktree/remove + /api/session/delete route handlers) still
gated on the old lowercase-only character set.  That would have shipped a
confusing UX where api-* and reachy-voice-* sessions could be loaded into
the sidebar but rejected with HTTP 400 on delete or worktree removal.

This commit factors the validation into a single is_safe_session_id helper
in api.models and updates all five call sites to use it.  Adds regression
coverage in tests/test_issue3023_safe_session_id_validators.py for both
the helper itself and a repo-wide guarantee that no narrow lowercase-only
magic string survives.

Closes the follow-up flagged by the parallel reviewer agent on #3023.
2026-05-28 02:09:05 +00:00
nesquena-hermes
7006bb5c7a Merge pull request #3008 2026-05-28 01:43:22 +00:00
nesquena-hermes
3560f37c8d Merge pull request #3027 2026-05-28 01:02:58 +00:00
ai-ag2026
9650b387fd fix: keep webui mirrored sessions out of cli filter 2026-05-28 00:51:12 +02:00
BonyFish
dc1e369f89 fix: remove webui-side writer to avoid conflict with agent
The .usage.json file is owned by hermes-agent (tools/skill_usage.py).
This change removes the webui-side increment logic to avoid:

1. File ownership conflict - both writing to same file
2. Schema mismatch - agent uses ISO strings, webui used floats
3. Concurrency issues - agent uses fcntl locks, webui had no locking
4. Double-counting - agent already increments counters server-side

Changes:
- api/skill_usage.py: keep only read_skill_usage(), remove increment functions
- api/streaming.py: remove skill usage counter hook
- api/routes.py: adapt response to pass through agent's format as-is,
  with defensive coercion for None values and metadata preservation
- tests/test_skill_usage.py: remove increment tests (17→7 cases)
2026-05-27 20:57:32 +08:00
BonyFish
3a676b704a feat: add skill usage counter to insights page
- Add api/skill_usage.py for .usage.json read/write with atomic updates
- Track skill_view (view_count), skill_manage (use_count), skill_patch
  (patch_count) invocations per SSE stream completion
- Add GET /api/skills/usage endpoint returning per-skill usage stats
- Render Skill Usage card in Insights panel after LLM Wiki card
- Add i18n keys for en, zh, zh-Hant, ja, it locales
- Add 17 tests covering read/increment logic, API route, and streaming hook
2026-05-27 14:44:28 +08:00
george-andraws
541c064a72 fix(session): route messaging metadata loads through display merge
Use _merged_session_messages_for_display for is_messaging_session even in the
metadata-only (messages=0) path. This ensures message_count and last_message_at
match the full load path for Telegram / external messaging sessions that have
stitched or duplicate rows in state.db + sidecar.

Prevents spurious refresh loops, scroll resets, and open panel closures when
resuming cross-surface sessions in the WebUI.

No impact on CLI, non-messaging, or full-message paths. All 580 session tests pass.

Fixes the root cause identified in the SessionDB / render interaction changes.
2026-05-26 22:05:58 -07:00
BonyFish
ea3d4ec0b3 feat(cron): dynamic delivery options from API instead of hardcoded select
Replace the hardcoded 4-option deliver dropdown (local/discord/telegram/slack)
with a dynamic select populated from a new GET /api/crons/delivery-options
endpoint that reads _KNOWN_DELIVERY_PLATFORMS from hermes-agent.

Key changes:
- Add GET /api/crons/delivery-options endpoint returning all known platforms
- Frontend loads options asynchronously on first cron form open, with caching
- Enable deliver editing for existing jobs (was previously disabled)
- Include deliver in update payload when editing cron jobs
- Fallback to local-only if API unavailable
- Custom deliver values (e.g. feishu:oc_xxx) shown with * suffix
- Add cron_deliver_custom i18n key to all 12 locales
- Add 5 integration tests for the new endpoint
2026-05-27 10:58:49 +08:00
nesquena-hermes
e0dfa0c586 Merge pull request #2792 2026-05-27 00:37:48 +00:00
nesquena-hermes
244258aa89 Merge pull request #2986 2026-05-27 00:19:00 +00:00
nesquena-hermes
f3fbb49ce4 Merge pull request #2983
# Conflicts:
#	CHANGELOG.md
2026-05-27 00:02:47 +00:00
nesquena-hermes
f061733c91 Merge pull request #2969
# Conflicts:
#	CHANGELOG.md
2026-05-27 00:02:32 +00:00
ai-ag2026
fceb255252 fix(session): anchor message windows on renderable tail rows 2026-05-26 23:37:08 +02:00
george-andraws
65cae8f1a5 Improve sidebar chat search highlighting 2026-05-26 14:17:59 -07:00
Frank Song
2ff73c9954 Document truncation watermark metadata intent 2026-05-26 20:13:01 +08:00
Frank Song
9db0d6869a fix: keep session switch metadata non-blocking 2026-05-26 16:40:35 +08:00
dobby-d-elf
ca9e821b5e Reduce session index churn on chat start 2026-05-25 16:25:23 -06:00
nesquena-hermes
0c6af12723 Merge pull request #2933
# Conflicts:
#	CHANGELOG.md
2026-05-25 17:48:05 +00:00
nesquena-hermes
c6869806f0 Merge pull request #2923
# Conflicts:
#	CHANGELOG.md
2026-05-25 17:47:23 +00:00
Frank Song
aa6daf82f2 fix(gateway): treat active sessions as configured 2026-05-25 21:48:51 +08:00
Frank Song
459286830b fix(session): preserve sidecar truncation boundary 2026-05-25 21:21:15 +08:00
MinhoJJang
d920d4222a Trim paginated session tool call payloads 2026-05-25 18:28:43 +09:00
Frank Song
dc86841547 fix: send joplin token in auth header 2026-05-25 08:51:44 +08:00
Roberto Villegas
a9ce2889af
fix(ui): hide reasoning chip when model lacks effort levels
Resolve supported reasoning efforts per active model/provider and pass
that context through /api/reasoning so Composer and other non-configurable
models no longer show a misleading effort picker.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 18:42:31 -06:00
Roberto Villegas
ef5eafcceb
fix(cursor-acp): route slash models and honor picker on new chat
Ensure cursor/composer IDs always resolve via @cursor-acp:, carry the
visible picker selection into POST /api/session/new, persist model
changes before a session exists, and evict cached agents on model switch.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 18:42:20 -06:00
nesquena-hermes
1c2d574882 Stage-batch14: Opus advisor SHOULD-FIX patches (UX + defense-in-depth)
Inline fixes for 4 of 5 Opus SHOULD-FIX items before tag:

1. /api/auth/status now gates passkeys_enabled / passwordless_enabled on
   _passkey_feature_flag_enabled() — when flag is off, status reports
   no credentials even if passkeys.json has legacy entries. New
   passkey_feature_flag field added to the response for the frontend.

2. Settings → System Passkeys block (passkeysSettingsBlock) now starts
   display:none and loadPasskeys() reveals it only when the server
   confirms passkey_feature_flag === true AND /api/auth/passkeys
   doesn't return {disabled: true}. Stops the broken-affordance trap
   where users would see Add passkey → click → 404.

3. /api/settings/save now refuses to set passwordless mode when the
   passkey feature flag is off. Closes the auth-bypass path Opus flagged:
   user goes passwordless while flag on → admin unsets flag → restart
   serves the WebUI fully unauthenticated.

4. CHANGELOG entries added for PR #2685 (replayed-context dedup +
   per-turn metering cap) and PR #2824 (Stop server affordance,
   relocated to Settings) — both PRs had functional changes but no
   release-notes entries. Also enriched the rate-limit detail on the
   #2739 entry (30 events / 60s / 4KB body cap).

Deferred to follow-up issue (#5 in Opus review):
- Live tool metering cumulative cap across many tool calls — non-trivial
  refactor of _bump_live_prompt_estimate, will be a separate PR
2026-05-25 00:26:40 +00:00
nesquena-hermes
46ed70bfde Stage-batch14: add HERMES_WEBUI_PASSKEY feature flag for #2859 passkey support
Per the stage-batch14 ship plan, passkey/WebAuthn support is shipped
opt-in default-off behind an explicit feature flag so deployments can
disable the entire surface (UI + endpoints + credential storage) without
needing to delete code.

Enable via either:
  - HERMES_WEBUI_PASSKEY=1 environment variable, OR
  - webui_passkey_enabled: true in config.yaml

With the flag off:
  - are_passkeys_enabled() returns False even if credentials exist
  - is_auth_enabled() falls back to password-only checking
  - /login renders password-only (no passkey button)
  - All 6 /api/auth/passkey/* endpoints return 404 with a clear message
  - Settings → System → Passkeys section is hidden

Mirrors the #2527 notes-drawer flag shape (env-or-config, truthy parse).
Auth is high-stakes; opt-in lets us land the code while keeping default
deployments on the well-tested password-only path.

Touches: api/auth.py (new _passkey_feature_flag_enabled helper, gated
are_passkeys_enabled), api/routes.py (6 endpoint guards).
2026-05-25 00:16:12 +00:00
AJV20
1b48643f63 feat: support passkey-only auth 2026-05-25 00:14:38 +00:00
AJV20
c60ff543b5 feat: add passkey sign-in 2026-05-25 00:14:38 +00:00
gavinssr
0ab3ad3bb2 fix: place shutdown route after CSRF gate
Move POST /api/shutdown routing after the CSRF check so drive-by
cross-origin requests cannot bring down a dev server with auth off.
Also replace os._exit(0) with os.kill(os.getpid(), signal.SIGINT)
so atexit handlers and pending session writes run during shutdown.
2026-05-25 00:10:52 +00:00
gavinssr
39121650d4 feat: add shutdown button to WebUI title bar
Add a power button (⏻) in the title bar that gracefully stops the
WebUI server process from the browser.

- api/routes.py: POST /api/shutdown endpoint with threaded os._exit(0)
- static/boot.js: shutdownServer() with confirm prompt, BroadcastChannel
  cross-tab notification, and _showServerStopped() placeholder UI
- static/index.html: shutdown button HTML in title bar (after reload btn)
- static/style.css: .app-titlebar-shutdown styles, hover turns red
2026-05-25 00:10:52 +00:00
Lumen Yang
5934c2fe8a fix: address context replay review feedback 2026-05-25 00:10:27 +00:00
ai-ag2026
8a2f11c770 fix(chat): log sanitized client sse diagnostics
(cherry picked from commit 749ca6e18c5e307fbf7e7fb5fffce97249545017)
2026-05-25 02:06:42 +02:00
ai-ag2026
2f1ca959f1 fix(chat): classify interrupted response causes
(cherry picked from commit 5c1e802cd6ee8565da74c7ffe57e6407fe21bf02)
2026-05-25 02:06:42 +02:00
AJV20
24979c8af1 Merge remote-tracking branch 'origin/master' into maint/pr-2527
# Conflicts:
#	CHANGELOG.md
2026-05-24 17:48:09 -04:00
hermes-agent
7796f74ca3 Stage 406: Opus MUST-FIX — parity-fix sync_session_usage profile= at api/routes.py:9007
Opus pre-release advisor caught a #2762 parity gap. api/streaming.py:5078
(_run_agent_streaming worker, background thread) correctly passes
profile= to sync_session_usage post-#2827. But the SECOND production
call site at api/routes.py:9007 (_handle_chat_sync, HTTP thread) does
not. Safe TODAY (HTTP thread sets TLS correctly), but it's a
defense-in-depth gap: anyone wrapping that handler in a worker pool
later silently regresses the fix. Closes the parity gap so the
threat-model invariant holds regardless of future threading changes.
2026-05-24 19:39:28 +00:00
hermes-agent
7222095413 Stage 405: Opus MUST-FIX + Stamp CHANGELOG for v0.51.129 (Release DA / stage-batch11 / 4-PR feature + perf batch)
Opus pre-release advisor MUST-FIX patched inline:
- api/routes.py:7290-7308 _handle_folder_download: add Connection: close
  header before end_headers() to satisfy HTTP/1.1 framing on the on-the-fly
  ZIP stream. Without it, post-#2836 protocol_version bump leaves clients
  hanging waiting for the next pipelined response after central-directory
  bytes finish. Opus verified this is the ONLY streaming response #2836
  missed — all other paths (j/t helpers, 12 hand-written responses, 8 SSE
  endpoints, auth flow) are already correctly framed.
2026-05-24 18:52:54 +00:00
hermes-agent
a86b378036 Stage 405: PR #2680 — feat: add Auxiliary Models settings card by @mccxj
Cherry-picked via 3-way apply (rebase had failed on static/index.html
conflict when applied via rebase commit chain; 3-way of the net delta
against stage HEAD applied cleanly).

Co-authored-by: mccxj <mccxj@github.users.noreply.github.com>
2026-05-24 18:28:26 +00:00
Qi
598fd4ff83 perf(http): enable HTTP/1.1 keep-alive
Enable HTTP/1.1 on the WebUI server so browsers can reuse TCP
connections across normal API/static requests. Tighten response framing
by adding Content-Length to short manual responses and marking
SSE/streaming responses as Connection: close, keeping HTTP/1.1 message
boundaries unambiguous.

Verified:
- python3 -m py_compile server.py api/auth.py api/routes.py api/kanban_bridge.py
- pytest tests/test_auth_*.py tests/test_*sse*.py tests/test_pr1350_*.py
        tests/test_pr1355_sse_handler_no_deadlock.py tests/test_kanban_bridge.py
        tests/test_logs_ui_static.py tests/test_onboarding_static.py
        tests/test_regressions.py tests/test_1038_pwa_auth_redirect.py
        tests/test_issue1623_sse_heartbeat_alignment.py
  → 239 passed, 1 skipped
2026-05-24 18:26:56 +00:00
Michael Lam
dd7648d56c feat(runtime): wire runner route selection harness 2026-05-24 18:26:55 +00:00
hermes-agent
2419b3a0a2 Stage 404: PR #2830 — fix(sessions): keep pin state authoritative by @franksong2702 (closes #2821)
Agent reviewer 'LGTM. Ship it.'
- Bug A fix: _session_field helper handles dict-vs-object snapshot in pin-limit check
- Bug B fix: removed stale client-side pinLimitReached short-circuit
- Bug C recovery: renderSessionList() on pin/unpin failure refreshes from server

Co-authored-by: franksong2702 <146128127+franksong2702@users.noreply.github.com>
2026-05-24 18:08:42 +00:00
hermes-agent
9d95ba0b92 Stage 404: PR #2716 — Performance optimizations by @dobby-d-elf
nesquena APPROVED 2026-05-22. Cherry-picked onto post-v0.51.127
master via 3-way apply. Resolved api/routes.py conflict: master had
the inline correctness fix from the deep-review iteration; PR
refactors it into _metadata_only_message_summary() helper. Took the
helper AND added profile= threading (post-#2827 master adds
profile-aware state.db reads). Kept master's pre-existing
test_api_session_reload_drops_stale_cached_user_tail_after_saved_assistant
alongside the PR's new test_metadata_fast_path_matches_reconciliation_for_restamped_replays.

Co-authored-by: dobby-d-elf <dobby.the.agent@gmail.com>
2026-05-24 18:08:41 +00:00
Uğur Murat Altıntas
c77936ff81 feat(i18n): add Turkish (tr) locale support
Add a complete Turkish locale to the WebUI and login page so users can
select Türkçe in Settings, with speech recognition via tr-TR.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 17:13:34 +00:00
Rory Ford
7be9a26018 feat: PATCH /api/mcp/servers/{name} — enable/disable toggle
Add `PATCH /api/mcp/servers/{name}` endpoint that accepts `{"enabled": bool}`,
updates `mcp_servers.<name>.enabled` in config.yaml, and calls `reload_config()`.
Mirrors the existing DELETE pattern.

Also wire the previously-defined-but-unrouted `_handle_mcp_server_delete` into
`handle_delete`, and `_handle_mcp_server_update` into a new `handle_put` +
`do_PUT` in server.py — fixing a pre-existing bug where those handlers existed
but were never reachable over HTTP.

UI: add a toggle button in each MCP server row in the system settings panel
(panels.js). Clicking it calls PATCH and reloads the list. Toggle button is
styled with `.mcp-toggle-enabled` / `.mcp-toggle-disabled` CSS classes. The
`toggle_supported` flag in the list response is now `True`.

i18n: add 5 new keys (`mcp_enable_server`, `mcp_disable_server`,
`mcp_enabled_toast`, `mcp_disabled_toast`, `mcp_toggle_failed`) to all 9
non-English locales (English values as placeholder translations).

Tests: add `TestMcpToggle` class with 7 tests covering disable, enable,
404-not-found, empty name, missing field, response payload, and URL-encoded name.
Update `test_empty_config` and visibility panel assertions to reflect
`toggle_supported: True` and the new toggle button in panels.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 17:13:32 +00:00
Frank Song
99c886c199 fix(workspace): open rendered preview links correctly 2026-05-24 15:52:35 +00:00
Frank Song
67a204773e fix(csrf): clarify rejection diagnostics 2026-05-24 15:52:34 +00:00
AJV20
b6f7412b53 Add option to ignore agent updates 2026-05-24 15:52:34 +00:00
Abdul Munim
7999d1c75a feat(workspace): add Open in VS Code action for files and folders (#2735)
Right-click any workspace file, folder, or root now shows
'Open in VS Code' alongside the existing Reveal in File Manager action.

- POST /api/file/open-vscode: resolves path via safe_resolve, finds VS
  Code via shutil.which() with fallbacks for macOS (/usr/local/bin/code,
  app bundle CLI), Linux (/usr/bin/code, /snap/bin/code), and Windows
  (%LOCALAPPDATA% and %PROGRAMFILES% user/system installs). Returns a
  descriptive error if not found rather than a bare OS error.
- Optional vscode block in config.yaml: command (default: code),
  host_path_prefix + container_path_prefix for Docker path mapping.
- i18n: open_in_vscode and open_in_vscode_failed translated in all 10
  locales (it, ja, ru, es, de, zh-CN, zh-TW, pt, ko).
- 26 tests in tests/test_2735_open_in_vscode.py covering source wiring,
  command resolution, i18n completeness, and live endpoint error paths.
2026-05-24 04:26:46 +00:00
Qi
d20da832b3 fix(static): tighten cache validators and 304 headers 2026-05-24 04:26:46 +00:00
ai-ag2026
cd029d801a fix: align messaging session display counts 2026-05-24 03:55:42 +00:00
carryzuo00
ee672df463 fix: prevent state.db messages being silently dropped during sidecar merge
Two bugs combined to cause historical messages to vanish from the WebUI
after a session was continued in a later conversation.

**Bug 1 — missing `id` in state.db SELECT (models.py)**
`get_state_db_session_messages()` did not include the `id` column in its
SELECT, so every row got a `("legacy", ...)` merge key instead of
`("message_id", ...)`.  The timestamp gate in
`merge_session_messages_append_only()` explicitly exempts `message_id`-keyed
rows from its "skip if older than newest sidecar message" rule, but
legacy-keyed rows are unconditionally dropped.  With a session that has any
new sidecar messages (max_sidecar_timestamp == today), all older state.db
rows were silently discarded.

Fix: include `id` when the column is present so rows get proper
`("message_id", ...)` keys and survive the timestamp filter.

**Bug 2 — always reads active profile's state.db, not the session's (models.py + routes.py)**
`get_state_db_session_messages()` always called `_active_state_db_path()`,
which returns the currently-active profile's database.  Sessions belonging to
a different profile (e.g. `jump`) were read from the wrong state.db, returning
either no rows or unrelated ones.

Fix: add an optional `profile` parameter; when supplied, resolve the path via
`_get_profile_home(profile)` with a fallback to the active path if the
profile-specific db does not exist.  The call-site in `routes.py` now reads
`session.profile` and passes it through.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 03:55:42 +00:00
AJV20
86b5a8fcff Merge remote-tracking branch 'origin/master' into feat/webui-notes-sources
# Conflicts:
#	CHANGELOG.md
2026-05-22 17:35:18 -04: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
AJV20
7305d470b9 feat(memory): gate third-party notes drawer 2026-05-22 14:54:41 -04:00
AJV20
e794b54e18 Merge remote-tracking branch 'origin/master' into feat/webui-notes-sources
# Conflicts:
#	CHANGELOG.md
2026-05-22 14:14:15 -04: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
Hermes Agent
905b3eba5e Stage 398: PR #2700 — feat: make pinned session limit configurable (builds on shipped #2614 3-cap)
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-05-21 17:43:56 +00:00
Hermes Agent
92f1896754 Stage 397: PR #2684 — fix: repair stale Codex OpenAI slash-qualified model state
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-05-21 17:13:56 +00:00
nesquena-hermes
c6587091a2 Stage 396: PR #2663 2026-05-21 00:26:54 +00:00
nesquena-hermes
45c7a693af Stage 394: PR #2625 2026-05-20 23:53:04 +00:00
nesquena-hermes
feb35893b9 Stage 393: PR #2637
# Conflicts:
#	static/sessions.js
2026-05-20 22:24:40 +00:00
nesquena-hermes
aaf30b7b0a Stage 392: PR #2643 2026-05-20 21:48:04 +00:00
dobby-d-elf
6278222596 tighten session refresh invalidation 2026-05-20 14:40:13 -06:00
dobby-d-elf
14dd5aa00d address session event review 2026-05-20 14:33:36 -06:00
Arsh Kumar Singh
2253cf5a32
chore: address review notes — dedup comment and 409-path clarification 2026-05-20 19:57:20 +00:00
nesquena-hermes
7c2d56c920 Stage 389 follow-up: close TOCTOU race in pin-cap (Opus advisor #2614) 2026-05-20 18:12:38 +00:00
Arsh Kumar Singh
d385db69d5
fix(clarify): require stable clarify_id and wait for backend ack so stale responses are rejected
The WebUI clarification popup had a response-delivery failure: users
submitted answers in the popup, but the agent still fell through to the
timeout fallback message.  Three bugs conspired:

1. No stable clarify_id — _ClarifyEntry had no unique identifier, so
   the frontend could not reference a specific pending prompt.  The
   backend used FIFO resolution which silently failed for stale/late
   responses.

2. Frontend hid the card before confirmation — respondClarify() called
   hideClarifyCard(true, 'sent') BEFORE the API call completed.  If the
   backend rejected the response, the card was already gone and the
   user's draft was discarded.

3. Backend lied about success — _resolve_clarify_legacy() returned
   bool(resolved) or not bool(clarify_id).  Since the frontend never
   sent clarify_id, the backend always reported ok:true even when
   nothing was resolved.

Changes:

api/clarify.py:
- _ClarifyEntry now auto-generates a stable clarify_id (uuid4.hex[:12])
- submit_pending() injects clarify_id into the data dict visible to the
  frontend via SSE and polling
- New resolve_clarify_by_id() for O(1) lookup by id instead of FIFO pop

api/routes.py:
- _resolve_clarify_legacy() uses resolve_clarify_by_id when clarify_id
  is provided; returns actual bool result (no more unconditional True)
- _handle_clarify_respond() returns HTTP 409 + {ok:false, stale:true}
  when resolution fails

static/messages.js:
- respondClarify() now sends clarify_id in the POST body
- Waits for a positive backend acknowledgement before hiding the card
- Saves a draft copy before POST and restores it on failure
- On 409/network error: re-enables controls, shows error toast
- Guards against parallel-SSE race where clearing the cache after a
  successful response could erase a newly queued next prompt (codex P1)

tests:
- Updated test_sprint30.py for new ack-before-hide behaviour
- Updated test_clarify_unblock.py for 409 on stale responses

Closes #2639.
2026-05-20 16:35:15 +00:00
dobby-d-elf
5e378d3b38 sync session list from server events 2026-05-20 08:18:56 -06:00
stocky789
898e15a899 fix(workspace): restore branch changes on switch 2026-05-20 08:14:30 +00:00
stocky789
5fc7aee781 feat(workspace): add backend Git operations 2026-05-20 04:51:41 +00:00
Mark Baker
a2ce4e81b5
fix(plugins): distinguish exclusive/provider activation in Settings panel
The Settings → Plugins panel keyed off `loaded.enabled` and the four
agentic visibility hooks, both of which are False/empty for exclusive
plugins (memory.provider, model-provider, etc.). Those plugins were
mislabeled as "Disabled" with "No registered lifecycle hooks" even when
fully functional as the active provider for their category.

Surface `manifest.kind` and a derived `activation` field
("enabled" | "disabled" | "exclusive" | "provider") in /api/plugins.
The card render picks a third badge state ("Active (provider)") and a
dedicated empty-hooks line for those rows. `enabled` is preserved in
the payload so older clients still work; new clients should prefer
`activation`.

Fixes #2659
2026-05-20 00:01:02 -04:00
Michael Lam
9ca846eb8f feat: cap pinned sessions from sidebar 2026-05-19 20:50:26 -07:00
nesquena-hermes
ed6ee3e067 Stage 388: PR #2607
# Conflicts:
#	CHANGELOG.md
2026-05-20 00:17:48 +00:00
AJV20
cb0850208d fix(session): dedupe messaging transcript timestamps 2026-05-19 19:17:43 -04:00
AJV20
6805b3d5dd Merge remote-tracking branch 'origin/master' into audit-2527
# Conflicts:
#	CHANGELOG.md
2026-05-19 18:54:35 -04:00
AJV20
54b6c38578 feat(health): expose WebUI stream runtime diagnostics 2026-05-19 22:48:10 +00:00
nesquena-hermes
6d43116794 Stage 387: PR #2573 2026-05-19 22:10:20 +00:00
nesquena-hermes
93727897b6 Stage 387: PR #2605
# Conflicts:
#	api/routes.py
2026-05-19 22:10:20 +00:00
nesquena-hermes
1ddb18264e Stage 387: PR #2604
# Conflicts:
#	CHANGELOG.md
2026-05-19 22:08:56 +00:00
nesquena-hermes
4bb60d9b10 Stage 387: PR #2601 2026-05-19 22:08:56 +00:00
nesquena-hermes
536a8b7636 Stage 387: PR #2566 2026-05-19 22:08:55 +00:00
Lumen Yang
dc5c8168d1 fix(webui): refresh active session on external sidecar updates 2026-05-19 21:34:08 +00:00
Michael Lam
1ebfbf3527 fix: reconcile session metadata counts 2026-05-19 14:28:20 -07:00
keyos
ada59d73e6 fix(approval): simplify gateway_keys expression and document race window
Drop the redundant 'if gw_data else []' guard — gw_data is already
guaranteed to be a dict by the 'or {}' fallback above.

Add a one-line comment explaining the peek-without-pop race window:
a concurrent resolver may pop a different gateway entry, but
approve_session is idempotent over the session key set so the
outcome is the same regardless.
2026-05-19 20:56:22 +00:00
keyos
729ed415ff fix(approval): peek _gateway_queues for session-level approval when _pending is empty
During active streaming, dangerous-command approvals go through the
gateway path and are stored in _gateway_queues as _ApprovalEntry
objects, not in _pending. The _resolve_approval_legacy helper only
looked at _pending, so 'Allow for this session' never called
approve_session() — the user clicked Allow, the card vanished, but
the next dangerous command asked again.

Now when _pending has no matching entry, the helper peeks into
_gateway_queues to extract pattern_keys, calls approve_session(),
and marks found_target=True so resolve_gateway_approval also fires.

This commit is re-scoped to peek-only (no agent_session_key round-trip,
no state_db metadata changes).

Includes:
- Import + fallback for _gateway_queues
- Null-safe key filtering in all_keys
- Source-contract test (static) + functional test with
  @requires_agent_modules skip marker for CI
- All comments and docstrings in English
2026-05-19 20:24:05 +00:00
starship-s
37df7d76a4 fix(webui): prevent composer draft rollback on refresh 2026-05-19 13:31:12 -06:00
Bryan Bartley
94ceb66c17 docs: clarify folder-zip cap bounds wall-clock/bandwidth not RSS
Per reviewer note: because the zip streams straight into handler.wfile
(no io.BytesIO buffering), peak memory is bounded by zipfile's per-file
read buffer, not the HERMES_WEBUI_FOLDER_ZIP_MAX_MB cap. Adds a comment
so the next reader doesn't have to trace it to learn the cap's actual
shape.
2026-05-19 13:44:56 -05:00
Lumen Yang
6ca63e5815 perf(webui): keep external refresh metadata cheap 2026-05-19 08:02:43 +00:00
Lumen Yang
a63ab310b5 fix(webui): preserve reconciled session invariants 2026-05-19 08:02:43 +00:00
Lumen Yang
467ef33a24 feat(webui): reconcile external session updates
When API server runs append messages directly to state.db, reconcile WebUI sidecar sessions with those canonical rows across API responses, model-facing streaming context, and active browser refresh.

Add append-only state.db merge helpers, metadata-only counts for refresh polling, and regression coverage for API visibility, context incorporation, and frontend refresh behavior.
2026-05-19 08:02:43 +00:00
Bryan Bartley
6caf86ba96 feat(workspace): download folder as zip via /api/folder/download
Adds a "Download Folder" item to the workspace file-tree right-click
menu and a GET /api/folder/download endpoint that streams the
directory as a zip with Content-Disposition: attachment.

Configurable caps:
  HERMES_WEBUI_FOLDER_ZIP_MAX_MB    (default 1024)
  HERMES_WEBUI_FOLDER_ZIP_MAX_FILES (default 50000)

Pre-flights the walk so cap-exceeded returns 413 + JSON BEFORE any
zip bytes are sent. Symlinks resolving outside the workspace are
skipped. Mirrors the existing _handle_file_raw shape (session_id
resolution, safe_resolve, RFC 5987 filename via
_content_disposition_value). Stdlib zipfile only; no new dependencies.

Tests: 11 static-inspection tests matching the style of
tests/test_issue1867_upload_size_preflight.py. All passing on
Python 3.11/3.12/3.13.
2026-05-18 21:40:02 -05:00
nesquena-hermes
4f90fc5339 Stage 384: PR #2544 2026-05-18 22:44:02 +00:00
Michael Lam
6a68bab114 fix(runtime): clarify goal adapter seam semantics 2026-05-18 12:10:17 -07:00
Michael Lam
e94827f460 fix: stop replaying reasoning-only history 2026-05-18 10:50:42 -07:00
Michael Lam
b23fb6ccaa feat(runtime): route goal through adapter seam 2026-05-18 10:30:26 -07:00
nesquena-hermes
8286061fb0 Stage 383: PR #2294
# Conflicts:
#	tests/test_gateway_sync.py
2026-05-18 16:44:26 +00:00
AJV20
8c08acec5a feat(memory): show AI-used Joplin notes 2026-05-18 10:24:05 -04:00
AJV20
54ca6bf2e3 feat(memory): browse Joplin notes from notes drawer 2026-05-18 10:10:48 -04:00
AJV20
2f7883580f fix(memory): infer configured notes source tools 2026-05-18 10:04:03 -04:00
AJV20
faf1160ca9 fix(memory): show configured notes sources without tools 2026-05-18 09:57:45 -04:00
AJV20
1f3b7aa2c3 feat(memory): show third-party notes sources 2026-05-18 09:28:11 -04:00
junjunjunbong
3a53592107 Add previous messaging session controls 2026-05-17 21:27:32 -07:00
junjunjunbong
98f2814ecd Fix empty gateway session hiding messaging history 2026-05-17 21:13:24 -07:00
nesquena-hermes
cec1e87d04 Stage 382: PR #2500 (with whitespace polish on panels.js + ARCHITECTURE.md) 2026-05-18 03:45:02 +00:00
nesquena-hermes
42c677b223 Stage 382: PR #2496 2026-05-18 03:43:59 +00:00
nesquena-hermes
ea1261d03b Stage 382: PR #2501 2026-05-18 03:43:26 +00:00
nesquena-hermes
eef47ea27b Stage 381: PR #2484 2026-05-18 01:32:21 +00:00
nesquena-hermes
50d4f4cfb9 Stage 379: PR #2480
# Conflicts:
#	CHANGELOG.md
2026-05-17 23:35:19 +00:00
nesquena-hermes
6f9cead15e Stage 379: PR #2479 2026-05-17 23:35:18 +00:00
nesquena-hermes
935d9e6402 Stage 379: PR #2461
# Conflicts:
#	CHANGELOG.md
2026-05-17 23:35:18 +00:00
Frank Song
996942429c Add session-bound CSRF token checks 2026-05-18 07:14:26 +08:00
Michael Lam
f986507809 fix: align fork-from-here with merged messaging history 2026-05-17 15:01:57 -07:00
Michael Lam
a5385e5859 feat(runtime): route cancel through RuntimeAdapter seam 2026-05-17 13:23:22 -07:00
nesquena-hermes
46810bd4b2 Stage 376: PR #2457
# Conflicts:
#	CHANGELOG.md
2026-05-17 16:42:10 +00:00
nesquena-hermes
9f09f7e657 Stage 376: PR #2452
# Conflicts:
#	CHANGELOG.md
2026-05-17 16:42:10 +00:00
nesquena-hermes
c1f0f069da Stage 376: PR #2444
# Conflicts:
#	CHANGELOG.md
2026-05-17 16:42:09 +00:00
starship-s
a48cacec73 fix: evict cleared sessions outside session lock 2026-05-17 05:22:39 -06:00