fix(chat): route ALL synthetic backend user rows to system events (#937)
The backend persists a whole class of internal scaffolding as plain role=user rows with NO display_kind (SessionDB strips underscore metadata): runtime nudges (max tool-call iterations), context injections ([System:/[CONTEXT/ [PRIOR CONTEXT), background/cron/delegation notices ([IMPORTANT: Background/ Cronjob Response:/[ASYNC DELEGATION), out-of-band markers ([OUT-OF-BAND), and todo/planning injections ([Your active task list/[Planning state preserved). These are NOT real human turns, but the mobile rendered them as user bubbles. Mirror the backend's own synthetic-detection contract (agent.context_compressor._synthetic_user_row) with a prefix map. Any role=user row with no display_kind that starts with a known synthetic prefix is routed to a SystemEvent instead of a user turn. The 90-calls nudge maps to a specific "Reached tool-call limit" chip; the rest map to a generic "System notice" chip. Real user messages (no synthetic prefix) are unaffected. - FullBleedTurns: SYNTHETIC_USER_PREFIX_KINDS map + syntheticSystemKind() that tags matched rows so the timeline chip can name them. - timelineMarkerLabelRes: max_iterations_reached + system_notice labels. - Strings en/zh/ko for both. - Tests: assert the new labels map; assert every synthetic prefix routes to a SystemEvent (not a user turn); assert a real user message stays a user turn. The display_kind-tagged events (model/personality switches, auto-continue, delegation complete, skill invocation, internal notification) are already handled separately (PR #936, merged). ktlint + Android Lint + unit tests green locally. Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
0d1b4fee11
commit
caed4db316
7 files changed
+54
-1
No files matched your search
@@ -90,6 +90,25 @@ class FullBleedTurnsTest {
|
||||
assertEquals(listOf<ChatTurn>(entries(AgentEntry.SystemEvent(marker))), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `max-iterations nudge is a SystemEvent not a user bubble`() {
|
||||
// Backend persists the max-iterations notice as a plain role=user row
|
||||
// with NO display_kind (it's stripped on SessionDB persistence); the
|
||||
// mobile must still treat it as a system event so it renders with the
|
||||
// distinct system design instead of a fake user bubble.
|
||||
val content =
|
||||
"You've reached the maximum number of tool-calling iterations allowed. " +
|
||||
"Please provide a final response."
|
||||
val nudge = msg("n1", MessageRole.USER, content = content)
|
||||
val result = groupIntoTurns(listOf(nudge))
|
||||
assertEquals(
|
||||
listOf<ChatTurn>(
|
||||
entries(AgentEntry.SystemEvent(nudge.copy(displayKind = "max_iterations_reached"))),
|
||||
),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `marker between assistant and user does not close the agent turn`() {
|
||||
val a = msg("a1", MessageRole.ASSISTANT)
|
||||
|
||||
@@ -26,6 +26,10 @@ class TimelineMarkerTest {
|
||||
R.string.timeline_marker_internal_notification,
|
||||
timelineMarkerLabelRes("internal_notification"),
|
||||
)
|
||||
assertEquals(
|
||||
R.string.timeline_marker_max_iterations,
|
||||
timelineMarkerLabelRes("max_iterations_reached"),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user