Session Database
Manages terminal sessions, heartbeats, and crash recovery.Copy
╔═════════════════════════════════════════════════════════════════════╗
║ SESSION.DB OVERVIEW ║
╠═════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌─────────────────────────┐ ┌─────────────────────────┐ ║
║ │ terminal_heartbeats │ │ terminals │ ║
║ │ │ │ │ ║
║ │ - terminal_id (PK) │ │ - id (PK) │ ║
║ │ - instance_id │ │ - name │ ║
║ │ - last_heartbeat │ │ - created_at │ ║
║ │ - pid │ │ - last_active │ ║
║ │ - is_alive │ │ - ended_at │ ║
║ │ - cols, rows │ │ - is_active │ ║
║ │ - cwd │ │ - settings │ ║
║ │ - terminal_name │ │ │ ║
║ │ - pty_path │ │ │ ║
║ └─────────────────────────┘ └─────────────────────────┘ ║
║ ║
║ ┌─────────────────────────┐ ┌─────────────────────────┐ ║
║ │ terminal_sessions │ │ terminal_group_log │ ║
║ │ │ │ │ ║
║ │ - session_id (UNIQUE) │ │ - group_id │ ║
║ │ - tab_name │ │ - group_name │ ║
║ │ - scrollback │ │ - tab_ids │ ║
║ │ - cols, rows │ │ - terminal_names │ ║
║ │ - cursor_x, cursor_y │ │ - created_at │ ║
║ │ - timestamp │ │ │ ║
║ └─────────────────────────┘ └─────────────────────────┘ ║
║ ║
║ session.db ║
╚═════════════════════════════════════════════════════════════════════╝
Session State Machine
How terminal sessions transition through states.Copy
╔═════════════════════════════════════════════════════════════════════╗
║ SESSION STATE MACHINE ║
╠═════════════════════════════════════════════════════════════════════╣
║ ║
║ Terminal Created ║
║ │ ║
║ │ save_terminal() ║
║ │ INSERT (is_alive=1) ║
║ ▼ ║
║ ┌───────────────────┐ ║
║ │ ALIVE │◀─────────────────────────────────────────┐ ║
║ │ │ │ ║
║ │ - Responding │ │ ║
║ │ - Heartbeat OK │ │ ║
║ └───────────────────┘ │ ║
║ │ │ │ ║
║ │ │ (every 10 seconds) │ ║
║ │ │ heartbeat() │ ║
║ │ │ UPDATE last_heartbeat = NOW │ ║
║ │ └─────────────────────────────────────────────┘ ║
║ │ ║
║ ├────────────────────────────┐ ║
║ │ │ ║
║ │ User closes tab │ Heartbeat timeout (30s) ║
║ │ end_terminal() │ cleanup_dead_terminals() ║
║ │ UPDATE is_alive=0 │ UPDATE is_alive=0 ║
║ ▼ ▼ ║
║ ┌───────────────────┐ ┌───────────────────┐ ║
║ │ ENDED │ │ DEAD │ ║
║ │ │ │ │ ║
║ │ - Graceful close │ │ - Stale session │ ║
║ │ - User initiated │ │ - No heartbeat │ ║
║ └───────────────────┘ └───────────────────┘ ║
║ │ ║
║ │ App restart ║
║ │ Socket file exists? ║
║ │ recover_orphaned_pty() ║
║ ▼ ║
║ ┌───────────────────┐ ║
║ │ RECOVERED │ ║
║ │ │ ║
║ │ - Reconnected │ ║
║ │ - PTY restored │ ║
║ └───────────────────┘ ║
║ │ ║
║ │ UPDATE is_alive=1 ║
║ └─────────────────────────────────▶┘
║ ║
╚═════════════════════════════════════════════════════════════════════╝
Heartbeat Flow
How session health is monitored.Copy
╔═════════════════════════════════════════════════════════════════════╗
║ HEARTBEAT FLOW ║
╠═════════════════════════════════════════════════════════════════════╣
║ ║
║ Frontend (10-second interval) ║
║ │ ║
║ │ invoke("session_db_heartbeat", { terminal_id }) ║
║ ▼ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ UPDATE terminal_heartbeats │ ║
║ │ SET last_heartbeat = NOW() │ ║
║ │ WHERE terminal_id = ? │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
║ TIMING PARAMETERS ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ Parameter │ Value │ Purpose │ ║
║ │ ═════════════════│═════════│════════════════════════════════│ ║
║ │ Heartbeat │ 10 sec │ Frontend sends heartbeat │ ║
║ │ interval │ │ │ ║
║ │ Stale threshold │ 30 sec │ Terminal marked dead if │ ║
║ │ │ │ no heartbeat for 30 seconds │ ║
║ │ Recovery window │ Startup │ Check for orphaned PTYs │ ║
║ │ │ only │ at app startup │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
║ TIMELINE ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ 0s 10s 20s 30s 40s │ ║
║ │ │─────────│─────────│─────────│─────────│ │ ║
║ │ ▲ ▲ ▲ ✕ │ ║
║ │ │ │ │ │ │ ║
║ │ HB HB HB Marked DEAD (no HB for 30s) │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
Crash Recovery Flow
How sessions are recovered after app crash.Copy
╔═════════════════════════════════════════════════════════════════════╗
║ CRASH RECOVERY FLOW ║
╠═════════════════════════════════════════════════════════════════════╣
║ ║
║ App Startup ║
║ │ ║
║ ▼ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ Step 1: Cleanup stale terminals │ ║
║ │ │ ║
║ │ UPDATE terminal_heartbeats │ ║
║ │ SET is_alive = 0 │ ║
║ │ WHERE last_heartbeat < (NOW - 30 seconds) │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ │ ║
║ ▼ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ Step 2: Find recoverable terminals │ ║
║ │ │ ║
║ │ SELECT * FROM terminal_heartbeats │ ║
║ │ WHERE is_alive = 0 │ ║
║ │ AND last_heartbeat > (NOW - 5 minutes) │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ │ ║
║ ▼ ║
║ For each terminal: ║
║ │ ║
║ └───▶ [PTY socket file exists on disk?] ║
║ │ │ ║
║ │ │ No ──▶ Mark as DEAD, continue ║
║ │ │ ║
║ │ │ Yes ║
║ │ ▼ ║
║ │ [Can connect to socket?] ║
║ │ │ ║
║ │ │ No (ECONNREFUSED) ║
║ │ │ ──▶ Delete socket file, mark DEAD ║
║ │ │ ║
║ │ │ Yes ║
║ │ ▼ ║
║ │ [PTY responds to ping?] ║
║ │ │ ║
║ │ │ No (timeout) ║
║ │ │ ──▶ Close socket, mark DEAD ║
║ │ │ ║
║ │ │ Yes ║
║ │ ▼ ║
║ │ RECOVERED! ║
║ │ UPDATE is_alive = 1 ║
║ │ Attach to session ║
║ │ ║
║ └───▶ Next terminal ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
Scrollback Storage
Persist terminal content for recovery.Copy
╔═════════════════════════════════════════════════════════════════════╗
║ SCROLLBACK PERSISTENCE ║
╠═════════════════════════════════════════════════════════════════════╣
║ ║
║ SCROLLBACK JSON FORMAT ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ { │ ║
║ │ "lines": [ │ ║
║ │ { "cells": [...], "isWrapped": false }, │ ║
║ │ { "cells": [...], "isWrapped": true }, │ ║
║ │ ... │ ║
║ │ ], │ ║
║ │ "cursorX": 0, │ ║
║ │ "cursorY": 24, │ ║
║ │ "viewportY": 0 │ ║
║ │ } │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
║ SAVE TRIGGERS ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ - Periodic (configurable interval) │ ║
║ │ - On tab close (graceful shutdown) │ ║
║ │ - Before app quit │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
║ RESTORE TRIGGERS ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ - Tab reopen (same session_id) │ ║
║ │ - Crash recovery (orphaned PTY attach) │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
Write/Read Operations
Copy
╔═════════════════════════════════════════════════════════════════════╗
║ WRITE/READ OPERATIONS ║
╠═════════════════════════════════════════════════════════════════════╣
║ ║
║ WRITE ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ Function │ SQL │ Trigger │ ║
║ │ ══════════════════════│═════════════════════│════════════════│ ║
║ │ save_terminal │ INSERT OR REPLACE │ Tab open │ ║
║ │ │ terminal_heartbeats │ │ ║
║ │ heartbeat │ UPDATE │ 10s timer │ ║
║ │ │ last_heartbeat │ │ ║
║ │ end_terminal │ UPDATE is_alive=0 │ Tab close │ ║
║ │ cleanup_dead │ UPDATE is_alive=0 │ App start │ ║
║ │ │ (30s stale check) │ │ ║
║ │ save_scrollback │ INSERT/UPDATE │ Periodic │ ║
║ │ │ terminal_sessions │ │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
║ READ ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ │ ║
║ │ Function │ SQL │ ║
║ │ ══════════════════════│═════════════════════════════════════│ ║
║ │ get_alive_terminals │ SELECT WHERE is_alive=1 │ ║
║ │ get_max_terminal_num │ SELECT MAX(terminal_number) │ ║
║ │ get_scrollback │ SELECT WHERE session_id=? │ ║
║ │ │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
Design Principles Applied
Copy
╔═════════════════════════════════════════════════════════════════════╗
║ DESIGN PRINCIPLES ║
╠═════════════════════════════════════════════════════════════════════╣
║ ║
║ SIMPLICITY (SMPC) ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ [x] Simple heartbeat mechanism (10s UPDATE) │ ║
║ │ [x] Binary is_alive state (0 or 1) │ ║
║ │ [x] 30-second stale threshold │ ║
║ │ [x] No complex state machine in DB │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
║ ORDER FROM CHAOS (OFAC) ║
║ ┌───────────────────────────────────────────────────────────────┐ ║
║ │ [x] Crash recovery via is_alive + socket file existence │ ║
║ │ [x] cleanup_dead_terminals() on startup │ ║
║ │ [x] Scrollback persistence for session restore │ ║
║ │ [x] terminal_group_log for audit trail │ ║
║ └───────────────────────────────────────────────────────────────┘ ║
║ ║
╚═════════════════════════════════════════════════════════════════════╝
Quick Reference
| Aspect | Value |
|---|---|
| Location | ~/Library/Application Support/Monolex/protocols/niia/database/session.db |
| Primary Owner | Session DB module |
| Main Tables | terminal_heartbeats, terminal_sessions |
| Heartbeat Interval | 10 seconds |
| Stale Threshold | 30 seconds |
| Recovery | At app startup only |