# Session Persistence Implementation Plan

## Overview

Implement client-side session persistence to localStorage so browser refresh preserves conversation state. Server remains stateless - client is the source of truth.

## Problem Statement

**Current behavior:**
- Session cleared after every successful response (line 1019)
- Browser refresh loses conversation
- Every message starts fresh conversation

**Why current approach doesn't work:**
- If we don't clear: Stale sessions accumulate in localStorage
- If we do clear: Conversation lost after every message

**Solution:**
- Keep session during active connection
- Clear session on connection loss
- Auto-expire stale sessions (1 hour)

## Implementation Changes

### File: `connectonion-ts/src/connect.ts`

---

### 1. Add Session Persistence Methods

**Location:** After `_clearSessionId()` method (~line 688)

```typescript
/**
 * Save full session state to localStorage with timestamp.
 * Enables conversation recovery after browser refresh.
 * Sessions auto-expire after 1 hour to prevent stale data.
 */
private _saveSession(session: SessionState): void {
  if (isBrowserEnv() && typeof globalThis !== 'undefined' && 'localStorage' in globalThis && session.session_id) {
    try {
      const sessionWithTimestamp = {
        ...session,
        _savedAt: Date.now()
      };
      const key = `connectonion_session_${session.session_id}`;
      (globalThis as any).localStorage.setItem(key, JSON.stringify(sessionWithTimestamp));
    } catch {
      // Ignore localStorage errors (quota exceeded, disabled, etc.)
    }
  }
}

/**
 * Load session state from localStorage.
 * Returns null if session doesn't exist or is stale (> 1 hour old).
 */
private _loadSession(sessionId: string): SessionState | null {
  if (isBrowserEnv() && typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {
    try {
      const key = `connectonion_session_${sessionId}`;
      const stored = (globalThis as any).localStorage.getItem(key);
      if (stored) {
        const data = JSON.parse(stored);
        const age = Date.now() - (data._savedAt || 0);

        // Auto-expire sessions older than 1 hour
        if (age > 3600000) {
          (globalThis as any).localStorage.removeItem(key);
          return null;
        }

        // Remove timestamp before returning
        delete data._savedAt;
        return data as SessionState;
      }
    } catch {
      // Ignore localStorage errors
    }
  }
  return null;
}

/**
 * Load active session ID from localStorage.
 */
private _loadActiveSessionId(): string | null {
  if (isBrowserEnv() && typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {
    try {
      return (globalThis as any).localStorage.getItem('connectonion_active_session');
    } catch {
      return null;
    }
  }
  return null;
}

/**
 * Clear specific session from localStorage.
 */
private _clearSession(sessionId: string): void {
  if (isBrowserEnv() && typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {
    try {
      const key = `connectonion_session_${sessionId}`;
      (globalThis as any).localStorage.removeItem(key);
    } catch {
      // Ignore localStorage errors
    }
  }
}
```

---

### 2. Update `_clearSessionId()` to Also Clear Session Data

**Location:** ~line 680

**Change from:**
```typescript
private _clearSessionId(): void {
  if (isBrowserEnv() && typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {
    try {
      (globalThis as any).localStorage.removeItem('connectonion_active_session');
    } catch {
      // Ignore localStorage errors
    }
  }
}
```

**To:**
```typescript
private _clearSessionId(): void {
  if (isBrowserEnv() && typeof globalThis !== 'undefined' && 'localStorage' in globalThis) {
    try {
      // Get active session_id and clear its data
      const activeId = (globalThis as any).localStorage.getItem('connectonion_active_session');
      if (activeId) {
        this._clearSession(activeId);
      }
      (globalThis as any).localStorage.removeItem('connectonion_active_session');
    } catch {
      // Ignore localStorage errors
    }
  }
}
```

---

### 3. Load Session Before Sending INPUT

**Location:** `_streamInput()` method, before creating INPUT message (~line 782)

**Add BEFORE line 782:**
```typescript
// Try to restore session from localStorage if not in memory
const activeSessionId = this._loadActiveSessionId();
if (activeSessionId && !this._currentSession) {
  const loaded = this._loadSession(activeSessionId);
  if (loaded) {
    this._currentSession = loaded;
  }
}

// Now get session_id (from restored session or generate new)
const sessionId = this._currentSession?.session_id || generateUUID();
this._saveSessionId(sessionId);
```

**Current code at line 782:**
```typescript
const sessionId = this._currentSession?.session_id || generateUUID();
this._saveSessionId(sessionId);
```

**Replace with above code block.**

---

### 4. Save Session on OUTPUT (Keep for Next Message)

**Location:** ~line 1006

**Change from:**
```typescript
// Sync session from server
if (data.session) {
  this._currentSession = data.session;
}
```

**To:**
```typescript
// Sync session from server and persist to localStorage
if (data.session) {
  this._currentSession = data.session;
  this._saveSession(data.session);  // NEW: Save for next message
}
```

---

### 5. DON'T Clear Session on Success

**Location:** ~line 1019

**Change from:**
```typescript
this._activeWs = null;
this._clearSessionId();  // ❌ This clears session after every message!
try { ws.close(); } catch { /* ignore */ }
resolve({ text: result, done: true });
```

**To:**
```typescript
this._activeWs = null;
// Don't clear session - keep for next message in conversation
// Session will be cleared on connection loss or reset()
try { ws.close(); } catch { /* ignore */ }
resolve({ text: result, done: true });
```

---

### 6. Clear Session on Error

**Location:** ~line 1031 (ERROR handler)

**Keep existing behavior (already correct):**
```typescript
this._activeWs = null;
this._clearSessionId();  // ✅ Clear on error
try { ws.close(); } catch { /* ignore */ }
reject(new Error(`Agent error: ...`));
```

---

### 7. Clear Session on WebSocket Error

**Location:** ~line 1067 (ws.onerror)

**Keep existing behavior (already correct):**
```typescript
this._clearSessionId();  // ✅ Clear on connection error
reject(new Error(`WebSocket error: ${String(err)}`));
```

---

### 8. Clear Session on Connection Close

**Location:** ~line 1090 (ws.onclose, after polling attempt)

**After polling fails, around line 1090:**
```typescript
this._clearSessionId();  // ✅ Clear on connection close
reject(new Error('Connection closed'));
```

---

### 9. Clear Session on Timeout

**Location:** ~line 825 (timeout handler)

**Keep existing behavior (already correct):**
```typescript
this._clearSessionId();  // ✅ Clear on timeout
reject(new Error('Connection timed out'));
```

---

### 10. Clear Session on Reset

**Location:** `reset()` method (~line 493)

**Change from:**
```typescript
reset(): void {
  if (this._activeWs) {
    try { this._activeWs.close(); } catch { /* ignore */ }
    this._activeWs = null;
  }
  this._currentSession = null;
  this._chatItems = [];
  this._status = 'idle';
  this._uiIdCounter = 0;
}
```

**To:**
```typescript
reset(): void {
  if (this._activeWs) {
    try { this._activeWs.close(); } catch { /* ignore */ }
    this._activeWs = null;
  }

  // Clear session from localStorage
  if (this._currentSession?.session_id) {
    this._clearSession(this._currentSession.session_id);
  }
  this._clearSessionId();

  this._currentSession = null;
  this._chatItems = [];
  this._status = 'idle';
  this._uiIdCounter = 0;
}
```

---

### 11. Update SessionState Interface

**Location:** ~line 368

**Change from:**
```typescript
export interface SessionState {
  session_id?: string;
  messages?: Array<{ role: string; content: string }>;
  trace?: unknown[];
  turn?: number;
}
```

**To:**
```typescript
export interface SessionState {
  session_id?: string;
  messages?: Array<{ role: string; content: string }>;
  trace?: unknown[];
  turn?: number;
  _savedAt?: number;  // Internal: timestamp for staleness check
}
```

---

## Summary of Changes

### New Methods (4)
1. `_saveSession(session)` - Save to localStorage with timestamp
2. `_loadSession(sessionId)` - Load from localStorage, check staleness
3. `_loadActiveSessionId()` - Get active session_id from localStorage
4. `_clearSession(sessionId)` - Remove specific session from localStorage

### Modified Methods (3)
1. `_clearSessionId()` - Also clear session data, not just ID
2. `reset()` - Clear session from localStorage
3. `_streamInput()` - Load session before sending INPUT

### Modified Handlers (2)
1. OUTPUT handler - Save session, DON'T clear
2. (Keep existing: ERROR, onerror, onclose, timeout - all clear session)

### Modified Types (1)
1. `SessionState` - Add `_savedAt` field

---

## Behavior Matrix

| Event | Session in Memory | Session in localStorage | Action |
|-------|-------------------|-------------------------|--------|
| First INPUT | ❌ None | ❌ None | Generate new session_id |
| OUTPUT received | ✅ Update | ✅ Save with timestamp | Keep for next message |
| Second INPUT (same connection) | ✅ Exists | ✅ Exists | Use existing session |
| Browser refresh | ❌ Cleared | ✅ Exists | Load from localStorage |
| WebSocket error | ❌ Clear | ❌ Clear | Connection lost |
| WebSocket close | ❌ Clear | ❌ Clear | Connection lost |
| Timeout | ❌ Clear | ❌ Clear | Connection lost |
| User calls reset() | ❌ Clear | ❌ Clear | User action |
| Load stale session (> 1hr) | ❌ None | ❌ Remove | Auto-cleanup |

---

## Testing Checklist

- [ ] First message creates new session
- [ ] Second message continues conversation (uses same session)
- [ ] Browser refresh preserves conversation
- [ ] Network disconnect clears session
- [ ] Timeout clears session
- [ ] Error clears session
- [ ] reset() clears session
- [ ] Stale session (> 1 hour) auto-expires
- [ ] localStorage quota exceeded doesn't crash
- [ ] Non-browser environment (Node.js) works without localStorage

---

## File Changes Summary

**Files to modify:**
- `connectonion-ts/src/connect.ts` (all changes in one file)

**Lines to modify:**
- ~368: Update SessionState interface
- ~493: Update reset() method
- ~680: Update _clearSessionId() method
- ~688: Add 4 new methods
- ~782: Load session before INPUT
- ~1006: Save session on OUTPUT
- ~1019: Remove clearSessionId on success
- (Keep existing clears at ~1031, ~1067, ~1090, ~825)

**Total changes:**
- ~50 lines of new code
- ~10 lines modified
- All in single file

---

## Migration Notes

**Backwards compatibility:**
- Existing code continues to work
- Old sessions in localStorage will auto-expire
- No breaking API changes
- Server-side unchanged

**Performance impact:**
- Minimal: localStorage read/write only on browser
- No network overhead
- No server-side changes

**Storage impact:**
- ~1-10KB per active session
- Auto-cleanup after 1 hour
- Browser localStorage limit: ~5-10MB (hundreds of sessions)
