Commit graph

5617 commits

Author SHA1 Message Date
nesquena-hermes
4e71be2268
Merge pull request #5150 from nesquena/stage/5147-approval
fix(gateway): clear stale approval mirrors on stream teardown (#5147)
2026-06-28 15:04:47 -07:00
nesquena-hermes
beb883a159 docs(changelog): clear stale gateway approval mirrors on teardown (#5147) 2026-06-28 21:57:12 +00:00
nesquena-hermes
c556262159 Merge branch 'pr-5147-head' into stage/5147-approval 2026-06-28 21:48:51 +00:00
nesquena-hermes
9bb23faf79
Merge pull request #5146 from nesquena/stage/5118-scroll
fix: pinned streaming scroll restoration (#5118)
2026-06-28 14:47:10 -07:00
nesquena-hermes
174d804da5
Merge branch 'master' into stage/5118-scroll 2026-06-28 14:44:08 -07:00
nesquena-hermes
230a3af684 docs(changelog): pinned streaming scroll restoration (#5118) 2026-06-28 21:40:18 +00:00
nesquena-hermes
247aa55011
Merge pull request #5143 from nesquena/stage/5119-i18n
fix(i18n): complete Polish localization (#5119)
2026-06-28 14:34:39 -07:00
Rod Boev
62be5b02ea fix(#5135): clear stale gateway approval mirrors on stream teardown 2026-06-28 17:32:55 -04:00
nesquena-hermes
4db4b3d6f9
Merge branch 'master' into stage/5119-i18n 2026-06-28 14:26:38 -07:00
nesquena-hermes
fc087c067f Merge branch 'pr-5118-head' into stage/5118-scroll 2026-06-28 21:22:53 +00:00
nesquena-hermes
10642bc8bd
Merge pull request #5140 from nesquena/stage/5131-cron
fix(cron): recompute provider/model snapshots in the selected profile (#5131)
2026-06-28 14:13:31 -07:00
nesquena-hermes
48e6df9e39 Merge branch 'pr-5119-head' into stage/5119-i18n 2026-06-28 21:10:54 +00:00
nesquena-hermes
d482f8d256 docs(changelog): cron-snapshot profile recompute (#5131) 2026-06-28 21:10:28 +00:00
nesquena-hermes
fb7b16e723 Merge branch 'pr-5131-head' into stage/5131-cron 2026-06-28 20:51:44 +00:00
nesquena-hermes
d8ff8bf2c5
Merge pull request #5138 from nesquena/stage/5113-brand
Brand: new cyan caduceus favicon + top-nav logo (#5113)
2026-06-28 13:48:56 -07:00
nesquena-hermes
5c2d4f243b docs(changelog): new cyan caduceus brand mark (#5113) 2026-06-28 20:37:05 +00:00
nesquena-hermes
a17080d61f Merge branch 'pr-5113-stage' into stage/5113-brand 2026-06-28 20:36:46 +00:00
nesquena-hermes
bba0ef37d1
Merge pull request #5137 from nesquena/stage/p1-lowrisk-a
docs(architecture): settled fallback ownership matrix + guard test (#5090)
2026-06-28 13:27:13 -07:00
nesquena-hermes
9b17c1c80a Merge branch 'pr-5090-head' into stage/p1-lowrisk-a 2026-06-28 20:22:22 +00:00
nesquena-hermes
822effe0d5 test(graphite-skin): drop stale .app-titlebar-icon rect assertion
PR #5113 (new cyan caduceus brand mark) intentionally removed the per-skin
.app-titlebar-icon rect/path/circle recolor overrides — the old gold mark was
forced to the skin accent, but the new brand badge is self-colored and constant
across themes. test_graphite_skin_tunes_workbench_chrome still asserted that
removed rule. Dropped the obsolete assertion; the rest of the graphite chrome
coverage is unchanged.
2026-06-28 20:11:22 +00:00
nesquena-hermes
1cbf364f48 Merge remote-tracking branch 'origin/master' into pr5113 2026-06-28 20:05:50 +00:00
Rod Boev
2eeb0f0f53 fix(#5130): serialize profile-scoped snapshot repair 2026-06-28 16:03:25 -04:00
nesquena-hermes
e3271b099a
Merge pull request #5133 from nesquena/stage/brick-5096
fix(session): context-correctness bundle (#5096 A-D) + SSE live-turn blank-out (#5122)
2026-06-28 13:03:11 -07:00
Rod Boev
fd139132b6 fix(#5130): harden cron snapshot repair follow-up 2026-06-28 15:56:50 -04:00
nesquena-hermes
e60689bf28 docs(changelog): brick batch entries (#5122 SSE blank-out, #5096 fork/edit/rewind context bundle #5124/#5125/#5126) 2026-06-28 19:37:26 +00:00
nesquena-hermes
2d249089fc fix(session): guard _evict_session_agent against closing a live session's _session_db
Mirror the worker's LRU-eviction ACTIVE_RUNS liveness check in
_evict_session_agent so truncate/clear/model-switch can't close the SessionDB
out from under an in-flight turn on the same session (#5096 Bug D follow-up).
The cache handle still drops (harmless — worker holds a local ref); only the
lifecycle commit + _session_db.close() are skipped while a run is active.

Co-authored-by: claw-io <claw-io@users.noreply.github.com>
2026-06-28 19:36:59 +00:00
Rod Boev
7e806d7194 fix(#5130): recompute cron snapshots in selected profile 2026-06-28 15:29:40 -04:00
nesquena-hermes
2ebcaeac64 Merge branch 'pr-5122-head' into stage/brick-5096 2026-06-28 18:26:34 +00:00
nesquena-hermes
aae7cc58d2 Merge branch 'pr-5126-head' into stage/brick-5096
# Conflicts:
#	api/session_ops.py
2026-06-28 18:14:03 +00:00
nesquena-hermes
57061d0b19 Merge branch 'pr-5125-head' into stage/brick-5096 2026-06-28 18:13:29 +00:00
nesquena-hermes
c62a23d0bf Merge branch 'pr-5124-head' into stage/brick-5096 2026-06-28 18:13:29 +00:00
b3nw
6e499eb1dd fix(session): semantic context truncate on rewind + evict agent
Refs nesquena/hermes-webui#5096

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-06-28 18:01:56 +00:00
b3nw
bf1f8339b8 fix(ui): use absolute keep_count for edit and regenerate truncate
Refs nesquena/hermes-webui#5096

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-06-28 18:01:54 +00:00
b3nw
aacf860ccc fix(session): truncate context_messages when branching from fork point
Refs nesquena/hermes-webui#5096

Co-authored-by: b3nw <b3nw@users.noreply.github.com>
2026-06-28 17:59:46 +00:00
allenliang2022
b0d01e492a fix(test): update run-journal reconnect anchor for the (1/N) template literal
test_error_reconnect_path_can_restore_from_journal anchored on the exact string
setComposerStatus('Reconnecting — but the first status was changed to a template
literal `Reconnecting… (1/${_retryDelays.length})` for the staged-probe counter,
so that single-quoted anchor no longer exists (ValueError: substring not found in
CI shard 2). Re-anchor on the block's stable entry point _reconnectAttempted=true;
(unique in source) and widen the window to 1100 chars; the st.active /
st.replay_available / Restoring stream / _runJournalReplayParams() assertions are
unchanged.
2026-06-29 01:22:40 +08:00
allenliang2022
265e176f6c address greptile P2s: stage counter starts at (1/N); brace-match test window
- messages.js: declare _retryDelays before the first setComposerStatus and show
  'Reconnecting… (1/4)' on the initial probe so the counter progresses
  (1/4)->(2/4)->(3/4)->(4/4) instead of jumping from a bare 'Reconnecting…' to
  (2/4) (looked like it started at 2).
- test: replace the fixed block_start+1600 char window with a brace-matched
  _reconnect_block() helper so the ordering / no-early-clear assertions cannot
  silently under-assert if the reconnect block grows. Add a source-lock for the
  (1/N) stage-counter start.
2026-06-29 01:16:53 +08:00
allenliang2022
8edbf48f32 fix(webui): stage SSE-error reconnect probes so the live turn doesn't blank out
After the chat EventSource fires 'error', attachLiveStream made a single 1.5s
reconnect probe and, if /api/chat/stream/status did not yet report active or
replay_available, fell straight through to _handleStreamError(): clearing the
owner INFLIGHT state, nulling S.activeStreamId, pushing a 'Connection
interrupted' message and re-rendering. That wiped the live message DOM even
when the backend was still producing tokens or the run-journal replay file was
a beat away from becoming visible, so the settled response disappeared and then
reappeared from sidecar/replay (or only on refresh).

Replace the single probe with a short staged retry window
(_retryDelays=[1500,3000,5000,8000] ms) driven by a recursive _probeReconnect.
Each stage re-queries stream status and reconnects/replays when the backend is
reachable; the live DOM and S.activeStreamId/INFLIGHT state are kept intact
across the whole window, and _handleStreamError is only reached after every
stage has failed.

Adds tests/test_sse_error_multi_probe_reconnect.py source-locking the staged
shape and the ordering invariant (terminal error only after the retry window is
exhausted; no inflight/activeStreamId clear mid-window).
2026-06-29 01:04:26 +08:00
santastabber
b6b7f1df1d Fix pinned stream scroll restoration 2026-06-29 00:52:36 +10:00
Leszek
7a1583938a fix(i18n): translate missing keys and add helper functions for Polish language 2026-06-28 15:32:09 +02:00
Frank Song
73ff1cd53b Merge origin/master into anchor fallback audit
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-28 17:13:55 +08:00
nesquena-hermes
e24e6c54a6
Merge pull request #5108 from nesquena/salvage/3976-mobile-pwa-reflow
fix(mobile): PWA reflow guard + avoid redundant same-session refresh (salvage of #3976)
2026-06-28 02:05:50 -07:00
Frank Song
2ef838be8c test(anchor): widen JS regex extraction guard
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-28 17:05:16 +08:00
Frank Song
767285f21f Merge origin/master into anchor fallback audit
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-28 17:02:52 +08:00
nesquena-hermes
7ec7b9c942 fix(mobile): PWA reflow guard + avoid redundant same-session refresh (salvage of #3976)
Salvages PR #3976 by ksj5131513-ops, ported fresh onto current master and
re-applied on top of #4195's external-refresh gate (the original PR predated
#3916/#4195's rework of refreshActiveSessionIfExternallyUpdated).

Two parts:

1. Avoid an unconditional same-session force reload the moment streaming
   settles. _scheduleActiveSessionIdleReload previously called
   loadSession(force:true) on every idle reconcile, which on mobile PWA caused
   a visible end-of-turn flash and could briefly restore the pane with stale
   layout geometry. It now reconciles against server metadata first via
   refreshActiveSessionIfExternallyUpdated('idle-reconcile', {ignoreStreamJustFinished:true})
   — reloading ONLY when the message count actually changed — and falls back to
   the original forced reload only if the metadata probe itself fails.

   This COEXISTS with #4195: the poll-only external-session early-return
   (if((reason||'poll')==='poll' && !_isExternalSession(S.session)) return) is
   left untouched. The idle path uses a non-'poll' reason so it never trips that
   gate, and opts (read via arguments[1], same pattern as loadSession) only
   bypasses the post-stream cooldown, not the external gate. The function keeps
   its (reason) signature and now returns a status string
   ('skipped'/'unchanged'/'reloaded'/'failed') for callers that want it.

2. Mobile viewport reflow guard for PWA visualViewport changes. When the
   on-screen keyboard / browser chrome shows or hides, the phone layout can stay
   painted against stale geometry. _forceMobileViewportReflow() toggles a
   one-frame html.viewport-reflow class (cheap GPU-promotion transform) to force
   a repaint, then resyncs the workspace panel + sidebar aria. Wired to
   visualViewport resize/scroll (debounced) where available, falling back to the
   window resize handler otherwise. Adds safe-area-inset padding to topbar,
   messages-inner, and composer-wrap on phones. Uses overflow-x:clip (NOT hidden)
   on .layout per the #4856 lesson to avoid coercing overflow-y into a scroller.

Co-authored-by: ksj5131513-ops <ksj5131513-ops@users.noreply.github.com>
2026-06-28 09:02:10 +00:00
nesquena-hermes
0c1ed87d41
Merge pull request #5102 from nesquena/salvage/3967-none-providers-guard
fix: harden None providers config across onboarding/providers/routes (salvage of #3967)
2026-06-28 02:01:32 -07:00
nesquena-hermes
94d952c3bb fix: harden None providers config across onboarding/providers/routes (salvage of #3967)
A config.yaml with an explicit empty/null `providers:` key parses to None in
PyYAML. `cfg.get("providers", {})` only returns the {} default when the key is
*absent*, so an explicit null yields None instead of an empty mapping. master
already guards each read with `isinstance(providers_cfg, dict)`, but the value
fed to that guard is still a footgun: the natural chained form
`cfg.get("providers", {}).get(...)` (already fixed once in api/config.py)
crashes with AttributeError on None.

Harden every providers-key read in the three remaining files to
`cfg.get("providers") or {}` so an explicit null degrades to an empty dict at
the source, matching the existing api/config.py convention. Sites hardened:

  api/onboarding.py:578  (_provider_api_key_present)
  api/providers.py:1051  (_provider_has_shadowed_codex_oauth_value)
  api/providers.py:1219  (_provider_has_key)
  api/providers.py:1265  (_get_provider_api_key)
  api/providers.py:2348  (get_providers catalog)
  api/providers.py:2781  (_clean_provider_key_from_config)
  api/routes.py:5045     (_context_length_config_api_key_for_provider)
  api/routes.py:5110     (_context_length_lookup_inputs_for_model)
  api/routes.py:15916    (_handle_live_models)

Adds tests/test_none_providers_config_guard.py: per-file source-form pins (fail
on master / on any single-file revert) plus behavioural checks driving the real
functions with `providers: None` to prove no crash and parity with the
empty-mapping config. Updates the existing test_issue3717 routes-string pin to
match the hardened form.

This is a focused salvage of grab-bag PR #3967 -- only the None-providers
hardening is taken; the PR's unrelated changes (account-usage worker-pool
refactor, function removals) are intentionally dropped.

Co-authored-by: lidi1011 <lidi1011@users.noreply.github.com>
2026-06-28 08:57:39 +00:00
nesquena-hermes
c9677d6f45
Merge pull request #5104 from nesquena/salvage/4113-image-routing
fix(streaming): delegate image routing to agent canonical, preserve unknown-model multimodal (salvage of #4113)
2026-06-28 01:57:03 -07:00
nesquena-hermes
af0835a083 fix(streaming): delegate image routing to agent canonical, preserve unknown-model multimodal (salvage of #4113)
WebUI's api/streaming.py:_resolve_image_input_mode was a LOCAL
re-implementation of agent/image_routing.py:decide_image_input_mode that
had DIVERGED from the canonical function. The local copy never consulted
the active model's vision capability — it only checked a handful of config
heuristics — so it returned "native" (embedding the image as an image_url
part) even for models KNOWN to lack vision. That silently sends pixels to a
blind model (#21160-class failure) instead of routing through the text
(vision_analyze) pipeline.

Fix: delegate to the canonical agent.image_routing.decide_image_input_mode
(single source of truth), with a try/except fallback that preserves the
historical WebUI behaviour when the agent package is not importable (e.g.
the WebUI standalone test environment).

Carve-out preserved: the canonical router conservatively returns "text" for
UNKNOWN/custom models (no models.dev capability data). WebUI historically
forwards those NATIVELY and relies on the agent's strip-and-retry guard
(_try_shrink_image_parts_in_messages / _strip_images_from_messages) to
downgrade on a provider rejection. We keep that: a canonical "text" verdict
is only honoured when there's a real signal — an explicit user choice
(image_input_mode: text or a configured auxiliary.vision backend) or a model
KNOWN to lack vision. Otherwise we forward native.

Tests:
- Existing gateway test (forwards_image_attachments_as_multimodal_parts)
  stays green — unknown "test-model" still forwards native.
- New non-vacuous regression: a model KNOWN to be text-only
  (supports_vision == False) now routes "text" — this FAILS against master,
  where the old local copy returned "native" for the same config.
- Added coverage for the unknown-model carve-out, known-vision native path,
  explicit-text-signal precedence, and the agent-unavailable fallback.

Salvage of #4113 by @gkd2323c. The original PR delegated fully to canonical
but did not add the unknown-model carve-out, which would have regressed the
WebUI native-forwarding behaviour the gateway test encodes.

Co-authored-by: gkd2323c <gkd2323c@users.noreply.github.com>
2026-06-28 08:48:06 +00:00
Frank Song
f786a70887 Merge origin/master into anchor fallback audit
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-28 16:41:42 +08:00
nesquena-hermes
9bf9508c0f
Merge pull request #5114 from nesquena/salvage/3912-cache-hit-insights
feat(insights): surface prompt-cache hit rate (salvage of #3912)
2026-06-28 01:24:27 -07:00