* fix(ios): stop camera work when invoke is cancelled
* fix(ios): mark AVFoundation cancellation boundary
* chore(ios): refresh native i18n inventory
* fix(camera): clean up cancelled warm-up sessions
* fix(ios): make camera cancellation lifecycle-safe
---------
Co-authored-by: NianJiuZst <180004567+NianJiuZst@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(ios): support per-gateway custom headers for proxied gateway connections
Per-gateway custom request headers (Cloudflare Access-style service tokens)
for gateways behind authenticating reverse proxies. Headers are credentials:
Keychain-stored per gateway stableID, masked in settings UI, injected on the
WebSocket upgrade at connect time via a provider so edits apply on the next
reconnect without re-pairing. Reserved handshake headers and control chars
are sanitized out. TLS pinning and pairing flows unchanged. Push relay and
other non-gateway destinations proven header-free by regression test.
Related: #100698
* chore(ios): sync native i18n inventory for custom header strings
* fix(ios): harden gateway custom headers
* chore(ios): resync native i18n inventory
* fix(ios): validate custom header names
Gateway (additive, no protocol version bump): SessionEntry gains
lastReadAt/markedUnreadAt/lastActivityAt; session rows expose a derived
unread flag (explicit mark, or last read before latest activity; never-read
sessions stay read so upgrades do not light up). lastActivityAt is stamped
in the canonical post-run store update - user, channel, and cron runs count
as activity; heartbeat, internal-event, and preserved-state runs do not.
sessions.patch gains unread; sessions.create gains fork (transcript fork
from parentSessionKey under the parent lifecycle lock, refusing active,
concurrently-changed, and oversized parents, cross-agent aware).
Web sidebar: Pinned/custom-group/Ungrouped sections, unread dots, kebab and
right-click context menu (pin, mark unread/read, rename, fork, move to group,
archive, delete guarded for agent main sessions and active runs), mark-read
on view with loop-safe re-acknowledgement and failure retry; sessions page
gets unread + fork actions and shared custom-group helpers.
iOS Command Center: grouped sections, unread/pin indicators, Show Archived
gated on per-entry state, full context menu with rename/new-group alerts and
delete confirmation, current-session preview guarantee, read-episode
re-acknowledgement; new patch/delete/fork transport calls; Swift protocol
models regenerated.
Android SessionsScreen: grouped headers, unread/pin indicators, Archived
filter gated on per-entry state, long-press menu with the full control set,
agent-scoped forks, explicit label/category clears from session events,
main-session fallback when archiving/deleting the open chat, read-episode
re-acknowledgement with failure retry.
Closes#100739
* feat(ios): durable offline command outbox for chat sends
Text messages sent while the gateway is unreachable queue in a durable
per-gateway outbox (new outbox_commands table in the per-gateway chat
cache SQLite store, schema v2) instead of failing. Queued bubbles render
with visible Queued/Sending/Not sent states and flush strictly in
createdAt order once transport health recovers; each command's client
UUID rides as the send idempotency key, so at-least-once delivery plus
gateway dedupe keeps the transcript exact.
Contract summary:
- Bounds: 50 queued commands per gateway; refused enqueues keep the
draft. Queued rows older than 48h expire to failed("expired") rather
than silently sending stale commands; tap-to-retry refreshes
createdAt so an expired row can resend as new intent.
- Failure taxonomy: transport-level failures keep rows queued without
burning retry attempts (backoff ladder, then health drops so the
reconnect machinery owns pacing); gateway rejections burn attempts
and fail terminally after 3, with context-menu retry/delete.
- Deletes are tombstoned synchronously and rechecked after the claim
await so an active flush can never send a removed command; Delete is
hidden while a bubble is already in flight; offline enqueue is
guarded against double submit during the health probe.
- Post-reconnect live sends route behind draining outbox rows (FIFO),
and a cold open assumes a backlog until restore adopts durable rows.
- Crash safety: 'sending' rows revert to 'queued' at startup; flushed
turns are spliced into the session's cached transcript before their
outbox row is deleted, and stale history snapshots cannot evict a
just-flushed turn until a snapshot confirms it.
- Per-gateway scoping and purge ride the transcript cache: one SQLite
file per gateway; reset/forget drops the queue with the cache.
Fixture/unpaired transports get no outbox.
Part of #46664
* chore(ios): sync native i18n inventory
* style(chat-ui): satisfy strict SwiftFormat lint (doc comment, scope blank line)
* fix(ios): restore in-flight chat runs from gateway history
Restore active Apple chat ownership across reconnect, foreground, and sequence-gap recovery using the existing chat.history snapshot. Preserve agent/session scoping and Gateway user-turn identity across Codex and Copilot mirrors, including current offline-cache integration.
* fix(ios): restore in-flight chat runs from gateway history
* feat(ios): read-only offline cache for chat sessions and transcripts
Cache-first cold open for the iOS chat tab: the last known transcript and
session list render immediately from a local SQLite cache, then live gateway
history replaces them wholesale through the existing reconciliation path.
When the gateway is unreachable, recent sessions and transcripts stay
browsable read-only; sending remains gated by the existing connection state.
- New OpenClawChatTranscriptCache protocol seam plus SQLite-backed store in
OpenClawChatUI (raw SQLite3, no new dependencies), scoped per gateway
stableID so transcripts never leak across paired gateways.
- Bounds: 50 sessions, 50 transcripts, 200 messages per session; text rows
only (attachment/binary payloads and tool arguments are stripped).
- Disposable cache: schema versioned via user_version; any open/schema/decode
mismatch drops and rebuilds silently. File protection
completeUntilFirstUserAuthentication on iOS.
- View model pre-paints from cache only until a live response applies
(hasAppliedLiveHistory/hasAppliedLiveSessions guards); write-through is
chained and off the render path.
- iOS wiring keys the chat view model identity on transport mode plus cache
gateway ID so switching paired gateways rebuilds the view model.
Part of #100194
* fix(ios): harden offline transcript cache
* fix(ios): expose offline cached sessions
* fix(ios): isolate transcript caches by gateway
* chore(i18n): sync native inventory
* fix(ios): allow cache extension to replace messages
* fix(ios): show gateway errors once as a swipeable animated toast
Gateway connection errors on the Settings > Gateway page appeared twice:
as the app-wide toast and again as an embedded banner section. Remove the
embedded banner (and its now-dead sheet/action helpers) so problems surface
only via the root toast, animate the toast in smoothly from the top, and
support swipe-up to dismiss with re-arm on new problems.
* fix(ios): re-surface gateway toast on repeated problem reports
A swiped-away toast stayed hidden when a retry failed with an identical
problem, because value equality alone produces no observable change. The
model now counts every problem report; a report while hidden re-shows the
toast, and a report while visible shakes it instead of stacking a new one.
* fix(ios): keep reset-onboarding action on the gateway toast and sync i18n inventory
The root toast is now the only gateway problem surface outside covers, so it
must keep the reset-onboarding primary action the removed settings banner had:
auth-token-mismatch problems now show Reset onboarding and run the full
GatewayOnboardingReset flow. Also refreshes the native i18n inventory for the
changed Swift strings.
* fix(ios): preserve gateway problem recovery state
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>