Release v0.51.279 — Release IU (stage-p3h — preserve Activity/streaming turn on mid-stream scroll #3665) - #3686
Conversation
|
| Filename | Overview |
|---|---|
| api/models.py | Core merge/dedup/visible key functions now include tool_calls (JSON-serialized, sort_keys=True) so assistant messages with different tool invocations but identical empty content/timestamp are no longer collapsed. A preservation branch in merge_session_messages_append_only lets state.db rows with distinct tool_calls pass through the sidecar-timestamp guard. |
| api/routes.py | Removes the conditional that zeroed out session-level tool_calls when truncated messages already had per-message tool metadata; now always sends windowed (or full) session-level tool_calls so the browser can merge older messages that lack per-message variants. |
| static/sessions.js | _syncToolCallsForLoadedMessages now returns early when S.busy |
| static/messages.js | Adds clearVisibleMessageRowCache() in the stream-start session-replace block. Done handler expands _messageRenderWindowSize before the final renderMessages. The 5-second cooldown flag (window._streamJustFinished) is set here. |
| static/ui.js | Adds _visWithIdxCacheSrc reference tracking for cache invalidation on wholesale S.messages replacement. jumpToSessionStart skips the full message load and renderMessages during active streaming. Tool-card DOM insertion unlocked during streaming when S.toolCalls is populated. |
| tests/test_merge_key_tool_calls.py | New regression test suite covering merge_key, dedup_key, visible_key differentiation for messages with distinct tool_calls, and end-to-end merge_session_messages_append_only behaviour in both directions. |
| tests/test_session_tail_payload.py | Test updated to reflect the routes.py change: payload now includes windowed session tool_calls even when messages carry per-message tool metadata. |
| tests/test_smooth_text_fade.py | Fixes the test helper that extracted the _scheduleRender function block — the brace-counting parser chokes on template-literal braces; now searches the full file string. |
Sequence Diagram
sequenceDiagram
participant Browser
participant SSE as SSE Stream
participant API as /api/session
participant Sidecar as Sidecar (memory)
participant StateDB as state.db
Note over Browser: User scrolls up mid-stream
Browser->>API: "GET /api/session?msg_limit=N"
Note over Browser: S.busy=true - skip _ensureAllMessagesLoaded
Note over Browser: skip renderMessages during stream
SSE-->>Browser: tool_call event - S.toolCalls updated
SSE-->>Browser: done event
Browser->>Browser: "window._streamJustFinished=true (5s cooldown)"
Browser->>Browser: Expand _messageRenderWindowSize
Browser->>Browser: renderMessages preserveScroll
Note over Browser: S.toolCalls intact - Activity panel preserved
API->>Sidecar: merge_session_messages_append_only
Sidecar->>StateDB: fetch state rows
Note over Sidecar,StateDB: tool_calls now in merge/dedup/visible keys
Note over Sidecar,StateDB: different tool_calls + same timestamp - PRESERVE
StateDB-->>Sidecar: state rows
Sidecar-->>API: merged messages (distinct tool turns retained)
API-->>Browser: session payload with windowed tool_calls
Browser->>Browser: _syncToolCallsForLoadedMessages
Note over Browser: guard: if S.busy OR S.activeStreamId - skip
Reviews (1): Last reviewed commit: "docs(changelog): v0.51.279 — Release IU ..." | Re-trigger Greptile
| if _ck in seen_content_keys and dedup_key not in seen_dedup_keys: | ||
| pass # different tool_calls from sidecar — preserve |
There was a problem hiding this comment.
The
dedup_key not in seen_dedup_keys sub-expression is always True here. By line 4272, any message whose dedup_key was already in seen_dedup_keys has already been skipped via continue, so the condition can never be False when execution reaches this point. The guard is harmless but misleading — a reader might assume it provides a meaningful second check. Simplifying to just _ck in seen_content_keys makes the intent clearer.
| if _ck in seen_content_keys and dedup_key not in seen_dedup_keys: | |
| pass # different tool_calls from sidecar — preserve | |
| if _ck in seen_content_keys: | |
| pass # different tool_calls from sidecar — preserve |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| // Cooldown: don't force-reload immediately after streaming ends — the | ||
| // "done" event already delivered the final messages. Reloading here would | ||
| // clear S.toolCalls and lose Activity. | ||
| if(typeof window !== 'undefined' && window._streamJustFinished) return; |
There was a problem hiding this comment.
window._streamJustFinished is not scoped to the active session. If the user switches sessions while the flag is live (within the 5-second window), refreshActiveSessionIfExternallyUpdated will be silently suppressed for the new session as well, even though that session's stream has nothing to do with the flag. A session-keyed value (e.g. window._streamJustFinishedSid = activeSid set alongside the flag, then checked as window._streamJustFinished && window._streamJustFinishedSid === S.session.session_id) would limit the cooldown to only the session whose stream just ended.
…ng turn on mid-stream scroll nesquena#3665) (nesquena#3686) * fix(streaming): preserve Activity + streaming turn when loading earlier messages mid-stream (nesquena#3665, nesquena#3346) Co-authored-by: mysoul12138 <839465496@qq.com> * docs(changelog): v0.51.279 — Release IU (stage-p3h) --------- Co-authored-by: nesquena-hermes <[email protected]> Co-authored-by: mysoul12138 <839465496@qq.com>
Release v0.51.279 — Release IU (stage-p3h)
Phase-3-MEDIUM (high-risk-per-LOC merge/data-loss family) — #3665 (mysoul12138; fixes #3346). Full gate run with deep both-directions scrutiny.
Fixed
merge_session_messages_append_onlyfails to deduplicate legacy messages in state #3346) — loading earlier messages during an active stream no longer wipes the Activity panel or the current streaming turn.tool_callsis now part of the session message merge/dedup/visible keys (distinct tool invocations with identical empty content + same-second timestamp no longer collapse, which was dropping state.db tool-calls), plus the frontend keeps live tool-call/streaming state when paging history.Gates
node -c+ast.parseclean, ruff CLEAN, ESLint runtime CLEAN, browser smoke CLEANjson.dumps(tool_calls,sort_keys=True)key); genuine same-message sidecar replays still collapse (full dedup runs before the preservation branches); indexed visible-key unpacking safe; no cross-sessionS.toolCallsleak (cleared on switch)test_same_tool_calls_..._merge_to_one(no duplication) +test_different_tool_calls_..._both_preserved(no drop) passCloses #3665. (Non-blocking nit noted by Opus: the
dedup_key not in seen_dedup_keysguard at models.py:4327 is defensive/unreachable-False — left as-is.)