Commit Graph
100 Commits
Author SHA1 Message Date
nesquena-hermes 4eec21c434 docs(changelog): stamp #4093 activity-scene projection (inert) as v0.51.382 (Release MU) 2026-06-13 05:56:53 +00:00
nesquena-hermes 9cf67cbeb2 Merge #4093 (anchor activity scene projection + tool_limit_reached stream label) onto master 2026-06-13 05:56:09 +00:00
nesquena-hermes f230a16f4d docs(changelog): stamp #4092 settled final-answer projection as v0.51.381 (Release MT) 2026-06-13 05:36:35 +00:00
nesquena-hermes 841d78d4e5 Merge #4092 (assistant turn anchor settled final projection) onto master 2026-06-13 05:36:00 +00:00
nesquena-hermes e4bdcbfeae Merge #3823 (tool iteration limit terminal state) onto master
# Conflicts:
#	CHANGELOG.md
2026-06-13 05:06:46 +00:00
nesquena-hermes 2bd8b40b15 harden(#4063): stamp disclosure-state snapshot with session id (Opus belt-and-suspenders)
_captureWorklogDetailDisclosureState stamps state._sid; _restoreWorklogDetailDisclosureState
skips restore on sid mismatch. Cross-session isolation no longer depends solely on the
sessions.js #msgInner wipe invariant — a future change to that loading-placeholder
behavior can't silently replay one session's collapse state onto another.
2026-06-13 04:47:36 +00:00
nesquena-hermes c9930d2a3d Merge #4063 (preserve Worklog detail disclosure across rebuilds) onto master
# Conflicts:
#	CHANGELOG.md
#	static/ui.js
2026-06-13 04:41:17 +00:00
nesquena-hermes f5fb50983b fix(#4006): defensive !==false at the 2 remaining settings-hydration sites (Opus note)
panels.js _autosaveAppearanceSettings (6309) + _applySavedSettingsUi (7837) read
auto_scroll_follow from a settings/body object — use !==false (default True) for
consistency with boot.js + the checkbox render, so a future partial-body refactor
can't silently default the True setting OFF. The .checked reads (6223/8170) keep !!
(a real bool).
2026-06-13 04:27:01 +00:00
nesquena-hermes 9f6ed5a61b test(#4006): re-anchor scroll tests to the explicit-settle signature + new snapshot-capture condition
scrollToBottom now calls _settleMessageScrollToBottom(false, true); renderMessages
captures scrollSnapshot when (preserveScroll || (!_autoScrollFollow && _messageUserUnpinned)).
Updated the 4 change-detector assertions in test_tars_scroll_reset_regressions.py +
test_issue1690_scroll_completion.py to the new strings; behavioral intent unchanged.
2026-06-13 04:20:25 +00:00
nesquena-hermes 6ced477822 fix(#4006): explicit-settle flag + pre-wipe snapshot capture (Codex r3)
1. _settleMessageScrollToBottom(force, explicit): when explicit (End-button /
   scrollToBottom), late-layout settling runs even with Auto-follow OFF — the
   setting only suppresses AUTOMATIC follow, not a deliberate jump. The RO + 2s
   fallback guards now bail on (!_autoScrollFollow && !explicit). scrollToBottom()
   passes explicit=true; the automatic scrollIfPinned() path stays guarded.
2. renderMessages now captures the pre-wipe scrollSnapshot when (Auto-follow off
   && user unpinned) too, so the non-preserve restore lands the reader where they
   were instead of receiving a null snapshot (no-op).
2026-06-13 04:16:48 +00:00
nesquena-hermes 36239700e6 i18n(#4006): add auto-follow setting label+desc to the remaining 11 locales (Opus note)
The keys shipped only in en+zh; added it/ja/ru/es/de/zh-Hant/pt/ko/fr/tr/pl so the
Settings → Appearance auto-follow toggle isn't English-only outside those two.
2026-06-13 04:12:11 +00:00
nesquena-hermes 2bdef4166f fix(#4006): gate the #3920 RO settle + render fall-through on _autoScrollFollow (Codex cross-PR catch)
Codex found that with Auto-follow OFF the #3920 ResizeObserver settle + 2s fallback
+ the non-preserve render path still force-scrolled to bottom (the setting was
incomplete vs the scroll machinery shipped in #3920). Fixes:
1. RO settle guard (ui.js:3153): bail when !_autoScrollFollow.
2. 2s static fallback guard (ui.js:3186): bail when !_autoScrollFollow.
3. non-preserve render fall-through (ui.js:8653): when Auto-follow off AND the user
   has unpinned, restore the scroll snapshot instead of scrollToBottom() — also
   closes the send() race (renderMessages runs before S.activeStreamId is set).
   A fresh session load (not unpinned) still lands at the bottom. Explicit
   user-initiated scrollToBottom() (End button) is untouched.
2026-06-13 04:09:58 +00:00
nesquena-hermes 00ea7766db feat(#4006): default auto-follow ON (Codex/Claude-Code parity) + fix default-hydration mismatch + CHANGELOG v0.51.378
Per maintainer decision, auto_scroll_follow defaults TRUE (sticky-bottom: follow
new output while streaming, scroll-up unpins and is respected) — matching Codex
CLI / Claude Code. The PR shipped it default-False.

Default-mismatch bug fixed (would have shown the True default as OFF for existing
users with no saved value):
- boot.js settings path: !!s.auto_scroll_follow -> s.auto_scroll_follow!==false
- boot.js no-settings fallback: false -> true
- panels.js checkbox render: !!settings.auto_scroll_follow -> !==false

Resolved the config.py + panels.js merge conflicts (kept both the Transparent
Stream setting and auto_scroll_follow). Added test_issue4006_auto_scroll_follow_default.py
pinning the True default across config/boot/panels + the follow-gate references.
2026-06-13 03:55:28 +00:00
nesquena-hermes c02c31c837 Merge #4006 (scroll auto-follow setting + pin guards) onto master
# Conflicts:
#	api/config.py
#	static/panels.js
2026-06-13 03:54:11 +00:00
nesquena-hermes d3c694251f test(#3920): re-anchor tars scroll regression tests to the ResizeObserver settle
The 2 failing assertions pinned the removed implementation (const passes=[0,16,80,180]
setTimeout fan-out + _settleMessageScrollToBottom(true) in scrollToBottom). Updated to
assert the new behavior contract: ResizeObserver on #msgInner + 2s static fallback +
force=false so the observer runs. Behavioral intent (settle survives late markdown
growth; sync write before settle scheduling) preserved.
2026-06-13 03:39:11 +00:00
nesquena-hermes b5eabe1494 fix(#3920): apply Codex gate findings — observe #msgInner, instance-owned RO cleanup, restore .messages overflow-anchor
Codex SHIP-ONLY-WITH-FIXES (3 real issues; Codex+Opus disagreed on #1, stricter wins):
1. CORE — .messages lost overflow-anchor:none, reopening the #1360 (d21c9720) streaming-scroll-anchor regression. Restored it (native anchoring stays OFF; the RO does the settle). Opus had judged this removal acceptable; took Codex's stricter call.
2. SILENT — the ResizeObserver observed #messages (the scroll container, fixed by flex layout, never resizes) instead of #msgInner (.messages-inner, the growing transcript node) → callback would never fire. Now observes #msgInner.
3. SILENT — stale RO callbacks mutated the global _settleRO, which could disconnect a newer active observer after settle re-entry. Now instance-owned: close over const ro, only clear the global when _settleRO===ro.
2026-06-13 03:32:16 +00:00
nesquena-hermes f9055c5d97 fix(#3920): address review — restore sidebar overflow-anchor, wire _settleFinalScroll via 2s fallback, + CHANGELOG v0.51.377
3 maintainer-review items the contributor hadn't addressed:
1. (blocking) restored .session-list overflow-anchor:none — the PR accidentally reverted the deliberate Firefox sidebar fix (2dfe765b); only the on-topic .messages removal is kept.
2/3. _settleFinalScroll was dead code + the promised 2s static-content safety timeout was missing — wired _settleFinalScroll(token) via a single 2s top-level fallback after _settleRO.observe(el), declared _settleFinalTimer + cleared it in _cancelBottomSettle and at settle re-entry.
2026-06-13 03:25:48 +00:00
nesquena-hermes 965baeb78c Merge #3920 (Firefox scroll ResizeObserver) onto master 2026-06-13 03:23:49 +00:00
nesquena-hermes 02e201fcf1 docs(changelog): stamp #3903 Worklog-reasoning-hide as v0.51.376 (Release MO) 2026-06-13 03:19:44 +00:00
nesquena-hermes 35877dab1b fix(#3971): move show-thinking gate to the real Worklog render paths (Opus catch)
Opus final gate caught that the staged re-implementation placed the
window._showThinking===false gate in _worklogReasonNodeFromText, which has
ZERO callers — so live/settled reasoning rows (built by _syncWorklogReasonFromAnchor
and _appendWorklogReason) were never actually hidden, and removeThinking swept
data-worklog-reason-source=reasoning which the real rows (data-worklog-anchor-reason=1)
don't carry. The string-match tests passed against the dead function = green-suite blind spot.

Fix:
- Gate _syncWorklogReasonFromAnchor (live + settled): when _showThinking===false, remove any
  existing reasoning row and bail before building.
- Gate _appendWorklogReason (settled rebuild): return null when _showThinking===false.
- removeThinking + the settled-rebuild cleanup now sweep BOTH data-worklog-anchor-reason=1
  (the real attribute) and the legacy data-worklog-reason-source=reasoning.
- Re-anchor the tests to assert against the real render functions + the correct attribute.
2026-06-13 03:09:50 +00:00
nesquena-hermes 52c502a984 Merge #3971 (hide Worklog reasoning when Thinking disabled) onto master 2026-06-13 02:52:31 +00:00
nesquena-hermes 9f1933f81e chore(#4024): drop docs/ui-ux/review-shots/ trifecta scratch from the release diff
These 5 PNGs (722K) are the agent trifecta review-pass screenshots copied into
the worktree so the Opus gate could read them — review scratch, not durable UX
evidence. The intended evidence lives in docs/ui-ux/evidence/pr4024-*.png and is
kept. Mirrors the .review-crops/ cleanup; gitignore the dir so review scratch
can't leak into a release diff. No code or test change.
2026-06-13 02:15:01 +00:00
nesquena-hermes c45adb910a fix(#4024): wire dormant recency-fade to live turn + include Interrupted in progress-bar completed set (Opus final-gate observations)
- _applyTransparentRowFading was only called from the settled loop (which the fn skips), so live-turn recency fade never applied — now called from _syncTransparentEventControls (internally live-gated, no-ops on settled).
- _attachProgressBar isCompleted now includes Interrupted so an interrupted row's bar fills 100% instead of rendering empty.
2026-06-13 01:53:11 +00:00
nesquena-hermes f18e8d6545 docs(changelog): stamp Transparent Stream (#3820/#4024) as v0.51.375 (Release MN) 2026-06-13 01:43:25 +00:00
nesquena-hermes d952dfe3a2 fix(#4024): trifecta round-2 — rehydrate root-turn, always-preserve detail tab, drop redundant Running: name prefix
- C1-r2 (both Codex+Opus): _rehydrateTransparentStreamDom now also rewires the root element when it IS the assistant turn (querySelectorAll only matched descendants), so turn-toggle + event-controls re-bind after a live-turn restore.
- O-Bug2-r2: preserve the Full/Output detail tab regardless of open state.
- r2 #4: drop the 'Running: ' name prefix — the legible status badge already carries it (V6 redundancy class).
2026-06-13 01:16:56 +00:00
nesquena-hermes 5dad3e03e5 test(#4024): update CSS-literal assertions to the new quiet treatment + add 8 trifecta-fix regression guards
- Re-anchor test_old_event_fading + test_transparent_event_row_quiet_metadata_visual_rhythm to the new contract (left-rail output, inline-row args, underline active tab, WCAG fade floor, live-only fade).
- Add guards: live-turn-restore rehydrate (C1), echoed-thinking dedup (O-Bug1), thinking-card flat reset (V1), skin-proof reset (V3), legible failed status (V2), interrupted settled status (O-Edge), completion preserves expand state (O-Bug2), live-only entrance animation (V9).
2026-06-13 01:12:09 +00:00
nesquena-hermes ed3ac13fc5 fix(#4024): trifecta UX + correctness pass — rehydrate live-restore, thinking dedup, quiet thinking rows, legible failures, skin-proof reset, compact args, flat output, live-only fade/anim
Functional (Codex+Opus trifecta findings):
- C1: restoreLiveTurnHtmlForSession now calls _rehydrateTransparentStreamDom (controls were dead after active-session live-turn restore).
- O-Bug1: per-turn echoed-thinking dedup in the transparent settled path (mirrors the compact seenReasons), fixes duplicate out-of-order Thinking rows.
- O-Bug2: tool completion preserves the row's open state + Full/Output tab.
- O-Edge: settled/reloaded done===false tools show 'Interrupted' (not a permanent Running shimmer); progress bar clears for Interrupted too.

Visual (trifecta CSS):
- V1: reset .thinking-card to flat/transparent (was the heaviest object in the stream).
- V2: failed=error color + left border, running/interrupted legible (was an invisible muted badge).
- V3: scoped !important reset beats the 6 per-skin .tool-card re-card rules.
- V4: inline key:value args (was a tall stacked form).
- V5/V7: output pre flattened to a quiet left-rail, max-height:none (one expanded zone, no nested scroll).
- V6: drop redundant tool/status pseudo-args from the detail body.
- V8: row fading gated to the live turn only + floor raised for WCAG; settled history stays full-opacity.
- V9: entrance animation scoped to #liveAssistantTurn (was replaying on every renderMessages).
- V10: larger mobile header tap targets + active detail-tab underline.

O-Bug3 (live thinking overwrite when no seq) intentionally deferred — documented follow-up; settled render is already correct and a live heuristic risks a streaming regression.
2026-06-13 01:08:41 +00:00
nesquena-hermes 92f7a24448 Merge #4024 Transparent Stream onto master (rebase)
# Conflicts:
#	CHANGELOG.md
2026-06-13 00:40:17 +00:00
nesquena-hermes e8529c84e5 Merge #4060: pass custom provider keys to context probes (#4059) 2026-06-13 00:17:16 +00:00
nesquena-hermes 89ca46eeb5 harden(#4020): prune lineage cache by collapsed-row key too (Opus SHOULD-FIX)
Opus advisor found a key-space asymmetry: _pruneLineageReportCacheToVisibleSessions
built visibleKeys from RAW rows via _sidebarLineageKeyForRow, but the render loop
keys the lineage-report cache by _sidebarLineageKeyForRow on the COLLAPSED row,
which can differ when collapse merges segments. On a malformed/edge chain the
expanded row's cache could be evicted every payload and re-fetched ~every 5s
(partial regression of the bug #4020 fixes). Fold the collapsed rows' cache keys
into the visible set too, mirroring the _resolveSessionIdFromSidebarLineage
precedent, behind a defensive try/catch.

docs(changelog): stamp #4020+#4055 as v0.51.373 (Release ML)
2026-06-13 00:03:30 +00:00
nesquena-hermes 0fdfa4b9fa Merge #4055 into stage-ml 2026-06-12 23:43:28 +00:00
nesquena-hermes 3ddf99c6f1 Merge #4020 into stage-ml 2026-06-12 23:43:28 +00:00
nesquena-hermes f083cf54d1 docs(changelog): stamp Phase-2 batch as v0.51.372 (Release MK) 2026-06-12 23:37:34 +00:00
nesquena-hermes 4874126b6d Merge #4066 into stage-mk 2026-06-12 23:18:00 +00:00
nesquena-hermes 0eee1b85ec Merge #4010 into stage-mk 2026-06-12 23:18:00 +00:00
nesquena-hermes f4ce459f79 Merge #4017 into stage-mk 2026-06-12 23:18:00 +00:00
nesquena-hermes ce013de195 Merge master (docs #4061) into stage-mj 2026-06-12 20:40:35 +00:00
nesquena-hermes 31936c3026 docs(changelog): stamp Phase-1 low-risk batch as v0.51.371 (Release MJ) 2026-06-12 20:29:28 +00:00
nesquena-hermes 6ec5527386 harden(#4057): pass local skills dir to _skill_category_from_path explicitly (Opus SHOULD-FIX)
_active_skill_search_dirs filters to existing dirs, so on a host with no local
skills dir but configured external dirs the local root is dropped from the list
and the position-based skills_dirs[0]==local assumption misclassified the first
external root as local (its flat skills silently lost their category label).
Pass the local dir explicitly (backward-compatible optional param defaulting to
the old skills_dirs[0] behavior) + regression test for the absent-local-dir case.
2026-06-12 20:28:46 +00:00
nesquena-hermes 2fea2d588b Merge #4058 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes 4ad3299449 Merge #4057 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes 1e5a741f50 Merge #4054 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes 10481d4f34 Merge #4051 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes 13fdb0755f Merge #4049 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes ffa9f533ef Merge #4048 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes 1052f4ab87 Merge #4047 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes 99ee11a733 Merge #4046 into stage-mj 2026-06-12 20:14:42 +00:00
nesquena-hermes fb3d689619 Merge #4050 into stage-mj 2026-06-12 20:14:41 +00:00
nesquena-hermes 82fcf665eb harden(#4052): tolerate providers.*.models list-of-dicts keyed by model/name (Opus SHOULD-FIX)
The richer static-catalog builder's group-building loop used a strict item["id"]
extraction that would KeyError (caught → degrade to the minimal one-model catalog)
for legal config shapes where providers.<id>.models is a list of dicts keyed by
"model"/"name" rather than "id". Mirror the tolerant id-or-model-or-name resolution
the detection loop already uses, skip entries with nothing usable, and add a
regression test. No behavior change for the common list-of-strings / list-of-id-dicts
shapes.

docs(changelog): stamp #4052 model-picker budget fallback as v0.51.370 (Release MI)
2026-06-12 20:05:12 +00:00
nesquena-hermes 1c82dab798 Merge #4052: real static catalog when /api/models times out (#3928) 2026-06-12 19:50:52 +00:00
nesquena-hermes 6204dbe9ea docs(changelog): stamp #4022 runtime base_url fix as v0.51.369 (Release MH) 2026-06-12 08:16:00 +00:00
nesquena-hermes c35c1c6e70 test(#4022): update sprint42 runtime-route assertion for target_model threading
#4022 now calls resolve_runtime_provider(requested=..., target_model=...) so
per-model base_url normalization applies; the sprint42 test pinned the old
single-arg signature. Updated the assertion to expect target_model (pre-existing
test, not a behavior regression — confirmed it passes on master only because the
call signature changed here).
2026-06-12 07:57:26 +00:00
nesquena-hermes 005b2ca460 fix(#4022): preserve different-endpoint config base_url override (Codex CORE)
Codex caught a regression: the contributor's _runtime_preferred_base_url preferred
the runtime base_url for ALL non-custom providers, which would clobber an explicit
providers.<id>.base_url override pointing at a different host/port (LM Studio at a
LAN IP, an OpenRouter mirror). Now only prefer the runtime URL when it's the SAME
scheme+host+port as the configured one (the #3895 /v1-dedup case is path-only on
the same host); a genuinely different configured endpoint wins. Added
_same_base_url_endpoint() + 2 regression tests (different-endpoint preserved,
same-endpoint normalization).
2026-06-12 07:54:48 +00:00
nesquena-hermes 983f059acd Merge #4022 — honor runtime target model+base_url in WebUI streaming (#3895) 2026-06-12 07:48:26 +00:00
nesquena-hermes 061eda9b91 docs(changelog): stamp #4023 profile-cookie session binding as v0.51.368 (Release MG) 2026-06-12 07:43:46 +00:00
nesquena-hermes 03799f8e4a harden(#4023): apply Opus security findings — verify-side name-pattern gate + require handler when auth enabled
Opus independent security review concurred SAFE and surfaced 2 LOW defense-in-depth
items, both applied: (1) verify_profile_cookie_value now validates the profile name
against _PROFILE_ID_RE itself (not only in get_profile_cookie) so a future second
caller can't return an unvalidated name; (2) build_profile_cookie raises when auth is
enabled and no handler is passed, so a future call site can't silently emit an
unsigned (session-unbound) profile cookie. +3 regression tests.
2026-06-12 07:37:48 +00:00
nesquena-hermes aef15ca559 Merge #4023 — bind active profile cookie to session (#803) 2026-06-12 07:19:46 +00:00
nesquena-hermes 3eac7ccd96 docs(changelog): stamp #4021 + #4032 + #4018 fix batch as v0.51.367 (Release MF) 2026-06-12 07:14:16 +00:00
nesquena-hermes 07585a11f8 Merge #4018 2026-06-12 07:07:34 +00:00
nesquena-hermes 0e7cf6c388 Merge #4032 2026-06-12 07:07:33 +00:00
nesquena-hermes fd28024cc4 Merge #4021 2026-06-12 07:07:33 +00:00
nesquena-hermes fbd7f82478 docs(changelog): stamp #4009 + #4015 fix batch as v0.51.365 (Release MD) 2026-06-11 22:55:04 +00:00
nesquena-hermes ff3de220d8 Merge #4015 — keep reasoning effort status on the active session model (#3958) 2026-06-11 22:48:36 +00:00
nesquena-hermes 8a849a1bb8 Merge #4009 — open clicked lineage segments without sid rewrite (#4003) 2026-06-11 22:48:36 +00:00
nesquena-hermes cb93a8014e docs(changelog): stamp #3993 session-load self-heal as v0.51.364 (Release MC) 2026-06-11 22:43:07 +00:00
nesquena-hermes 952ac146e0 test(#3993): re-anchor _load_session_error_block past the new stale-load guard
The stale-load guard (#3993 Codex race fix) adds an early  at the top
of the loadSession catch block; the test helper anchored  on the first
 after the catch, which now truncated at the guard. Anchor the body
extraction past the guard (from ) so it captures the 404/non-404
self-heal body as before. Behavior unchanged.
2026-06-11 22:37:09 +00:00
nesquena-hermes 8e7d3b086d fix(#3993): add stale-load guard before self-heal so a superseded boot load can't wipe a healthy session (Codex CORE race)
currentSid is snapshotted before the awaited /api/session fetch; if the user
clicks a healthy session while a boot-time restore is in flight and that boot
load then fails non-404, _clearStuckSessionOnBoot(sid, null) would wipe the
healthy session's localStorage/URL. Guard the catch block on
_loadingSessionId !== sid (a newer load superseded this one) — re-arm the active
stream and bail before any self-heal/DOM mutation. Protects both the non-404 and
404 inline self-heal paths. + regression test.
2026-06-11 22:27:01 +00:00
nesquena-hermes 7e341a0d70 test(#3993): cover _clearStuckSessionOnBoot self-heal guards (clears on boot-failure, preserves a live session) 2026-06-11 22:20:57 +00:00
nesquena-hermes 63c62194a1 Merge #3993 — self-heal stuck session loads on non-404 failures 2026-06-11 22:19:14 +00:00
nesquena-hermes 1e61aea10e docs(changelog): stamp #3994 materialize-CLI-sessions fix as v0.51.363 (Release MB) 2026-06-11 21:51:29 +00:00
nesquena-hermes 7a5c3bc374 fix(#3994): scope happy-path guard to read_only flag; complete #1436 mock stub
Refined the Codex-CORE fix: the happy-path (already-stored session) guard rejects
only an explicit read_only flag — a stored messaging session already owns its
sidecar, so the messaging-fork risk is specific to the materialize FALLBACK
(which creates a sidecar) where the _is_messaging_session_record check stays.
Also completed test_issue1436's _stub_session MagicMock (read_only=False,
_loaded_metadata_only=False) — bare MagicMock auto-attrs were truthy, tripping
the new read-only guard + _ensure_full_session_before_mutation reload.
2026-06-11 21:45:36 +00:00
nesquena-hermes 6f41fe7490 fix(#3994): reject read-only + messaging sessions on BOTH happy-path and materialize fallback (Codex CORE x2)
Codex caught two data-integrity gaps in the contributor's guard: (1) the
get_session() happy path returned a stored session without checking read_only/
messaging, so an already-imported read-only session could be mutated via
rename/update/move; (2) the fallback only checked cli_meta.read_only, but agent
rows normalize messaging sources WITHOUT setting read_only — materializing a
writable sidecar for a state.db-owned messaging session forks its title/state.
Now reject getattr(s,'read_only') OR _is_messaging_session_record on the happy
path, and cli_meta.read_only OR _is_messaging_session_record(cli_meta) in the
fallback. Replaced the messaging-stub test with 3 regression tests (stored
read-only, stored messaging, messaging cli_meta without read_only flag).
2026-06-11 21:41:51 +00:00
nesquena-hermes e934fdde70 test(#3994): cover _get_or_materialize_session (in-store, missing, read-only-403, CLI-import, messaging-stub) 2026-06-11 21:36:28 +00:00
nesquena-hermes 89fbc898f3 Merge #3994 — materialize CLI sessions on rename/move/update (#3985) 2026-06-11 21:35:13 +00:00
nesquena-hermes 951abb7753 docs(changelog): stamp #4008 malformed-providers-config crash fix as v0.51.362 (Release MA) 2026-06-11 21:15:11 +00:00
nesquena-hermes c572e63c50 Merge #4008 — treat malformed providers config as unconfigured (#3979) 2026-06-11 21:08:43 +00:00
nesquena-hermes 0ec59de3ac docs(changelog): stamp #3981 configurable cookie name as v0.51.361 (Release LZ) 2026-06-11 20:21:37 +00:00
nesquena-hermes de7c7f98ea Merge #3981 — configurable session cookie name via env var 2026-06-11 20:15:18 +00:00
nesquena-hermes 6f410a81b5 docs(changelog): stamp #3996 SSE-visibility fix as v0.51.360 (Release LY) 2026-06-11 19:33:44 +00:00
nesquena-hermes 2cbae63a8d fix(#3996): preserve pending sid on hidden-tab skip so a bg-tab session reopens (Codex CORE)
If startSessionStream(sid) runs while the tab is ALREADY hidden (session loaded/
restored in a background tab), it set _sessionStreamSessionId then returned at the
hidden-skip WITHOUT recording _sessionStreamHiddenSid — so the visibility handler
(which reopens only from the holder) never reattached, silently dropping
bg_task_complete/server_turn_started until the next explicit startSessionStream.
Set _sessionStreamHiddenSid = sid on the hidden-skip return. Adds a regression test.
2026-06-11 19:17:11 +00:00
nesquena-hermes 8a22540df4 test(#3996): cover SSE visibility hooks + per-session reopen-on-re-show regression 2026-06-11 19:13:03 +00:00
nesquena-hermes 5fdfb70bce fix(#3996): reopen per-session SSE on tab re-show (reopen-bug fix)
#3996 closes the gateway + per-session SSE on hidden tabs to free HTTP/1.1
connection-pool slots (#3992). But the per-session-stream visibility hook
guarded its reopen on _sessionStreamSessionId, which stopSessionStream() nulls —
so the per-session SSE closed on hide but never reopened on re-show (regressing
live-stream reattach). Capture the id into a dedicated _sessionStreamHiddenSid
before the close and reopen from it on visibility return. Gateway hook (sessions.js)
was already correct (reopens unconditionally + re-guards internally).
2026-06-11 19:10:38 +00:00
nesquena-hermes 202d6df310 Merge #3996 — close idle SSE on hidden tabs 2026-06-11 19:08:59 +00:00
Hermes AgentandHinotoi-agent f2ef2851d3 Release LV stage — v0.51.358 (#3964 first-run password bootstrap local gate)
#3964 [security] gate first-password bootstrap (_set_password on POST /api/settings
while auth disabled) to local clients — blocks remote unauth first-run ownership.
Uses request-start auth snapshot (auth_enabled_before), so no mid-request TOCTOU.
Self-rebased onto v0.51.357 (8-behind, 3-dot fidelity verified byte-identical).

#3970 (oauth single-flight) DROPPED from this stage: Codex+Opus both caught a
check-then-insert race — _pending_oauth_flow_for releases the lock before the
device-code request + flow insertion, so concurrent unauth starts still spawn
multiple workers (Codex empirically reproduced w/ 2 threads). Returned to author
w/ the atomic per-(provider,home) start-lock fix. Re-gating the (N-1) stage.

Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
2026-06-11 04:39:34 +00:00
Hermes Agent 4857f928bd docs(changelog): stamp #3877 flicker fix as v0.51.357 (Release LU) 2026-06-11 03:58:00 +00:00
Hermes Agent 0a561f240a chore(streaming): move dataset.sessionId stamp to the whole-turn restore paths only (Opus/Codex nit)
Both reviewers noted the _preservedLiveTurn.dataset.sessionId stamp was a dead
write in the segment-swap path (only the segment enters the DOM there; the
rebuilt turn was already stamped at build time). Move the stamp into the two
whole-turn paths where the preserved turn actually enters the DOM. No behavior
change; removes the dead write.
2026-06-11 03:20:17 +00:00
Hermes Agent 02c6696e53 chore: drop stray uv.lock accidentally swept in (not tracked on master) 2026-06-11 03:14:03 +00:00
Hermes Agent 3ec93ff6bf fix(streaming): count live-worklog shell/reason as preserved structure (Codex r2)
Codex round 2 caught that _structuralCount() only enumerated .tool-call-group/
.tool-card-row/.tool-worklog-group but NOT the live Worklog shell
(.live-worklog[data-live-worklog-shell=1]) or its .wl-reason content (built by
_flushPendingSegmentRender / ensureLiveWorklogContainer, see messages.js:2386 +
ui.js:6712/6655). So a turn whose live-ahead structure is a Worklog (not a bare
tool-call-group) undercounted preserved structure → wrongly took the segment-swap
path → detached the visible live Worklog container for a frame.

Fix: add .live-worklog[data-live-worklog-shell=1] and .wl-reason to the structural
count (mirrors the canonical 'has current worklog content' selector set at
sessions.js:1125-1131). Added a structural-test assertion + verified live: a
preserved turn with a live worklog .wl-reason the rebuild lacks now takes the
whole-turn restore path and the worklog + parser both survive.

Convergence: Codex findings narrowing each round (tie-guard → tail-segment →
worklog-selector), all real, all fixed + empirically verified.
2026-06-11 03:12:40 +00:00
Hermes Agent c37c0d1397 fix(streaming): whole-turn restore when live DOM is structurally ahead (Opus)
Opus flagged a tool-card-frame-loss edge in the segment-only swap: the premise of
#3877 is that the live DOM can be AHEAD of S.messages, so a tool/worklog group can
land in the live turn between the last throttled persist and a mid-stream rebuild.
The rebuild (built from the lagging S.messages) would then lack that group, and a
segment-only swap would drop it for a frame (empirically confirmed: a live-only
tool card present before the rebuild was absent after).

Fix: count structural blocks ([data-live-assistant=1] + tool/worklog/thinking
groups) on both turns. Segment-swap only when the rebuild is the structural
SUPERSET (rebuilt >= preserved); otherwise restore the WHOLE preserved turn so
live-only structure the user already saw never vanishes. Verified live: tool-card-
ahead → whole-turn restore keeps the card + parser; single-segment tie → segment
swap, parser connected; multi-live-segment tail → tail parser connected, both
segments kept.

Also addresses Opus's dead-code note implicitly — the dataset.sessionId stamp now
matters on the whole-turn restore path (it's the branch that actually inserts the
preserved wrapper).
2026-06-11 03:03:55 +00:00
Hermes Agent 95e874d7be fix(streaming): target the parser-owned TAIL live segment in the #3877 re-attach (Codex CORE)
Codex caught an asymmetry in the first cut: the rebuilt side selected the tail
live segment ([...].pop()) but the preserved side used querySelector() = the
FIRST [data-live-assistant=1] segment. In a multi-live-segment turn (reconnect /
post-tool activity boundaries — see messages.js ensureAssistantRow re-attaching
to the LAST live segment), the smd parser writes into the tail segment, so
swapping the first preserved segment would move the wrong node and leave the
parser-owned tail detached — re-introducing the flicker for multi-segment turns.

Fix: select the preserved segment from querySelectorAll, defaulting to the LAST
(tail), and prefer the one whose data-live-segment-seq matches the rebuilt tail;
compute _preservedLen from that segment. Added a structural regression test
pinning the tail selection.

Verified live: a 2-live-segment turn (seq 1 + seq 2, parser on seq 2) keeps the
parser-owned tail connected across a mid-stream rebuild (was orphaned with the
first-segment selection).
2026-06-11 02:57:46 +00:00
Hermes Agent 9803a037fe fix(streaming): re-attach preserved live turn on length tie (#3877 reopen)
The #3877 fix (#3892) preserves the live assistant turn's DOM node across a
mid-stream renderMessages() rebuild and swaps it back in when the rebuilt turn
has less streamed text than the preserved (smd-parser-referenced) node. But the
guard was strict: _rebuiltLen < _preservedLen. At the throttled session
write-back boundary the rebuilt turn's live segment can carry EXACTLY the same
text length as the preserved node, so the strict guard skipped the swap and left
the streaming parser writing into the now-detached original node — the residual
'disappears, then reappears' frame the reporter still saw on v0.51.347.

Fix:
- Relax the guard to _rebuiltLen <= _preservedLen so the equal-length tie also
  restores the parser-referenced node (on a tie the preserved node is strictly
  preferable: it holds the live parser reference and nothing is lost). When the
  rebuilt turn genuinely has MORE content (reconnect where S.messages caught up
  past the parser) the guard still skips and the parser re-resolves to the fuller
  node.
- Swap at the SEGMENT level (replace only the rebuilt live segment with the
  preserved one) instead of a whole-turn replaceWith, so a multi-segment turn
  (earlier settled segments + tool/worklog groups built by the rebuild) keeps
  that rebuilt-only structure. Whole-turn replace remains the fallback when the
  rebuilt turn has no live segment to target.

Verified live (isolated server on the edited ui.js, real shipped renderMessages):
- single-segment tie: parser node stays connected (was orphaned on master);
- multi-segment tie: parser connected AND both assistant segments preserved;
- rebuilt-longer: correctly keeps the fuller rebuilt node, no content loss.

Updated the #3877 structural tests to pin the <= guard + segment-level swap.

Closes #3877.
2026-06-11 02:51:14 +00:00
Hermes Agent 808b3fa511 docs(changelog): stamp #3957 fix as v0.51.356 (Release LT) 2026-06-11 02:15:28 +00:00
Hermes Agentandnesquena-hermes 85ee6ec2c8 fix(profiles): address greptile P2 flags on #3960
- _load_models_cache_from_disk: resolve _get_models_cache_path() once
  (was called twice — exists() then open()) (greptile P2).
- conftest: use a _MISSING sentinel for the _active_profile restore guard so
  it restores whenever api.profiles was importable pre-test, independent of the
  value (greptile P2; _active_profile defaults to 'default' so the prior
  is-not-None guard was functionally fine but technically value-dependent).

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-11 01:56:58 +00:00
Hermes Agentandnesquena-hermes b5d382a3bb fix(profiles): scope the detached models-rebuild worker to the active profile (#3957)
Codex + Opus both independently caught a CORE gap in the first cut: the
bounded /api/models rebuild runs on a detached 'models-catalog-rebuild' daemon
thread that inherits neither the request-profile thread-local (#798) nor
os.environ. So on a non-default profile the worker probed the DEFAULT profile's
credentials and, when the 4s budget was exceeded, published the rebuilt catalog
to the DEFAULT profile's disk cache (cross-contamination) — exactly the slow
path a non-default cold rebuild takes.

Fix:
- profile_scope_for_detached_worker(profile_name): sets the request-profile TLS
  AND applies the profile .env on the worker thread, restoring both on exit
  (no-op for default). Distinct from profile_env_for_active_request (which reads
  the current thread's TLS and must not clear it).
- get_available_models() captures the active profile on the request thread and
  wraps the rebuild worker body (probe + over-budget publish + disk save +
  fingerprint) in that scope; the legacy synchronous rebuild applies the profile
  env on the foreground. /api/models route no longer wraps (the work moved into
  get_available_models so ALL callers — chat/start, resolution — are fixed).
- 2 new regression tests incl. the worker-thread before/inside/after assertion.

Empirically verified: a fresh worker thread resolved models_cache.json/default
WITHOUT the scope (the bug) and models_cache.work.json + the work .env + the
work auth.json WITH it.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-11 01:41:24 +00:00
Hermes Agentandnesquena-hermes 70d391c44a fix(profiles): scope /api/providers + /api/models to the active profile (#3957)
On a non-default profile, Settings → Providers timed out and the model
picker showed only the default profile's models. WebUI profile switching is
per-client/cookie-scoped (#798), but two read-only paths resolved from the
process-global default profile:

- Facet A: /api/providers + /api/models did not apply the active profile's
  .env around the read, so get_auth_status() / provider_model_ids() / custom
  key lookups resolved the default profile's credentials. On a non-default
  profile the auth probes could stall past the 30s frontend abort.
- Facet B: the /api/models disk cache was a single import-time
  STATE_DIR/models_cache.json shared across every profile, while the cache
  fingerprint is profile-specific -> a non-default profile rejected the shared
  snapshot every read and cold-rebuilt (the slow serial-probe path).

Fix:
- api.profiles.profile_env_for_active_request(): applies the active
  per-request profile's .env for the duration of the read (delegates to the
  existing profile_env_for_background_worker used by streaming). No-op for the
  default/root profile, so single-profile deployments are byte-identical.
- api.config._get_models_cache_path(): profile-keys the disk cache filename
  (models_cache.<profile>.json) derived from the default path; default profile
  keeps models_cache.json unchanged (no file migration).
- routes.py: wrap both GET handlers in profile_env_for_active_request.
- conftest: restore api.profiles._active_profile + clear request-profile TLS
  after each test (a pre-existing isolation hole that profile-keyed cache
  paths newly surface under sharding).

Tested: 10 new regression tests; live before/after on isolated servers shows
a non-default profile now surfaces its configured provider (deepseek) with
its own credentials + its own cache file, where master showed only the
default profile's providers and one shared cache.

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-06-11 01:17:22 +00:00
Hermes Agentandai-ag2026 01c1239fc9 refactor(sessions): simplify attention indicator to a color-coded dot
Per maintainer UX direction: drop the text badge pill ("APPROVAL" /
"N QUESTIONS") in favor of color-coding the existing right-side status dot
plus the colored left rail. Red dot (--error) for pending approvals, amber
dot (--warning) for pending clarifies; theme-driven, visible even when the
session is not streaming/unread. Approval dot gently pulses (disabled under
prefers-reduced-motion). Quieter and consistent with the existing pin/unread
dot family — the row no longer needs to truncate its title to fit a pill.

Updated test_session_attention_badges to assert the dot classes
(is-attention-approval/clarify) instead of the removed badge element/styles.

Co-authored-by: ai-ag2026 <261867348+ai-ag2026@users.noreply.github.com>
2026-05-30 19:54:51 +00:00
Hermes Agentandwind-chant 06842f6e9f fix(insights): exclude source='webui' from CLI session pass + add tests
The new CLI/gateway insights pass counted ALL state.db sessions, but
WebUI-native sessions are persisted to state.db with source='webui' AND
already counted from the sidecar _index.json first pass — double-counting
them in totals, model breakdown, and daily charts. Add
`AND COALESCE(source, '') != 'webui'` so only CLI/gateway/cron/tui rows are
added by the second pass.

Adds regression tests proving (a) CLI + Telegram sessions appear in totals
and (b) a webui-source state.db row is not double-counted against its
_index.json entry.

Co-authored-by: wind-chant <wind-chant@users.noreply.github.com>
2026-05-30 18:58:20 +00:00
Hermes Agent 9d10390852 Stamp CHANGELOG for v0.51.107 (Release CE / stage-400 / 8-PR batch) 2026-05-21 23:13:31 +00:00
Hermes Agentandai-ag2026 0718770fe1 Stage 400: PR #2719 — fix: apply release update target (clear sessionStorage check-once stamp + force banner re-evaluation on user click)
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-05-21 22:59:56 +00:00
Hermes Agentandai-ag2026 b14aae4ee5 Stage 400: PR #2717 — fix: surface update check fetch errors instead of failing silently
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-05-21 22:59:54 +00:00