Skip to content

feat: cross-device session continuity — resume conversation across desktop and mobile #121

@devartifex

Description

@devartifex

Problem

When switching between devices (desktop → mobile or vice versa), the user starts with an empty conversation rather than being able to continue from where they left off.

Currently:

  • Each browser has its own tabId (UUID stored in localStorage) and its own express-session cookie
  • Chat state is stored per userId + tabId, so desktop and mobile have entirely separate histories
  • There is no concept of a "current conversation" that spans devices

Proposed Solution

Add a primary session concept per GitHub user that allows any device to load and resume the most recently active conversation.

Phase 3a — Shared Conversation History (read)

  1. Primary session metadata file per user: {CHAT_STATE_PATH}/{userId}/primary-session.json

    • Contains: { tabId, sdkSessionId, model, mode, updatedAt }
    • Updated whenever a new message is added to the active conversation
  2. On WS connect, if the connecting device has no local chat history for its tabId:

    • Check for a primary session owned by the user
    • If found and recent (< 24h), offer to resume: send a primary_session_available message with the history
    • Client shows a "Continue your conversation" prompt, or auto-loads if the device has never chatted
  3. UI change: new ContinueSession banner component or modal

Phase 3b — Active SDK Session Resume

  1. SDK session resume across devices:

    • Store the Copilot SDK sessionId in the primary session metadata
    • When reconnecting, call client.resumeSession(sdkSessionId) to reconnect to the same backend session
    • Handle gracefully when the SDK session has expired (fall back to new session)
  2. Conflict resolution:

    • Pool key is still : — each device has its own WS pool entry
    • If both devices try to send simultaneously, the second request is queued (already handled by isProcessing)
    • Add a session_taken server message when another device is actively processing, with a short UI indicator

Phase 3c — Real-time Sync (optional enhancement)

  1. Broadcast all assistant messages to all connected devices for the same user (fan-out in poolSend)
    • Both desktop and mobile update in real-time when a response arrives on either

Files to Create/Modify

File Change
src/lib/server/chat-state-store.ts Add setPrimarySession() / getPrimarySession()
src/lib/server/ws/handler.ts Check primary session on connect; send primary_session_available
src/lib/server/ws/session-events.ts Update primary session metadata on turn_end
src/lib/types/index.ts Add primary_session_available, session_taken message types
src/lib/stores/chat.svelte.ts Handle primary_session_available message
src/lib/components/ New ContinueSessionBanner.svelte component

Notes

  • The tabId in localStorage is per-browser-profile (not per-tab). Desktop and mobile generate independent UUIDs — no conflict.
  • This is purely additive — existing per-device behavior is unchanged for users who don't want cross-device sync.
  • Phase 3a alone provides significant value and is low-risk.
  • Auth persistence (PR fix: auth persistence via encrypted cookie + notification system hardening #120) is a prerequisite — cross-device is only useful if auth survives device switches.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions