Skip to content

Commit 90e1ef1

Browse files
authored
fix(android): hide internal chat history rows (#100826)
* fix(android): hide internal chat history rows * chore(android): sync native i18n inventory
1 parent e66b470 commit 90e1ef1

8 files changed

Lines changed: 95 additions & 18 deletions

File tree

apps/.i18n/native-source.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5235,79 +5235,79 @@
52355235
},
52365236
{
52375237
"kind": "ui-call",
5238-
"line": 150,
5238+
"line": 151,
52395239
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
52405240
"source": "Thinking...",
52415241
"surface": "android",
52425242
"id": "native.android.a50e44d8f380f9ad"
52435243
},
52445244
{
52455245
"kind": "ui-call",
5246-
"line": 169,
5246+
"line": 170,
52475247
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
52485248
"source": "Running tools...",
52495249
"surface": "android",
52505250
"id": "native.android.3788802f6c138c8b"
52515251
},
52525252
{
52535253
"kind": "ui-call",
5254-
"line": 172,
5254+
"line": 173,
52555255
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
52565256
"source": "${display.emoji} ${display.label}",
52575257
"surface": "android",
52585258
"id": "native.android.5f93173e06eda5c0"
52595259
},
52605260
{
52615261
"kind": "ui-named-argument",
5262-
"line": 190,
5262+
"line": 191,
52635263
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
52645264
"source": "... +${toolCalls.size - 6} more",
52655265
"surface": "android",
52665266
"id": "native.android.29021f925bf290c7"
52675267
},
52685268
{
52695269
"kind": "ui-named-argument",
5270-
"line": 235,
5270+
"line": 236,
52715271
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
52725272
"source": "Retry",
52735273
"surface": "android",
52745274
"id": "native.android.9bd31c23ebdf7c7a"
52755275
},
52765276
{
52775277
"kind": "ui-named-argument",
5278-
"line": 238,
5278+
"line": 239,
52795279
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
52805280
"source": "Delete",
52815281
"surface": "android",
52825282
"id": "native.android.b728b15914267f57"
52835283
},
52845284
{
52855285
"kind": "conditional-branch",
5286-
"line": 306,
5286+
"line": 307,
52875287
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
52885288
"source": "You",
52895289
"surface": "android",
52905290
"id": "native.android.1d0bb431f08154a6"
52915291
},
52925292
{
52935293
"kind": "conditional-branch",
5294-
"line": 307,
5294+
"line": 308,
52955295
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
52965296
"source": "System",
52975297
"surface": "android",
52985298
"id": "native.android.d29098025118ce4b"
52995299
},
53005300
{
53015301
"kind": "conditional-branch",
5302-
"line": 308,
5302+
"line": 309,
53035303
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
53045304
"source": "OpenClaw",
53055305
"surface": "android",
53065306
"id": "native.android.82e9fe45e93909b6"
53075307
},
53085308
{
53095309
"kind": "ui-call",
5310-
"line": 334,
5310+
"line": 335,
53115311
"path": "apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt",
53125312
"source": "Unsupported attachment",
53135313
"surface": "android",

apps/android/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
Android chat history now excludes internal, reasoning, and tool-result rows from rendered messages and the offline transcript cache.
6+
57
The OpenClaw mascot now comes alive across onboarding and the app headers with the same float, blink, antenna-wiggle, and claw-snap animation as openclaw.ai.
68

79
Adds read-only Cron Job details in Settings, including schedule, payload and delivery state, job ID copy, refresh, and nested back navigation.

apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1645,7 +1645,7 @@ class ChatController internal constructor(
16451645
val messages =
16461646
array.mapNotNull { item ->
16471647
val obj = item.asObjectOrNull() ?: return@mapNotNull null
1648-
val role = obj["role"].asStringOrNull() ?: return@mapNotNull null
1648+
val role = normalizeVisibleChatMessageRole(obj["role"].asStringOrNull()) ?: return@mapNotNull null
16491649
val content = parseChatMessageContents(obj)
16501650
val ts = obj["timestamp"].asLongOrNull()
16511651
ChatMessage(

apps/android/app/src/main/java/ai/openclaw/app/chat/ChatModels.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
package ai.openclaw.app.chat
22

3+
import java.util.Locale
4+
5+
private val visibleChatMessageRoles = setOf("user", "assistant", "system", "custom")
6+
7+
/** Keeps transcript rows limited to roles Android renders as user-visible chat. */
8+
internal fun normalizeVisibleChatMessageRole(role: String?): String? =
9+
role
10+
?.trim()
11+
?.lowercase(Locale.US)
12+
?.takeIf(visibleChatMessageRoles::contains)
13+
314
/**
415
* Chat transcript item as delivered by gateway chat history and live chat events.
516
*/
@@ -57,7 +68,6 @@ data class ChatCommandEntry(
5768
val acceptsArgs: Boolean = false,
5869
)
5970

60-
6171
/**
6272
* Run still streaming on the gateway when a chat.history snapshot was captured;
6373
* [text] is the assistant text buffered so far (may be empty for runs without deltas).

apps/android/app/src/main/java/ai/openclaw/app/chat/ChatTranscriptCache.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,11 @@ class RoomChatTranscriptCache internal constructor(
234234
): List<ChatMessage> {
235235
val gateway = scopedGatewayId(gatewayId) ?: return emptyList()
236236
val key = sessionKey.trim().takeIf { it.isNotEmpty() } ?: return emptyList()
237-
return database.dao().messages(gateway, key).map { row ->
237+
return database.dao().messages(gateway, key).mapNotNull { row ->
238+
val role = normalizeVisibleChatMessageRole(row.role) ?: return@mapNotNull null
238239
ChatMessage(
239240
id = UUID.randomUUID().toString(),
240-
role = row.role,
241+
role = role,
241242
content = decodeTextParts(row.textPartsJson).map { ChatMessageContent(type = "text", text = it) },
242243
timestampMs = row.timestampMs,
243244
idempotencyKey = row.idempotencyKey,
@@ -300,16 +301,17 @@ class RoomChatTranscriptCache internal constructor(
300301
val rows =
301302
messages
302303
.mapNotNull { message ->
304+
val role = normalizeVisibleChatMessageRole(message.role) ?: return@mapNotNull null
303305
val textParts = message.content.filter { it.type == "text" }.mapNotNull { it.text }
304306
if (textParts.isEmpty()) return@mapNotNull null
305-
message to textParts
307+
Triple(message, role, textParts)
306308
}.takeLast(MAX_CACHED_MESSAGES_PER_SESSION)
307-
.mapIndexed { index, (message, textParts) ->
309+
.mapIndexed { index, (message, role, textParts) ->
308310
CachedMessageEntity(
309311
gatewayId = gateway,
310312
sessionKey = key,
311313
rowOrder = index,
312-
role = message.role,
314+
role = role,
313315
textPartsJson = json.encodeToString(textPartsSerializer, textParts),
314316
timestampMs = message.timestampMs,
315317
idempotencyKey = message.idempotencyKey,

apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import ai.openclaw.app.chat.ChatMessageContent
55
import ai.openclaw.app.chat.ChatOutboxItem
66
import ai.openclaw.app.chat.ChatOutboxStatus
77
import ai.openclaw.app.chat.ChatPendingToolCall
8+
import ai.openclaw.app.chat.normalizeVisibleChatMessageRole
89
import ai.openclaw.app.tools.ToolDisplayRegistry
910
import ai.openclaw.app.ui.MobileColorsAccessor
1011
import ai.openclaw.app.ui.mobileAccent
@@ -60,7 +61,7 @@ private data class ChatBubbleStyle(
6061
/** Renders one persisted chat message as text and image parts. */
6162
@Composable
6263
fun ChatMessageBubble(message: ChatMessage) {
63-
val role = message.role.trim().lowercase(Locale.US)
64+
val role = normalizeVisibleChatMessageRole(message.role) ?: return
6465
val style = bubbleStyle(role)
6566

6667
// Filter to only displayable content parts (text with content, or base64 images).

apps/android/app/src/test/java/ai/openclaw/app/chat/ChatControllerMessageIdentityTest.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package ai.openclaw.app.chat
22

3+
import kotlinx.coroutines.ExperimentalCoroutinesApi
4+
import kotlinx.coroutines.test.advanceUntilIdle
5+
import kotlinx.coroutines.test.runTest
36
import kotlinx.serialization.json.Json
47
import kotlinx.serialization.json.jsonObject
58
import org.junit.Assert.assertEquals
@@ -39,6 +42,43 @@ class ChatControllerMessageIdentityTest {
3942
assertEquals(listOf(ChatMessageContent(type = "text", text = "Hi there")), content)
4043
}
4144

45+
@Test
46+
@OptIn(ExperimentalCoroutinesApi::class)
47+
fun liveHistoryDropsInternalRoleRows() =
48+
runTest {
49+
val controller =
50+
ChatController(
51+
scope = this,
52+
json = json,
53+
requestGateway = { method, _ ->
54+
if (method == "chat.history") {
55+
"""
56+
{
57+
"messages": [
58+
{ "role": "user", "content": "hello" },
59+
{ "role": "toolResult", "content": "private tool output" },
60+
{ "role": "internal", "text": "private reasoning" },
61+
{ "role": "custom", "content": "visible plugin notice" },
62+
{ "role": "Assistant", "content": "reply" }
63+
]
64+
}
65+
""".trimIndent()
66+
} else {
67+
"{}"
68+
}
69+
},
70+
)
71+
72+
controller.load("main")
73+
advanceUntilIdle()
74+
75+
assertEquals(listOf("user", "custom", "assistant"), controller.messages.value.map { it.role })
76+
assertEquals(
77+
listOf("hello", "visible plugin notice", "reply"),
78+
controller.messages.value.map { it.content.single().text },
79+
)
80+
}
81+
4282
@Test
4383
fun reconcileMessageIdsReusesMatchingIdsAcrossHistoryReload() {
4484
val previous =

apps/android/app/src/test/java/ai/openclaw/app/chat/RoomChatTranscriptCacheTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,28 @@ class RoomChatTranscriptCacheTest {
104104
assertEquals(listOf("run-1:user", null), loaded.map { it.idempotencyKey })
105105
}
106106

107+
@Test
108+
fun transcriptRoundTripDropsInternalRoleRows() =
109+
runTest {
110+
val store = cache()
111+
store.saveTranscript(
112+
gatewayId = "gateway-a",
113+
sessionKey = "main",
114+
messages =
115+
listOf(
116+
message("hello", role = "user"),
117+
message("private tool output", role = "toolResult"),
118+
message("visible plugin notice", role = "custom"),
119+
message("reply", role = "assistant"),
120+
),
121+
)
122+
123+
val loaded = store.loadTranscript("gateway-a", "main")
124+
125+
assertEquals(listOf("hello", "visible plugin notice", "reply"), loaded.map { it.content.single().text })
126+
assertEquals(listOf("user", "custom", "assistant"), loaded.map { it.role })
127+
}
128+
107129
@Test
108130
fun transcriptWriteKeepsOnlyNewestBoundedMessages() =
109131
runTest {

0 commit comments

Comments
 (0)