feat(android): resume from the seq, and walk back through the session
The chat screen asked the claw for everything the cache did not hold by counting events, and a session that ran for weeks cost the whole record the first time it opened here. The stream now names each frame's seq and the cache stores it, so a reopening screen sends since=<seq> and receives only the gap. The cache migrates in place: the old count of events is exactly the last seq plus one, because the record numbers densely. A screen with no cache opens on the newest turn and walks back through the new history route when the reader reaches the top, one whole turn per page, so the phone does not hold a weeks-long transcript to show the last few messages. Pages are folded through the same reducer the stream uses and prepended, since the list runs oldest first: appended, they would land at the newest end. A reset comment (the record no longer holds the cursor) drops the mirror, and the tail that follows rebuilds it. The outcome tag colours a steered turn as an accent rather than a failure, matching the web UI.
This commit is contained in:
1 parent
70d9bb0d34
commit
fa0e3d4470
7 files changed
+277
-51
No files matched your search
+22
-12
@@ -46,15 +46,19 @@ manages the device fleet the claw keeps.
|
||||
- **Chat**: session list (`GET /v1/sessions`, interactive sessions first,
|
||||
the phone's own `phone:*` status keys hidden)
|
||||
and one chat screen per session. The applied events persist in a
|
||||
per-session cache (DataStore, newest 500 with the full count as the
|
||||
cursor), so a screen opens on its cached transcript at once and the
|
||||
stream connects with `?since=<count>` to receive only what is new
|
||||
instead of replaying the whole history; the cache flushes at every
|
||||
`turn_done` and when the screen closes. While a chat screen is open the
|
||||
app holds one SSE connection (`GET /v1/sessions/{key}/events`, OkHttp
|
||||
streaming GET with the auth header, hand-parsed frames: blank-line
|
||||
delimited `data:` lines, `:` comment keep-alives skipped, unknown event
|
||||
types skipped). The reader's own messages render as right-anchored
|
||||
per-session cache (DataStore, the newest 500 events, the seq of the
|
||||
last one as its cursor), so a screen opens on its cached transcript at
|
||||
once and the stream connects with `?since=<seq>` to receive only the
|
||||
gap instead of replaying the session again; the cache flushes at every
|
||||
`turn_done` and when the screen closes. A screen with no cache opens on
|
||||
the claw's newest turn and walks back through `GET
|
||||
/v1/sessions/{key}/history` as the reader scrolls up, one whole turn
|
||||
per page. While a chat screen is open the app holds one SSE connection
|
||||
(`GET /v1/sessions/{key}/events`, OkHttp streaming GET with the auth
|
||||
header, hand-parsed frames: blank-line delimited `data:` lines with the
|
||||
event's seq on the `id:` line, `:` comment keep-alives skipped, the
|
||||
claw's `reset` comment dropping the mirror, unknown event types
|
||||
skipped). The reader's own messages render as right-anchored
|
||||
bubbles (elevated surface, accent bar on the trailing edge); the
|
||||
agent's turns keep the avatar on the left. The
|
||||
composer's (+) door stages images for the next send — a photo-picker
|
||||
@@ -215,7 +219,8 @@ All under one base URL; `Authorization: Bearer <token>` on `/v1`, never on
|
||||
| `PUT /v1/providers/active` | switch the backend instance, persisted |
|
||||
| `GET /v1/sessions` | session list |
|
||||
| `POST /v1/sessions/{key}/messages` | `{"text"}`, optional `images` (≤ 8, ≤ 5 MiB decoded each, bare base64) → 202; reply via stream |
|
||||
| `GET /v1/sessions/{key}/events` | SSE, replay-then-live: `user_message`, `thinking_delta`, `text_delta`, `tool_start`, `tool_result`, `turn_done`, `attachment`, `consent_request`, `consent_resolved`. `?since=N` skips the replay's oldest N events — the transcript cache's cursor |
|
||||
| `GET /v1/sessions/{key}/events` | SSE, replay-then-live: `user_message`, `thinking_delta`, `text_delta`, `tool_start`, `tool_result`, `turn_done`, `attachment`, `consent_request`, `consent_resolved`. Every frame carries the record's seq on its `id:` line; `?since=<seq>` replays only the events past it, and no cursor replays the newest turn only — the transcript cache's cursor |
|
||||
| `GET /v1/sessions/{key}/history?before=&limit=` | one page of whole turns older than `before`, ending exactly there, with `more` saying whether anything precedes it — the chat screen's walk back |
|
||||
| `GET /v1/attachments/{id}` | an attachment event's bytes (bearer) |
|
||||
| `GET /v1/consents` | pending consent questions, oldest first |
|
||||
| `POST /v1/consents/{id}` | answer one: `{"secret"}` or `{"grant"}` |
|
||||
@@ -261,11 +266,16 @@ posts for that topic.
|
||||
notifications are as fresh as the WorkManager interval (15 min). The chat
|
||||
screen is always live via SSE.
|
||||
- **Transcript cache**: the applied event log persists per session (the
|
||||
newest 500 events plus the full count), so a screen opens on its cached
|
||||
newest 500 events plus their seqs), so a screen opens on its cached
|
||||
transcript and the stream resumes past it. The cache is a cursor over
|
||||
the claw's record, not a copy of it: a claw database wiped behind the
|
||||
phone's back leaves the cache pointing past a history that no longer
|
||||
exists, and that session replays nothing until it catches up.
|
||||
exists, which the claw answers with a `reset` comment and the newest
|
||||
turn — the screen drops its stale rows and rebuilds from those.
|
||||
- **Older turns are fetched, not cached**: the screen holds the cached
|
||||
window plus whatever the reader scrolled back to; reopening a session
|
||||
starts from the cache again, and the walk back re-reads the record.
|
||||
Nothing older is persisted, so the cache stays a fixed size.
|
||||
- **First launch**: nothing works until Settings validates `/healthz`.
|
||||
|
||||
## One-consumer principle
|
||||
|
||||
Reference in new issue
Block a user