diff --git a/apps/android/app/src/main/java/ai/openclaw/app/node/NodePresenceAliveBeacon.kt b/apps/android/app/src/main/java/ai/openclaw/app/node/NodePresenceAliveBeacon.kt index d7d12626ab84..90eb5de0db9b 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/node/NodePresenceAliveBeacon.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/node/NodePresenceAliveBeacon.kt @@ -1,5 +1,6 @@ package ai.openclaw.app.node +import ai.openclaw.app.takeUtf16Safe import android.os.Build import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonPrimitive @@ -109,6 +110,6 @@ internal object NodePresenceAliveBeacon { return value .map { ch -> if (ch.isISOControl()) ' ' else ch } .joinToString("") - .take(200) + .takeUtf16Safe(200) } } diff --git a/apps/android/app/src/test/java/ai/openclaw/app/node/NodePresenceAliveBeaconTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/node/NodePresenceAliveBeaconTest.kt index 6ce6b64aab29..21b218dc8ea1 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/node/NodePresenceAliveBeaconTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/node/NodePresenceAliveBeaconTest.kt @@ -98,4 +98,19 @@ class NodePresenceAliveBeaconTest { assertFalse(sanitized.contains("\t")) assertEquals(200, sanitized.length) } + + @Test + fun sanitizeReasonForLog_preservesUtf16BoundariesAtLimit() { + val splitPairPrefix = "bad\n${"x".repeat(195)}" + assertEquals( + "bad ${"x".repeat(195)}", + NodePresenceAliveBeacon.sanitizeReasonForLog("$splitPairPrefix😀tail"), + ) + + val completePairPrefix = "bad\n${"x".repeat(194)}" + assertEquals( + "bad ${"x".repeat(194)}😀", + NodePresenceAliveBeacon.sanitizeReasonForLog("$completePairPrefix😀tail"), + ) + } }