Skip to content

Mobile: agent presence shows offline after relay reconnect — no snapshot backstop, unlike desktop #6200

Description

@shawnhank

Summary

On mobile, agents (and other users) can show as "offline" in the app even while genuinely online and visible from desktop, for extended stretches after any relay reconnect. This is a confirmed asymmetry between the mobile and desktop presence implementations, not a hypothesis.

Root cause

PresenceCacheNotifier (mobile/lib/features/profile/presence_cache_provider.dart) is subscribe-only:

if (sessionState.status == SessionStatus.connected) {
  _subscribePresenceUpdates();
}
return {};

_subscribePresenceUpdates() opens a live kind:20001 subscription with limit: 0 — no snapshot of current state, only future events. track() (line 40-46) is explicitly a no-op for the actual fetch:

/// Currently a no-op for the actual fetch — we rely on live kind:20001
/// events. The tracked set is still used to filter incoming events so the
/// cache doesn't grow unbounded.
void track(List<String> pubkeys) { ... }
// TODO(presence): once the relay supports a `presence:true` filter
// extension, issue a one-shot fetch here for the latest known state per
// pubkey. Until then, presence is "online whenever they publish".

Every time sessionState transitions and build() re-fires — which happens on every relay reconnect — the presence map resets to {} and stays empty until a tracked pubkey happens to publish a fresh presence event during that session. iOS backgrounds/suspends the WebSocket aggressively (app backgrounding, network changes, lock screen), so mobile reconnects far more often than desktop ever does, and each reconnect blanks presence until something happens to re-publish.

Desktop has a real backstop for exactly this gap. desktop/src/features/presence/lib/presence.ts:40-50:

// get_presence omits offline/unknown pubkeys, so a live online event often
// targets a pubkey absent from the lookup — merge it in rather than dropping it.
export function mergePresenceUpdate(...)

Desktop calls a get_presence snapshot query and merges it with the live kind:20001 stream, so a reconnect doesn't wipe known state. Mobile has no equivalent call anywhere in presence_cache_provider.dart — this was apparently deferred pending a relay-side presence:true filter extension per the TODO, not an oversight introduced by a recent regression.

Suggested fix

Short term: add a one-shot presence snapshot fetch (mirroring desktop's get_presence call) in _subscribePresenceUpdates(), issued once per successful (re)connect, merged into state the same way desktop merges live updates into its query cache. Long term: swap to the relay-side presence:true filter extension mentioned in the TODO once it exists, to avoid a separate REST/query round trip.

Repro

Force a mobile relay reconnect (background the app for >30s, then foreground it, or toggle airplane mode briefly) while an agent that isn't actively posting is a member of a visible channel/DM. The agent will show offline on mobile until it next publishes, while desktop (open at the same time, same relay) shows it online throughout.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions