Merge pull request #5356 from nesquena/stage-5308

Release — subagent sessions no longer vanish from sidebar (#5308)
This commit is contained in:
nesquena-hermes authored and GitHub committed 2026-07-01 11:16:38 -07:00
commit 7321b6fceb
3 files changed
+98

No files matched your search

+2
View File
@@ -9,6 +9,8 @@ _No unreleased changes. Entries are moved into their version block when a releas
### Fixed
- **Delegated subagent sessions no longer vanish from the sidebar.** A subagent (delegate_task) child whose sidebar row was built from a stale sidecar reporting 0 messages now receives its real message count from the agent state.db, so it stays visible (nested under its parent) instead of being silently dropped by the sidebar visibility filter. (#5308)
- **No more scroll jump-back on mobile while messages load.** When the app realigns the viewport after loading older messages, mobile browsers no longer double-compensate the scroll position (their native overflow-anchor fighting the app's own scroll write), which caused a visible jump. Desktop behavior is unchanged. (#5338)
- **No more white flicker/flash while the assistant streams on light themes.** Live token-by-token markdown updates no longer inherit the global dark/light `color`/`background` transition, so the streaming turn paints instantly instead of briefly fading on each token. Theme-switch transitions elsewhere are unchanged. (#5328)
+14
View File
@@ -4028,6 +4028,20 @@ def _apply_sidebar_state_db_override_metadata(sessions: list[dict], metadata: di
session['session_source'] = state_db_session_source
session['source_label'] = state_db_source_label
session['is_cli_session'] = False
# Overlay the real state.db message count for WebUI-owned rows AND for
# delegated subagent children (#5308). A subagent child
# (state_db_source == 'subagent') is backed by the delegate runner's
# state.db session, but its sidebar row is built from a stale sidecar
# that often reports message_count == 0. Without overlaying the true
# count, the front-end visibility predicate
# (_sidebarRowHasVisibleMessages) drops the row and the subagent
# session vanishes from the sidebar entirely (regression seam behind
# #5308, same state.db-blind-metadata root as the #5307 transcript
# recovery). The count overlay keeps the same conservative
# anti-resurrection guard used for WebUI rows. The source-tag / title
# reassignment above stays WebUI-only — a subagent child keeps its
# subagent classification.
if state_db_source in ('webui', 'subagent'):
try:
current_count = max(0, int(session.get('message_count') or 0))
state_count = max(0, int(state_db_message_count or 0))
@@ -155,6 +155,88 @@ def test_sidebar_state_db_overlay_preserves_numeric_actual_count():
assert sessions[0]["actual_message_count"] == 5
def test_sidebar_state_db_overlay_counts_subagent_child_5308():
"""#5308: a delegated subagent child whose stale sidecar reports
message_count == 0 must receive its real state.db message count so the
front-end sidebar visibility predicate does not drop the row (the subagent
session vanishing regression). The overlay applies to source == 'subagent'
just like 'webui', while the subagent source classification is preserved.
"""
import api.models as models
sid = "subagent_child_5308"
sessions = [
{
"session_id": sid,
"source_tag": "subagent",
"raw_source": "subagent",
"session_source": "other",
"relationship_type": "child_session",
"parent_session_id": "parent_abc",
"message_count": 0,
"actual_message_count": 0,
"last_message_at": 0,
"updated_at": 0,
}
]
models._apply_sidebar_state_db_override_metadata(
sessions,
{
sid: {
"_state_db_source": "subagent",
"_state_db_message_count": 6,
"_state_db_last_message_at": 2002.0,
}
},
)
# Real state.db count is now overlaid (was 0) -> row survives the sidebar
# visibility predicate instead of vanishing.
assert sessions[0]["message_count"] == 6
assert sessions[0]["actual_message_count"] == 6
assert sessions[0]["last_message_at"] == 2002.0
# Subagent classification is preserved (source-tag reassignment stays
# WebUI-only) — the child does not get re-tagged as a webui session.
assert sessions[0]["source_tag"] == "subagent"
assert sessions[0].get("is_cli_session") is not False
def test_sidebar_state_db_overlay_does_not_count_foreign_cli_source_5308():
"""Guard the #5308 overlay scope: a non-webui, non-subagent foreign source
(e.g. a messaging/cron/tui CLI row) must NOT get the count overlay — only
WebUI-owned rows and delegated subagent children do.
"""
import api.models as models
sid = "cron_row_5308"
sessions = [
{
"session_id": sid,
"source_tag": "cron",
"message_count": 0,
"actual_message_count": 0,
"last_message_at": 0,
"updated_at": 0,
}
]
models._apply_sidebar_state_db_override_metadata(
sessions,
{
sid: {
"_state_db_source": "cron",
"_state_db_message_count": 9,
"_state_db_last_message_at": 3003.0,
}
},
)
# cron is neither webui nor subagent -> count overlay must NOT apply.
assert sessions[0]["message_count"] == 0
assert sessions[0]["actual_message_count"] == 0
def test_tail_cancelled_partial_blocks_state_db_replay():
from api.models import merge_session_messages_append_only