From f52792125630e4417ae05fb07f36d371da323fe1 Mon Sep 17 00:00:00 2001 From: Bailey Dixon Date: Thu, 15 Oct 2026 15:18:50 -0400 Subject: [PATCH 1/2] fix(android): avoid duplicate Gateway history and pause OOM restore --- CHANGELOG.md | 1 + .../ChatMemoryRecoveryInstrumentedTest.kt | 88 +++++++++++ .../network/upstream/GatewayChatClient.kt | 9 +- .../relay/runtime/HermesProcessRuntime.kt | 3 + .../relay/ui/components/CrashReportDialog.kt | 5 +- .../hermesandroid/relay/util/CrashReporter.kt | 10 ++ .../relay/viewmodel/ChatViewModel.kt | 29 +++- app/src/main/res/values-b+pt+BR/strings.xml | 1 + app/src/main/res/values-b+zh+Hans/strings.xml | 1 + app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + .../network/upstream/GatewayChatClientTest.kt | 64 +++++++- .../upstream/GatewayPayloadMemoryTest.kt | 75 ++++++++++ .../reliability/ReliabilityReportTest.kt | 7 + .../relay/viewmodel/ChatMemoryRecoveryTest.kt | 140 ++++++++++++++++++ docs/android-memory-recovery.md | 95 ++++++++++++ docs/gateway-contract-testing.md | 5 + docs/localization-status.json | 12 +- docs/spec.md | 1 + scripts/android-prepush.py | 3 + scripts/check-gateway-scenario-conformance.py | 22 ++- ...check_gateway_scenario_conformance_test.py | 12 ++ .../vanilla-gateway/tests/test_fixture.py | 18 +++ .../scenarios/initial_history_bind.json | 3 +- .../vanilla-gateway/vanilla_gateway/server.py | 4 + 28 files changed, 598 insertions(+), 15 deletions(-) create mode 100644 app/src/androidTest/kotlin/com/hermesandroid/relay/viewmodel/ChatMemoryRecoveryInstrumentedTest.kt create mode 100644 app/src/test/kotlin/com/hermesandroid/relay/network/upstream/GatewayPayloadMemoryTest.kt create mode 100644 app/src/test/kotlin/com/hermesandroid/relay/viewmodel/ChatMemoryRecoveryTest.kt create mode 100644 docs/android-memory-recovery.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d8ae6ab..51e3eaef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/), and this ### Fixed +- Android avoids redundant conversation history in Gateway send/recovery responses and pauses automatic conversation reopening after a recorded out-of-memory crash, preserving saved conversations and drafts. (#700) - Android keeps ONNX Runtime on 1.27.0 so sherpa-onnx native libs match the packaged runtime ABI. - Secure Link supports paired-device listing, session shortening, and revocation with the existing Relay authorization rules. - Android pauses both wake-word listener modes before voice capture and resumes listening after voice exits, preventing microphone conflicts. (#681) diff --git a/app/src/androidTest/kotlin/com/hermesandroid/relay/viewmodel/ChatMemoryRecoveryInstrumentedTest.kt b/app/src/androidTest/kotlin/com/hermesandroid/relay/viewmodel/ChatMemoryRecoveryInstrumentedTest.kt new file mode 100644 index 00000000..470ac086 --- /dev/null +++ b/app/src/androidTest/kotlin/com/hermesandroid/relay/viewmodel/ChatMemoryRecoveryInstrumentedTest.kt @@ -0,0 +1,88 @@ +package com.hermesandroid.relay.viewmodel + +import android.graphics.Bitmap +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.getValue +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.captureToImage +import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule +import androidx.compose.ui.test.isDialog +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.test.platform.app.InstrumentationRegistry +import com.hermesandroid.relay.R +import com.hermesandroid.relay.data.AgentDisplay +import com.hermesandroid.relay.network.upstream.ChatHandler +import com.hermesandroid.relay.network.upstream.models.MessageItem +import com.hermesandroid.relay.reliability.ReliabilityCenter +import com.hermesandroid.relay.ui.components.CrashReportGate +import com.hermesandroid.relay.ui.theme.HermesRelayTheme +import com.hermesandroid.relay.util.CrashReporter +import java.io.File +import java.util.concurrent.atomic.AtomicInteger +import kotlinx.serialization.json.JsonPrimitive +import org.junit.Assert.assertEquals +import org.junit.Rule +import org.junit.Test + +class ChatMemoryRecoveryInstrumentedTest { + @get:Rule val compose = createAndroidComposeRule() + + @Test + fun memoryReportDismissalAndLifecycleDoNotReopenHistoryUntilSelection() { + val reads = AtomicInteger() + val owner = AgentDisplay.profileContextKey("fixture", null) + lateinit var vm: ChatViewModel + compose.runOnUiThread { + val context = compose.activity + ReliabilityCenter.recordFatal(context, OutOfMemoryError("synthetic bounded recovery test"), "fixture") + vm = ChatViewModel().also { + it.pauseAutomaticSessionRestoreAfterMemoryFailure( + CrashReporter.isMemoryFailure(CrashReporter.peekPending(context)), + ) + it.initialize(null, ChatHandler()) + it.streamingEndpoint = "gateway" + it.setProfileMessageLoader { + reads.incrementAndGet() + Result.success(listOf(MessageItem(id = "row", role = "assistant", content = JsonPrimitive("Saved answer")))) + } + it.switchProfileContext(owner, "stored-long") + } + } + compose.setContent { + val messages by vm.messages.collectAsStateWithLifecycle() + HermesRelayTheme(themePreference = "dark") { + Column { + Button(onClick = { vm.switchSession("stored-long") }) { Text("Open saved conversation") } + messages.forEach { Text(it.content) } + } + CrashReportGate() + } + } + val explanation = compose.activity.getString(R.string.crash_memory_recovery_body) + compose.onNodeWithText(explanation).assertIsDisplayed() + compose.onNode(isDialog()).captureToImage().asAndroidBitmap().let { bitmap -> + val output = InstrumentationRegistry.getArguments().getString("additionalTestOutputDir") + ?.let(::File) ?: compose.activity.cacheDir + output.mkdirs() + File(output, "memory-recovery-dialog.png").outputStream().use { + bitmap.compress(Bitmap.CompressFormat.PNG, 100, it) + } + } + compose.onNodeWithText(compose.activity.getString(R.string.common_dismiss)).performClick() + compose.activityRule.scenario.moveToState(Lifecycle.State.CREATED) + compose.activityRule.scenario.moveToState(Lifecycle.State.RESUMED) + compose.runOnUiThread { vm.reconcileProfileContext(owner, "stored-long") } + compose.waitForIdle() + assertEquals(0, reads.get()) + compose.onNodeWithText("Open saved conversation").performClick() + compose.waitUntil(5_000) { reads.get() == 1 } + compose.onNodeWithText("Saved answer").assertIsDisplayed() + } +} diff --git a/app/src/main/kotlin/com/hermesandroid/relay/network/upstream/GatewayChatClient.kt b/app/src/main/kotlin/com/hermesandroid/relay/network/upstream/GatewayChatClient.kt index 83485cca..2cb33c52 100644 --- a/app/src/main/kotlin/com/hermesandroid/relay/network/upstream/GatewayChatClient.kt +++ b/app/src/main/kotlin/com/hermesandroid/relay/network/upstream/GatewayChatClient.kt @@ -1430,6 +1430,7 @@ class GatewayChatClient( "session.activate", buildJsonObject { put("session_id", preferredLiveId) + put("omit_messages", true) }, ) response = activated.getOrNull() @@ -1457,6 +1458,7 @@ class GatewayChatClient( put("session_id", storedId) put("cols", DEFAULT_COLS) put("source", sessionSource) + put("omit_messages", true) requestedProfile?.let { put("profile", it) } }, ).getOrElse { error -> @@ -3645,6 +3647,8 @@ class GatewayChatClient( put("session_id", requestedStoredId) put("cols", DEFAULT_COLS) put("source", sessionSource) + // Visible history comes from Dashboard HTTP, not this binding response. + put("omit_messages", true) requestedProfile?.let { put("profile", it) } }, ) @@ -4544,7 +4548,10 @@ class GatewayChatClient( val socket = webSocket ?: throw GatewayRpcException("gateway socket changed") val activated = rpc( "session.activate", - buildJsonObject { put("session_id", preservedLiveId) }, + buildJsonObject { + put("session_id", preservedLiveId) + put("omit_messages", true) + }, expectedConnection = ready, expectedSocket = socket, ) diff --git a/app/src/main/kotlin/com/hermesandroid/relay/runtime/HermesProcessRuntime.kt b/app/src/main/kotlin/com/hermesandroid/relay/runtime/HermesProcessRuntime.kt index 37728f24..c507364e 100644 --- a/app/src/main/kotlin/com/hermesandroid/relay/runtime/HermesProcessRuntime.kt +++ b/app/src/main/kotlin/com/hermesandroid/relay/runtime/HermesProcessRuntime.kt @@ -78,6 +78,9 @@ class HermesProcessRuntime internal constructor( val chatViewModel: ChatViewModel by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { viewModelProvider[ChatViewModel::class.java].also { chat -> + chat.pauseAutomaticSessionRestoreAfterMemoryFailure( + com.hermesandroid.relay.util.CrashReporter.pauseAutomaticSessionRestore, + ) chat.installComposerDraftStore( PersistentChatComposerDraftStore( File(application.noBackupFilesDir, "chat-composer-drafts"), diff --git a/app/src/main/kotlin/com/hermesandroid/relay/ui/components/CrashReportDialog.kt b/app/src/main/kotlin/com/hermesandroid/relay/ui/components/CrashReportDialog.kt index c1c78b14..6293c52c 100644 --- a/app/src/main/kotlin/com/hermesandroid/relay/ui/components/CrashReportDialog.kt +++ b/app/src/main/kotlin/com/hermesandroid/relay/ui/components/CrashReportDialog.kt @@ -128,7 +128,10 @@ private fun CrashReportDialog(report: ReliabilityReport, onDismiss: () -> Unit) Spacer(Modifier.height(8.dp)) Text( - text = stringResource(R.string.crash_body), + text = stringResource( + if (CrashReporter.isMemoryFailure(report)) R.string.crash_memory_recovery_body + else R.string.crash_body, + ), style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) diff --git a/app/src/main/kotlin/com/hermesandroid/relay/util/CrashReporter.kt b/app/src/main/kotlin/com/hermesandroid/relay/util/CrashReporter.kt index 49875caa..61825da3 100644 --- a/app/src/main/kotlin/com/hermesandroid/relay/util/CrashReporter.kt +++ b/app/src/main/kotlin/com/hermesandroid/relay/util/CrashReporter.kt @@ -24,11 +24,18 @@ object CrashReporter { @Volatile private var installed = false + /** Latched before UI/report dismissal; acknowledging a report cannot reopen the failed chat. */ + var pauseAutomaticSessionRestore: Boolean = false + private set + fun install(context: Context) { ReliabilityCenter.initialize(context) migrateLegacy(context) if (installed) return installed = true + pauseAutomaticSessionRestore = runCatching { + isMemoryFailure(ReliabilityCenter.pendingCrash(context)) + }.getOrDefault(false) val appContext = context.applicationContext val previous = Thread.getDefaultUncaughtExceptionHandler() Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> @@ -52,6 +59,9 @@ object CrashReporter { return ReliabilityCenter.pendingCrash(context) } + fun isMemoryFailure(report: ReliabilityReport?): Boolean = + report?.summary?.let { it == "OutOfMemoryError" || it.startsWith("OutOfMemoryError:") } == true + fun clearPending(context: Context, reportId: String? = null) { val target = reportId ?: peekPending(context)?.reportId ?: return ReliabilityCenter.markReviewed(context, target) diff --git a/app/src/main/kotlin/com/hermesandroid/relay/viewmodel/ChatViewModel.kt b/app/src/main/kotlin/com/hermesandroid/relay/viewmodel/ChatViewModel.kt index 48d54cef..d78aef04 100644 --- a/app/src/main/kotlin/com/hermesandroid/relay/viewmodel/ChatViewModel.kt +++ b/app/src/main/kotlin/com/hermesandroid/relay/viewmodel/ChatViewModel.kt @@ -722,6 +722,12 @@ class ChatViewModel : ViewModel() { ) private var deferredGatewayPrewarm: DeferredGatewayPrewarm? = null private var automaticGatewayPrewarmBlocked = false + private var memoryRecoverySelectionRequired = false + + /** Startup-only latch. Keep saved session slots, drafts and checkpoints until explicit selection. */ + fun pauseAutomaticSessionRestoreAfterMemoryFailure(paused: Boolean) { + memoryRecoverySelectionRequired = paused + } private var imageActivityJob: Job? = null private val historyLoadGeneration = AtomicInteger(0) private val sessionRefreshGeneration = AtomicInteger(0) @@ -1769,6 +1775,7 @@ class ChatViewModel : ViewModel() { return@launch } if (startingSessionId == null) { + memoryRecoverySelectionRequired = false handler.setSessionId(preparedSessionId) selectBackgroundProcessSession(preparedSessionId) gatewayProcessController.sessionReady(preparedSessionId) @@ -5055,6 +5062,7 @@ class ChatViewModel : ViewModel() { sessionId: String, ): Boolean { if (!selectConversationProfile(profileName, profile)) return false + memoryRecoverySelectionRequired = false if (activeProfileContextKey == contextKey && chatHandler?.currentSessionId?.value == sessionId && currentSessionProfileName() == profileName ) { @@ -5091,6 +5099,7 @@ class ChatViewModel : ViewModel() { contextKey: String, ): Boolean { if (!selectConversationProfile(profileName, profile)) return false + memoryRecoverySelectionRequired = false // A provisional phone Thread belongs to its original connection/chat_id // and cannot transfer to another profile. Exit it before binding or // persisting the destination draft so the next send uses session.create. @@ -5147,7 +5156,7 @@ class ChatViewModel : ViewModel() { fun switchProfileContext(contextKey: String, sessionId: String?) { clearOpenedSessionOwner() - switchProfileContextInternal(contextKey, sessionId) + switchProfileContextInternal(contextKey, sessionId.takeUnless { memoryRecoverySelectionRequired }) } /** @@ -5159,7 +5168,9 @@ class ChatViewModel : ViewModel() { * profile/session tuple, the reducer converges it to ordinary global state. */ fun reconcileProfileContext(contextKey: String, sessionId: String?) { - switchProfileContextInternal(contextKey, sessionId, reconciliation = true) + switchProfileContextInternal( + contextKey, sessionId.takeUnless { memoryRecoverySelectionRequired }, reconciliation = true, + ) } private fun switchProfileContextInternal( @@ -5622,6 +5633,9 @@ class ChatViewModel : ViewModel() { } private fun automaticGatewayWorkDeferred(sessionId: String?): Boolean { + // The empty recovery view still needs socket readiness. Only the saved + // session must stay detached until an explicit selection. + if (memoryRecoverySelectionRequired && sessionId != null) return true // The directory barrier exists only when this connection exposes a // Dashboard-backed, profile-scoped session directory. Legacy/test // Gateway bindings without that surface must retain socket readiness @@ -5977,6 +5991,8 @@ class ChatViewModel : ViewModel() { if (supervisedModePolicy.enabled && !supervisedModePolicy.capabilities.newChat) return val handler = chatHandler ?: return recordPreResetEvidence(handler, "new_chat") + if (memoryRecoverySelectionRequired) persistFreshDraft(currentSessionProfileName()) + memoryRecoverySelectionRequired = false // A new chat clears only the durable session identity. Keep the bound // profile/context so an All Profiles conversation becomes a fresh // draft for that same owner instead of falling back to the globally @@ -6229,6 +6245,7 @@ class ChatViewModel : ViewModel() { val handler = chatHandler ?: return dismissChatFailure() if (streamingEndpoint != "gateway" && apiClient == null) return + memoryRecoverySelectionRequired = false exitProvisionalThread() // Keep a Gateway sibling alive and detach its callbacks. SSE remains a @@ -8213,6 +8230,7 @@ class ChatViewModel : ViewModel() { handler: ChatHandler, sessionId: String, ): Boolean { + if (memoryRecoverySelectionRequired) return false val contextKey = activeProfileContextKey ?: return false val key = TurnCheckpointKey(contextKey, sessionId) val checkpoint = backgroundTurnCheckpoints.remove(key) @@ -9104,6 +9122,11 @@ class ChatViewModel : ViewModel() { isolateComposer: Boolean = false, ) { AppAnalytics.onMessageSent() + // Sending from the recovery view explicitly chooses a fresh draft. Clear + // only its remembered selection, or a later readiness emission could + // reopen the old conversation while the new session is being created. + if (memoryRecoverySelectionRequired) persistFreshDraft(currentSessionProfileName()) + memoryRecoverySelectionRequired = false val displayText = text.trim() val outboundText = transportText.trim() val interfaceContextPrompt = if (isolateComposer) { @@ -9292,6 +9315,8 @@ class ChatViewModel : ViewModel() { fun startRealtimeAgentTurn(userText: String, chatSessionId: String?): String { val handler = chatHandler ?: return UUID.randomUUID().toString() + if (memoryRecoverySelectionRequired && chatSessionId == null) persistFreshDraft(currentSessionProfileName()) + memoryRecoverySelectionRequired = false AppAnalytics.onMessageSent() val trimmed = userText.trim().ifBlank { "Listening..." } val userMessageId = UUID.randomUUID().toString() diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index de743d01..850ad1cc 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -4475,4 +4475,5 @@ Não foi possível reduzir a duração da sessão. Atualize as sessões do Relay e tente uma duração menor, ou pareie novamente com um novo código aprovado pelo operador para renovar o acesso. Atualizando sessão… Não há uma duração predefinida mais curta. Você pode revogar esta sessão ou parear novamente para renovar o acesso. + O aplicativo ficou sem memória. A reabertura automática de conversas está pausada nesta inicialização. Feche este relatório e escolha uma conversa em Sessões ou inicie um novo chat. As conversas e os rascunhos salvos não foram excluídos. O Hermes pode continuar trabalhando. diff --git a/app/src/main/res/values-b+zh+Hans/strings.xml b/app/src/main/res/values-b+zh+Hans/strings.xml index 6ecfba1e..a3434e40 100644 --- a/app/src/main/res/values-b+zh+Hans/strings.xml +++ b/app/src/main/res/values-b+zh+Hans/strings.xml @@ -4556,4 +4556,5 @@ 无法缩短会话有效期。请刷新 Relay 会话并尝试更短的时长,或使用经操作员批准的新配对码重新配对以续期访问权限。 正在更新会话… 没有更短的预设时长。你可以撤销此会话,或重新配对以续期访问权限。 + 应用内存不足。本次启动已暂停自动重新打开对话。关闭此报告后,请从“会话”中选择一个对话或开始新聊天。已保存的对话和草稿未被删除。Hermes 上的任务可能仍在运行。 diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2835e136..7d0b20ef 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -4632,4 +4632,5 @@ Die Sitzung konnte nicht verkürzt werden. Aktualisiere die Relay-Sitzungen und versuche eine kürzere Dauer, oder kopple erneut mit einem neuen, vom Betreiber genehmigten Code, um den Zugriff zu erneuern. Sitzung wird aktualisiert… Keine kürzere Vorgabe verfügbar. Du kannst diese Sitzung widerrufen oder zur Erneuerung erneut koppeln. + Der App ist der Arbeitsspeicher ausgegangen. Das automatische Wiederöffnen von Unterhaltungen ist für diesen Start pausiert. Schließe diesen Bericht und wähle dann unter Sitzungen eine Unterhaltung aus oder starte einen neuen Chat. Gespeicherte Unterhaltungen und Entwürfe wurden nicht gelöscht. Aufgaben auf Hermes können noch laufen. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 63d5e61a..8d122c66 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -4323,4 +4323,5 @@ No se pudo acortar la sesión. Actualiza las sesiones de Relay y prueba una duración menor, o vuelve a emparejar con un nuevo código aprobado por el operador para renovar el acceso. Actualizando sesión… No queda ninguna duración predefinida más corta. Puedes revocar esta sesión o volver a emparejar para renovar el acceso. + La aplicación se quedó sin memoria. La reapertura automática de conversaciones está pausada durante este inicio. Cierra este informe y elige una conversación en Sesiones o inicia un chat nuevo. No se han eliminado las conversaciones ni los borradores guardados. Hermes podría seguir trabajando. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 6911a14c..20496564 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -4627,4 +4627,5 @@ セッションの期間を短縮できませんでした。Relay セッションを更新してより短い期間を試すか、オペレーターが承認した新しいコードで再度ペアリングしてアクセスを更新してください。 セッションを更新中… これより短い選択肢はありません。このセッションを取り消すか、再度ペアリングしてアクセスを更新できます。 + アプリのメモリが不足しました。今回は会話を自動で開き直しません。このレポートを閉じてから、セッションで会話を選ぶか、新しいチャットを開始してください。保存済みの会話と下書きは削除されていません。Hermes では処理が続いている可能性があります。 diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6d3dfd76..f67edb7c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -4371,4 +4371,5 @@ Не удалось сократить срок сеанса. Обновите список сеансов Relay и выберите меньший срок или выполните сопряжение заново с новым кодом, одобренным оператором, чтобы обновить доступ. Обновление сеанса… Более коротких готовых вариантов нет. Можно отозвать этот сеанс или выполнить сопряжение заново для обновления доступа. + Приложению не хватило памяти. Автоматическое открытие последнего разговора приостановлено на этот запуск. Закройте этот отчёт, затем выберите разговор в разделе «Сессии» или начните новый чат. Сохранённые разговоры и черновики не удалены. Задачи в Hermes могут ещё выполняться. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b0851fc4..4a4476ec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ + The app ran out of memory. Automatic conversation reopening is paused for this launch. Dismiss this report, then choose a conversation from Sessions or start a new chat. Saved conversations and drafts have not been deleted. Work on Hermes may still be running. Question %1$d of %2$d All questions answered This request has ended diff --git a/app/src/test/kotlin/com/hermesandroid/relay/network/upstream/GatewayChatClientTest.kt b/app/src/test/kotlin/com/hermesandroid/relay/network/upstream/GatewayChatClientTest.kt index 415493c7..45a9b0a0 100644 --- a/app/src/test/kotlin/com/hermesandroid/relay/network/upstream/GatewayChatClientTest.kt +++ b/app/src/test/kotlin/com/hermesandroid/relay/network/upstream/GatewayChatClientTest.kt @@ -63,6 +63,7 @@ class GatewayClientHarness( private val allServerSockets = ConcurrentLinkedQueue() val rpcLog = ConcurrentLinkedQueue>() val serverResponses = LinkedBlockingQueue() + val responseSizes = ConcurrentHashMap() var failTicketMint = false var malformedTicketMint = false val transientTicketFailures = AtomicInteger(0) @@ -593,7 +594,16 @@ class GatewayClientHarness( } } } - webSocket.send(reply.toString()) + // Upstream omits display history, but retains live state and metadata. + val wireReply = if (method in setOf("session.resume", "session.activate") && + (params["omit_messages"] as? JsonPrimitive)?.booleanOrNull == true && result != null + ) { + JsonObject(reply + ("result" to JsonObject(result + + ("messages" to JsonArray(emptyList()))))) + } else reply + val wire = wireReply.toString() + responseSizes[method] = wire.length + webSocket.send(wire) } } @@ -2371,6 +2381,54 @@ class GatewayChatClientTest { assertEquals(listOf("yours"), r.textDeltas.toList()) } + @Test + fun `long history send omits unused rows before websocket decoding`() { + installLargeStoredHistory() + val r = Recorder() + client.sendTurn("stored-long", "continue", null, r.callbacks) { + r.preflightFailures += it + } + harness.awaitRpc("prompt.submit") + assertEquals(true, (harness.awaitRpc("session.resume")["omit_messages"] as? JsonPrimitive)?.booleanOrNull) + assertTrue("resume transferred history: ${harness.responseSizes}", harness.responseSizes.getValue("session.resume") < 2048) + assertEquals(48, harness.recoveryMessages.size) + assertTrue(r.preflightFailures.isEmpty()) + assertFalse(harness.rpcLog.any { it.first == "session.create" }) + } + + @Test + fun `long history recovery omits unused rows but preserves inflight state`() = runBlocking { + installLargeStoredHistory() + harness.recoveryRunning = true + harness.recoveryAssistant = "partial answer" + val recovery = client.recoverTurn("stored-long", "live-original", Recorder().callbacks).getOrThrow() + assertEquals("partial answer", recovery.inflight?.assistant) + assertTrue(recovery.running) + assertEquals(true, (harness.awaitRpc("session.activate")["omit_messages"] as? JsonPrimitive)?.booleanOrNull) + assertTrue(harness.responseSizes.getValue("session.activate") < 2048) + assertEquals(48, harness.recoveryMessages.size) + } + + @Test + fun `long history fallback resume omits unused rows`() = runBlocking { + installLargeStoredHistory() + harness.methodNotFound += "session.activate" + harness.recoveryRunning = true + val recovery = client.recoverTurn("stored-long", "expired-live", Recorder().callbacks).getOrThrow() + assertTrue(recovery.running) + assertEquals(true, (harness.awaitRpc("session.resume")["omit_messages"] as? JsonPrimitive)?.booleanOrNull) + assertTrue(harness.responseSizes.getValue("session.resume") < 2048) + assertEquals(48, harness.recoveryMessages.size) + } + + private fun installLargeStoredHistory() { + // Bounded synthetic tool history, retained independently of live state. + val text = "tool output \"quoted\"\n".repeat(4096) + harness.recoveryMessages = JsonArray(List(48) { + buildJsonObject { put("role", "tool"); put("text", text) } + }) + } + @Test fun `existing session id is resumed not recreated`() { val r = Recorder() @@ -2654,6 +2712,7 @@ class GatewayChatClientTest { @Test fun `socket loss mid-turn activates original session without resume and completes`() { + installLargeStoredHistory() val r = Recorder() client.sendTurn(null, "hello", null, r.callbacks) { r.preflightFailures += it } val ws1 = harness.awaitServerSocket() @@ -2670,6 +2729,7 @@ class GatewayChatClientTest { assertTrue("reconnect must mint a fresh ticket", harness.ticketMints.get() >= 2) val activate = harness.awaitRpc("session.activate") assertEquals("live-1", (activate["session_id"] as? JsonPrimitive)?.contentOrNull) + assertEquals(true, (activate["omit_messages"] as? JsonPrimitive)?.booleanOrNull) ws2.send( harness.eventFrame("message.delta", buildJsonObject { put("text", "after rejoin") }, "live-1"), @@ -2679,6 +2739,8 @@ class GatewayChatClientTest { ) assertTrue("turn never completed after rejoin", r.completeLatch.await(10, TimeUnit.SECONDS)) + awaitCondition { harness.responseSizes.containsKey("session.activate") } + assertTrue(harness.responseSizes.getValue("session.activate") < 2048) assertEquals(listOf("after rejoin"), r.textDeltas.toList()) assertEquals(1, r.reconcileRequests.get()) assertTrue("rejoined turn must not error, got ${r.errors}", r.errors.isEmpty()) diff --git a/app/src/test/kotlin/com/hermesandroid/relay/network/upstream/GatewayPayloadMemoryTest.kt b/app/src/test/kotlin/com/hermesandroid/relay/network/upstream/GatewayPayloadMemoryTest.kt new file mode 100644 index 00000000..0288d997 --- /dev/null +++ b/app/src/test/kotlin/com/hermesandroid/relay/network/upstream/GatewayPayloadMemoryTest.kt @@ -0,0 +1,75 @@ +package com.hermesandroid.relay.network.upstream + +import java.io.File +import java.nio.file.Files +import java.util.concurrent.TimeUnit +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +/** Controlled decoder pressure, separate from Android rendering and the transport harness. */ +class GatewayPayloadMemoryTest { + @Test + fun omittedHistoryAvoidsDecoderExhaustionUnderBoundedHeap() { + val directory = Files.createTempDirectory("gateway-memory").toFile() + try { + val full = File(directory, "full.json") + full.bufferedWriter().use { writer -> + writer.write("{\"id\":1,\"result\":{\"session_id\":\"live\",\"messages\":[{\"text\":\"") + // 32 Mi characters, with escapes exercising the release stack's string builder. + val block = "a\\n".repeat(1024) + repeat(10923) { writer.write(block) } + writer.write("\"}],\"running\":false}}") + } + val omitted = File(directory, "omitted.json").apply { + writeText("""{"id":1,"result":{"session_id":"live","messages":[],"running":false}}""") + } + assertEquals("full history should reproduce decoder OOM", 42, decodeInBoundedProcess(full)) + assertEquals("metadata-only response should decode repeatedly", 0, decodeInBoundedProcess(omitted)) + } finally { + directory.listFiles()?.forEach { it.delete() } + directory.delete() + } + } + + private fun decodeInBoundedProcess(payload: File): Int { + val classpath = listOf( + GatewayPayloadMemoryProbe::class.java, Json::class.java, + KSerializer::class.java, Unit::class.java, + ).map { File(requireNotNull(it.protectionDomain).codeSource.location.toURI()).absolutePath } + .distinct().joinToString(File.pathSeparator) + val java = File(System.getProperty("java.home"), "bin/java").absolutePath + val log = File(payload.parentFile, "${payload.name}.log") + val process = ProcessBuilder( + java, "-Xmx128m", "-XX:-CompactStrings", "-cp", classpath, + GatewayPayloadMemoryProbe::class.java.name, payload.absolutePath, + ).redirectErrorStream(true).redirectOutput(log).start() + try { + assertTrue("memory probe timed out", process.waitFor(30, TimeUnit.SECONDS)) + assertTrue("probe failed before decoding: ${log.readText().take(4000)}", process.exitValue() in setOf(0, 42)) + return process.exitValue() + } finally { + if (process.isAlive) process.destroyForcibly() + } + } +} + +object GatewayPayloadMemoryProbe { + private lateinit var retained: ByteArray + + @JvmStatic + fun main(args: Array) { + // Fixed retained state models an already-open transcript. No Compose or network is present. + retained = ByteArray(16 * 1024 * 1024) + val text = Files.readString(File(args.single()).toPath()) + try { + repeat(20) { Json.parseToJsonElement(text) } + check(retained.size == 16 * 1024 * 1024) + } catch (_: OutOfMemoryError) { + // Only decoding is caught: a failure reading the frame is a different test result. + kotlin.system.exitProcess(42) + } + } +} diff --git a/app/src/test/kotlin/com/hermesandroid/relay/reliability/ReliabilityReportTest.kt b/app/src/test/kotlin/com/hermesandroid/relay/reliability/ReliabilityReportTest.kt index b648e30e..235b7a2b 100644 --- a/app/src/test/kotlin/com/hermesandroid/relay/reliability/ReliabilityReportTest.kt +++ b/app/src/test/kotlin/com/hermesandroid/relay/reliability/ReliabilityReportTest.kt @@ -12,6 +12,13 @@ import org.junit.Assert.assertTrue import org.junit.Test class ReliabilityReportTest { + @Test + fun memoryRecoveryDoesNotTreatOtherCrashesAsMemoryFailures() { + val base = report("memory", "2026-10-15T00:00:00Z") + assertTrue(CrashReporter.isMemoryFailure(base.copy(summary = "OutOfMemoryError: allocation failed"))) + assertFalse(CrashReporter.isMemoryFailure(base)) + assertFalse(CrashReporter.isMemoryFailure(null)) + } private val environment = ReliabilityEnvironment( versionName = "1.6.1", versionCode = 38, diff --git a/app/src/test/kotlin/com/hermesandroid/relay/viewmodel/ChatMemoryRecoveryTest.kt b/app/src/test/kotlin/com/hermesandroid/relay/viewmodel/ChatMemoryRecoveryTest.kt new file mode 100644 index 00000000..a0eaa86a --- /dev/null +++ b/app/src/test/kotlin/com/hermesandroid/relay/viewmodel/ChatMemoryRecoveryTest.kt @@ -0,0 +1,140 @@ +package com.hermesandroid.relay.viewmodel + +import android.os.Looper +import com.hermesandroid.relay.data.AgentDisplay +import com.hermesandroid.relay.data.ChatComposerDraft +import com.hermesandroid.relay.data.ChatComposerDraftKey +import com.hermesandroid.relay.data.ChatTurnCheckpoint +import com.hermesandroid.relay.data.ChatTurnCheckpointStore +import com.hermesandroid.relay.data.InMemoryChatComposerDraftStore +import com.hermesandroid.relay.network.upstream.ChatHandler +import com.hermesandroid.relay.network.upstream.models.MessageItem +import com.hermesandroid.relay.network.upstream.models.SessionItem +import kotlinx.coroutines.runBlocking +import kotlinx.serialization.json.JsonPrimitive +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.Shadows.shadowOf +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34]) +class ChatMemoryRecoveryTest { + @Test + fun startupPauseKeepsDirectoryDraftAndCheckpointUntilExplicitSelection() { + val handler = ChatHandler().also { it.setSessionId("long-session") } + val owner = AgentDisplay.profileContextKey("connection", null) + val draftKey = ChatComposerDraftKey("connection", "default", "long-session") + val drafts = InMemoryChatComposerDraftStore() + runBlocking { drafts.save(draftKey, ChatComposerDraft("unsent draft")) } + val historyReads = mutableListOf() + val persistedSelections = mutableListOf() + var checkpointReads = 0 + var checkpointClears = 0 + var checkpointWrites = 0 + val vm = ChatViewModel().also { + it.pauseAutomaticSessionRestoreAfterMemoryFailure(true) + it.installComposerDraftStore(drafts) + it.initialize(null, handler) + it.streamingEndpoint = "gateway" + it.setProfileMessageLoader { id -> + historyReads += id + Result.success(listOf(MessageItem(id = "row", role = "assistant", content = JsonPrimitive("Saved answer")))) + } + it.setProfileSessionLister { Result.success(listOf(SessionItem(id = "long-session"))) } + it.onSessionChanged = { id -> persistedSelections += id } + it.setChatTurnCheckpointStore(object : ChatTurnCheckpointStore { + override suspend fun read(): ChatTurnCheckpoint? { checkpointReads++; return null } + override suspend fun write(checkpoint: ChatTurnCheckpoint) { checkpointWrites++ } + override suspend fun clear() { checkpointClears++ } + }) + } + vm.switchProfileContext(owner, "long-session") + vm.reconcileProfileContext(owner, "long-session") + vm.refreshSessions() + await { handler.sessions.value.isNotEmpty() } + assertNull(handler.currentSessionId.value) + assertTrue(vm.initialChatSettled.value) + assertTrue(historyReads.isEmpty()) + assertTrue(persistedSelections.isEmpty()) + assertEquals(0, checkpointReads) + assertEquals(0, checkpointClears) + assertEquals(0, checkpointWrites) + assertEquals("unsent draft", runBlocking { drafts.snapshot(draftKey).text }) + + vm.switchSession("long-session") + await { handler.messages.value.isNotEmpty() } + assertEquals(listOf("long-session"), historyReads) + assertEquals("long-session", handler.currentSessionId.value) + assertEquals("unsent draft", runBlocking { drafts.snapshot(draftKey).text }) + vm.reconcileProfileContext(owner, "long-session") + assertEquals("long-session", handler.currentSessionId.value) + assertEquals(0, checkpointClears) + } + + @Test + fun explicitNewChatRetiresOnlyTheRememberedSelection() { + val handler = ChatHandler() + val selections = mutableListOf() + val owner = AgentDisplay.profileContextKey("connection", null) + val vm = ChatViewModel().also { + it.pauseAutomaticSessionRestoreAfterMemoryFailure(true) + it.initialize(null, handler) + it.streamingEndpoint = "gateway" + it.onSessionChanged = { id -> selections += id } + } + vm.switchProfileContext(owner, "long-session") + assertTrue(selections.isEmpty()) + vm.createNewChat() + assertEquals(listOf(null), selections) + vm.reconcileProfileContext(owner, null) + assertNull(handler.currentSessionId.value) + } + + @Test + fun normalStartupStillLoadsItsRememberedConversation() { + val handler = ChatHandler() + val reads = mutableListOf() + val vm = ChatViewModel().also { + it.initialize(null, handler) + it.streamingEndpoint = "gateway" + it.setProfileMessageLoader { id -> + reads += id + Result.success(emptyList()) + } + } + vm.switchProfileContext(AgentDisplay.profileContextKey("connection", null), "saved-session") + await { reads.isNotEmpty() } + assertEquals(listOf("saved-session"), reads) + assertEquals("saved-session", handler.currentSessionId.value) + } + + @Test + fun explicitVoiceTurnIsNotReplacedByAutomaticReconciliation() { + val handler = ChatHandler() + val owner = AgentDisplay.profileContextKey("connection", null) + val vm = ChatViewModel().also { + it.pauseAutomaticSessionRestoreAfterMemoryFailure(true) + it.initialize(null, handler) + it.streamingEndpoint = "gateway" + } + vm.switchProfileContext(owner, "long-session") + vm.startRealtimeAgentTurn("spoken prompt", "voice-session") + vm.reconcileProfileContext(owner, "voice-session") + assertEquals("voice-session", handler.currentSessionId.value) + assertTrue(handler.messages.value.any { it.content == "spoken prompt" }) + } + + private fun await(condition: () -> Boolean) { + val deadline = System.nanoTime() + 5_000_000_000L + while (!condition() && System.nanoTime() < deadline) { + shadowOf(Looper.getMainLooper()).idle() + Thread.sleep(10) + } + assertTrue(condition()) + } +} diff --git a/docs/android-memory-recovery.md b/docs/android-memory-recovery.md new file mode 100644 index 00000000..d1e6b146 --- /dev/null +++ b/docs/android-memory-recovery.md @@ -0,0 +1,95 @@ +# Android conversation memory recovery + +## Release evidence + +Issue [#700](https://github.com/Codename-11/hermes-relay/issues/700) reports +Google Play 1.18.1 (version code 59) exhausting its 256 MiB heap while sending +in a long conversation, then crashing repeatedly when that conversation reopens. +The failed 576-byte allocation is the final failed allocation, not a frame size. + +The immutable `android-v1.18.1` tag resolves to +`ca941b6ac999f74fff72e3363eb852cf5cbecb12`. The published Play AAB has SHA-256 +`df89e47194a0fce71d2e6328d948a8e14ee4eb5489703c692920f252d759251c`. +Its embedded `BUNDLE-METADATA/com.android.tools.build.obfuscation/proguard.map` +has SHA-256 `ff6825451d86d7f83f25a172ed16bcefd0e0934f601df6d8e44af851ec6cd912`. +That exact mapping resolves `ua3.onMessage(SourceFile:51)` to +`GatewayChatClient.handleFrame` at release source line 3920, inlined through the +listener at line 3798. The failing expression is `json.parseToJsonElement`. + +This identifies the Gateway listener, but not the RPC/event or frame contents. +It does not establish whether one oversized frame, retained history, decoding +copies, rendering pressure, or a combination exhausted the reporter's heap. +Markdown issue #465 and attachment issue #531 remain separate failures. + +The text frame had already reached the listener. Parsing can retain that text +alongside decoded strings, escape-decoding buffers and the JSON tree. The +visible transcript and rendering allocations share the heap, but this stack +does not measure their contribution or demonstrate a retained-state leak. + +## Binding without duplicate display history + +Android's visible Gateway transcript is loaded by the profile-scoped Dashboard +HTTP history reader. Gateway resume and activation still supply live identity, +model/profile information, in-flight output, queued work and open requests. +Their `messages` display-history array is unused by foreground send and recovery. + +The prewarm path already requested `omit_messages`; cold send, checkpoint resume, +checkpoint activation and mid-turn activation now request it as well. This +avoids serializing, buffering and decoding the redundant history array on +supporting upstream versions. It does not limit or delete HTTP history and does +not omit in-flight output. Child-watch resume still requests its history because +that consumer uses the returned rows. Existing prewarm `defer_history` behavior +is unchanged; send/recovery do not newly defer server initialization. + +The upstream contract was inspected at +`NousResearch/hermes-agent@f9bdaa303e1614245cf1104db52ed0efd90b6f13`: +`tui_gateway/methods_session.py` reads `omit_messages` for resume and activation; +`_Resume.messages` and `tui_gateway/session_registry.py::_live_session_payload` +skip display projection when requested. The latter retains live state and open +requests. Older servers may ignore this additive option and retain the larger +response. The client still buffers complete WebSocket frames and parses JSON +trees; this change is not a universal incoming-frame memory bound. + +PR [#709](https://github.com/Codename-11/hermes-relay/pull/709), reviewed at +`86b5a27eaa50822ff63aa8e8beb205905fcd4364`, changes HTTP history pagination to +include compacted rows. It can increase retained visible history, but does not +change this WebSocket decoder. Its history change is not incorporated here. + +## Recovery after process death + +At application startup, a pending locally recorded `OutOfMemoryError` latches +a pause before the process chat runtime binds. Automatic profile/session +reconciliation opens an empty view and leaves the remembered session slot, +composer drafts and turn checkpoints intact. The session directory remains +available. Socket-only warmup can establish readiness, but automatic session +attachment/checkpoint recovery stays paused. + +The existing crash dialog explains how to dismiss the report and choose a +conversation or start a new chat. Dismissing the report does not lift the pause +in that process; explicit conversation selection or a new send does. Retrying +the same oversized conversation on an older server may still fail. Capture must +have succeeded before process termination for the recorded-crash gate to apply. + +## Verification lanes + +- `GatewayChatClientTest` uses real loopback HTTP/WebSocket transport with + bounded synthetic tool history. Send, activation, fallback resume and socket + rejoin must omit unused rows while preserving session identity and live state. +- `GatewayPayloadMemoryTest` runs the production JSON library in a separate + 128 MiB JVM with UTF-16 strings, a fixed 16 MiB retained allocation and a + synthetic 32 Mi-character escaped frame. Reading and decoding are separate + phases. It distinguishes decoder OOM from input-buffer failure, then repeats + metadata-only decoding. This is controlled pressure, not Pixel/ART profiling + or a measurement of the reporter's conversation. +- `ChatMemoryRecoveryTest` checks directory access, deferred history/checkpoint + loading, saved selection and draft preservation, and explicit reopening. +- `ChatMemoryRecoveryInstrumentedTest` exercises the production crash dialog, + dismissal, lifecycle return and explicit history loading on Android. +- The shared `initial_history_bind` scenario checks metadata-only resume and + activation while retaining full HTTP history. Its manifest is checked against + current upstream. The source checker follows called local resume helpers so + upstream extraction does not masquerade as contract removal. + +No private conversation data or production payload is required by these tests. +Actual Pixel 8 Pro / Android 17 behavior and the reporter's exact trigger require +separate device evidence; a synthetic test cannot certify them. diff --git a/docs/gateway-contract-testing.md b/docs/gateway-contract-testing.md index 06656a00..8b3df15b 100644 --- a/docs/gateway-contract-testing.md +++ b/docs/gateway-contract-testing.md @@ -27,6 +27,11 @@ hermetic tests. ## Session directory boundary +For duplicate resume-history memory pressure and post-OOM startup recovery, see +[Android conversation memory recovery](android-memory-recovery.md). The +`initial_history_bind` scenario also proves that `omit_messages` on resume and +activation leaves HTTP history available. + The Gateway fixture proves live chat transport, activity, reconnect, and authoritative post-turn history reconciliation. It is not the route authority for the session drawer. On the standard path, profile-scoped session browsing diff --git a/docs/localization-status.json b/docs/localization-status.json index 36504caf..7122a236 100644 --- a/docs/localization-status.json +++ b/docs/localization-status.json @@ -13,7 +13,7 @@ "verification": "ai-translated", "review_refs": [], "source_sha256": { - "main": "bccd06f1a0b9afc5b09a7ddb287d9f5ac7d37861c45075bc63e80c03a76f6de1", + "main": "330f082596881b2d74fcba1ca1fe45773b42c8b1670d5f140f0f0889979ed28c", "sideload": "4abff4f1069091ec2de735c3037a7ec7d77699cb4321e8511a622437bceaf7c2" }, "surfaces": { @@ -48,7 +48,7 @@ "verification": "ai-translated", "review_refs": [], "source_sha256": { - "main": "bccd06f1a0b9afc5b09a7ddb287d9f5ac7d37861c45075bc63e80c03a76f6de1", + "main": "330f082596881b2d74fcba1ca1fe45773b42c8b1670d5f140f0f0889979ed28c", "sideload": "4abff4f1069091ec2de735c3037a7ec7d77699cb4321e8511a622437bceaf7c2" }, "surfaces": { @@ -72,7 +72,7 @@ "verification": "ai-translated", "review_refs": [], "source_sha256": { - "main": "bccd06f1a0b9afc5b09a7ddb287d9f5ac7d37861c45075bc63e80c03a76f6de1", + "main": "330f082596881b2d74fcba1ca1fe45773b42c8b1670d5f140f0f0889979ed28c", "sideload": "4abff4f1069091ec2de735c3037a7ec7d77699cb4321e8511a622437bceaf7c2" }, "surfaces": { @@ -96,7 +96,7 @@ "verification": "ai-translated", "review_refs": [], "source_sha256": { - "main": "bccd06f1a0b9afc5b09a7ddb287d9f5ac7d37861c45075bc63e80c03a76f6de1", + "main": "330f082596881b2d74fcba1ca1fe45773b42c8b1670d5f140f0f0889979ed28c", "sideload": "4abff4f1069091ec2de735c3037a7ec7d77699cb4321e8511a622437bceaf7c2" }, "surfaces": { @@ -120,7 +120,7 @@ "verification": "ai-translated", "review_refs": [], "source_sha256": { - "main": "bccd06f1a0b9afc5b09a7ddb287d9f5ac7d37861c45075bc63e80c03a76f6de1", + "main": "330f082596881b2d74fcba1ca1fe45773b42c8b1670d5f140f0f0889979ed28c", "sideload": "4abff4f1069091ec2de735c3037a7ec7d77699cb4321e8511a622437bceaf7c2" }, "surfaces": { @@ -135,7 +135,7 @@ "verification": "ai-translated", "review_refs": [], "source_sha256": { - "main": "bccd06f1a0b9afc5b09a7ddb287d9f5ac7d37861c45075bc63e80c03a76f6de1", + "main": "330f082596881b2d74fcba1ca1fe45773b42c8b1670d5f140f0f0889979ed28c", "sideload": "4abff4f1069091ec2de735c3037a7ec7d77699cb4321e8511a622437bceaf7c2" }, "surfaces": { diff --git a/docs/spec.md b/docs/spec.md index f3300c2a..99410404 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -745,6 +745,7 @@ Bottom navigation bar with 4 tabs: - **Model-selection consent** — Every Gateway model transition, including Server default, uses upstream `config.set` as a preflight. A fresh draft first creates a profile-bound default session without raw `model`/`provider` fields, preventing `session.create` from bypassing the guard and preventing sessionless global writes. A named-profile draft is usable only when the create result confirms the exact `info.profile_name`; missing or different ownership fails closed before `config.set`. When Hermes returns `confirm_required`, Android restores the previous selection, displays the exact server `confirm_message` (including cost and data-training policy warnings), and applies the model only through a second request carrying `confirm_expensive_model: true`. A profile or session change invalidates the pending confirmation. Older hosts that do not return the fields keep the existing one-step selection behavior. - **Host resource pressure** — Android reads the optional upstream Dashboard `/api/status.memory` and `.disk` blocks during the separate post-selection status refresh, never the lightweight route-readiness probe. `elevated` and `critical` values, plus a server-reported suspected out-of-memory restart, produce a persistent in-app warning about chat continuity and save risk. Android uses the upstream classifications as-is, adds no client thresholds or sampling, sends no telemetry, and stays quiet when older hosts omit the blocks. - **Empty state** — Logo + "Start a conversation" + suggestion chips that populate input +- **Memory-crash recovery** — After a locally recorded out-of-memory crash, automatic conversation reopening and Gateway checkpoint recovery pause for that launch. Sessions remain browsable; explicit selection or a new chat resumes normal operation. Dismissing the crash report does not reopen the previous conversation. Saved history, drafts and checkpoints remain intact. See [Android conversation memory recovery](android-memory-recovery.md). - **Agent Passport — profile inspection/configuration** — upstream Hermes profiles are selected from the Profile Shelf or the Passport's shared full switcher. Passport retains identity customization, model/personality/reasoning/safety configuration, inspection, and session analytics; it is not a second profile-picker implementation. See `docs/decisions.md` §21 and ADR 48. - **Agent sheet — Personality section** — personalities fetched from `GET /api/config` (`config.agent.personalities`). Shows server default (from `config.display.personality`) + all configured. Active personality name shown on assistant chat bubbles. - **Agent sheet — Approval controls** — gateway contract v3 exposes the profile-persisted `approvals.mode` policy (`manual` / `smart` / `off`) separately from YOLO. The launch/default profile gets the three-way control; multiplexed non-launch profiles reconcile `session.info.approval_mode` read-only until upstream config RPCs honor profile scope. The existing YOLO switch remains an explicit per-session override and never silently writes profile configuration. Older gateways keep chat and YOLO available while the profile control explains that an upstream update is required. diff --git a/scripts/android-prepush.py b/scripts/android-prepush.py index 1e75377d..d4381c27 100644 --- a/scripts/android-prepush.py +++ b/scripts/android-prepush.py @@ -21,6 +21,9 @@ REPO_ROOT = pathlib.Path(__file__).resolve().parents[1] FOCUSED_TESTS = ( + "com.hermesandroid.relay.network.upstream.GatewayPayloadMemoryTest", + "com.hermesandroid.relay.viewmodel.ChatMemoryRecoveryTest", + "com.hermesandroid.relay.reliability.ReliabilityReportTest", "com.hermesandroid.relay.data.ChatUnreadStoreTest", "com.hermesandroid.relay.data.ChatTurnCheckpointStoreTest", "com.hermesandroid.relay.notifications.ChatNotificationIntentTest", diff --git a/scripts/check-gateway-scenario-conformance.py b/scripts/check-gateway-scenario-conformance.py index fa825125..b820a4c1 100644 --- a/scripts/check-gateway-scenario-conformance.py +++ b/scripts/check-gateway-scenario-conformance.py @@ -294,8 +294,24 @@ def _check_resume(methods: SourceFile) -> CheckResult: contract = SESSION_RESUME try: handler = methods.method_handler("session.resume") - text = methods.segment(handler) - strings = _string_constants(handler) + # Current upstream extracted resume into local helpers and a _Resume + # context. Follow only definitions reachable from this handler, so an + # unused legacy implementation cannot satisfy the contract. + definitions = { + node.name: node for node in methods.tree.body + if isinstance(node, (ast.FunctionDef, ast.ClassDef)) and node.name != "_" + } + nodes = [handler] + seen = {id(handler)} + for node in nodes: + for call in ast.walk(node): + if isinstance(call, ast.Call) and isinstance(call.func, ast.Name): + target = definitions.get(call.func.id) + if target is not None and id(target) not in seen: + nodes.append(target) + seen.add(id(target)) + text = "\n".join(methods.segment(node) for node in nodes) + strings = set().union(*(_string_constants(node) for node in nodes)) required_markers = ( "db.get_session(", "db.resolve_resume_session_id(", @@ -316,7 +332,7 @@ def _check_resume(methods: SourceFile) -> CheckResult: if not {4007, 4130}.issubset( { child.value - for child in ast.walk(handler) + for node in nodes for child in ast.walk(node) if isinstance(child, ast.Constant) and isinstance(child.value, int) } ): diff --git a/scripts/tests/check_gateway_scenario_conformance_test.py b/scripts/tests/check_gateway_scenario_conformance_test.py index 4e684bb9..f38d14ae 100644 --- a/scripts/tests/check_gateway_scenario_conformance_test.py +++ b/scripts/tests/check_gateway_scenario_conformance_test.py @@ -269,6 +269,18 @@ def test_complete_contract_fixture_passes(self): self.assertEqual(module.ALL_CONTRACTS, tuple(result.contract for result in results)) self.assertTrue(all(result.passed for result in results), results) + def test_resume_follows_extracted_helpers_but_not_unreachable_history(self): + path = self.root / module.SESSION_METHODS + original = path.read_text(encoding="utf-8") + marker = '@method("session.resume")\ndef _(rid, params):' + extracted = original.replace(marker, + '@method("session.resume")\ndef _(rid, params):\n' + ' return _resume_impl(rid, params)\n\ndef _resume_impl(rid, params):') + path.write_text(extracted, encoding="utf-8") + self.assertTrue(module.audit_sources(self.root, (module.SESSION_RESUME,))[0].passed) + path.write_text(extracted.replace('return _resume_impl(rid, params)', 'return {}'), encoding="utf-8") + self.assertFalse(module.audit_sources(self.root, (module.SESSION_RESUME,))[0].passed) + def test_extracted_registry_keeps_identity_and_status_requirements(self): path = self.root / "tui_gateway/session_registry.py" path.write_text(SERVER_SOURCE, encoding="utf-8") diff --git a/test-fixtures/vanilla-gateway/tests/test_fixture.py b/test-fixtures/vanilla-gateway/tests/test_fixture.py index eedb2aa2..b738e95d 100644 --- a/test-fixtures/vanilla-gateway/tests/test_fixture.py +++ b/test-fixtures/vanilla-gateway/tests/test_fixture.py @@ -18,6 +18,24 @@ class FixtureTestCase(unittest.IsolatedAsyncioTestCase): + async def test_metadata_only_bind_preserves_http_history(self) -> None: + fixture, base_url = await self.start("initial_history_bind") + ws, _ = await self.connect(base_url) + for request_id, method, session_id in ( + (1, "session.resume", fixture.scenario.stored_session_id), + (2, "session.activate", fixture.scenario.live_session_id), + ): + await self.rpc(ws, request_id, method, {"session_id": session_id, "omit_messages": True}) + result = (await ws.receive_json())["result"] + self.assertEqual([], result["messages"]) + self.assertEqual(2, result["message_count"]) + self.assertEqual(fixture.scenario.live_session_id, result["session_id"]) + self.assertFalse(result["running"]) + async with self.session.get(f"{base_url}/api/sessions/{fixture.scenario.stored_session_id}/messages") as response: + self.assertIn("Durable Bot Chat history is ready.", await response.text()) + await self.rpc(ws, 3, "session.resume", {"session_id": fixture.scenario.stored_session_id}) + self.assertEqual(2, len((await ws.receive_json())["result"]["messages"])) + async def test_quiet_turns_finish_and_publish_authoritative_unsolicited_state(self) -> None: fixture, base_url = await self.start("quiet_turn_recovery") ws, _ = await self.connect(base_url) diff --git a/test-fixtures/vanilla-gateway/vanilla_gateway/scenarios/initial_history_bind.json b/test-fixtures/vanilla-gateway/vanilla_gateway/scenarios/initial_history_bind.json index 77713d0f..bb2bc446 100644 --- a/test-fixtures/vanilla-gateway/vanilla_gateway/scenarios/initial_history_bind.json +++ b/test-fixtures/vanilla-gateway/vanilla_gateway/scenarios/initial_history_bind.json @@ -4,7 +4,8 @@ "stored_session_id": "20260829_120000_bot_chat", "profile": "research", "contract_requirements": [ - "gateway.session_resume_durable" + "gateway.session_resume_durable", + "gateway.session_activate_live" ], "initial_history": [ {"id": 1, "role": "user", "content": "Open the durable Bot Chat.", "timestamp": 1.0}, diff --git a/test-fixtures/vanilla-gateway/vanilla_gateway/server.py b/test-fixtures/vanilla-gateway/vanilla_gateway/server.py index 52b80204..d8895be6 100644 --- a/test-fixtures/vanilla-gateway/vanilla_gateway/server.py +++ b/test-fixtures/vanilla-gateway/vanilla_gateway/server.py @@ -231,6 +231,8 @@ async def _handle_rpc( await self._rpc_error(socket, request_id, 4040, "Stored session not found") return result = self._session_snapshot(include_stored=True) + result["message_count"] = len(self._history_rows) + result["messages"] = [] if params.get("omit_messages") else list(self._history_rows) if self._clarify is not None or self._native_request is not None: self._clarify_owner = socket, connection elif method == "session.activate": @@ -239,6 +241,8 @@ async def _handle_rpc( await self._rpc_error(socket, request_id, 4041, "Live session not found") return result = self._session_snapshot(include_stored=True) + result["message_count"] = len(self._history_rows) + result["messages"] = [] if params.get("omit_messages") else list(self._history_rows) if self._clarify is not None or self._native_request is not None: self._clarify_owner = socket, connection elif method == "prompt.submit": From c84f6a6adcd0b6b8d43ff39db15c36f43819556d Mon Sep 17 00:00:00 2001 From: Bailey Dixon Date: Thu, 15 Oct 2026 15:46:00 -0400 Subject: [PATCH 2/2] docs(android): record API 36 memory recovery evidence --- docs/android-memory-recovery.md | 11 +++++++++++ .../assets/issue-700/memory-recovery-dialog.png | Bin 0 -> 92939 bytes 2 files changed, 11 insertions(+) create mode 100644 docs/audits/assets/issue-700/memory-recovery-dialog.png diff --git a/docs/android-memory-recovery.md b/docs/android-memory-recovery.md index d1e6b146..fcd2a335 100644 --- a/docs/android-memory-recovery.md +++ b/docs/android-memory-recovery.md @@ -93,3 +93,14 @@ have succeeded before process termination for the recorded-crash gate to apply. No private conversation data or production payload is required by these tests. Actual Pixel 8 Pro / Android 17 behavior and the reporter's exact trigger require separate device evidence; a synthetic test cannot certify them. + +## Rendered recovery evidence + +The focused `ChatMemoryRecoveryInstrumentedTest` passed on Standard Phone API 36 +(Pixel 6 profile, x86_64) at `08ba907721c7e180f37e15a7e45f47fb09f1cbd2`: one test, +zero failures or skips. It exercised the production crash dialog and chat state +under dismissal and Activity lifecycle return, followed by explicit history loading. +The dialog capture uses the default dark app theme in the instrumentation harness; +it is not a physical-device or full production-startup certification. + +![OOM recovery dialog on Standard Phone API 36](audits/assets/issue-700/memory-recovery-dialog.png) diff --git a/docs/audits/assets/issue-700/memory-recovery-dialog.png b/docs/audits/assets/issue-700/memory-recovery-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..86271fcdd17b9cb0c60b9ceaae8de66c0642769b GIT binary patch literal 92939 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
f67K0f4ZFvBG~17lN_D4c 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

->|_M9Yl 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