Commit Graph

1603 Commits

Author SHA1 Message Date
nesquena-hermes 0cf405cc16 Stage 319: PR #1868 — oversized upload preflight by @franksong2702 2026-05-08 15:16:19 +00:00
Frank Song 29829c3edf fix: preflight oversized browser uploads 2026-05-08 15:16:19 +00:00
nesquena-hermes a11cbd3ee9 Stage 319: PR #1862 — preserve local custom provider model ids by @franksong2702 2026-05-08 15:16:18 +00:00
Frank Song 414c474d97 fix: preserve local custom provider model ids 2026-05-08 15:16:18 +00:00
nesquena-hermes 1105d496e9 Stage 319: PR #1887 — cross-container gateway liveness via state-file freshness fallback by @Sanjays2402 2026-05-08 15:15:50 +00:00
Sanjay Santhanam efcfff3d7f fix(#1879): cross-container gateway liveness via state-file freshness
The dashboard banner 'Hermes agent is not responding' fires on every
multi-container deployment that doesn't set 'pid: "service:hermes-agent"'
in compose, because get_running_pid() relies on fcntl.flock and
os.kill(pid, 0) — both PID-namespace-scoped and invisible across container
boundaries.

Fix: when get_running_pid() returns None, fall back to a freshness check on
gateway_state.json. The gateway already writes that file on every tick with
gateway_state == 'running' and an aware ISO-8601 updated_at timestamp, so a
recent (<= 120s) timestamp is an equivalent live-process signal that needs
only a shared volume — no PID namespace, no compose workaround, no extra
HTTP probe URL.

Behavior preserved:
- In-namespace deployments still hit the PID-based path first; payload shape
  unchanged (no 'reason' key) so #716 contract holds.
- Cross-container alive path adds reason='cross_container_freshness' so
  support diagnostics can tell which signal succeeded.
- Stale updated_at, non-running gateway_state, malformed/naive/missing
  timestamps, and timestamps far in the future all still report 'down' — the
  fallback never produces a false positive.
- Same redaction rules: argv/command/executable/env/raw pid never leak.

Tests: 15 new cases in test_issue1879_cross_container_gateway_liveness.py
covering the cross-container alive path, every refusal case, clock-skew
tolerance, and backward compat with the #716 PID path. Existing #716
heartbeat tests (8) continue to pass.
2026-05-08 15:15:50 +00:00
nesquena-hermes 2c2e5142e3 Stage 319: PR #1883 — phantom duplicate Custom group when active provider is ai-gateway by @Sanjays2402 2026-05-08 15:15:49 +00:00
Sanjay Santhanam a958c29373 fix(config): phantom Custom group when active provider is ai-gateway (#1881)
Two bugs in get_available_models() conspired to duplicate the active
provider's auto-detected models under a phantom 'Custom' group whenever
custom_providers was also declared in config.yaml:

1. custom:* PIDs not in _named_custom_groups (e.g. stale slugs left from
   prior configs) fell through to the auto_detected_models fallback, copying
   the active provider's whole catalog into a phantom Custom: <slug> group.
   Fix: continue unconditionally for ANY custom:* PID — the named-group
   branch is the only legitimate population path.

2. The bare 'custom' PID, with the active provider being concrete (e.g.
   ai-gateway), hit 'elif auto_detected_models: copy.deepcopy(...)' and
   built a duplicate Custom group of the active provider's models with
   mismatched provider prefixes. Fix: when pid == 'custom' and the active
   provider is non-custom, leave models_for_group empty.

The reporter also suggested a third fix gating resolve_model_provider() on
config_provider — that's intentionally NOT applied because it conflicts with
the long-standing model-specific-override semantics covered by
test_model_resolver.py::test_custom_provider_*_routes_to_named_custom_provider
(custom_providers entries explicitly override the active provider's routing
when the user opted-in). The reporter's symptom (duplicate UI group) lives
entirely in get_available_models()'s group construction and is fully fixed
by the two changes above.

Tests: 6 new regression tests (3 in #1881 file + reuse), 774 broader
tests still green (model/provider/custom/config domain).
2026-05-08 15:15:49 +00:00
nesquena-hermes 82aa628317 Merge pull request #1899 from nesquena/stage-318
v0.51.23 — Release A: 7-PR contributor batch (stale-cleanup, title refresh, ja i18n, Kanban + cron + workspace polish)
v0.51.23
2026-05-08 08:13:49 -07:00
nesquena-hermes 8e72dc771a chore(release): stamp v0.51.23 — 7-PR Release A contributor batch 2026-05-08 15:11:13 +00:00
nesquena-hermes 2c66d349ab Stage 318: PR #1872 — Fix workspace heading affordance without workspace by @franksong2702 2026-05-08 15:01:50 +00:00
nesquena-hermes 0ba6724e16 Stage 318: PR #1871 — Fix no-agent cron edit snapshot source by @franksong2702 2026-05-08 15:01:50 +00:00
nesquena-hermes 94d3cd5e95 Stage 318: PR #1870 — Fix Kanban stale-client false-positive by @franksong2702 2026-05-08 15:01:49 +00:00
nesquena-hermes b5f8a48de5 Stage 318: PR #1869 — Test Kanban double-404 guard across methods by @franksong2702 2026-05-08 15:01:49 +00:00
nesquena-hermes 2730d775c2 Stage 318: PR #1863 — i18n: add Japanese (ja) locale bundle by @koshikai 2026-05-08 15:01:49 +00:00
nesquena-hermes 0dcce8e434 Stage 318: PR #1859 — fix: persist generated title refresh marker by @ai-ag2026 2026-05-08 15:01:48 +00:00
nesquena-hermes c8e6207ca3 Stage 318: PR #1856 — fix: preserve pending turn during stale cleanup by @ai-ag2026 2026-05-08 15:01:48 +00:00
Frank Song ee0828f53d fix: disable workspace heading affordance without workspace 2026-05-08 13:32:05 +08:00
Frank Song b0876982c4 fix: use cron edit snapshot for no-agent saves 2026-05-08 13:18:29 +08:00
Frank Song 153c34cac0 fix: tighten Kanban stale-client heuristic 2026-05-08 13:12:16 +08:00
Frank Song b684317554 test: parametrize kanban double-404 guard across HTTP methods 2026-05-08 12:48:23 +08:00
koshikai 9ddd1ae02c i18n: add Japanese (ja) locale bundle 2026-05-08 10:16:54 +09:00
ai-ag2026 755c18bdf9 fix: persist generated title refresh marker 2026-05-08 01:36:10 +02:00
ai-ag2026 f69a81c8c3 fix: preserve pending turn during stale cleanup 2026-05-07 23:57:01 +02:00
nesquena-hermes 5005f1c8ba Merge pull request #1853 from nesquena/fix/1793-workspace-prefs-kebab
fix(workspace): move 'Show hidden files' toggle into kebab + accent-dot state indicator (#1793)
2026-05-07 14:19:34 -07:00
nesquena-hermes 8804a5c5e9 Merge pull request #1854 from nesquena/stage-316
Stage 316: 3-PR batch — P0 markdown streaming hotfix + CSP source-map allowance + LaTeX delimiter rendering
v0.51.22
2026-05-07 14:17:42 -07:00
nesquena-hermes bbf707aa1c chore(release): document late absorbed commits — d703959 (code-fence-vs-math ordering) + 1448f42 (csp test pathlib)
Both stage-316 absorption commits documented in CHANGELOG. Test count
bumped 4815 → 4817 (+2 from d703959 regression coverage). Pre-release
pytest re-run confirmed 4790 passed, 0 failed.
2026-05-07 21:16:59 +00:00
ChaseFlorell 9a6e7483f6 test: align csp test with pathlib rooting pattern from existing suite
Use Path(__file__).resolve().parents[1] so the test survives being run
from a non-repo-root cwd, matching test_issue1112_csp_google_fonts.py.

Absorbed from PR #1852 follow-up commit 1448f42 by @ChaseFlorell.

Co-authored-by: Chase Florell <ChaseFlorell@users.noreply.github.com>
2026-05-07 21:14:16 +00:00
Nathan Esquenazi d703959b74 fix(user-bubble): stash code fences before math to keep code-blocks literal
PR #1854 added a math stash to _renderUserFencedBlocks so backslash LaTeX
delimiters (\[..\], \(..\)) survive esc() and reach the KaTeX renderer in
user bubbles. The stash ran BEFORE the existing code-fence stash, so a
user-typed code block containing LaTeX-like syntax was extracted as
KaTeX and rendered as math inside <pre><code>:

    ```
    \[ a + b \] is wrong
    ```
  → <pre><code><div class="katex-block"> a + b </div> is wrong</code></pre>

renderMd() (assistant path) handles this correctly by running fence_stash
before math_stash. The user-bubble path got the order inverted. Fix:
stash code fences first, then run the math regexes on the
outside-of-fence text only. Both top-level math and code-fenced literals
now render correctly:

  - "math: \[ x + y \]"           → KaTeX block
  - "```\n\[ a + b \]\n```"       → literal <pre><code>\[ a + b \]</code></pre>

Adds two regression tests:
  - test_user_code_block_with_latex_syntax_renders_as_literal_code
    (fails pre-fix, asserts no KaTeX wrappers inside <pre><code>)
  - test_user_bubble_top_level_latex_still_renders_after_fence_reorder
    (sibling guard against over-correcting and disabling math entirely)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 14:03:04 -07:00
nesquena-hermes 945e7af751 fix: keep panel-header label at flex-shrink:2 (preserves shrink hierarchy)
Earlier in this branch I'd reduced .panel-header > span:first-child to
flex-shrink:1 thinking it would let heading + chip fit better at the
default 300px panel width. That broke
test_workspace_label_shrinks_with_ellipsis which pins the
git-badge:3 > label:2 > icons:0 shrink hierarchy as load-bearing
(git badge collapses first, label second, icons never).

The chip-on-narrow-panel concern is now addressed by the @container
query that hides the chip entirely below 420px container width — the
heading no longer competes with the chip for horizontal space, so
flex-shrink:2 is fine again.
2026-05-07 20:50:13 +00:00
nesquena-hermes 4c51521c89 chore(release): stamp v0.51.22 — 3-PR batch (P0 markdown streaming hotfix + CSP source-map allowance + LaTeX delimiter rendering) 2026-05-07 20:48:09 +00:00
Michaelyklam d44513aabd fix: render backslash LaTeX delimiters in chat
Closes #1847

Co-authored-by: Michaelyklam <Michaelyklam@users.noreply.github.com>
2026-05-07 20:43:01 +00:00
ChaseFlorell d8612ba323 fix: add cdn.jsdelivr.net to CSP connect-src to allow xterm source map fetches
Closes #1850

Co-authored-by: Chase Florell <ChaseFlorell@users.noreply.github.com>
2026-05-07 20:42:55 +00:00
nesquena-hermes 4ffa40282f test: tighten smd import shape — forbid bare AND root-absolute, require './' relative
The two tests that pin streaming-markdown's import shape were updated
to require the './' relative form and forbid BOTH the bare specifier
(broken by ES spec, #1849) AND the root-absolute form (broken under
subpath deployments like /hermes/). The original tests only forbade
root-absolute, which let the bare-specifier regression land
unnoticed.
2026-05-07 20:42:55 +00:00
ChaseFlorell 94aeb538f2 fix: use './' relative ES module specifier for smd.min.js (closes #1849)
The original specifier 'static/vendor/smd.min.js' was a bare module
specifier, which the [HTML spec](https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier)
rejects: relative ES module references must start with '/', './', or
'../'. The block failed silently, window.smd was never set, and live
streaming markdown was broken for all users.

Fix: change to './static/vendor/smd.min.js' — the './'-relative form
satisfies both the ES module spec AND keeps the import resolution
mount-agnostic, so subpath deployments like /hermes/ continue to work.
Tests test_smd_vendor_import_is_mount_agnostic and
test_static_vendor_import_is_relative_to_current_mount updated to
require the './' form and forbid both the bare-specifier and
root-absolute forms.

Adapted from PR #1851 by @ChaseFlorell. Original PR fix used the
root-absolute form which fixed the bare-specifier bug but broke
subpath deployments; the './' form is the only shape that satisfies
both constraints.

Co-authored-by: Chase Florell <ChaseFlorell@users.noreply.github.com>
2026-05-07 20:42:19 +00:00
nesquena-hermes 1a533ec770 ux(workspace): hide hidden-files chip entirely on narrow panels
At the default 300px panel width, even the icon-only chip + 'Workspace'
heading + 5 action buttons overflowed and triggered ellipsis on the
heading ('WORKSP...'). Cleaner: hide the chip below 420px container
width and rely on the kebab's accent dot as the non-default-state
signal. The dot costs zero horizontal space (absolute-positioned over
the kebab icon) and the kebab's tooltip still labels what's happening.
On wider panels (user-resized, or future layouts), the full chip with
text appears.
2026-05-07 19:39:46 +00:00
nesquena-hermes d8afba8001 ux(workspace): mute chip color + collapse to icon-only on narrow panels
Vision review of v1 flagged the chip's accent-yellow as 'loud and ugly'.
Switched to muted hover-bg + 1px border for a subtler badge look. Also
addressed heading truncation: at the default 300px panel width, heading
(95px) + 5 action buttons (154px) + chip text (110px) overflows, so the
heading was ellipsing to 'W...'. Added a container query on the existing
.rightpanel container that drops the chip text below 360px container
width, leaving just the eye icon (tooltip still labels it).
2026-05-07 19:36:27 +00:00
nesquena-hermes 9d971b7d3f ux(workspace): move 'Show hidden files' toggle to kebab menu (#1793)
Replaces the always-visible inline toggle row that ate ~32px below the
breadcrumb on every panel view (root, subdir, file preview). The toggle
is a set-once preference — most users flip it once or never — so the
control hides behind a kebab dropdown in the panel-actions row instead.

A small 'hidden visible' indicator next to the WORKSPACE heading flags
the non-default state so users don't forget the pref is on. Click the
indicator to reopen the menu and uncheck.

The localStorage key, filtering behavior, and the canonical
\`workspaceShowHiddenFiles\` checkbox id are unchanged — the checkbox
is rebuilt inside the dropdown each time it opens. All 11 existing
regression tests for #1793 stay green; 7 new tests pin the kebab
affordance shape.
2026-05-07 19:32:51 +00:00
nesquena-hermes 9f7f5a03e4 Merge pull request #1844 from nesquena/stage-315
v0.51.21 — 3-PR batch (P0 hotfix for #1828 + auto-compression UI + shell HTML fallback)
v0.51.21
2026-05-07 11:55:52 -07:00
hermes-agent 2b2dd23e03 chore(release): stamp v0.51.21 — 3-PR batch (P0 hotfix + auto-compression UI + shell HTML fallback)
3 PRs across kanban (#1843: P0 hotfix for v0.51.20 #1828's double-404
JSON corruption on the wire), streaming (#1838: SSE compressing event
bridge for auto-compression running state), and shell route (#1836:
HTML 503 fallback so / never returns JSON during restart races).

In-stage absorb:
- api/kanban_bridge.py: documented handle_kanban_* three-valued return
  contract with bool|None type annotations + docstring after PR #1843
  made False-vs-None load-bearing for the caller's 404 decision.

4805 → 4810 collected (+5). 4799 pass + 8 skip + 1 xfail + 2 xpass.
Browser API harness 11/11 green. JS syntax 1/1 clean.
Opus advisor SHIP verdict, 1 absorbed in-release, 1 deferred to follow-up.

Closes #1832, #1835. Hotfix for v0.51.20 #1828.
2026-05-07 18:53:37 +00:00
hermes-agent 5f6a55185c stage-315 absorb: document handle_kanban_* three-valued return contract
Per Opus pre-release verdict on PR #1843: the four handle_kanban_*
entry points declare '-> bool' but actually return True | None | False
(after PR #1843 made the False-vs-None distinction load-bearing for
the caller's '_kanban_unknown_endpoint' decision). Update the type
annotations to 'bool | None' and add a docstring on handle_kanban_get
(with cross-references on the three siblings) so a future contributor
adding a new return path doesn't accidentally produce a 0/'' value
that would silently revert the double-404 fix.

Test-only verification: kanban tests pass (49/49). Production behavior
unchanged. Cheap defensive cleanup per Nathan's standing absorb-in-release
default for ≤20-LOC documentation/type-annotation fixes.
2026-05-07 18:52:01 +00:00
nesquena-hermes d750fab14a Stage 315: PR #1836 — keep shell route errors html by @Michaelyklam 2026-05-07 18:41:14 +00:00
nesquena-hermes 740e5412a5 Stage 315: PR #1838 — show auto-compression running state by @Michaelyklam 2026-05-07 18:41:13 +00:00
Michael Lam 78c09e1fd9 fix: keep shell route errors html 2026-05-07 18:41:13 +00:00
Michael Lam e31b7e72d6 fix: show auto-compression running state 2026-05-07 18:41:13 +00:00
nesquena-hermes a6301e426d Stage 315: PR #1843 — avoid double 404 response when bridge already sent error by @nesquena 2026-05-07 18:41:12 +00:00
Nathan Esquenazi f3b56d8793 fix(kanban): avoid double 404 when bridge already sent error response
PR #1837's new `_kanban_unknown_endpoint` wrapper was triggered for any
falsy bridge return — but `handle_kanban_*` returns `None` (not `True`)
when an inner handler calls `bad(...)` to send an error response. The
wrapper then sent a SECOND 404 on top of the bridge's response, producing
concatenated JSON bodies on the wire.

Concrete reproducer (caught by behavioural harness, not the merged tests):

    GET /api/kanban/tasks/<missing-id>/log
    →  '{"error":"task not found"}{"error":"unknown Kanban endpoint: GET ..."}'

This affected every `bad(...)`-shaped error path in the bridge:
- task-not-found returns from `_task_log_payload` / `_task_detail_payload`
- exception handlers for ImportError (503), LookupError (404),
  ValueError (400), RuntimeError (409) across all four method handlers
- the `_handle_events_sse_stream` board-resolution failure path

The fix: distinguish an explicit `False` (truly unmatched path) from
`None` (handled, response already sent). Only `False` should trigger
the unknown-endpoint diagnostic.

Adds a regression test that exercises the task-not-found path through
`routes.handle_get` and asserts only one JSON body is on the wire.

Follow-on to #1837 (already merged into master at v0.51.20).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 11:35:57 -07:00
nesquena-hermes ac8a41bc1f Merge pull request #1837 from nesquena/stage-314
v0.51.20 — 5-PR contributor follow-on batch + 2 in-stage absorbs
v0.51.20
2026-05-07 11:26:35 -07:00
hermes-agent ab348219ff chore(release): stamp v0.51.20 — 5-PR follow-on batch + 2 in-stage absorbs
5 contributor PRs across Kanban (#1828: stale-client recovery + hard-refresh
button + board-pointer drift fix), providers (#1827: Codex card live+cache
merge enhancing v0.51.19 #1812), cron (#1826: no-agent edits without prompt),
and workspace UI (#1825: cruft filter; #1822: heading root actions).

In-stage absorbs:
- static/panels.js: removed duplicate loadKanbanBoards tail call to avoid
  doubling /api/kanban/boards traffic under SSE-driven refreshes.
- tests/test_issue1807_codex_provider_card_live_models.py: CODEX_HOME
  isolation for v0.51.19 tests now load-bearing under PR #1827's cache merge.

Parallel-discovery resolution: #1821 (ai-ag2026, leaner) closed as
superseded by #1826 (Michaelyklam, more thorough — Mode badge,
disabled-prompt, i18n hint, screenshot).

4790 → 4805 collected (+15). 4794 pass + 8 skip + 1 xfail + 2 xpass.
Browser API harness 11/11 green. JS syntax 3/3 clean.
Opus advisor SHIP verdict, 1 absorbed in-release, 4 deferred to follow-ups.

Closes #1786, #1793, #1820, #1823.
2026-05-07 18:23:59 +00:00
hermes-agent a1eec6d191 stage-314 absorb: remove duplicate loadKanbanBoards tail call in loadKanban
PR #1828 added an await loadKanbanBoards() at the START of loadKanban() to
resolve the active board before board-scoped requests fire (so a stale saved
slug can fall back to default cleanly). The existing tail-of-function refresh
at line 1278 was harmless under one-time loads but doubles /api/kanban/boards
traffic under SSE-driven refreshes (debounced at 250ms via
_scheduleKanbanRefresh). The 30-second polling interval started by
_kanbanStartPolling() picks up any board state changes that arrive after
the render, so the tail call is redundant in PR #1828's new model.

Per Opus pre-release verdict: SHIP with this perf cleanup as in-release
absorb (5 LOC delta, clearly defensive, no behavior change for the
single-load case).
2026-05-07 18:21:56 +00:00