Skip to content

feat(android): resume chat from the afterSeq cursor after reconnect#100301

Closed
steipete wants to merge 8 commits into
feat/gateway-chat-history-after-seqfrom
feat/android-resume-after-seq
Closed

feat(android): resume chat from the afterSeq cursor after reconnect#100301
steipete wants to merge 8 commits into
feat/gateway-chat-history-after-seqfrom
feat/android-resume-after-seq

Conversation

@steipete

@steipete steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Stacked PR: based on the gateway branch feat/gateway-chat-history-after-seq (#100267) and must land after it.

Part of #100197

What Problem This Solves

After a gateway disconnect, the Android client refetched nothing automatically and any manual/terminal-event refresh re-downloaded the entire recent history page. On flaky connections this wastes bandwidth, delays the transcript, and re-renders rows the client already has.

Why This Change Was Made

The gateway now exposes an afterSeq catch-up cursor on chat.history (#100267, including the recent-tail fix that stamps transcript-global __openclaw.seq on default pages so a plain full fetch can seed the cursor baseline — a gap found while building this adoption). The client tracks the highest applied transcript seq per session and, on the first healthy transition after a disconnect, fetches only the missed rows and appends them through the existing identity/dedup and generation-tracked reconciliation, paging nextAfterSeq/hasMore with an advance-only guard. Version skew is the single required fallback: gateways that reject the unknown param (schema additionalProperties: false → error) or ignore it (no cursor echo) get today's full-history fetch with wholesale replace and a baseline reset. Fresh sessions and session switches reset the baseline. The three duplicated full-refetch blocks collapsed into one helper pair (−55 lines of duplication).

User Impact

Reconnects on the Android app resume the chat transcript by fetching only messages that arrived while offline, in order and without duplicates. No behavior change for fresh sessions, manual refresh, or older gateways.

Evidence

  • cd apps/android && ./gradlew :app:testPlayDebugUnitTest --tests 'ai.openclaw.app.chat.*' — 42 tests pass, including 6 new ChatControllerReconnectCatchUpTest cases: delta append without duplicates, multi-page cursor loop, legacy no-echo fallback with baseline reset, legacy param-rejection fallback, seq baseline reset on session switch, fresh-session full fetch unchanged.
  • Gateway lane on the base: server.chat.gateway-server-chat-b.test.ts 116/116 (including the new transcript-global recent-tail seq test, now carried by feat(gateway): optional afterSeq catch-up cursor on chat.history #100267); chat-history-budget.test.ts 18/18 post-rebase.
  • ktlint clean on touched files; oxfmt clean on the (now upstreamed) TS.
  • Codex autoreview of the branch diff (post-rebase): clean, no actionable findings ("patch is correct", 0.78).

Known gap: no live-device E2E; one CI/Testbox run of the gateway lane recommended before landing the stack (a final local rerun was blocked by a sibling session's repo-wide heavy-check lock — coverage documented in the PR discussion above holds).

@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: 8a671d9f89

ℹ️ 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".

val retained = _messages.value.filterNot { messageIdentityKey(it) in deltaKeys }
val merged = retained + delta
prunePersistedOptimisticMessages(merged)
_messages.value = mergeOptimisticMessages(incoming = merged, optimistic = optimisticMessagesByRunId.values)

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 Restore sessionId when applying reconnect deltas

When reconnect catch-up succeeds on the cursor path, onDisconnected() has already cleared _sessionId, but this delta apply path only updates _messages. After a disconnect/reconnect with missed rows, the transcript looks recovered while startNewChatAwait() still sees hasLoadedParentSession == false and omits parentSessionKey/emitCommandHooks, so creating a new chat from the recovered session loses the parent linkage until the user performs a full refresh. Please mirror the wholesale path's _sessionId = history.sessionId update for cursor pages.

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, 9:17 AM ET / 13:17 UTC.

Summary
The branch adds Android chat reconnect catch-up with chat.history.afterSeq, tracks transcript sequence baselines, falls back to full history for older gateways, and carries the stacked Gateway cursor protocol/docs/tests.

PR surface: Source +245, Tests +635, Docs +3, Other +520. Total +1403 across 16 files.

Reproducibility: yes. from source inspection: disconnect clears _sessionId, the cursor delta path updates messages without restoring it, and the same path prunes optimistic bookkeeping against a list that can include local optimistic rows. I did not run Android tests in this read-only review.

Review metrics: 1 noteworthy metric.

  • RPC cursor surface: 1 optional chat.history cursor parameter adopted by Android. Android now sends afterSeq after reconnect, so reviewers need the older-gateway fallback and stacked Gateway base order to be checked before merge.

Stored data model
Persistent data-model change detected: serialized state: src/gateway/server-methods/chat-history-budget.test.ts, serialized state: src/gateway/server.chat.gateway-server-chat-b.test.ts, serialized state: src/gateway/session-transcript-readers.ts, serialized state: src/gateway/session-utils.fs.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #100197
Summary: This PR is the Android implementation candidate for the canonical mobile reconnect cursor issue; the Gateway PR is a prerequisite and the iOS PR is a sibling client adoption path.

Members:

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

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🌊 off-meta tidepool
Patch quality: 🦪 silver shellfish
Result: blocked by patch quality or review findings.

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

Rank-up moves:

  • [P2] Fix both Android state blockers and add focused reconnect tests for new-chat parent linkage and failed optimistic send cleanup.
  • Keep this stacked behind the Gateway cursor PR until that protocol surface is approved and merged.

Risk before merge

  • [P1] Merging this head can recover transcript rows after reconnect while leaving _sessionId null, so later new-chat creation loses parent linkage and command hooks.
  • [P1] Merging this head can remove optimistic-run bookkeeping before the gateway confirms a newly sent message, leaving stale local echoes after send failures or terminal-error acknowledgements.
  • [P1] The PR is stacked on the open Gateway cursor PR at feat(gateway): optional afterSeq catch-up cursor on chat.history #100267, so final landing still depends on that protocol surface being approved and merged first.

Maintainer options:

  1. Repair Android state before merge (recommended)
    Restore session identity on accepted cursor pages, prune optimistic state only against gateway-delivered delta rows, and cover both flows with Android unit tests.
  2. Pause until the stack is ready
    Keep this PR open but do not merge it until the Gateway cursor base PR is approved and the Android state repairs are present.
Copy recommended automerge instruction
@clawsweeper automerge

Special instructions:
Restore `_sessionId` from successful cursor catch-up pages, prune optimistic bookkeeping only against gateway-delivered delta rows before re-merging pending optimistic messages, add Android unit tests for reconnect-to-new-chat parent linkage and send-failure cleanup after reconnect catch-up, and keep legacy full-history fallback behavior unchanged.

Next step before merge

  • [P1] A narrow automated repair can address the Android ChatController/test blockers, while final landing still needs maintainer review for the stacked Gateway protocol workstream.

Maintainer decision needed

  • Question: After the Android repair blockers are fixed and feat(gateway): optional afterSeq catch-up cursor on chat.history #100267 lands, should Android adopt the chat.history.afterSeq reconnect cursor in this stack?
  • Rationale: The remaining code defects are mechanical, but final acceptance still depends on maintainer intent for a new mobile reconnect behavior built on an unmerged Gateway protocol surface.
  • Likely owner: steipete — steipete owns the linked reconnect workstream and authored the stacked Gateway and Android cursor changes.
  • Options:
    • Accept after fixes (recommended): Fix the Android session-state blockers, preserve legacy-gateway fallback, and land this after the Gateway cursor PR.
    • Pause for stack review: Hold Android adoption until Gateway, iOS, Android, and in-flight run semantics are reviewed together.
    • Reject Android cursor adoption: Keep Android on full-history reconnect refresh and leave cursor-based mobile recovery out of this stack.

Security
Cleared: Cleared: the diff changes Android chat state handling plus stacked Gateway protocol/docs/tests, with no new dependencies, workflows, secret handling, or supply-chain execution surface.

Review findings

  • [P2] Restore session identity after cursor catch-up — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:675
  • [P2] Prune optimistic sends only against delivered delta rows — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:674
Review details

Best possible solution:

Repair the Android delta-merge state bugs, keep older-gateway full-history fallback, and land this only after the Gateway afterSeq contract is approved and merged.

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

Yes, from source inspection: disconnect clears _sessionId, the cursor delta path updates messages without restoring it, and the same path prunes optimistic bookkeeping against a list that can include local optimistic rows. I did not run Android tests in this read-only review.

Is this the best way to solve the issue?

No. The afterSeq owner boundary is plausible, but this patch is incomplete until successful cursor pages preserve the same session identity and optimistic-send bookkeeping guarantees as full-history refreshes.

Full review comments:

  • [P2] Restore session identity after cursor catch-up — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:675
    This prior blocker is still present: onDisconnected() clears _sessionId, and a successful cursor page reaches this merge path without restoring it. startNewChatAwait() uses _sessionId to decide whether to send parentSessionKey and emitCommandHooks, so a new chat after reconnect recovery can lose parent linkage until a full refresh runs.
    Confidence: 0.91
  • [P2] Prune optimistic sends only against delivered delta rows — apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt:674
    Late discovery: this line was unchanged at the earlier reviewed head. If the user sends after healthOk flips true but before catch-up finishes, merged already includes the local optimistic row, so pruning against it can remove the optimisticMessagesByRunId entry before the gateway confirms the send; later failures can no longer remove the stale echo.
    Confidence: 0.88
    Late finding: first raised on code an earlier review cycle already covered.

Overall correctness: patch is incorrect
Overall confidence: 0.89

AGENTS.md: found and applied where relevant.

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

Label changes

Label changes:

  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🌊 off-meta tidepool and patch quality is 🦪 silver shellfish.
  • remove rating: 🦐 gold shrimp: Current PR rating is rating: 🦪 silver shellfish, so this older rating label is no longer current.

Label justifications:

  • P1: The PR targets the high-priority mobile reconnect workflow where missed live events force slow or lossy history recovery.
  • merge-risk: 🚨 session-state: The current delta merge path can leave session identity and optimistic-run state inconsistent after reconnect.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🌊 off-meta tidepool and patch quality is 🦪 silver shellfish.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Not applicable: The PR is maintainer-labeled, so the external contributor proof gate is not applied; the body reports unit/CI evidence but no live Android reconnect E2E.
Evidence reviewed

PR surface:

Source +245, Tests +635, Docs +3, Other +520. Total +1403 across 16 files.

View PR surface stats
Area Files Added Removed Net
Source 6 262 17 +245
Tests 3 639 4 +635
Docs 2 3 0 +3
Config 0 0 0 0
Generated 0 0 0 0
Other 5 605 85 +520
Total 16 1509 106 +1403

Acceptance criteria:

  • [P1] cd apps/android && ./gradlew :app:testPlayDebugUnitTest --tests 'ai.openclaw.app.chat.*'.
  • [P1] Run the repository's changed-file check for the touched Android files through the approved OpenClaw check wrapper or CI lane.

What I checked:

Likely related people:

  • steipete: Authored the canonical reconnect issue and this stacked Android/Gateway cursor series, and has recent adjacent gateway/session work in the same design area. (role: reconnect workstream owner; confidence: high; commits: 33524a6ed595, 5921f31d461a, 6df0fb818d67; files: apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt, apps/android/app/src/test/java/ai/openclaw/app/chat/ChatControllerReconnectCatchUpTest.kt, src/gateway/server-methods/chat.ts)
  • obviyus: Recent merged history includes Android chat rebuild, review-finding repairs, and message identity refresh behavior that the reconnect delta path relies on. (role: recent Android chat contributor; confidence: medium; commits: ff50cdf39622, 989e53c20d39, a41be2585fca; files: apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt, apps/android/app/src/test/java/ai/openclaw/app/chat/ChatControllerMessageIdentityTest.kt)
  • joshavant: Recent merged Android chat work fixed keeping sent chat messages in history, directly adjacent to optimistic-message preservation during reconnect merges. (role: adjacent Android message-state contributor; confidence: medium; commits: 485446af8c95; files: apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt, apps/android/app/src/test/java/ai/openclaw/app/chat/ChatControllerMessageIdentityTest.kt)
  • IWhatsskill: Most recent live GitHub history for the Android chat controller includes chat command controls, making this a plausible adjacent reviewer for Android chat flow regressions. (role: recent Android chat surface contributor; confidence: low; commits: c680312ef6f3; files: apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.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 (1 earlier review cycle)
  • reviewed 2026-07-05T11:36:28.349Z sha 8a671d9 :: needs real behavior proof before merge. :: [P2] Restore sessionId when applying reconnect deltas

@clawsweeper clawsweeper Bot added rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P1 High-priority user-facing bug, regression, or broken workflow. merge-risk: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. labels Jul 5, 2026
@steipete
steipete force-pushed the feat/gateway-chat-history-after-seq branch 2 times, most recently from d522cc6 to c9a5ce2 Compare July 5, 2026 12:25
steipete added 6 commits July 5, 2026 08:34
… pages

The default (no offset/afterSeq) chat.history path numbered __openclaw.seq
within its byte-bounded tail window, so clients seeding the afterSeq
reconnect cursor from a plain full fetch would start from a window-local
value and re-walk or duplicate transcript rows. Reuse the stats reader so
recent-tail rows carry the same transcript-global seq as cursor and offset
pages; the response envelope is unchanged.
@steipete
steipete force-pushed the feat/gateway-chat-history-after-seq branch from c9a5ce2 to eb669c2 Compare July 5, 2026 12:34
steipete added 2 commits July 5, 2026 08:41
…after reconnect

ChatController now tracks the highest applied __openclaw.seq for the
active session and, on the first healthy transition after a disconnect,
pages chat.history with afterSeq/nextAfterSeq/hasMore to append only the
rows missed while offline. Catch-up pages dedupe by the existing message
identity keys and reuse the generation-tracked reconciliation; the seq
baseline resets wherever messages reset (session switch, wholesale
history replace). Gateways that reject or ignore afterSeq fall back to
the existing full-history fetch. The duplicated full-history refetch
blocks collapse into one fetch-and-apply helper.

Part of #100197
@steipete
steipete force-pushed the feat/android-resume-after-seq branch from 8a671d9 to 5921f31 Compare July 5, 2026 12:43

@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: 5921f31d46

ℹ️ 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 +674 to +675
prunePersistedOptimisticMessages(merged)
_messages.value = mergeOptimisticMessages(incoming = merged, optimistic = optimisticMessagesByRunId.values)

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 optimistic bookkeeping when merging deltas

When a user sends a new message after healthOk flips true but before the reconnect catch-up finishes, _messages.value already contains that optimistic row. Passing merged into prunePersistedOptimisticMessages makes the optimistic row match itself, so its optimisticMessagesByRunId entry is removed before the gateway has confirmed it; later chat.send failures or terminal-error acks can no longer remove the unsent echo, leaving a stale user message in the transcript. Prune only against gateway-delivered delta rows before re-merging the still-pending optimistic messages.

Useful? React with 👍 / 👎.

@steipete
steipete force-pushed the feat/gateway-chat-history-after-seq branch from eb669c2 to 262de5b Compare July 5, 2026 12:50
@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. and removed rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. labels Jul 5, 2026
@steipete

steipete commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

Closing with the rejected cursor stack. #100267 is closed and #100277 was rewritten to use the existing Gateway-owned chat.history.inFlightRun snapshot instead of adding afterSeq.

This branch cannot be repaired as an Android-only follow-up: it carries the closed Gateway protocol stack, and its delta path also leaves _sessionId unset after reconnect and prunes optimistic-send bookkeeping against rows that can already include the local echo. Fixing those defects would still preserve the wrong new protocol and duplicate reconciliation state.

If Android needs active-run ownership recovery, the follow-up should consume the same existing run snapshot contract at the current ChatController history boundary and keep full-history refresh canonical. That is a separate, bounded client change with focused session/optimistic-state tests and live reconnect proof, not a continuation of this branch.

@steipete steipete closed this Jul 5, 2026
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: 🚨 session-state 🚨 May lose, corrupt, stale, or mis-associate session, agent, or context state. P1 High-priority user-facing bug, regression, or broken workflow. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: L status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant