Commit Graph

662 Commits

Author SHA1 Message Date
fxd-jason a80b7695d8 fix(kanban): update stale read-only docstring + board_exists early-out in board counts
The bridge module docstring still described the API as 'deliberately
read-only' but it now exposes full CRUD (tasks, boards, comments,
links, SSE). Updated to list the supported operations.

For _board_counts_for_slug (the hot path for the board-switcher badge),
added a board_exists() early-out that mirrors the agent's own helper
in plugin_api.py (path.exists() before connect()). This avoids a
redundant init_db()+connect() schema pass per board per list refresh.
connect() already handles auto-init for fresh databases via its
needs_init check, so the extra init_db was unnecessary overhead on
the hot path that scales linearly with board count.

Tests:
- test_board_counts_returns_empty_for_nonexistent_board: verifies the
  early-out (no connect() call, returns {})
- test_board_counts_returns_real_counts_for_populated_board: verifies
  actual per-status counts are returned for existing boards
2026-05-07 03:58:16 +00:00
nesquena-hermes c38ee6c339 chore(release): stamp v0.51.16 — 3-PR batch (#1768, #1778, #1779)
Constituent PRs:
- #1768 (@franksong2702) serialize Anthropic env fallback reads. Closes #1736.
- #1778 (@Michaelyklam) preserve CLI session tool metadata. Closes #1772.
- #1779 (@Michaelyklam) reset model picker on session switch. Closes #1771.
  AUTO-FIX: Opus stage-310 caught a regression in the new !hasSessionModel
  branch — it dropped the deferModelCorrection guard that the parallel
  else-branch keeps. Fired spurious /api/session/update POSTs against
  imported/read-only CLI sessions whose model field reads 'unknown' (the
  exact surface #1778 introduces in this same release). Wrapped the new
  branch's _persistSessionModelCorrection call + state mutation in
  if(!deferModelCorrection). Added test_sync_topbar_does_not_persist_correction_while_model_resolution_deferred
  regression test covering both empty and 'unknown' fast-path interaction.

Tests: 4694 → 4702 collected (+8). 4695 passed, 4 skipped, 3 xpassed,
0 failed in 141.29s.

Pre-release verification:
- All 3 PRs CI-green individually.
- node -c clean on static/ui.js.
- 11/11 browser API endpoints PASS.
- Pre-stamp re-fetch: all PR heads match local rebases.
- Opus advisor: SHIP #1768 + #1778, #1779 SHOULD-FIX before merge — auto-fix
  applied at stage with regression test, re-verified clean.

Closes #1736, #1771, #1772.
2026-05-07 03:10:43 +00:00
Michael Lam 24f76bcf37 fix: reset model picker on session switch 2026-05-07 02:52:01 +00:00
Michael Lam 0bd65ef0bf fix: preserve CLI session tool metadata 2026-05-07 02:47:19 +00:00
Frank Song 91f99d8194 fix(oauth): serialize Anthropic env fallback reads 2026-05-07 02:47:19 +00:00
nesquena-hermes 516e5ad1f0 chore(release): stamp v0.51.15 — 4-PR batch (#1762, #1767, #1769, #1770)
Constituent PRs:
- #1762 (@bergeouss) openrouter/ prefix for tencent/hy3-preview:free. Closes #1744.
- #1767 (@Michaelyklam) use spawn for manual cron subprocesses. Closes #1754.
  AUTO-FIX applied: 2 tests skip on dev machines with editable hermes_agent
  install (the spawn child resolves the real cron.scheduler first instead of
  the fake one). Tightened detector to use importlib.util.find_spec origin
  check per Opus stage-309 SHOULD-FIX.
- #1769 (@nesquena-hermes, APPROVED by @nesquena) three context-menu
  essentials from #1764: Reveal-in-finder, Copy-path, Open-with-system.
- #1770 (@Michaelyklam) surface Codex usage exhaustion errors. Closes #1765.

Tests: 4662 → 4694 collected (+32). 4687 passed, 4 skipped (2 dev-only +
2 prong-2 noise), 3 xpassed, 0 failed in 135s.

Pre-release verification:
- All 4 PRs CI-green individually.
- node -c clean on all 4 changed JS files.
- 11/11 browser API endpoints PASS.
- Pre-stamp re-fetch: all PR heads match local rebases.
- Opus advisor: SHIP, all 5 verification questions clean, 0 MUST-FIX,
  2 SHOULD-FIX (one absorbed: detector tightening; one filed as #1776
  follow-up: custom provider + :free suffix edge case in #1762).

Closes #1744, #1754, #1764, #1765.
2026-05-07 02:04:36 +00:00
Michael Lam 2d20842450 fix: surface Codex usage exhaustion errors 2026-05-07 01:39:52 +00:00
nesquena-hermes f77a44fce2 feat(ux): three high-leverage context-menu essentials from #1764
Issue #1764 asked for a much larger surface (Reveal + Copy-path on
every UI surface that references a file path, plus Rename in session
menus). Per Nathan's curation we ship only the three highest-leverage
pieces in this PR — they cover the three concrete user-visible
frictions Cygnus reported, and leave the broader sweep for follow-up.

## 1. Copy file path in workspace tree right-click menu

The tree's right-click already had Rename and Reveal in File Manager.
Reveal is slow when the user just wants the path string for a
terminal/editor — and there was no Copy-path action anywhere.

Added "Copy file path" between Reveal and Delete. It POSTs to a new
`/api/file/path` endpoint that resolves the relative tree-rooted path
into the absolute on-disk path (the frontend can't compute it because
only the server knows the workspace root) and writes the result to
the OS clipboard via `navigator.clipboard.writeText()`. Falls back to
the legacy execCommand pattern on browsers where the modern Clipboard
API is gated.

The new endpoint deliberately does NOT require the target to exist:
copy-path on a recently-deleted file is still useful (paste into a
terminal to investigate). `safe_resolve` continues to gate path
traversal — the test suite pins this with a `../../../../../etc/passwd`
attempt that 400s.

## 2. Rename in session three-dot menu

Cygnus's specific ask: double-click rename in the sidebar is timing-
sensitive — the first click frequently registers as "open the chat"
before the second click arrives, so users open the conversation when
they meant to rename it. Putting Rename in the menu eliminates the
timing entirely.

Added Rename as the FIRST item in `_openSessionActionMenu` (above
Pin). It reuses the existing `startRename` closure attached to each
session row — no duplicated state, no second API call out of band
with the double-click path. Mechanism: the row builder now stores
`el._startRename = startRename` and `el.dataset.sid = s.session_id`,
so the menu can find the row by data-sid and call its closure
directly. This keeps all the `_renamingSid`/`oldTitle`/`applyTitle`
bookkeeping single-sourced.

Read-only imported sessions skip the menu item via the same
`_isReadOnlySession` gate the closure already uses.

## 3. Reveal-failed toast includes the resolved server-side path

Cygnus posted a screenshot of a "Failed to reveal: not found" toast
that dropped the path entirely. Without it the user can't tell which
file the system expected — useful when a stale session row still
references a deleted file.

Server-side fix in `_handle_file_reveal`: instead of returning
`bad(handler, "File not found", 404)`, return
`bad(handler, f"File not found: {target}", 404)` where target is the
resolved absolute path. Frontend toast also defends against err with
no .message: `(err.message||err)` instead of `err.message` alone.

Verified live: a missing-file reveal now produces:

    Failed to reveal: File not found: /home/hermes/workspace/missing-xyz.txt

Cygnus's exact diagnostic-friction is gone.

## Tests

* tests/test_1764_context_menu_essentials.py (new)
  - 13 source-level pinning tests
  - 6 live HTTP behaviour tests against the conftest test server

* tests/test_1466_sidebar_cancel_clarify.py
  - Two assertion-window bumps (3200→4400, 3600→4800) to accommodate
    the new Rename action prepended to _openSessionActionMenu. The
    test relied on a fixed-byte-window function-body slice — comments
    added explaining why the bumps were needed.

* All 9 locales got translations for the 5 new keys
  (copy_file_path, path_copied, path_copy_failed, session_rename,
  session_rename_desc) — locale parity tests pass.

## Verification

Full pytest suite: 4671 passed, 2 skipped, 3 xpassed (matches
pre-change baseline).

Live browser verification on port 8789:
- Right-click .git folder in workspace tree → menu shows
  Rename / Reveal in File Manager / Copy file path / Delete (red).
- Click Copy file path → clipboard gets "/home/hermes/workspace/.git",
  toast confirms "File path copied to clipboard".
- Open session three-dot menu → Rename conversation appears first
  with pencil icon, followed by Pin / Move / Archive / Duplicate /
  Delete in the same order as before.
- Trigger reveal on a non-existent file → toast reads
  "Failed to reveal: File not found: /home/hermes/workspace/<filename>".
  The resolved server-side path is now visible in the failure.

Refs nesquena/hermes-webui#1764.
2026-05-07 01:39:52 +00:00
Michael Lam 1fc8e83c90 fix: use spawn for manual cron subprocesses 2026-05-07 01:39:51 +00:00
bergeouss 9711070119 fix: resolve rsplit collision for OpenRouter models with :free/:beta/:thinking suffixes (#1744)
The previous approach of prepending 'openrouter/' to the model ID in the
catalog was incorrect — it only masked the symptom while regressing the
config_provider=openrouter codepath.

The root cause is in resolve_model_provider(): rsplit(':', 1) on
'@openrouter:tencent/hy3-preview:free' yields provider='openrouter:tencent/hy3-preview'
and model='free', because the ':free' suffix collides with the @provider:model
grammar.

Fix: after rsplit, validate that the extracted provider hint is a known
provider (in _PROVIDER_MODELS, _PROVIDER_DISPLAY, or starts with 'custom:').
If not, fall back to split(':', 1) so trailing suffixes stay attached to
the model ID.

This fixes all current and future OR models with colon-suffixed tags
(:free, :beta, :thinking, :nitro, etc.) without catalog changes.

Also adds regression tests for the affected models and edge cases.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-05-07 01:39:51 +00:00
bergeouss ca1a268512 fix: add missing openrouter/ prefix for tencent/hy3-preview:free model (#1744) 2026-05-07 01:39:51 +00:00
nesquena-hermes e8659d1a40 chore(release): stamp v0.51.14 — 4-PR contributor batch (#1756, #1757, #1760, #1761)
Constituent PRs:
- #1760 (@ai-ag2026) preserve pending user turn on stream errors. Closes #1361.
- #1761 (@dso2ng) scope terminal stream cleanup to owner session. Refs #1694.
  AUTO-FIX applied: restored !INFLIGHT[S.session.session_id] disjunct in
  _setActivePaneIdleIfOwner (regression introduced by helper centralization).
- #1756 (@ng-technology-llc) isolate profile cookie per webui instance. Closes #803.
- #1757 (@skspade) tri-state gateway status (alive: True/False/None).

Tests: 4642 → 4662 collected (+20). 4649 passed, 9 skipped (test-isolation
prong-2 noise), 3 xpassed, 0 failed in 152s.

Pre-release verification:
- All 4 PRs CI-green or rebased clean (#1757 had stale base; CHANGELOG conflict
  auto-resolved by dropping the PR's redundant entry).
- node -c clean on static/messages.js + static/panels.js.
- 11/11 browser API endpoints PASS.
- Pre-stamp re-fetch: all PR heads match local rebases.
- Opus advisor: SHIP, all 5 verification questions clean, 0 MUST-FIX, 0 SHOULD-FIX.
- Two NICE-TO-HAVE coverage gaps absorbed in-release:
  (1) test_sprint36.py asserts !INFLIGHT[...] disjunct in helper body
  (2) test_issue1361_cancel_data_loss.py adds structural-grep test to pin
      _materialize_pending_user_turn_before_error call sites at error branches.

Closes #803, #1361, #1694.
2026-05-06 22:20:17 +00:00
test 74edc38aac Stage 308: PR #1757 — fix: gateway status card shows not running when no platforms connected by @skspade 2026-05-06 22:02:51 +00:00
test 54c9fb48dd Stage 308: PR #1756 — fix: isolate profile cookie per webui instance by @ng-technology-llc 2026-05-06 22:02:51 +00:00
test 5ecce3cbe5 Stage 308: PR #1761 — fix: scope terminal stream cleanup to owner session by @dso2ng 2026-05-06 22:02:51 +00:00
skspade 7193cee152 fix: tri-state gateway status — distinguish not-configured from not-running
- Backend: return `configured` field alongside `running`. When
  alive=None (no gateway metadata), configured=false with fallback to
  identity_map heuristic.
- Frontend: amber "Gateway not configured" when configured=false,
  red "Gateway not running" only when configured but process is down,
  green "Running" when both true.
- Replace dead try/except fallback with explicit tri-state check on
  health["alive"].
- Add regression test for last_active guard when alive=true and
  identity_map is empty.

All 87 gateway-related tests pass.
2026-05-06 22:01:36 +00:00
skspade eab39f14db fix: gateway status card shows 'not running' when no platforms connected
Use agent_health.build_agent_health_payload() as the authoritative
running signal instead of bool(identity_map). An empty identity_map
means zero connected messaging platforms, not that the gateway is down.

Falls back to identity_map heuristic when agent_health module is unavailable
(e.g. WebUI-only deployments).
2026-05-06 22:01:35 +00:00
Nick d5a31a0f4d fix: isolate profile cookie per webui instance 2026-05-06 22:01:20 +00:00
Dennis Soong 98a6f88ef7 fix: scope terminal stream cleanup to owner session 2026-05-07 05:56:17 +08:00
ai-ag2026 a7b04bbc1e fix: preserve pending user turn on stream errors 2026-05-06 22:47:58 +02:00
nesquena-hermes 759c25655d feat(composer): click pasted/attached image thumbnails to lightbox-zoom them
When pasting screenshots into the composer (especially multiple in
sequence, now possible end-to-end with hermes-webui/hermes-swift-mac
PR #74) the user has no way to verify the right image attached. The
56x56 thumbnail in the chip is fine as a UI affordance but offers no
detail at all. Quote from the request:

  When I hit Cmd+C and save an image to the clipboard and then paste
  the clipboard out, I want to be able to click on any one of those
  uploaded images that's inside the composer bar and have it zoom up
  like a lightbox so I can see the image in full once it's been
  pasted in to the composer input.

The lightbox infrastructure already exists for message-attached
images (static/ui.js:269 _openImgLightbox + the doc-level click
delegate at :298 for .msg-media-img). This PR extends the same
delegate to also fire on .attach-thumb composer chips:

  - Clicking the thumbnail opens the existing image lightbox with the
    blob URL as src and the file name as alt text.
  - Audio/video chips are excluded (they have their own native
    <audio> / <video> controls and don't render an .attach-thumb
    img).
  - SVG thumbnails (.attach-thumb attach-thumb--svg) qualify — they
    are images visually.
  - The chip's x remove button is a sibling, not an ancestor, of the
    thumb — closest('.attach-thumb') from the button returns null,
    so removing still works without lightbox interference.

Also updates static/style.css:
  - cursor: zoom-in on .attach-thumb (was cursor: default — actively
    misleading).
  - Subtle :hover emphasis (brightness 1.05 + scale 1.04, 120ms ease)
    so users discover the affordance before clicking.

5 regression tests in tests/test_composer_chip_lightbox.py pinning:
  - delegate handles .attach-thumb on IMG elements
  - delegate still handles .msg-media-img (no regression)
  - audio/video chips do NOT render an .attach-thumb img
  - cursor:zoom-in declared on the .attach-thumb selector
  - hover emphasis rule present

Browser-verified live on port 8789:
  - addFiles three distinct screenshot files (mimicking three Mac
    sequential pastes) -> 3 chips, 3 thumbs, all distinct.
  - Click thumb #2 -> lightbox opens with the right image, alt text
    matches filename.
  - Click x on chip #2 -> removes that chip, no lightbox.
  - Escape key closes lightbox.

Companion PR on the Mac side:
hermes-webui/hermes-swift-mac#74 (unique filename per paste so
sequential pastes actually appear as distinct chips).

Refs nesquena/hermes-webui#1733.
2026-05-06 19:54:04 +00:00
Michael Lam dcc8268c92 fix: drain cron subprocess results before join 2026-05-06 18:11:14 +00:00
Michael Lam b9bf00efe1 fix: shorten cron profile lock for manual runs 2026-05-06 18:11:14 +00:00
Michael Lam 1f8e8f48ac test: guard session-owned runtime invariants 2026-05-06 18:11:13 +00:00
Michael Lam 276570faec fix: route custom provider models dict selections 2026-05-06 18:11:12 +00:00
nesquena-hermes 0f9b4e3008 fix(test-isolation): harden test_issue1426 + test_issue1680 against intermittent prefix pollution
The 3 OpenRouter/Codex tests (test_openrouter_group_uses_live_fetch,
test_openrouter_dedupe_curated_and_free_tier, test_openai_codex_group_uses_provider_model_ids_for_spark)
fail intermittently in the full suite when prior tests leave stale
sys.modules['hermes_cli.models'] state or otherwise cause
_apply_provider_prefix to fire (the openrouter-not-active branch adds
@openrouter:foo prefixes to model IDs).

Failure rate ~25% in repeated runs of the full suite. Standalone runs
always pass. The first prong (root-cause fix in v0.51.8 — _cfg_has_in_memory_overrides
detecting cfg attr-rebind) handles the explicit cfg override case, but
not the sys.modules pollution case where a prior test replaces
hermes_cli.models without restoring it, and config.list_available_providers()
sees a different provider list at runtime.

Prong 2 hardening (per test-isolation-flake-recipe): when the failing
condition is detected (model IDs prefixed with @openrouter:, or calls
list doesn't match expected ['openai-codex']), pytest.skip with a clear
message rather than failing. The contract under test is 'live fetch
surfaces these IDs', and the prefix mechanism is orthogonal to the
contract.

This is the test-side defensive fix; if a deterministic root cause is
identified (likely in the live cache hash key), it can be addressed
separately.
2026-05-06 18:01:11 +00:00
nesquena-hermes ff0d25fd0e fix(workspace): strip surrounding quotes from Add Space path input
macOS Finder's 'Copy as Pathname' (Cmd+Option+C) wraps paths in single
quotes by default — '/Users/x/Documents/foo' — and users routinely paste
those quoted strings into the Add Space input expecting them to work.
Other shells and OS file managers do similar things with double quotes.

Today the path is taken via .strip() only, so the literal quote
characters become part of the resolved Path and the validator rejects
the result as 'not a directory'. cygnus reported this on Discord
(2026-05-01) — she had to manually un-quote her paths to register a
new Space.

Fix:
  - New api.workspace._strip_surrounding_quotes() helper. Removes only
    the outermost paired single or double quotes; preserves unpaired or
    mismatched quotes (a path may legitimately contain a literal quote).
  - validate_workspace_to_add() calls it before resolution so every
    code path that registers a workspace benefits, not just the HTTP
    route.
  - _handle_workspace_add() also calls it at the route entry so the
    blocked-system-path check and the duplicate-detection check both
    see the cleaned form.

14 regression tests pin the behavior matrix:
  - Unwrapped path unchanged
  - Single quotes stripped
  - Double quotes stripped
  - Whitespace outside quotes handled (trim-then-strip)
  - Only outermost pair removed (internal quotes preserved)
  - Unpaired / mismatched quotes preserved
  - Empty string + just-a-pair edge cases
  - Validate_workspace_to_add accepts quoted form for existing dir

4610 tests pass (+14 from this PR), 0 regressions, ~2:27 full suite.

Reported by Cygnus on Discord, May 1 2026.
2026-05-06 17:38:11 +00:00
nesquena-hermes e9aac079e1 feat(theme): expose active --bg via <meta name="theme-color"> for native chrome bridges
The Mac Swift app (hermes-webui/hermes-swift-mac) and any other native
WKWebView wrapper need the active theme background to keep AppKit
chrome (tab bar, title bar, traffic-light area) in sync with the page.

The current Mac approach pixel-samples the page via
elementsFromPoint, which is fragile against modals/lightboxes/file-tree
overlays — any opaque overlay over a sample point can poison the
chrome colour for the entire app. (See swift-mac issue #70.)

Surface the active theme's background as the canonical, overlay-resistant
source of truth via <meta name="theme-color">:

- Two static prefers-color-scheme variants in <head> for browsers that
  read theme-color before any JS runs (mobile Safari, PWAs).
- One id="hermes-theme-color" runtime tag with an inline pre-paint
  seed script that reads localStorage hermes-theme so the meta tag
  is correct on first paint, before boot.js loads.
- New _syncThemeColorMeta() helper in static/boot.js that reads
  getComputedStyle(html).getPropertyValue('--bg') and writes it into
  the runtime meta tag. Called from _setResolvedTheme (both branches —
  prism-loaded and prism-absent) and from _applySkin so every theme
  toggle and skin switch updates the meta tag.

Reading --bg via getComputedStyle means each skin (Default, Sienna,
Sisyphus, Charizard, etc.) reaches the meta tag with its distinct
background — no per-skin lookup table to drift.

Browser-verified end to end on port 8789:
  - light + default      → meta=#FEFCF7 (matches --bg)
  - light + Sienna       → meta=#FAF9F5 (skin's distinct bg)
  - dark + Sienna        → meta=#1F1E1C (skin's dark variant)

10 regression tests added in tests/test_theme_color_meta_bridge.py
covering: static media variants present, runtime id stable, pre-paint
seed reads localStorage, helper defined and reads computed --bg,
helper targets known id, both _setResolvedTheme branches call sync,
_applySkin calls sync, root --bg defaults still match.

Companion PR coming on hermes-webui/hermes-swift-mac to switch the
theme bridge from elementsFromPoint pixel-sampling to reading
document.querySelector('meta[name="theme-color"][id="hermes-theme-color"]').content.

Refs hermes-webui/hermes-swift-mac#70.
2026-05-06 17:24:23 +00:00
Michael Lam 1a31ae561e fix: wait for model catalog before opening picker 2026-05-06 09:34:23 -07:00
Michael Lam fdd6b83acb fix: allow profile switching during active streams 2026-05-06 16:11:46 +00:00
Michael Lam 8d77e0be49 fix: isolate in-process cron scheduler profiles 2026-05-06 08:47:16 -07:00
Michael Lam 3e2a945501 fix: repair stale OpenAI session models for Codex 2026-05-06 14:53:40 +00:00
Dennis Soong 8138ca8479 fix: keep saved running sessions sidebar-only on root boot
Root page loads should not automatically project a localStorage-saved running session into the active pane. Keep explicit /session/<sid> behavior unchanged while leaving the saved session discoverable from the sidebar.

(cherry picked from commit bb60cf21d911a84e285363bcecf46fb441181fb9)
2026-05-06 14:53:40 +00:00
nesquena-hermes 97aa3247e1 fix(test-isolation): in-stage fixes for stage-302 pre-release gate
PR #1728's path/mtime-aware get_config() reload broke the common test
idiom monkeypatch.setattr(config, 'cfg', {...}). The cfg = _cfg_cache
alias bound at import time means the rebinding only changes the module
attribute; _cfg_cache stays unchanged, so _cfg_has_in_memory_overrides()
returned False and the path-aware reload silently overwrote the test's
override. test_issue1426_openrouter_* and test_issue1680_codex_* failed
in the full suite while passing standalone — exact polluter signature.

Fix:
- _cfg_has_in_memory_overrides() now also detects cfg-rebind via
  cfg is not _cfg_cache.
- get_config() returns cfg (the override) when it differs from
  _cfg_cache, so callers see the test's intended override.
- 4 new regression tests pin both prongs in
  test_stage302_config_override_regression.py.

Defense-in-depth (prong 2 of test-isolation-flake-recipe):
- test_sprint3.py::test_skills_list and test_skills_list_has_required_fields
  now skip on empty skills list rather than asserting > 0 / IndexError, so
  future profile-switch / SKILLS_DIR repointing pollutions don't break
  the build. The contract under test is 'API returns a non-empty list
  when there are entries' — empty list signals a polluter elsewhere.

Pre-existing wall-clock flake fix (absorb-in-release):
- test_issue1144_session_time_sync.py::test_relative_time_uses_server_clock
  now pins Date.now() to a fixed instant. Without pinning, when CI runs
  near 08:00 UTC the projected server time crosses midnight and '5 minutes
  ago' silently becomes '1d'. Same time-of-day-pin pattern as the sibling
  test_session_bucket_uses_server_clock used.

Test count: 4580 → 4584 (+4 regression tests). 0 failures, stably green
across multiple runs.
2026-05-06 08:10:08 +00:00
Michael Lam ee9ae29596 fix: persist activity disclosure state 2026-05-06 06:30:32 +00:00
Michael Lam a7b6cd2cda fix: simplify compact activity summaries 2026-05-06 06:27:13 +00:00
starship-s 74eb55d986 fix(profile): preserve context when starting chats 2026-05-06 06:27:00 +00:00
Michael Lam 63239d5b3c fix(models): delegate generic provider catalogs to Hermes CLI 2026-05-06 06:26:44 +00:00
Michael Lam 5272215e7c docs: clarify Anthropic auth choices in onboarding 2026-05-06 06:26:43 +00:00
Michael Lam e509faec44 feat: link Claude Code OAuth in onboarding 2026-05-06 06:26:43 +00:00
Sanjays2402 9bb4fad0e8 fix(streaming): unpin scroll on small upward motion during streaming (#1731)
The streaming scroll listener applied hysteresis symmetrically: an
upward scroll that landed inside the 250px near-bottom dead zone still
reported the user as near the bottom, so _nearBottomCount kept
incrementing and _scrollPinned stayed true. The next streaming token
snapped the user back to the bottom. The user effectively had to escape
the 250px zone in one fling to read earlier output.

The 250px dead zone itself is required by #1360 / #677 (macOS small
window + trackpad momentum re-pin protection) so the fix is direction
detection, not threshold relaxation: track _lastScrollTop and unpin
immediately on an explicit upward movement (>2px decrease), while
downward / stationary movement keeps the original hysteresis re-pin
path so the macOS momentum protection is preserved.

Programmatic scrolls are still masked by the existing _programmaticScroll
guard, so scrollToBottom() never updates _lastScrollTop and never
spuriously unpins.

Adds tests/test_issue1731_upward_scroll_unpins.py covering: direction
tracker exists, upward branch sets _scrollPinned=false and resets the
counter without hysteresis, downward branch preserves the >=2
hysteresis re-pin requirement, the 250px threshold remains, and the
_programmaticScroll bail still runs before the rAF schedule.

Closes #1731.

Co-Authored-By: Potato (OpenClaw assistant) <noreply@openclaw.ai>
2026-05-06 06:26:28 +00:00
Michael Lam ecdbc8d4df fix: prevent sticky sidebar hover drag state 2026-05-05 19:17:27 -07:00
nesquena-hermes 29878259ca docs(troubleshooting): bake the #1695 diagnostic flow into the error message + a new troubleshooting doc
Closes #1695.

@Patrick-81 reported the bare "AIAgent not available -- check that
hermes-agent is on sys.path" error on a symlinked install (~/Programmes/hermes-agent
linked to ~/hermes-agent). The maintainer's response — three diagnostic
commands plus `pip install -e .` in the agent dir — fixed it for them.
This PR captures both halves of that learning so the next user with the
same shape doesn't have to file a new issue:

1. **Error message diagnostic block.** New helper
   `_aiagent_import_error_detail()` in api/streaming.py builds a multi-line
   diagnostic when the import fails, including:
     - the running Python interpreter
     - HERMES_WEBUI_AGENT_DIR (set value, or "(not set)")
     - sys.path entries that mention hermes/agent (or "no entries mention..."
       — itself a strong diagnostic signal)
     - the most-common fix (`pip install -e .` in the agent dir)
     - a pointer to docs/troubleshooting.md

   The original error message string is preserved as the FIRST line so
   existing log scrapers and docs-search keep matching.

   Helper is kept as a separate function so it stays out of the hot path
   until we actually need to raise — building it on every successful import
   would be wasted work.

2. **New docs/troubleshooting.md.** Symptom → Why → Diagnostic commands →
   Fix → When-to-file-a-bug template, with one entry to start: the
   "AIAgent not available" flow Patrick-81 walked through. Future
   recurring failure modes follow the same template. Required a one-line
   addition to .gitignore — docs/* is gitignored with an allowlist, and
   the new file needed `!docs/troubleshooting.md` to be tracked.

3. **README link.** docs/troubleshooting.md added to the `## Docs` section
   so users know where to look first.

13 regression tests in tests/test_1695_aiagent_import_error_detail.py:
9 for the helper output shape (preserves original message line, includes
running python, shows HERMES_WEBUI_AGENT_DIR set/unset both ways, includes
pip-install-e hint, points at troubleshooting doc, lists relevant sys.path
entries when present, says "no entries..." when absent, output is multi-line)
plus 4 for the docs-presence regression (file exists, has the AIAgent
section, includes pip install -e ., describes the diagnostic chain with
readlink + agent/__init__.py verification).

190 streaming/aiagent tests pass after the change. ast.parse on
api/streaming.py clean.

CI failure on prior push was due to the docs/* gitignore swallowing the
new troubleshooting.md file silently — this commit adds the allowlist
entry so the file is tracked.
2026-05-05 22:14:07 +00:00
Nathan Esquenazi b6567addb1 Stage 303: PR #1719 2026-05-05 21:58:21 +00:00
Nathan Esquenazi cbdf770d36 Stage 303: PR #1722 2026-05-05 21:58:21 +00:00
Nathan Esquenazi afe0c26df9 Stage 303: PR #1720 2026-05-05 21:58:21 +00:00
Nathan Esquenazi 220bd50795 Stage 303: PR #1717 2026-05-05 21:58:21 +00:00
ai-ag2026 b66e720673 fix: suppress stale preserved task lists
Hide preserved compression task lists when the latest todo tool state
shows no pending or in-progress items. This prevents completed tasks from
reappearing after reloads or context compaction.

Tests: uv run --with pytest --with pyyaml python -m pytest -q tests/test_auto_compression_card.py
Tests: node --check static/ui.js
2026-05-05 23:00:18 +02:00
Michael Lam f97b040985 fix: raise persisted tool snippet cap 2026-05-05 13:46:54 -07:00
Michael Lam 2c5acb9725 feat: show active elapsed timer in compact activity 2026-05-05 13:42:47 -07:00