fix(webui): overlay real state.db message count for subagent children so they don't vanish from the sidebar (#5308)
Regression seam behind #5308: a delegated subagent child's sidebar row is built from a stale sidecar that reports message_count==0, and the state.db count overlay in _apply_sidebar_state_db_override_metadata was gated on `state_db_source == 'webui'`. A subagent child (state_db_source=='subagent') therefore never received its true message count, so the front-end visibility predicate (_sidebarRowHasVisibleMessages) dropped the row and the subagent session disappeared entirely (not nested, not orphaned) after #5244+#5306. Fix: widen the count/last-message overlay to `state_db_source in ('webui','subagent')`, keeping the same conservative anti-resurrection guard. The source-tag/title reassignment stays WebUI-only so a subagent child keeps its subagent classification. Same state.db-blind-metadata root as the #5307 transcript recovery, fixed server-side rather than by loosening the front-end predicate (which would fight the #5306 active-parent scoping). Tests: subagent child gets its count overlaid + classification preserved; a non-webui/non-subagent foreign source (cron) still gets NO overlay. Fixes #5308.
This commit is contained in:
1 parent
412cf01ac2
commit
8b8ee92eca
2 files changed
+96
No files matched your search
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user