Commit graph

1488 commits

Author SHA1 Message Date
Isla Liu
9870e8f111 fix(session): address Copilot review — scope tool-card dedupe by stream id + tighten docs
Four code-review comments from the automated Copilot reviewer on this PR:

1. `_journal_tool_already_present` dedupe was session-wide, so a
   legitimately-repeated tool (e.g. a second `terminal: ls` in an
   earlier turn) could cause the retry path to falsely skip
   materializing the recovered tool card.  The helper now takes a
   keyword `stream_id` argument; when supplied, a tool card whose
   `_recovered_stream_id` is set AND differs from the candidate is no
   longer treated as a duplicate.  Untagged tool cards (live tools, or
   tool cards carried over from a pre-tagging core transcript) still
   match, preserving the existing 'core transcript already has this
   tool, don't duplicate' invariant.  Two new tests in
   `TestJournalToolDedupeScoping` cover both legs of the rule.

2./3. The troubleshooting FAQ pointed at `~/.hermes/webui/sessions/session_<sid>.json`
   and `~/.hermes/_run_journal/...`.  The actual sidecar filename has
   no `session_` prefix and the run-journal lives under the WebUI
   sessions dir (`~/.hermes/webui/sessions/_run_journal/<sid>/<stream>.jsonl`,
   default).  Both paths fixed and an explicit note added about
   `HERMES_WEBUI_STATE_DIR` overriding the state root.

4. Drop unused `json` / `queue` / `Path` imports from
   `tests/test_session_lost_response_regression.py` so the file stops
   carrying noise that future linting would flag.
2026-05-20 12:18:03 +08:00
Isla Liu
2387720068 test(session): unit + backwards-compat + WSL race coverage for lazy-retry path
Adds five test classes that together pin down the contract added in the
previous commit and protect pre-fix session shapes:

  * `TestInterruptedRecoveryMarker` — pure-function tests for the new
    `pending_retry=True` keyword and the mutual-exclusion rule between
    `recovered_output=True` and `pending_retry=True`.
  * `TestRetryJournalRecoveryInPlace` — promote-on-success,
    increment-on-failure, demote-after-max-attempts,
    demote-after-giveup-seconds, no-op when no pending marker, and the
    `_session_has_pending_journal_retry` short-circuit (which stops at
    the most recent normal assistant turn).
  * `TestGetSessionLazyRetryHook` — both `get_session()` entry paths
    (cache-hit and cold-load) trigger the helper when a pending marker
    is present; the short-circuit avoids the helper when nothing is
    pending; and `metadata_only=True` skips the helper to keep sidebar
    refresh free.
  * `TestLazyRetryBackwardsCompat` — pre-fix sessions whose markers
    use the legacy "no agent output" wording (no flag) are not touched
    by `get_session()`. The four retry-meta keys round-trip cleanly
    through `Session.save()` / `Session.load()`.
  * `TestWslPageCacheRace` — covers the WSL2 / network-FS shape: a
    first `read_run_events` raising IOError followed by a successful
    read; a journal that grows visible tokens between sidecar repair
    and retry; and two concurrent `get_session(sid)` calls converging
    on a single promoted marker with a single recovered body
    (deduped by `dedupe_existing=True`).

Two pre-existing assertions had to be relaxed because they encoded the
buggy contract (permanent "no agent output was recovered" / "user
message above was preserved" wording in the journal-empty + stream-id
known case). Both tests now accept either the legacy wording or the
new "Recovering the partial output…" wording with the pending-retry
flag, reflecting the broader fact that the old wording was the bug.
2026-05-20 11:58:54 +08:00
Isla Liu
e8cd0bcc66 test(session): end-to-end regression for lost-response self-heal
Reproduces the production failure mode:

1. Stage 1 — sidecar repair runs while the run-journal for the dead
   stream is empty on disk. Assert the marker arms the lazy-retry
   hook (`_pending_journal_recovery=True`,
   `_journal_retry_stream_id`, `_journal_retry_attempts=0`,
   `_journal_retry_first_seen_ts`) and does NOT carry the legacy
   "no agent output was recovered" wording. Pending sidecar fields
   are cleared regardless.

2. Stage 2 — journaled token / tool / tool_complete / token events
   appear on disk. Call `get_session(sid)` and assert the marker
   self-heals: wording promotes to "recovered from the run journal",
   journaled assistant rows + tool card land above the marker in
   chronological order, all retry meta is stripped.

Without the lazy-retry path this test fails at the very first
assertion (marker still carries the legacy no-output wording).
2026-05-20 11:58:37 +08:00
Michael Lam
9ca846eb8f feat: cap pinned sessions from sidebar 2026-05-19 20:50:26 -07:00
AJV20
8109bcb7e4 fix: honor configured title generation model 2026-05-19 22:35:11 -04:00
AJV20
f966e99231 fix: preserve IPv6 dashboard link brackets 2026-05-19 21:35:10 -04:00
nesquena-hermes
fc5639db15 test: exempt skin-specific scoped overrides from session-title color assertion
PR #2521 (Geist Contrast skin) adds a scoped
`:root[data-skin="geist-contrast"] .session-item.active .session-title` rule
that legitimately uses its own palette values. The existing assertion in
test_sprint40_ui_polish.py matched on any line containing the
`.session-item.active .session-title` substring, picking up the skin-scoped
override and asserting against its palette.

Exclude lines containing `:root[data-skin=` from the base-rule scan so
skin-scoped overrides are free to use their own design tokens, while the
global rule still enforces var(--gold) / var(--accent-text).
2026-05-20 00:26:11 +00:00
nesquena-hermes
1a8c7b65c9 test: anchor picker-active-state assertions to global #mainSettings rule
PR #2521 (Geist Contrast skin) legitimately adds a scoped
`:root[data-skin="geist-contrast"] .theme-pick-btn.active` override that
appears earlier in style.css than the global `#mainSettings .theme-pick-btn.active`
rule. The naive substring search in tests/test_1059_settings_picker_active_state.py
found the skin-specific override first (which correctly uses --border2 for its
palette), failing the global assertion that wanted --accent.

Tighten both assertions to anchor on the `#mainSettings` selector prefix so
they always match the global rule regardless of how many skin-specific
overrides land in the file.
2026-05-20 00:23:01 +00:00
nesquena-hermes
ed6ee3e067 Stage 388: PR #2607
# Conflicts:
#	CHANGELOG.md
2026-05-20 00:17:48 +00:00
nesquena-hermes
a201401236 Stage 388: PR #2524 2026-05-20 00:17:48 +00:00
nesquena-hermes
bd819f5e68 Stage 388: PR #2533 2026-05-20 00:17:47 +00:00
nesquena-hermes
7c3dcce1d0 Stage 388: PR #2598 2026-05-20 00:17:47 +00:00
Eleanor Berger
2e91c0f81e fix: honour skin value in theme command 2026-05-20 00:09:06 +00:00
Eleanor Berger
4598adfd04 feat: add Geist Contrast skin 2026-05-20 00:09:06 +00:00
AJV20
cb0850208d fix(session): dedupe messaging transcript timestamps 2026-05-19 19:17:43 -04:00
AJV20
54b6c38578 feat(health): expose WebUI stream runtime diagnostics 2026-05-19 22:48:10 +00:00
AJV20
f14138659f test: Sanitize dashboard URL fixture 2026-05-19 22:47:55 +00:00
AJV20
739c948e74 fix(system): allow browser-only dashboard links 2026-05-19 22:47:55 +00:00
AJV20
612fcd30fe fix: avoid duplicate live tool events 2026-05-19 18:41:08 -04:00
nesquena-hermes
6d43116794 Stage 387: PR #2573 2026-05-19 22:10:20 +00:00
nesquena-hermes
cc8ef201be Stage 387: PR #2600 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
3a404874aa Stage 387: PR #2603 2026-05-19 22:08:56 +00:00
nesquena-hermes
c3fd395bd6 Stage 387: PR #2597 2026-05-19 22:08:56 +00:00
nesquena-hermes
e63de7c15f Stage 387: PR #2593
# Conflicts:
#	CHANGELOG.md
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
starship-s
692ea22f9e fix(streaming): finish auto-compression card after rotation 2026-05-19 14:35:11 -06: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
Michael Lam
5770323188 feat(runtime): add runner adapter facade 2026-05-19 12:06:57 -07:00
AJV20
ebb4dffc7d fix: stream live tool callback events 2026-05-19 14:55:19 -04:00
Lumen Yang
8d2b9d4a16 feat(webui): render indexed context metadata 2026-05-19 18:52:50 +00:00
Dennis Soong
acd1df1112 fix: time out hung browser api requests 2026-05-20 02:41:00 +08:00
nesquena-hermes
6c0f864b10 Stage 386: PR #2587
# Conflicts:
#	CHANGELOG.md
2026-05-19 18:20:47 +00:00
nesquena-hermes
86f52f67b8 Stage 386: PR #2581
# Conflicts:
#	api/streaming.py
2026-05-19 18:20:47 +00:00
nesquena-hermes
0585881511 Stage 386: PR #2583 2026-05-19 18:20:07 +00:00
nesquena-hermes
7675f2f139 Stage 386: PR #2588
# Conflicts:
#	CHANGELOG.md
2026-05-19 18:20:07 +00:00
nesquena-hermes
9a512194d5 Stage 386: PR #2582
# Conflicts:
#	CHANGELOG.md
2026-05-19 18:20:07 +00:00
nesquena-hermes
42c2eda0fc Stage 386: PR #2579 2026-05-19 18:20:06 +00:00
nesquena-hermes
4b72539b3c Stage 386: PR #2576 2026-05-19 18:20:06 +00:00
nesquena-hermes
05de68f5b3 Stage 386: PR #2580 2026-05-19 18:20:06 +00:00
Michael Lam
0736e45485 fix: dedupe tool-only partial recovery markers 2026-05-19 11:16:21 -07:00
Michael Lam
bc7648271f fix: preserve provider for configured model picker selections 2026-05-19 08:05:52 -07:00
Lumen Yang
a8d429775c fix(webui): preserve casual chat compaction guard 2026-05-19 14:34:58 +00:00
AJV20
f93e288214 Fix stale stream recovery writeback race 2026-05-19 10:26:45 -04:00
dobby-d-elf
2a95c1e482 Fix profile-aware assistant display names 2026-05-19 07:17:11 -06:00
Michael Lam
71d8a8fb1b fix: reap terminal shells on shutdown 2026-05-19 04:57:51 -07:00
Michael Lam
79652935d3 fix: centralize workspace tree toggle width 2026-05-19 01:49:52 -07:00
starship-s
2e9ca283dc fix: display canonical cache hit percentage 2026-05-19 02:27:12 -06: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
f12fef280d fix(webui): clear stale prompts on external refresh
Force same-session external refreshes to dismiss stale approval and clarification prompts immediately so completed state.db updates do not leave the composer blocked.
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
dobby-d-elf
11e1e9a342 Fix settled rendering for file markdown links 2026-05-18 22:32:20 -06:00
dobby-d-elf
ebf60ba7f9 Merge remote-tracking branch 'origin/master' into fix/session-list-touch-actions 2026-05-18 22:22:35 -06:00
Michael Lam
960c95cfe3 docs(runtime): define runner sidecar gate 2026-05-18 21:06:05 -07:00
nesquena-hermes
a978612b76 Update brittle-source-string assertions for widened compressed-event guard 2026-05-19 03:21:26 +00:00
nesquena-hermes
54875f2110 Stage 385: PR #2550 2026-05-19 03:13:47 +00:00
nesquena-hermes
d92e44ef5a Stage 385: PR #2568
# Conflicts:
#	CHANGELOG.md
2026-05-19 03:13:47 +00:00
nesquena-hermes
e9079548ab Stage 385: PR #2567
# Conflicts:
#	CHANGELOG.md
2026-05-19 03:13:47 +00:00
nesquena-hermes
975928ffb7 Stage 385: PR #2561 2026-05-19 03:13:46 +00:00
nesquena-hermes
ed4e7e67ce Stage 385: PR #2563
# Conflicts:
#	CHANGELOG.md
2026-05-19 03:13:46 +00:00
Michael Lam
1827ea3efd fix: add Grok OAuth provider catalog support 2026-05-18 19:51:01 -07:00
Dennis Soong
ea978a1989 fix: surface auto-compression handoff 2026-05-19 10:45:43 +08: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
Michael Lam
b473b21a2f fix: align workspace tree file icons 2026-05-18 18:38:13 -07:00
Nanook Claw
b6b9deca4c
fix: refresh project move cache 2026-05-19 00:08:07 +00:00
Michael Lam
11bcf812e0 docs(runtime): clarify queue adapter staging 2026-05-18 16:34:16 -07:00
nesquena-hermes
4f90fc5339 Stage 384: PR #2544 2026-05-18 22:44:02 +00:00
nesquena-hermes
589e0bf490 Stage 384: PR #2556
# Conflicts:
#	CHANGELOG.md
2026-05-18 22:44:02 +00:00
nesquena-hermes
91099051c6 Stage 384: PR #2505
# Conflicts:
#	CHANGELOG.md
2026-05-18 22:44:02 +00:00
nesquena-hermes
2675b6266d Stage 384: PR #2520
# Conflicts:
#	CHANGELOG.md
2026-05-18 22:44:02 +00:00
Michael Lam
6917b9a0e7 fix: sanitize custom provider env hints 2026-05-18 15:18:20 -07:00
Ruslan
6d8e5031fc
Merge branch 'master' into fix/archive-extract-respects-attachment-dir 2026-05-18 23:01:23 +03:00
keyos
b2e1bac149 fix(config): keep anonymous custom endpoints in picker when /v1/models probe fails
When an anonymous custom endpoint (bare base_url, not a named custom_providers[] entry) fails its /v1/models probe, the provider group was silently dropped from the model picker entirely. This made the endpoint unusable even when /v1/chat/completions would work fine.

The fix adds an elif branch: if pid == 'custom', a cfg_base_url is configured, but no models were returned by the probe, the group is still added with an empty model list. Users can then select the Custom group and type a model ID manually in the picker's free-form input.

Closes #2542.
2026-05-18 19:28:46 +00:00
Michael Lam
6a68bab114 fix(runtime): clarify goal adapter seam semantics 2026-05-18 12:10:17 -07:00
r.kulbaev
2fe0ece991 fix(upload): scope archive extraction to per-session attachment dir
handle_upload_extract() used Path(s.workspace) as the extraction root,
bypassing HERMES_WEBUI_ATTACHMENT_DIR entirely. Route through
_session_attachment_dir(session_id) so archives land alongside
single-file uploads and session cleanup covers them.

Add tests and CHANGELOG entry.

Ref #2247
2026-05-18 21:22:02 +03: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
Hermes Agent
42b97d15f6 fix: clear fallback streaming warnings 2026-05-18 12:21:59 -05:00
nesquena-hermes
c28fa63cc9 Stamp removal-required comment on Slice 3c gate test (Opus advisor followup #3) 2026-05-18 16:57:40 +00:00
nesquena-hermes
63d75df818 Stage 383: PR #2530
# Conflicts:
#	CHANGELOG.md
2026-05-18 16:44:36 +00:00
nesquena-hermes
d161e9a074 Stage 383: PR #2528
# Conflicts:
#	CHANGELOG.md
2026-05-18 16:44:36 +00:00
nesquena-hermes
69b2289671 Stage 383: PR #2512
# Conflicts:
#	CHANGELOG.md
2026-05-18 16:44:36 +00:00
nesquena-hermes
c7badae039 Stage 383: PR #2515
# Conflicts:
#	CHANGELOG.md
2026-05-18 16:44:35 +00:00
nesquena-hermes
75f6d2a03c Stage 383: PR #2522
# Conflicts:
#	CHANGELOG.md
2026-05-18 16:44:35 +00:00
nesquena-hermes
3bf2b10a68 Stage 383: PR #2509
# Conflicts:
#	CHANGELOG.md
2026-05-18 16:44:35 +00:00
nesquena-hermes
cd7ee68e02 Stage 383: PR #2511
# Conflicts:
#	CHANGELOG.md
2026-05-18 16:44:35 +00:00
nesquena-hermes
c713b80197 Stage 383: PR #2459 2026-05-18 16:44:26 +00:00
nesquena-hermes
8286061fb0 Stage 383: PR #2294
# Conflicts:
#	tests/test_gateway_sync.py
2026-05-18 16:44:26 +00:00
Frank Song
0cd62ab474 fix: keep kanban drag drop from opening detail 2026-05-18 22:13:04 +08:00
Michael Lam
b76d698a79 fix: guard new conversation cold-start clicks 2026-05-18 06:48:05 -07:00
Michael Lam
f3f9f3ed40 fix: allow keyless named custom endpoints 2026-05-18 04:27:31 -07:00
Michael Lam
037652308d fix: load remote models for named custom providers 2026-05-18 01:08:09 -07:00
Frank Song
9d6eb5f0df Fix board selector label overlap 2026-05-18 16:02:56 +08:00
Dennis Soong
d72b3382fd fix: clarify compression elapsed cap 2026-05-18 14:22:29 +08:00