Commit Graph
393 Commits
Author SHA1 Message Date
c5bb1fcaa0 stage #4688 (franksong2702): running-first session ordering in sidebar (#4699)
Overlays live pending_started_at/updated_at/last_message_at onto cached /api/sessions rows
(when newer), sorts active/running sessions first then by freshest effective timestamp, applies
the same running-first order in the frontend sidebar renderer, and groups date buckets with the
same runtime-aware timestamp (no stale header on resumed running sessions). routes.py +
sessions.js + 81-line test. Composes with #4680 cache-freeze + #4597 (98 session-list tests pass).

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-22 03:52:19 -07:00
3edbec7c97 stage #4613 (dso2ng): preserve reader scroll position across re-render/reload (#4675)
Sticky-unpin across idle renderMessages(), 3-tier anchor restore (data-message-anchor-key
-> data-session-msg-idx -> data-msg-idx) for tail-window->full-session reloads, deferred
external refresh while unpinned (flushed on explicit scrollToBottom), iOS/touch momentum
protection. CONFLICT RESOLVED with tonight's shipped #4584: kept #4584's deltaY<-30
jitter-resistant unpin threshold AND #4613's _markMessageTouchScrollIntent(true) on touchmove
(both present, 34 scroll-family tests pass). Authoritative head 03343e04b3f2.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: dso2ng <dso2ng@users.noreply.github.com>
2026-06-21 23:57:24 -07:00
74d7732778 stage #4597 (santastabber): reduce session-list work for archived histories (perf) (#4665)
Converged after deep bounce cycles: Finding A (production regression — skipped the
state.db source correction before the CLI filter, dropping WebUI sessions with stale-CLI
sidecar flags) FIXED via _apply_sidebar_state_db_overrides(result) before the routes.py
filter on both index + full-scan paths; Finding B (flaky lineage test) FIXED via hermetic
_persisted_session_ids_snapshot fixture. 16 own tests deterministic. Code byte-faithful
from PR head e1a3ecf22eca.

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: santastabber <santastabber@users.noreply.github.com>
2026-06-21 22:57:28 -07:00
nesquena-hermes 64ca1f8686 harden(#2980): profile-scope continuation lookup + safe-sid + no speculative restore write
Gate findings (Codex 2-CORE / Opus SHIP-WITH-FIXES), both fixed:
1. cross-profile leak — _pre_compression_continuation_session_id scanned all profiles' sessions,
   matched only by parent_session_id; now filters children by _profiles_match(child, snapshot)
   + validates returned sid with is_safe_session_id. +cross-profile regression test.
2. restore-state poison — frontend wrote continuationSid to localStorage/URL BEFORE the inner
   loadSession proved it loadable; removed the speculative write (success path at the loaded id
   handles it). Updated the source-grep test to assert the safe form.
Bounded BFS (range(20)+seen) + recursion guard confirmed safe by both gates.
2026-06-21 08:17:32 +00:00
nesquena-hermes dbeb437bcc rebase #2980 onto master (v0.51.548) for ship-gate — recover mobile reloads after compression rotation
george-andra: GET /api/session surfaces continuation_session_id for a hidden pre-compression
snapshot; loadSession follows it so a mobile reload mid-compression recovers to the visible
continuation instead of a hidden snapshot (chat looked 'lost'). Resolved 2 conflicts (1615 behind):
sessions.js merged w/ #2971 re-arm guard; routes.py merged w/ _session_source_is_webui branch.
Co-authored-by: george-andra <george-andra@users.noreply.github.com>
2026-06-21 08:17:32 +00:00
nesquena-hermes 6951d10ae5 rebase #4411 onto master (v0.51.542) for deep-review — Stable Assistant Turn Anchors capstone
franksong2702's activity_scene_v1 anchor-scene model: live-stream/settled/refresh/re-entry
show the same ordered turn. Rebased clean (no conflicts) onto current master.
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-06-20 21:48:55 +00:00
nesquena-hermes 2f3abb00be stage #4540 + CHANGELOG 2026-06-20 07:12:51 +00:00
nesquena-hermesandbergeouss f57c0330ec fix(sessions): scope #4354 unstick to skip the active mid-send session (#2689 Opus gate)
Gate-found (Opus SHIP-WITH-FIXES; Codex SAFE — split resolved by reading the
contract: no dropped-turn, but a real transient start-race flicker). #4424
removed the _sendInProgress guards entirely; that re-opened the #2689 start-race
SYMPTOM for the one session mid-send (a /api/sessions poll landing during the
/api/chat/start round-trip would blank the just-sent bubble + queue a spurious
force-reload). Re-add the guard SCOPED to sid===_sendInProgressSid only, in both
_reconcileActiveSessionIdleStateFromList and _purgeStaleInflightEntries — a
long-hung session has _sendInProgress===false so it still gets unstuck (#4354),
only the in-flight start window is protected. Also fixes Opus's test-masking
finding: test_inflight_send_start_race now strips JS comments before asserting +
requires a real , so a comment can't green-light the #2689 guard, and
adds coverage for the reconcile path.

Co-authored-by: bergeouss <bergeouss@users.noreply.github.com>
2026-06-19 23:02:58 +00:00
nesquena-hermes 0a1e5d97a4 stage #4424: unstuck Running indicator when SSE dies during provider retry (#4354) + v0.51.526 CHANGELOG 2026-06-19 22:51:02 +00:00
nesquena-hermes bd3d079c6a fix(toolsets): trim staging comment + widen #2518 provenance test window
The pre-session toolset comment pushed the #2518 provenance comment past the
test_issue2518 4000-char window assertion. Trim the comment to one line and
widen the test window to 5000 (the fallback block it actually guards is well
within range; newSession() has legitimately grown).
2026-06-19 18:44:58 +00:00
nesquena-hermesandclaw-io 4e8e6714d5 fix(toolsets): only forward staged toolsets from empty composer + clear on session load (#4490 Codex follow-up)
Gate-found: staged _pendingSessionToolsets could leak into a later New Chat
started from an existing session (newSession forwarded it whenever it was an
array; loadSession never cleared it). Gate the forward on !S.session and clear
the staged value when a real session loads. +2 regression tests.

Co-authored-by: claw-io <claw-io@users.noreply.github.com>
2026-06-19 18:35:46 +00:00
Frank Song fce927d5ac Fix pre-session toolsets staging 2026-06-19 18:35:04 +00:00
santastabber f8d57a0747 fix(sidebar): hide archived parent child rows 2026-06-19 06:28:17 +10:00
bergeouss d812f70116 fix(#4313): guard EventSource.close() calls to suppress ERR_ABORTED noise
Add readyState !== CLOSED (2) guards to all EventSource.close() calls across
messages.js, sessions.js, panels.js, and terminal.js. Chromium reports
net::ERR_ABORTED when close() is called on an already-closed EventSource,
polluting the console during normal lifecycle transitions (session switch,
panel close, stream teardown).

Key hotspots:
- closeLiveStream() double-close via onerror → _closeSource chain
- _closeSessionEventsSSE, _gatewaySSE.onerror missing try/catch
- _kanbanStopPolling, terminal close handlers

20 close() calls guarded across 4 files, no structural changes to stream
lifecycle.
2026-06-18 17:25:18 +00:00
nesquena-hermesandRod Boev 8246b4aad4 feat(#2543): collect selected chat text as named context blocks
"Reply with selection" no longer appends the selected text straight into
the composer. It now adds a named chip ("Context N") to a row above the
message box (composerSelectionChips). Chips can be renamed (double-click,
esc()-escaped) or removed (x); multiple stack. On send(),
_flushSelectionBlocksToComposer() materializes pending blocks into the
composer as **Name:** + markdown-quoted text, then the normal send path
runs unchanged on composer.value.

Regression-safety:
- _flushSelectionBlocksToComposer early-returns when no blocks are pending,
  so send() is byte-identical for anyone who never uses the feature.
- loadSession() AND newSession() both clear pending blocks before replacing
  S.session, so chips never leak across a sidebar switch OR a New Chat
  (the New Chat clear + its regression test were added per Codex review).
- chip name/text rendered via esc(); XSS-safe.

Verified live (headless CDP drive): add/render/remove/rename, flush
produces correct markdown, empty-flush no-op, session-switch clears,
injected <img onerror=...> chip-name escaped (not executed).

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-17 20:15:02 +00:00
nesquena-hermesanddso2ng 849aba1c8a Release v0.51.474 — reconcile WebUI session detail source flags (#4348)
Ships @dso2ng's fix: /api/session now reconciles stale CLI source flags
against the state.db WebUI-origin row, and _isExternalSession() refuses to
classify an explicit WebUI-source session as external — so a WebUI-native
transcript with a stale sidecar isn't force-reloaded like an imported one.

Rebased onto master (contributor's net code-diff applied; byte-identical to
PR head). CHANGELOG stamped v0.51.474.

Co-authored-by: dso2ng <dso2ng@users.noreply.github.com>
2026-06-17 05:11:45 +00:00
nesquena-hermes c96fa3cb1f fix(sessions): refresh active WebUI sessions on session events 2026-06-16 22:35:47 +00:00
nesquena-hermesandai-ag2026 0b57a7101f fix: keep session sidebar visible on slow loads (#4167)
Sidebar vanished during resilient/slow session loads. Preserve sidebar
fields on resilient loads + add an opt-in api() retry so a slow /api/sessions
shows a retryable error state instead of an empty sidebar.

Maintainer fixes for the 3 review-blocking SILENT regressions:
- Restore read_only / is_read_only / gateway_routing to the sidebar response
  allowlist, so read-only (imported CLI + Claude Code) sessions keep their
  rename/action-menu/swipe suppression and the gateway model label renders.
  (gateway_routing_history intentionally NOT sent — the label reader prefers
  the latest gateway_routing, and the full history would bloat each row.)
- Scope-tag the session cache (active profile + all-profiles flag). When a
  /api/sessions refresh fails right after a profile switch, the catch path now
  clears the stale rows and shows the error state instead of re-rendering the
  PRIOR profile's sessions.

Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-06-16 18:42:01 +00:00
nesquena-hermesandnesquena-hermes 2fa67ad699 Release PL (v0.51.451): show named-profile external sessions in the all-profiles list (#4067) (#4296)
* Release PL (v0.51.451): show named-profile external sessions in the all-profiles list (#4067, #4065)

All-profiles session enumeration now scans across profiles only when the
request explicitly asks (all-profiles view); cross-profile import resolves
to the active profile's state.db for unqualified requests (foreign id -> 404),
mirroring the #3982/#3991 detail/export profile-scoping gate.

Co-authored-by: rodboev

* gate fix (Codex CORE + Opus): close cross-profile leak on the import_cli refresh/existing-session branch

The already-imported branch of _handle_session_import_cli forced allow_all_profiles=True
whenever the globally-stored session carried a profile, with no active-profile visibility
gate — so an unqualified request from profile A could read/refresh a session imported under
profile B's live state.db (both advisors reproduced the foreign-transcript leak). Now gated
exactly like the /api/session detail + export endpoints: unqualified requests require
_session_visible_to_active_profile; explicit all_profiles requires a matching profile.
+ 3 regression tests.

* gate fix (Codex SILENT, re-gate): thread source_filter into the all-profiles CLI scan (models.py:4113)

The all-profiles branch keys its cache on source_filter but called
_load_cli_sessions_uncached without it, so /api/sessions?all_profiles=1 with a
source filter returned every-source sessions under a filtered key. + regression test.

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 21:55:31 -07:00
1bf2ef9f5e Release PF (v0.51.445): scope gateway watcher to active profile (#2848) (#4275)
* stage-3629: scope gateway watcher to active profile (profile-keyed registry), rebased onto master

* stage-3629: + stop-race-safe subscribe() (Codex CORE) + regression test

Codex caught a TOCTOU between get_watcher() and subscribe(): a watcher reaped as
subscriber-free during a concurrent profile switch left a late subscriber's queue
without the None sentinel -> SSE loop hangs open (keepalives, no events). Opus
independently found the same race (rated follow-up). subscribe() now checks
_stop_event under _sub_lock after appending and self-enqueues the sentinel.
test_subscribe_after_stop_gets_sentinel_immediately pins it.

* stage-3629: + reconnect gateway SSE to new profile on switch (Codex CORE #2)

Second Codex finding: after a profile switch this tab's _gatewaySSE stayed
subscribed to the PREVIOUS profile's watcher (switchToProfile never restarted it;
the probe reattach is gated on !_gatewaySSE which is false while the old ES is
open). switchToProfile() now calls startGatewaySSE() after S.activeProfile is set
— it closes the old ES + reconnects with the new profile cookie, self-gating on
_showCliSessions.

* Release PF: scope gateway watcher to active profile (#2848)

rodboev's #3629, re-pushed to fix both bounce findings (client SSE reconnect +
singleton→profile-keyed registry). Two Codex CORE races caught + fixed inline:
(1) stop/subscribe sentinel race, (2) tab's own SSE staying on the old profile
after switch. Codex SAFE + Opus SHIP (architecture) + gateway-watcher tests green.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 15:41:21 -07:00
a004e49ebf Release OW (v0.51.436): cron sessions mark unread on new runs (#3460) (#4253)
* stage-3921: cron-session unread disambiguation (#3460, rodboev)

* Release OW: cron sessions mark unread on new runs (#3460)

Self-rebased rodboev's #3921 onto v0.51.435; full Codex+Opus+suite gate.
Fixes cron-session unread cross-attribution: resolver now considers all known
job ids and attributes each session to the longest matching cron_<jobid>_ prefix
(provably unambiguous). Read-only state.db scan, fires only on real completions.

Co-authored-by: rodboev <rodboev@users.noreply.github.com>

---------

Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-06-15 10:52:52 -07:00
f927fc3a42 fix(sidebar): treat TUI sessions like CLI for discoverability (#4213) (#4237)
* Fix TUI session sidebar discoverability

* chore(release): stamp v0.51.432 (Release OS) for #4213

---------

Co-authored-by: Dennis Soong <dso2ng@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 00:48:56 -07:00
d29da34c42 feat(sessions): carry changed session_id in session-list-changed events (#4221) (#4233)
* fix(sessions): include target session in session events

* chore(release): stamp v0.51.430 (Release OQ) for #4221

---------

Co-authored-by: Dennis Soong <dso2ng@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-15 00:10:01 -07:00
d769acf43f fix(sessions): paginate tool-heavy session tails by visible messages (#4069) (#4215)
* fix: bound paginated session tails by visible messages

* fix(#4070): always merge state.db tail rows (drop the stale-tail timestamp shortcut)

Deep-review finding: the sidecar-only timestamp shortcut returned the sidecar
verbatim whenever state.db had no strictly-newer rows, but a state.db row
at-or-before the sidecar's newest timestamp (recovery / edited-in-place /
missing-timestamp) is still absent from the sidecar -> the paginated tail
diverged from the full merge path (empirically 3/6 adversarial cases). Always
run the cheap in-memory append-only merge; the real latency win (skipping the
lineage-parent DISK load) is preserved.

Co-authored-by: t3chn0pr13st <t3chn0pr13st@users.noreply.github.com>

* fix(#4070): keep matching trailing tool-result row in paginated window

Codex ship-gate finding: _message_window_for_display pulled end_idx back to the
last renderable (user/assistant) row, dropping a trailing role:tool result row
even when it's the result of the newest assistant tool-call. The renderer
rebuilds tool cards from these rows (CLI-origin / empty S.toolCalls path,
static/ui.js resultsByTid), so the card lost its result snippet. Now extend
end_idx forward through trailing tool rows whose tool_call_id matches a tool-call
present in the window; orphan trailing tool rows (no match) are still skipped to
preserve the visible-row budget. +2 regression tests.

Co-authored-by: t3chn0pr13st <t3chn0pr13st@users.noreply.github.com>

* chore(#4070): remove now-dead _max_message_timestamp + its import

Opus gate nit: _max_message_timestamp was only called by the removed
sidecar-timestamp shortcut, and the _message_timestamp_as_float import existed
solely to feed it. Both removed; _message_timestamp_as_float still lives in
api/models.py for its real callers.

* chore: add #4069 CHANGELOG entry (visible-message tail pagination)

* chore(release): stamp v0.51.424 (Release OK) for #4069

---------

Co-authored-by: t3chn0pr13st <technopriest@live.ru>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
Co-authored-by: t3chn0pr13st <t3chn0pr13st@users.noreply.github.com>
2026-06-14 19:18:11 -07:00
fe12eebbf3 perf(chat): virtualize long message transcripts (#500) (#4214)
* perf(chat): virtualize long message transcripts (#500)

* fix(chat): preserve virtual height cache on append (#500)

* fix(chat): bound virtual tail follow-up (#500)

* fix(chat): surface older history and bound virtual rerenders (#500)

* fix(chat): stabilize virtual history fallback (#500)

* fix(chat): preserve virtual compression anchors (#500)

* Keep virtualization regressions aligned with rebased sources (#500)

* fix(chat): preserve virtual heights across prepends (#500)

* test(update): isolate restart timing checks (#814)

* fix(chat): restore render contracts around virtual cache

* fix(chat): preserve legacy assistant anchor predicates

* fix(#3714): restore worklog-disclosure capture after HTML-cache early-return

Deep-review finding: the capture of _captureWorklogDetailDisclosureState was
moved before the HTML-cache fast-path return, failing the invariant test
test_render_rebuild_preserves_worklog_detail_disclosure_click_state (it must
not traverse the previous-session DOM when the cache path returns early).
Moved it back to just before inner.innerHTML='' (after the cache return).

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* fix(#3714): restore Transparent Stream rehydration in HTML-cache fast path

Full-suite gate caught test_issue3820_chat_activity_display_mode failure: the
virtualization rewrite of the renderMessages HTML-cache fast-path block dropped
the _rehydrateTransparentStreamDom(inner) call (master has it; PR had 2 of 3
occurrences). On a cache-restore session switch, Transparent Stream controls
lost their property-assigned handlers. Restored the call in the same position
master has it (right after _sessionHtmlCacheSid=sid).

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* fix(#3714): preserve live turn on scroll-rebuild even with zero streamed text

Opus ship-review residual (now fixed, not deferred): the live-turn re-attach
after a scroll-triggered virtualized rebuild was gated on _preservedLen>0, so a
turn showing only Activity/tool/worklog blocks with no streamed text yet (mid
tool-call) would not be preserved — scrolling up into history on a >80-row
transcript mid-stream could blink those live-only blocks for a frame. Broaden
the gate to also restore when the preserved turn has MORE structural blocks than
the rebuilt (lagging-S.messages) turn. Hoisted the _structuralCount helper above
the gate; whole-turn-vs-segment restore logic unchanged.

Co-authored-by: Rod Boev <rod.boev@gmail.com>

* chore(release): stamp v0.51.423 (Release OJ) for #500 transcript virtualization

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: nesquena-hermes <agent@nesquena-hermes>
2026-06-14 19:11:32 -07:00
nesquena-hermes faec854499 Merge PR #4195 2026-06-14 19:23:06 +00:00
Rod Boev 8c105bd31d fix(sessions): guard external refresh poll to skip non-external sessions (#3916) 2026-06-14 15:05:10 -04:00
Rod Boev 84990f4a69 fix(sidebar): preserve GitHub issue numbers in session titles (#4154) 2026-06-14 14:59:54 -04:00
Rod Boev c98f3c3abf feat(sidebar): nest forked sessions under their parent (#3224)
Re-pushed by rodboev fixing both bounce findings (parent streaming-state
isolation + nested-fork batch-select) plus a select-handler selector fix.
On current master (0 behind); sessions.js byte-identical to PR head.

Co-authored-by: Rod Boev <rod.boev@gmail.com>
2026-06-14 04:21:54 +00:00
Nathan EsquenaziandClaude Opus 4.8 266d9cdbd3 fix(sse): guard gateway reopen on focus so transient blur can't thrash it (#4151)
The #4151 focus hook reopened both global sidebar streams on `focus` with a
bare `startGatewaySSE()`. But `startGatewaySSE()` is NOT idempotent — it begins
with an unconditional `stopGatewaySSE()` (sessions.js:3778), which closes the
live gateway EventSource, cancels its poll fallback (`stopGatewayPollFallback`),
and resets `_gatewayProbeInFlight` / `_gatewaySSEWarningShown`.

So on a transient blur shorter than the 1s debounce — where the blur-close
timer is cleared on the following focus and the stream was never torn down —
the focus handler still dropped and reconnected the gateway on every
window-switch, cancelling its fallback and resetting probe/warning state. That
is the exact thrash the debounce exists to prevent, in the multi-window PWA
scenario this PR targets. (`ensureSessionEventsSSE()` is already idempotent via
`if(_sessionEventsSSE) return`, so the session-events stream was unaffected;
only the gateway churned. greptile P1.)

Guard the reopen on the gateway actually being closed: `if(!_gatewaySSE)
startGatewaySSE()`, mirroring the session-events idempotency. A sustained blur
(timer fires → both streams closed) still reopens the gateway on focus; a
transient blur leaves the still-open gateway untouched. Verified with a
blur/focus/timer harness (transient → no teardown/reconnect; sustained → close
then reopen; CLI-disabled → stays closed) and a new regression guard
`test_focus_reopen_does_not_thrash_the_gateway_stream`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 18:07:12 -07:00
nesquena-hermesandddiall 408dbeb4f0 fix(sse): close global sidebar streams on PWA window blur (#4151)
Two side-by-side PWA standalone windows saturate the browser's per-origin
HTTP/1.1 connection limit, causing 'Request timed out' toasts to cycle every
~30s. The idle-SSE close added in #3992/#3996 keys off the Page Visibility API
(visibilitychange / document.hidden), but a PWA standalone window does not
reliably fire visibilitychange when it loses focus to another window of the
same app -- document.hidden only flips on minimize. So both windows stay
'visible', each holds its two global sidebar SSE streams (session-events +
gateway) open, 2x3 = 6 hits the connection cap, and subsequent fetch() calls
(the 30s background polls) queue behind the saturated pool and time out.

The two global sidebar streams now also close on a sustained window blur (gated
on document.hasFocus(), the signal visibilitychange misses) and reopen --
catching up the session list -- on focus, via a shared _sidebarSseBackgrounded()
predicate and a debounced _installSidebarSseFocusHook().

The per-session live stream (messages.js startSessionStream) is intentionally
left visibility-only: it carries live bg_task_complete toasts and
server_turn_started live-view that an unfocused-but-visible window must still
receive (the OS-notification path is gated on document.hidden, so the in-app
toast is the only completion signal a visible-unfocused window gets). Closing
it on blur would regress the multi-window live-view UX.

Reported by @ddiall with a thorough root-cause analysis.

Co-authored-by: ddiall <ddiall@users.noreply.github.com>
2026-06-14 00:54:28 +00:00
nesquena-hermes 4f1ce2661e Merge #3998 v2 (imported-session titles + reload-before-save fix, #3987) onto master 2026-06-13 21:46:36 +00:00
ai-ag2026 cea9d42fcc fix: extend timeout for full history session loads
(cherry picked from commit 27cbff974eeaa58780d91fb4f25802cf0ac76381)
2026-06-13 22:37:19 +02:00
Frank Song 0fdcd00858 Fix PWA final answer scroll refresh 2026-06-14 01:46:04 +08:00
Frank Song aa96bc52bc Add assistant turn anchor live shadow feed 2026-06-13 16:26:52 +08:00
nesquena-hermes 5bbddbad1e fix(#3900): false streaming + activity-timer reset on session switch (absorb #3899) + re-anchor regression tests
Production fixes (Tamaz-sujashvili, reviewed sound by maintainer):
- loadSession clears S.busy/S.activeStreamId as soon as metadata confirms no active_stream_id, before the async message-load gap (idle session no longer shows streaming chrome).
- Snapshots the live turn before wiping msgInner + seeds INFLIGHT, restores on the active-stream return path (timer/trace survive switch-back).

Re-anchored the 2 brittle regression tests per maintainer review: test_..snapshots.. now anchors on the unique 'Loading conversation...' marker (was matching the no-space 'Session not available' error path); test_..restores.. now asserts the LIVE Phase 2a restore (after loadInflightState) instead of the unreachable Phase-2b/1184 branch. CHANGELOG stamped v0.51.384 (MW).
2026-06-13 06:39:22 +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
Rod Boev 6a1109475b fix(sidebar): count inactive rows through the render path 2026-06-12 12:03:04 -04:00
Rod Boev 0ed902bd05 fix(sidebar): drop the dead WebUI count path 2026-06-12 11:55:43 -04:00
Rod Boev dde9f06b03 test(sidebar): avoid shard churn from static regressions 2026-06-12 09:52:49 -04:00
Rod Boev 19448c3267 fix(sidebar): keep rendered source counts on one path 2026-06-12 09:36:10 -04:00
Rod Boev c507684b10 fix(sidebar): align session source counts with rendered rows (#3966) 2026-06-12 09:14:23 -04:00
Rod Boev 220d255d5c fix(#4005): preserve expanded lineage segments during streaming refresh 2026-06-11 20:55:35 -04:00
nesquena-hermes 8a849a1bb8 Merge #4009 — open clicked lineage segments without sid rewrite (#4003) 2026-06-11 22:48:36 +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 63c62194a1 Merge #3993 — self-heal stuck session loads on non-404 failures 2026-06-11 22:19:14 +00:00
Rod Boev 749f97144b fix(#4003): open clicked lineage segments without sid rewrite 2026-06-11 16:28:33 -04:00
John Torcivia 8eadfb048f fix(sessions): remove self-heal from if(!data) 401 path
On 401, api() redirects to /login and returns undefined. The browser
navigates away immediately, so this code rarely runs. But even if it
did, clearing localStorage on transient auth expiry is wrong — it wipes
the saved session id and sends users to empty state after re-login.

Keep the self-heal in the catch-block else branch for non-401, non-404
errors (400/403/500/network) which genuinely trap the user on recurring
boot failures. 401 has no such property because the redirect already
breaks the retry loop.

Greptile: 'Keep the if (!data) early-return clearing-free (its old
behavior), and only call _clearStuckSessionOnBoot() from the else
branch of the catch.'

(#4028 follow-up)
2026-06-11 14:54:42 -04:00
John Torcivia 3bf3d6004e fix(sessions): remove unreachable e.status===401 branch in loadSession catch
The api() function returns undefined on 401 (redirects to login) rather
than throwing. So the 401 path exits via the if(!data) guard at line ~988,
not through the catch block — the e.status===401 branch inside the catch
is dead code (#4028 follow-up).
2026-06-11 14:52:25 -04:00