Skip to content

feat(android): read-only offline cache for chat sessions and transcripts#100227

Merged
steipete merged 7 commits into
mainfrom
feat/android-chat-offline-cache
Jul 5, 2026
Merged

feat(android): read-only offline cache for chat sessions and transcripts#100227
steipete merged 7 commits into
mainfrom
feat/android-chat-offline-cache

Conversation

@steipete

@steipete steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Cold-opening the chat tab shows a blank screen until the gateway responds, and when the gateway is unreachable there is nothing to read at all. Previously fetched conversations were held only in memory and lost on every process restart.

Part of #100194

Why This Change Was Made

The app already has everything needed for a read-only cache: a stable per-gateway endpoint identity (the same id used for TLS fingerprint pinning) and a history reconciliation path that drops stale responses via generation tracking. This change adds a small, disposable Room database (androidx.room via KSP) that write-throughs live chat.history / sessions.list results and replays them on the next cold open. No merge algorithm was invented: cached rows are emitted first, then the live response replaces them wholesale through the existing reconciliation path, which also keeps list item identity stable. The cache is bounded (50 sessions, 200 newest messages per session, eviction on write), schema-versioned with destructive migration only, and stores text parts only — attachments and binary content never reach disk. Cache rows are keyed by the gateway's stable identity so transcripts cannot cross gateways; pairing/auth resets purge the database, and gateway-side session deletes purge the matching cached rows.

User Impact

  • Chat opens instantly with the last-known transcript, then updates in place when live history arrives.
  • While the gateway is unreachable, the session list and cached transcripts remain browsable read-only; sending is still blocked by the existing health gate.
  • The cache is disposable: any schema change drops and rebuilds it; no user action or migration ever required.
  • Licenses screen: no new notice file needed — the bundled license list convention excludes androidx artifacts.
  • Possible follow-up if reviewers want it: encryption-at-rest for the cache DB (it currently relies on app-private storage, like other app databases).

Evidence

  • cd apps/android && ./gradlew :app:testPlayDebugUnitTest --tests 'ai.openclaw.app.chat.*' — green (59 tests, 13 new); full :app:testPlayDebugUnitTest suite green (run twice, incl. after final changes).
  • New tests: RoomChatTranscriptCacheTest (round-trip, text-only rows, 200/50 bounds + eviction incl. full-cache deep-session case, gateway-identity scoping, delete/clear purges) and ChatControllerTranscriptCacheTest (offline cold open shows cache + send stays blocked, cache-first emission then wholesale live replacement with stable message ids and write-through, offline session switch, delete-event purge).
  • Codex autoreview: 4 cycles; two accepted findings fixed with regression tests (full-cache eviction edge, session-delete purge); final run clean.

Known gaps: sub-coroutine-step cross-gateway write race during an active gateway switch (self-heals on next write; strict fix needs connection-epoch plumbing — follow-up); per-message events deliberately not written through (cache refreshes on each online open); no on-device instrumentation pass.

@openclaw-barnacle openclaw-barnacle Bot added app: android App: android size: L maintainer Maintainer-authored PR labels Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af990f29bd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

private val chatTranscriptCache: RoomChatTranscriptCache =
RoomChatTranscriptCache(
context = appContext,
gatewayId = ::chatCacheGatewayId,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Bind cache writes to the request gateway

When a chat.history/sessions.list request to gateway A is in flight and the user starts connecting or pairing gateway B, connectAfterTlsCheck updates connectedEndpoint before the old response necessarily returns, while ChatController.persistTranscript/persistSessions write after parsing the response. Because this cache resolves gatewayId through the live getter here, the stale A response can be saved under B's stable id and later replay A's transcript during an offline B cold open. Capture the gateway id or connection epoch at the start of the history/session load and drop responses that no longer match before writing the cache.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 5, 2026, 4:10 PM ET / 20:10 UTC.

Summary
Adds an Android Room/KSP-backed read-only cache for recent chat sessions and text transcript rows, keyed by gateway identity and wired into ChatController/NodeRuntime with tests and Gradle dependency updates.

PR surface: Other +977. Total +977 across 9 files.

Reproducibility: yes. for the review blockers by source inspection: the pending-connect cache scope, async purge, session replacement, and Sessions-tab refresh paths are visible at the current PR head. I did not run an emulator or device reproduction because this review is read-only.

Review metrics: 2 noteworthy metrics.

  • Persistent Android cache schema: 1 Room database, 2 tables added. The PR starts storing session and transcript text on disk, so reset ordering, retention, upgrade, and storage-boundary behavior need review before merge.
  • Android dependency/generator surface: 1 Gradle plugin, 2 Room artifacts added. KSP and Room add generated-code build behavior plus a packaged runtime dependency that Android CI and license policy need to cover.

Stored data model
Persistent data-model change detected: persistent cache schema: apps/android/app/src/main/java/ai/openclaw/app/chat/ChatTranscriptCache.kt, persistent cache schema: apps/android/app/src/test/java/ai/openclaw/app/chat/RoomChatTranscriptCacheTest.kt. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #100194
Summary: The canonical remaining product request is the mobile offline session cache umbrella; this Android PR is a candidate implementation for the Android half, while the iOS cache PR is a sibling candidate and offline outbox/reconnect items are adjacent but distinct.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🧂 unranked krab
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Fix the pending-gateway scope, auth-reset purge ordering, deep-session retention, and Sessions-tab cache priming blockers.
  • Attach redacted Android device or emulator proof showing cache-first cold open, offline session browsing, and live replacement after reconnect.
  • [P1] Resolve whether app-private plaintext Room storage is acceptable or encryption-at-rest is required.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR has unit/Robolectric/Testbox/CI evidence, but no redacted Android device or emulator screenshot, recording, live output, or logs showing cache-first cold open, offline browsing, and live replacement after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Mantis proof suggestion
A short Android emulator/device visual proof would materially help verify the cache-first cold open and offline browsing flow. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify Android cache-first cold open, offline session browsing, and live replacement after reconnect in an emulator.

Risk before merge

  • [P1] During a pending gateway switch, cached rows from the previous gateway can be replayed after the UI is cleared for the new target.
  • [P1] A runtime auth reset can reconnect and prime old cached transcript rows before the asynchronous Room purge finishes.
  • [P1] Session-list replacement can delete explicitly cached deep-session transcripts, and direct Sessions-tab offline entry still lacks a cache-prime path.
  • [P1] The PR stores transcript text in an app-private Room database; maintainers still need to accept that storage boundary or require encryption-at-rest.
  • [P1] The contributor has not provided real Android device or emulator proof for cache-first cold open, offline browsing, and live replacement.

Maintainer options:

  1. Fix cache isolation before merge (recommended)
    Clear or mark pending gateway scope before cache resolution, await reset purges before reconnect/cache reads, preserve deep cached sessions, and prime cached sessions from the Sessions path.
  2. Record the storage boundary
    After code blockers are fixed, explicitly accept app-private plaintext Room storage or require encryption-at-rest before merge.
  3. Pause if persistence is not approved
    If local transcript text persistence is not acceptable product direction, pause this branch and keep the umbrella issue open for a different design.

Next step before merge

  • [P1] Manual review is needed because the PR has missing contributor real behavior proof, a protected maintainer label, unresolved session-state/security findings, and a transcript-at-rest boundary choice.

Maintainer decision needed

  • Question: After the correctness blockers and real Android proof are fixed, may Android store cached transcript text in an app-private plaintext Room database, or must this PR add encryption-at-rest before merge?
  • Rationale: The branch intentionally persists conversation text on device and the umbrella issue carries security-review signals, so automation should not decide the acceptable transcript-at-rest boundary.
  • Likely owner: steipete — Peter is the assigned owner and the strongest recent Android chat/runtime history signal for this storage-boundary decision.
  • Options:
    • Accept app-private cache after fixes (recommended): Require the cache-isolation fixes and real Android proof first, then record that app-private Room storage is acceptable for this bounded disposable read-only cache.
    • Require encrypted cache: Make the transcript cache encrypted-at-rest before merge if app-private storage alone is not an acceptable boundary for conversation text.
    • Pause transcript persistence: Hold or close this branch if local transcript persistence is not accepted Android product direction yet.

Security
Needs attention: The diff persists transcript text locally and still has cache-scope and purge-ordering races that can expose old gateway/principal transcript rows.

Review findings

  • [P1] Clear pending gateway scope before cache reads — apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:862
  • [P1] Await transcript cache purge during auth reset — apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:1278
  • [P2] Preserve deep-session stubs on list replacement — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatTranscriptCache.kt:236-238
Review details

Best possible solution:

Land only after cache reads are blocked during pending connects, reset purges are ordered before reconnect/cache reads, cached deep sessions and direct Sessions-tab offline entry work, real Android proof is attached, and maintainers accept the transcript-at-rest boundary.

Do we have a high-confidence way to reproduce the issue?

Yes for the review blockers by source inspection: the pending-connect cache scope, async purge, session replacement, and Sessions-tab refresh paths are visible at the current PR head. I did not run an emulator or device reproduction because this review is read-only.

Is this the best way to solve the issue?

No. The Room cache-first design is plausible, but it is not the best mergeable form until cache isolation, reset ordering, deep-session retention, Sessions-tab priming, real Android proof, and transcript-at-rest acceptance are resolved.

Full review comments:

  • [P1] Clear pending gateway scope before cache reads — apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:862
    beginConnect() increments the connection generation and clears chat state, but chatCacheGatewayId() still returns the old connectedEndpoint until TLS/trust finishes. Any Chat or Sessions load during that pending window can build a fresh scope for the previous gateway and replay its cached transcript after the UI was cleared for the new target.
    Confidence: 0.91
  • [P1] Await transcript cache purge during auth reset — apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:1278
    saveGatewayConfigAndConnect() calls the reset path and then immediately writes replacement credentials and reconnects, but this runtime path only launches chatTranscriptCache.clearAll(). A different principal at the same endpoint id can prime old rows before the purge runs, so reset needs to wait for the Room purge before reconnect/cache reads can happen.
    Confidence: 0.93
  • [P2] Preserve deep-session stubs on list replacement — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatTranscriptCache.kt:236-238
    saveTranscript() creates a stub row so a deep session remains reachable, but saveSessions() deletes every session row, inserts only the refreshed list window, and evicts orphaned transcripts. A current deep session outside the top window can disappear from the next offline cold open after any list refresh that omits it.
    Confidence: 0.89
  • [P2] Prime cached sessions from the sessions path — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:633-654
    fetchSessions() only attempts the live sessions.list request and ignores offline failures; cached sessions are loaded only from chat-history bootstrap. Since SessionsScreen refreshes on entry only when connected, an offline cold open directly to Sessions can still show an empty list until Chat has been opened first.
    Confidence: 0.86

Overall correctness: patch is incorrect
Overall confidence: 0.91

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against a04b6ced4f96.

Label changes

Label justifications:

  • P2: This is a normal-priority Android feature PR with limited blast radius but concrete session-state, proof, and security-boundary blockers before merge.
  • merge-risk: 🚨 session-state: Merging as-is can show cached sessions or transcripts from the wrong gateway scope and can drop explicitly cached deep-session transcripts.
  • merge-risk: 🚨 security-boundary: The PR persists transcript text locally and still has cross-principal cache purge/scope races during gateway or auth replacement.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🧂 unranked krab.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR has unit/Robolectric/Testbox/CI evidence, but no redacted Android device or emulator screenshot, recording, live output, or logs showing cache-first cold open, offline browsing, and live replacement after the fix. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Other +977. Total +977 across 9 files.

View PR surface stats
Area Files Added Removed Net
Source 0 0 0 0
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 9 1064 87 +977
Total 9 1064 87 +977

Security concerns:

  • [high] Old gateway cache can reappear during pending connect — apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:862
    During TLS/trust for a new gateway, cache scope can still resolve to the previous connectedEndpoint and repopulate old cached transcript rows after chat state is cleared.
    Confidence: 0.91
  • [high] Auth reset can race transcript purge — apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:1278
    The runtime reset path schedules chatTranscriptCache.clearAll() asynchronously, so replacement credentials can reconnect and prime cache before old transcript rows are deleted.
    Confidence: 0.93
  • [medium] Transcript-at-rest boundary needs acceptance — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatTranscriptCache.kt:79
    The new Room cache stores text transcript rows in an app-private database; maintainers should explicitly accept that boundary or require encryption-at-rest before merge.
    Confidence: 0.78

What I checked:

  • Repository policy read: Root AGENTS.md and apps/android/AGENTS.md were read fully; Android dependency/license, persisted-state, session-state, and security-boundary guidance apply to this PR. (AGENTS.md:55, a04b6ced4f96)
  • Current main lacks this cache: A current-main search found no ChatTranscriptCache, RoomChatTranscriptCache, messagesFromCache, chat-transcript-cache database, androidx.room dependency, or KSP plugin in the Android app surface. (a04b6ced4f96)
  • Pending gateway switch can read the old cache: At PR head, beginConnect increments the connection generation and clears ChatController state, but chatCacheGatewayId still returns the old connectedEndpoint until TLS/trust finishes, so cache reads during that window can use the previous gateway id. (apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:862, a259a408991c)
  • Auth reset purge remains asynchronous: resetGatewaySetupAuth launches chatTranscriptCache.clearAll without waiting, while MainViewModel.saveGatewayConfigAndConnect can immediately write replacement credentials and reconnect. (apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt:1278, a259a408991c)
  • Session-list replacement can orphan cached transcripts: saveSessions deletes all cached session rows for a gateway, inserts only the fresh bounded list, then evicts orphaned transcripts, which drops deep-session stubs created by saveTranscript when the refreshed list omits that session. (apps/android/app/src/main/java/ai/openclaw/app/chat/ChatTranscriptCache.kt:236, a259a408991c)
  • Sessions path still ignores the cache when offline: fetchSessions only attempts live sessions.list and swallows failures; SessionsScreen only calls refreshChatSessions when connected, so an offline cold open directly to Sessions can remain empty until Chat primes the cache. (apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:633, a259a408991c)

Likely related people:

  • steipete: Current-main blame/log for ChatController and NodeRuntime points to Peter's recent Android chat/runtime commits, he authored and merged adjacent Android chat reconnect work, and he is assigned to this PR and the umbrella issue. (role: recent Android chat/runtime contributor and likely follow-up owner; confidence: high; commits: 286d0b9fb340, f1641571f119; files: apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt, apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (8 earlier review cycles)
  • reviewed 2026-07-05T08:29:53.547Z sha af990f2 :: needs real behavior proof before merge. :: [P1] Bind cache writes to the request gateway
  • reviewed 2026-07-05T10:03:26.875Z sha e0701c7 :: needs real behavior proof before merge. :: [P1] Bind cache writes to the request gateway
  • reviewed 2026-07-05T11:56:51.778Z sha df9eb0e :: needs real behavior proof before merge. :: [P1] Await the cache purge before reconnecting
  • reviewed 2026-07-05T12:36:51.592Z sha b658f9b :: needs real behavior proof before merge. :: [P1] Await the cache purge before reconnecting | [P2] Preserve deep-session stubs when refreshing sessions | [P2] Add the Room license notice
  • reviewed 2026-07-05T16:52:49.209Z sha f2d1528 :: needs real behavior proof before merge. :: [P1] Await the cache purge before reconnecting | [P2] Preserve deep-session stubs during list refresh | [P2] Add the Room license notice
  • reviewed 2026-07-05T17:04:32.399Z sha f2d1528 :: needs real behavior proof before merge. :: [P1] Await the cache purge before reconnecting | [P2] Preserve deep-session stubs during list refresh | [P2] Prime cached sessions from the Sessions path | [P2] Add the Room license notice | [P3] Drop the release-owned changelog edit
  • reviewed 2026-07-05T19:37:09.548Z sha b9fc80e :: needs real behavior proof before merge. :: [P1] Clear the pending gateway scope before cache reads | [P1] Await transcript cache purge during auth reset | [P2] Preserve deep-session stubs on list replacement | [P2] Prime cached sessions from the sessions path | [P3] Remove the release-owned changelog edit
  • reviewed 2026-07-05T19:57:10.279Z sha 17b5cd4 :: needs real behavior proof before merge. :: [P1] Clear pending gateway scope before cache reads | [P1] Await transcript cache purge during auth reset | [P2] Preserve deep-session stubs on list replacement | [P2] Prime cached sessions from the sessions path | [P3] Drop the release-owned changelog edit

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 5, 2026
@steipete steipete self-assigned this Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0701c7dc5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

deviceAuthStore.clearToken(deviceId, "operator")
// A pairing/auth reset can precede pairing a different gateway principal at the same
// endpoint id; purge offline transcripts so they cannot surface under the new pairing.
scope.launch { runCatching { chatTranscriptCache.clearAll() } }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Await cache purge before reconnecting

When saved auth is replaced through the setup flow, MainViewModel.saveGatewayConfigAndConnect calls this method and then immediately writes the new endpoint/auth and reconnects. Because the transcript purge is only scheduled here, a re-pair to a different gateway principal at the same stable id can load the old cache before clearAll() runs (or keep it if the process dies first), so private transcripts can survive the very reset path that is supposed to prevent cross-principal replay. Make the auth-reset path wait for the Room purge before it returns or before any reconnect/cache prime can happen.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a19a3d4ba9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +236 to +238
dao.deleteSessions(gateway)
dao.insertSessions(rows)
dao.evictOrphanedTranscripts(gateway)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve active deep transcripts during list refresh

When a user opens a session that is not in the newest cached window, saveTranscript() first creates a stub so that deep session remains reachable, but a subsequent session-list refresh from ChatScreen/SessionsScreen replaces all session rows here and then drops orphaned transcripts. If the refreshed top-50 list does not include the active deep session, the just-saved transcript is deleted, so the current session will not be available on the next offline cold open. Preserve the active/stubbed session while replacing the list, or avoid orphan eviction for transcripts that were explicitly cached from a live history load.

Useful? React with 👍 / 👎.

@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Land-ready after the Android cache review and repair.

  • Captured gateway scope and connection generation now fence every delayed history/session-list response before both UI application and Room writes, so target switches cannot leak stale data across gateways.
  • Room owns one bounded, disposable cache: 50 sessions, 200 text messages per session, cache-first display, live-response replacement, and destructive schema fallback.
  • Blacksmith Testbox tbx_01kwrtn5326f5vr904aa0s9kws: focused controller/cache tests, both Play and third-party debug unit suites, and Play debug APK assembly all passed.
  • Exact-head CI is green, including android-test-play, android-test-third-party, and android-build-play.
  • Fresh Codex autoreview after the fixes: clean. git diff --check: pass.

Known proof gap: no emulator was installed and the available ADB target remained unauthorized, so this has no on-device screenshot/instrumentation evidence.

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: imessage Channel integration: imessage scripts Repository scripts docker Docker and sandbox tooling channel: qqbot labels Jul 5, 2026
@clawsweeper clawsweeper Bot removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

groupAllowFrom: [], // copy from bluebubbles.groupAllowFrom

P2 Badge Avoid recommending explicit empty groupAllowFrom

For migrations where the old BlueBubbles config had allowFrom but no groupAllowFrom, this example tells users to write groupAllowFrom: [], but the new behavior above says an explicit empty array disables the allowFrom fallback. The runtime does exactly that (configuredGroupAllowFrom is non-null in extensions/imessage/src/monitor/monitor-provider.ts:452-457), so following this step silently drops every group message under groupPolicy: "allowlist"; omit the key when copying no group allowlist, or populate it with the allowed senders/chat targets.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@clawsweeper clawsweeper Bot added the rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. label Jul 5, 2026
@steipete
steipete force-pushed the feat/android-chat-offline-cache branch from df9eb0e to b658f9b Compare July 5, 2026 12:00
@openclaw-barnacle openclaw-barnacle Bot removed docs Improvements or additions to documentation channel: imessage Channel integration: imessage scripts Repository scripts docker Docker and sandbox tooling channel: qqbot labels Jul 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b658f9b06a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


implementation(libs.androidx.security.crypto)
// Read-only offline cache for chat sessions/transcripts (disposable, destructive migrations only).
implementation(libs.androidx.room.runtime)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the Room notice to the Android license assets

Because this adds androidx.room:room-runtime to the packaged Android app, the scoped guide in apps/android/AGENTS.md requires auditing/updating apps/android/THIRD_PARTY_LICENSES/openclaw/licenses/ when Android dependencies change. The Licenses screen is backed only by those .txt assets (as reflected by AndroidLicenseNoticesTest's expected titles), and there is currently no Room/AndroidX notice, so release builds would ship the new Room runtime without a visible third-party license entry.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f2d1528b70

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

junit = "4.13.2"
junit-vintage = "6.1.0"
kotest = "6.1.11"
ksp = "2.3.9"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pin KSP to a published plugin coordinate

Adding alias(libs.plugins.ksp) makes every Android Gradle task resolve com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:2.3.9 during configuration; I ran ./gradlew :app:tasks --no-daemon --stacktrace from apps/android and Gradle fails before creating the task graph because version 2.3.9 is not found in Google, Maven Central, or the plugin portal. Until this is changed to a published KSP version compatible with the Kotlin plugin, no Android build or test can run.

Useful? React with 👍 / 👎.

Comment on lines +569 to +570
if (_sessions.value.isEmpty()) {
val cachedSessions = runCatching { cache.loadSessions(requestCacheScope.gatewayId) }.getOrDefault(emptyList())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prime cached sessions outside chat history loads

Because cached sessions are only read inside primeFromCache, an offline cold start that goes straight to the Sessions tab never populates the list: SessionsScreen skips refreshChatSessions when disconnected, and no chat-history bootstrap has run to reach this branch. Load cached session rows from the refreshSessions path as well, or prime them on Sessions entry, so the advertised offline session browsing works before the user opens Chat.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the feat/android-chat-offline-cache branch from f2d1528 to b9fc80e Compare July 5, 2026 19:23

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9fc80e6cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

* never leak across gateways. Null (nothing paired/configured) disables cache reads and writes.
*/
private fun chatCacheGatewayId(): String? {
connectedEndpoint?.stableId?.let { return it }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Clear the old gateway scope before pending connects

When a user starts connecting or re-pairing gateway B while gateway A is still in connectedEndpoint, beginConnect only clears chat state and connectedEndpoint is not replaced until connectAfterTlsCheck finishes the TLS probe/trust flow. Any Chat load/refresh during that pending window reaches this branch, resolves A's stable id, and primeFromCache/fetchSessions can repopulate A's cached sessions or transcript after the UI was cleared for B. Clear or mark the pending endpoint before cache scopes are resolved so an in-progress gateway switch cannot surface the previous gateway's transcript.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the feat/android-chat-offline-cache branch from b9fc80e to 17b5cd4 Compare July 5, 2026 19:43
steipete added 7 commits July 5, 2026 13:01
…scripts

Cache-first cold open plus offline browsing of the session list and cached
transcripts. Room DB (KSP) scoped by stable gateway identity, bounded to
50 sessions / 200 text-only messages per session with eviction on write,
destructive migrations only. Live chat.history/sessions.list responses
replace cached rows wholesale via the existing generation-tracked
reconciliation path; sending stays behind the existing health gate.
Pairing/auth resets and gateway-side session deletes purge cached rows.

Part of #100194
@steipete
steipete force-pushed the feat/android-chat-offline-cache branch from 17b5cd4 to a259a40 Compare July 5, 2026 20:02

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a259a40899

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

deviceAuthStore.clearToken(deviceId, "node")
deviceAuthStore.clearToken(deviceId, "operator")
// No runtime means no open Room handle, so the cache file can be deleted directly.
deleteChatTranscriptCacheDatabase(nodeApp)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Check the process runtime before deleting the cache DB

When a ViewModel is recreated while NodeApp still owns a live NodeRuntime, runtimeRef.value is still null until ensureRuntime() is called, so this path can delete the Room database file even though the existing runtime already has RoomChatTranscriptCache open. In that re-pair/setup flow the purge may not actually clear the open cache handle before saveGatewayConfigAndConnect() immediately reuses the singleton runtime, leaving old transcripts available under the new pairing; use the app singleton runtime (peekRuntime()/ensureRuntime() as appropriate) and clear through Room instead of assuming a null ViewModel ref means no open handle.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 09f9a85 into main Jul 5, 2026
77 checks passed
@steipete
steipete deleted the feat/android-chat-offline-cache branch July 5, 2026 20:14
@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 6, 2026
…pts (openclaw#100227)

* feat(android): add read-only offline cache for chat sessions and transcripts

Cache-first cold open plus offline browsing of the session list and cached
transcripts. Room DB (KSP) scoped by stable gateway identity, bounded to
50 sessions / 200 text-only messages per session with eviction on write,
destructive migrations only. Live chat.history/sessions.list responses
replace cached rows wholesale via the existing generation-tracked
reconciliation path; sending stays behind the existing health gate.
Pairing/auth resets and gateway-side session deletes purge cached rows.

Part of openclaw#100194

* chore(android): sync native i18n inventory

* fix(android): bind transcript cache writes to gateway scope

* docs(changelog): note Android offline chat cache

* chore(android): refresh native i18n inventory

* docs(changelog): keep Android offline cache unreleased

* docs(changelog): drop PR-carried entry; release generation owns CHANGELOG
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android maintainer Maintainer-authored PR merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P2 Normal backlog priority with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. size: XL status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant