nebula-notebook 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +222 -0
  2. package/bin/nebula-notebook.js +33 -0
  3. package/dist/assets/index-C1h_sArD.css +32 -0
  4. package/dist/assets/index-CDSTBon8.js +658 -0
  5. package/dist/favicon.svg +11 -0
  6. package/dist/index.html +73 -0
  7. package/node-server/dist/app.d.ts +5 -0
  8. package/node-server/dist/app.js +38 -0
  9. package/node-server/dist/auth/auth-middleware.d.ts +24 -0
  10. package/node-server/dist/auth/auth-middleware.js +276 -0
  11. package/node-server/dist/auth/auth-service.d.ts +84 -0
  12. package/node-server/dist/auth/auth-service.js +265 -0
  13. package/node-server/dist/auth/index.d.ts +3 -0
  14. package/node-server/dist/auth/index.js +8 -0
  15. package/node-server/dist/cluster/client-registration.d.ts +43 -0
  16. package/node-server/dist/cluster/client-registration.js +217 -0
  17. package/node-server/dist/cluster/cluster-secret.d.ts +11 -0
  18. package/node-server/dist/cluster/cluster-secret.js +90 -0
  19. package/node-server/dist/cluster/kernel-proxy.d.ts +100 -0
  20. package/node-server/dist/cluster/kernel-proxy.js +361 -0
  21. package/node-server/dist/cluster/server-registry.d.ts +109 -0
  22. package/node-server/dist/cluster/server-registry.js +217 -0
  23. package/node-server/dist/config/output-limits.d.ts +7 -0
  24. package/node-server/dist/config/output-limits.js +10 -0
  25. package/node-server/dist/discovery/discovery-service.d.ts +198 -0
  26. package/node-server/dist/discovery/discovery-service.js +811 -0
  27. package/node-server/dist/discovery/index.d.ts +5 -0
  28. package/node-server/dist/discovery/index.js +21 -0
  29. package/node-server/dist/discovery/types.d.ts +48 -0
  30. package/node-server/dist/discovery/types.js +24 -0
  31. package/node-server/dist/fs/fs-service.d.ts +218 -0
  32. package/node-server/dist/fs/fs-service.js +1422 -0
  33. package/node-server/dist/fs/index.d.ts +5 -0
  34. package/node-server/dist/fs/index.js +21 -0
  35. package/node-server/dist/fs/types.d.ts +132 -0
  36. package/node-server/dist/fs/types.js +5 -0
  37. package/node-server/dist/index.d.ts +13 -0
  38. package/node-server/dist/index.js +556 -0
  39. package/node-server/dist/kernel/default-kernel.d.ts +5 -0
  40. package/node-server/dist/kernel/default-kernel.js +138 -0
  41. package/node-server/dist/kernel/index.d.ts +7 -0
  42. package/node-server/dist/kernel/index.js +23 -0
  43. package/node-server/dist/kernel/kernel-service.d.ts +290 -0
  44. package/node-server/dist/kernel/kernel-service.js +1714 -0
  45. package/node-server/dist/kernel/kernelspec.d.ts +29 -0
  46. package/node-server/dist/kernel/kernelspec.js +210 -0
  47. package/node-server/dist/kernel/session-store.d.ts +87 -0
  48. package/node-server/dist/kernel/session-store.js +303 -0
  49. package/node-server/dist/kernel/types.d.ts +143 -0
  50. package/node-server/dist/kernel/types.js +17 -0
  51. package/node-server/dist/llm/index.d.ts +5 -0
  52. package/node-server/dist/llm/index.js +21 -0
  53. package/node-server/dist/llm/llm-service.d.ts +77 -0
  54. package/node-server/dist/llm/llm-service.js +454 -0
  55. package/node-server/dist/llm/types.d.ts +40 -0
  56. package/node-server/dist/llm/types.js +15 -0
  57. package/node-server/dist/notebook/cell-metadata.d.ts +27 -0
  58. package/node-server/dist/notebook/cell-metadata.js +76 -0
  59. package/node-server/dist/notebook/headless-handler.d.ts +127 -0
  60. package/node-server/dist/notebook/headless-handler.js +1530 -0
  61. package/node-server/dist/notebook/notebook-websocket.d.ts +12 -0
  62. package/node-server/dist/notebook/notebook-websocket.js +103 -0
  63. package/node-server/dist/notebook/operation-router.d.ts +115 -0
  64. package/node-server/dist/notebook/operation-router.js +641 -0
  65. package/node-server/dist/notebook/undoRedoManager.d.ts +194 -0
  66. package/node-server/dist/notebook/undoRedoManager.js +558 -0
  67. package/node-server/dist/output/display-data.d.ts +14 -0
  68. package/node-server/dist/output/display-data.js +134 -0
  69. package/node-server/dist/resources/resource-service.d.ts +69 -0
  70. package/node-server/dist/resources/resource-service.js +363 -0
  71. package/node-server/dist/routes/auth.d.ts +5 -0
  72. package/node-server/dist/routes/auth.js +61 -0
  73. package/node-server/dist/routes/cluster.d.ts +7 -0
  74. package/node-server/dist/routes/cluster.js +94 -0
  75. package/node-server/dist/routes/fs.d.ts +7 -0
  76. package/node-server/dist/routes/fs.js +392 -0
  77. package/node-server/dist/routes/kernel.d.ts +13 -0
  78. package/node-server/dist/routes/kernel.js +637 -0
  79. package/node-server/dist/routes/llm.d.ts +8 -0
  80. package/node-server/dist/routes/llm.js +105 -0
  81. package/node-server/dist/routes/notebook.d.ts +10 -0
  82. package/node-server/dist/routes/notebook.js +335 -0
  83. package/node-server/dist/routes/python.d.ts +8 -0
  84. package/node-server/dist/routes/python.js +187 -0
  85. package/node-server/dist/routes/resources.d.ts +7 -0
  86. package/node-server/dist/routes/resources.js +77 -0
  87. package/node-server/dist/scripts/show-auth-qr.d.ts +1 -0
  88. package/node-server/dist/scripts/show-auth-qr.js +81 -0
  89. package/node-server/dist/terminal/pty-manager.d.ts +100 -0
  90. package/node-server/dist/terminal/pty-manager.js +246 -0
  91. package/node-server/dist/terminal/server.d.ts +19 -0
  92. package/node-server/dist/terminal/server.js +254 -0
  93. package/node-server/dist/terminal/types.d.ts +50 -0
  94. package/node-server/dist/terminal/types.js +9 -0
  95. package/node-server/package.json +45 -0
  96. package/package.json +99 -0
  97. package/scripts/postinstall.cjs +26 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Kernelspec Discovery
3
+ *
4
+ * Discovers Jupyter kernelspecs from standard locations.
5
+ * Similar to jupyter_client.kernelspec.find_kernel_specs()
6
+ */
7
+ import { KernelSpec } from './types';
8
+ export type { KernelSpec } from './types';
9
+ /**
10
+ * Standard kernelspec search paths
11
+ */
12
+ export declare function getKernelSearchPaths(): string[];
13
+ /**
14
+ * Discover all available kernelspecs on the system
15
+ * Uses 60-second in-memory cache to avoid repeated disk I/O
16
+ */
17
+ export declare function discoverKernelSpecs(forceRefresh?: boolean): KernelSpec[];
18
+ /**
19
+ * Invalidate the kernelspec cache (call after installing a new kernel)
20
+ */
21
+ export declare function invalidateKernelspecCache(): void;
22
+ /**
23
+ * Get a specific kernelspec by name
24
+ */
25
+ export declare function getKernelSpec(name: string): KernelSpec | null;
26
+ /**
27
+ * Check if a kernelspec exists
28
+ */
29
+ export declare function hasKernelSpec(name: string): boolean;
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ /**
3
+ * Kernelspec Discovery
4
+ *
5
+ * Discovers Jupyter kernelspecs from standard locations.
6
+ * Similar to jupyter_client.kernelspec.find_kernel_specs()
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.getKernelSearchPaths = getKernelSearchPaths;
43
+ exports.discoverKernelSpecs = discoverKernelSpecs;
44
+ exports.invalidateKernelspecCache = invalidateKernelspecCache;
45
+ exports.getKernelSpec = getKernelSpec;
46
+ exports.hasKernelSpec = hasKernelSpec;
47
+ const fs = __importStar(require("fs"));
48
+ const path = __importStar(require("path"));
49
+ const os = __importStar(require("os"));
50
+ // In-memory cache for kernelspecs (avoids repeated disk I/O)
51
+ let kernelspecCache = null;
52
+ let kernelspecCacheTime = 0;
53
+ const KERNELSPEC_CACHE_TTL_MS = 60 * 1000; // 60 seconds
54
+ /**
55
+ * Standard kernelspec search paths
56
+ */
57
+ function getKernelSearchPaths() {
58
+ const paths = [];
59
+ const home = os.homedir();
60
+ // User-local paths
61
+ if (process.platform === 'darwin') {
62
+ paths.push(path.join(home, 'Library', 'Jupyter', 'kernels'));
63
+ }
64
+ else if (process.platform === 'win32') {
65
+ paths.push(path.join(home, 'AppData', 'Roaming', 'jupyter', 'kernels'));
66
+ }
67
+ else {
68
+ paths.push(path.join(home, '.local', 'share', 'jupyter', 'kernels'));
69
+ }
70
+ // System paths
71
+ if (process.platform !== 'win32') {
72
+ paths.push('/usr/local/share/jupyter/kernels');
73
+ paths.push('/usr/share/jupyter/kernels');
74
+ }
75
+ // Conda paths
76
+ const condaPrefix = process.env.CONDA_PREFIX;
77
+ if (condaPrefix) {
78
+ paths.push(path.join(condaPrefix, 'share', 'jupyter', 'kernels'));
79
+ }
80
+ // JUPYTER_PATH environment variable
81
+ const jupyterPath = process.env.JUPYTER_PATH;
82
+ if (jupyterPath) {
83
+ for (const p of jupyterPath.split(path.delimiter)) {
84
+ if (p) {
85
+ paths.push(path.join(p, 'kernels'));
86
+ }
87
+ }
88
+ }
89
+ // Homebrew Python paths (macOS)
90
+ if (process.platform === 'darwin') {
91
+ const brewPaths = [
92
+ '/opt/homebrew/share/jupyter/kernels',
93
+ '/usr/local/opt/python/Frameworks/Python.framework/Versions/Current/share/jupyter/kernels',
94
+ ];
95
+ paths.push(...brewPaths);
96
+ }
97
+ // pyenv paths
98
+ const pyenvRoot = process.env.PYENV_ROOT || path.join(home, '.pyenv');
99
+ if (fs.existsSync(pyenvRoot)) {
100
+ const versionsDir = path.join(pyenvRoot, 'versions');
101
+ if (fs.existsSync(versionsDir)) {
102
+ try {
103
+ const versions = fs.readdirSync(versionsDir);
104
+ for (const version of versions) {
105
+ const kernelPath = path.join(versionsDir, version, 'share', 'jupyter', 'kernels');
106
+ paths.push(kernelPath);
107
+ }
108
+ }
109
+ catch {
110
+ // Ignore read errors
111
+ }
112
+ }
113
+ }
114
+ return paths;
115
+ }
116
+ /**
117
+ * Read a kernel.json file and return the kernelspec
118
+ */
119
+ function readKernelSpec(kernelDir) {
120
+ const kernelJsonPath = path.join(kernelDir, 'kernel.json');
121
+ if (!fs.existsSync(kernelJsonPath)) {
122
+ return null;
123
+ }
124
+ try {
125
+ const content = fs.readFileSync(kernelJsonPath, 'utf-8');
126
+ const spec = JSON.parse(content);
127
+ return {
128
+ name: path.basename(kernelDir),
129
+ displayName: spec.display_name || path.basename(kernelDir),
130
+ language: spec.language || 'python',
131
+ path: kernelDir,
132
+ argv: spec.argv,
133
+ env: spec.env,
134
+ };
135
+ }
136
+ catch (err) {
137
+ console.error(`Error reading kernelspec from ${kernelDir}:`, err);
138
+ return null;
139
+ }
140
+ }
141
+ /**
142
+ * Perform actual kernelspec discovery (disk I/O)
143
+ */
144
+ function performKernelspecDiscovery() {
145
+ const specs = [];
146
+ const seenNames = new Set();
147
+ const searchPaths = getKernelSearchPaths();
148
+ for (const searchPath of searchPaths) {
149
+ if (!fs.existsSync(searchPath)) {
150
+ continue;
151
+ }
152
+ try {
153
+ const entries = fs.readdirSync(searchPath, { withFileTypes: true });
154
+ for (const entry of entries) {
155
+ if (!entry.isDirectory()) {
156
+ continue;
157
+ }
158
+ const kernelName = entry.name;
159
+ if (seenNames.has(kernelName)) {
160
+ continue;
161
+ }
162
+ const kernelDir = path.join(searchPath, kernelName);
163
+ const spec = readKernelSpec(kernelDir);
164
+ if (spec) {
165
+ specs.push(spec);
166
+ seenNames.add(kernelName);
167
+ }
168
+ }
169
+ }
170
+ catch (err) {
171
+ // Ignore errors reading directories
172
+ }
173
+ }
174
+ return specs;
175
+ }
176
+ /**
177
+ * Discover all available kernelspecs on the system
178
+ * Uses 60-second in-memory cache to avoid repeated disk I/O
179
+ */
180
+ function discoverKernelSpecs(forceRefresh = false) {
181
+ const now = Date.now();
182
+ // Return from cache if valid
183
+ if (!forceRefresh && kernelspecCache && (now - kernelspecCacheTime) < KERNELSPEC_CACHE_TTL_MS) {
184
+ return kernelspecCache;
185
+ }
186
+ // Perform discovery and update cache
187
+ kernelspecCache = performKernelspecDiscovery();
188
+ kernelspecCacheTime = now;
189
+ return kernelspecCache;
190
+ }
191
+ /**
192
+ * Invalidate the kernelspec cache (call after installing a new kernel)
193
+ */
194
+ function invalidateKernelspecCache() {
195
+ kernelspecCache = null;
196
+ kernelspecCacheTime = 0;
197
+ }
198
+ /**
199
+ * Get a specific kernelspec by name
200
+ */
201
+ function getKernelSpec(name) {
202
+ const specs = discoverKernelSpecs();
203
+ return specs.find(s => s.name === name) || null;
204
+ }
205
+ /**
206
+ * Check if a kernelspec exists
207
+ */
208
+ function hasKernelSpec(name) {
209
+ return getKernelSpec(name) !== null;
210
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Session Store
3
+ *
4
+ * SQLite-based persistence for kernel sessions.
5
+ * Enables recovery after server restarts.
6
+ */
7
+ import { PersistedSession } from './types';
8
+ export declare class SessionStore {
9
+ private db;
10
+ constructor(dbPath?: string);
11
+ /**
12
+ * Create tables if they don't exist
13
+ */
14
+ private initDb;
15
+ /**
16
+ * Insert or update a session
17
+ */
18
+ saveSession(session: PersistedSession): void;
19
+ /**
20
+ * Get a session by ID
21
+ */
22
+ getSession(sessionId: string): PersistedSession | null;
23
+ /**
24
+ * Get all active sessions
25
+ */
26
+ getActiveSessions(): PersistedSession[];
27
+ /**
28
+ * Get orphaned sessions (from previous server run)
29
+ */
30
+ getOrphanedSessions(serverId?: string): PersistedSession[];
31
+ /**
32
+ * Update heartbeat timestamp
33
+ */
34
+ updateHeartbeat(sessionId: string): void;
35
+ /**
36
+ * Update session status
37
+ */
38
+ updateStatus(sessionId: string, status: 'active' | 'orphaned' | 'terminated'): void;
39
+ /**
40
+ * Delete a session
41
+ */
42
+ deleteSession(sessionId: string): void;
43
+ /**
44
+ * Mark all active sessions as orphaned (called on startup)
45
+ */
46
+ markAllOrphaned(serverId?: string, serverInstanceId?: string): number;
47
+ /**
48
+ * Get session by file path
49
+ */
50
+ getSessionByFile(filePath: string): PersistedSession | null;
51
+ /**
52
+ * Cleanup old terminated/orphaned sessions
53
+ */
54
+ cleanupOldSessions(maxAgeHours?: number): number;
55
+ /**
56
+ * Get dead sessions (orphaned or terminated) - candidates for cleanup
57
+ */
58
+ getDeadSessions(serverId?: string): PersistedSession[];
59
+ /**
60
+ * Delete specific sessions by ID
61
+ */
62
+ deleteSessions(sessionIds: string[]): number;
63
+ /**
64
+ * Save kernel preference for a notebook file.
65
+ */
66
+ saveNotebookKernelPreference(filePath: string, kernelName: string, serverId?: string | null): void;
67
+ /**
68
+ * Get kernel preference for a notebook file.
69
+ */
70
+ getNotebookKernelPreference(filePath: string): {
71
+ kernelName: string;
72
+ serverId: string | null;
73
+ updatedAt: number;
74
+ } | null;
75
+ /**
76
+ * Delete kernel preference for a notebook file.
77
+ */
78
+ deleteNotebookKernelPreference(filePath: string): void;
79
+ /**
80
+ * Close database connection
81
+ */
82
+ close(): void;
83
+ /**
84
+ * Convert database row to PersistedSession
85
+ */
86
+ private rowToSession;
87
+ }
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ /**
3
+ * Session Store
4
+ *
5
+ * SQLite-based persistence for kernel sessions.
6
+ * Enables recovery after server restarts.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.SessionStore = void 0;
46
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
47
+ const path = __importStar(require("path"));
48
+ class SessionStore {
49
+ db;
50
+ constructor(dbPath) {
51
+ const resolvedPath = dbPath || process.env.NEBULA_SESSIONS_DB_PATH || path.join(__dirname, '../../sessions.db');
52
+ this.db = new better_sqlite3_1.default(resolvedPath);
53
+ this.initDb();
54
+ }
55
+ /**
56
+ * Create tables if they don't exist
57
+ */
58
+ initDb() {
59
+ this.db.exec(`
60
+ CREATE TABLE IF NOT EXISTS sessions (
61
+ session_id TEXT PRIMARY KEY,
62
+ kernel_name TEXT NOT NULL,
63
+ file_path TEXT,
64
+ kernel_pid INTEGER,
65
+ server_id TEXT,
66
+ server_instance_id TEXT,
67
+ kernel_start_time TEXT,
68
+ status TEXT DEFAULT 'active',
69
+ created_at REAL NOT NULL,
70
+ last_heartbeat REAL NOT NULL,
71
+ connection_file TEXT,
72
+ connection_config TEXT
73
+ )
74
+ `);
75
+ this.db.exec(`
76
+ CREATE TABLE IF NOT EXISTS notebook_kernel_prefs (
77
+ file_path TEXT PRIMARY KEY,
78
+ kernel_name TEXT NOT NULL,
79
+ server_id TEXT,
80
+ updated_at REAL NOT NULL
81
+ )
82
+ `);
83
+ // Migration: add connection_config column if it doesn't exist
84
+ try {
85
+ this.db.exec('ALTER TABLE sessions ADD COLUMN connection_config TEXT');
86
+ }
87
+ catch {
88
+ // Column already exists
89
+ }
90
+ try {
91
+ this.db.exec('ALTER TABLE sessions ADD COLUMN server_id TEXT');
92
+ }
93
+ catch {
94
+ // Column already exists
95
+ }
96
+ try {
97
+ this.db.exec('ALTER TABLE sessions ADD COLUMN server_instance_id TEXT');
98
+ }
99
+ catch {
100
+ // Column already exists
101
+ }
102
+ try {
103
+ this.db.exec('ALTER TABLE sessions ADD COLUMN kernel_start_time TEXT');
104
+ }
105
+ catch {
106
+ // Column already exists
107
+ }
108
+ this.db.exec(`
109
+ CREATE INDEX IF NOT EXISTS idx_sessions_status
110
+ ON sessions(status)
111
+ `);
112
+ this.db.exec(`
113
+ CREATE INDEX IF NOT EXISTS idx_sessions_file_path
114
+ ON sessions(file_path) WHERE file_path IS NOT NULL
115
+ `);
116
+ this.db.exec(`
117
+ CREATE INDEX IF NOT EXISTS idx_notebook_kernel_prefs_updated_at
118
+ ON notebook_kernel_prefs(updated_at)
119
+ `);
120
+ }
121
+ /**
122
+ * Insert or update a session
123
+ */
124
+ saveSession(session) {
125
+ const stmt = this.db.prepare(`
126
+ INSERT OR REPLACE INTO sessions
127
+ (session_id, kernel_name, file_path, kernel_pid,
128
+ server_id, server_instance_id, kernel_start_time,
129
+ status, created_at, last_heartbeat,
130
+ connection_file, connection_config)
131
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
132
+ `);
133
+ stmt.run(session.sessionId, session.kernelName, session.filePath, session.kernelPid, session.serverId ?? null, session.serverInstanceId ?? null, session.kernelStartTime ?? null, session.status, session.createdAt, session.lastHeartbeat, session.connectionFile, session.connectionConfig);
134
+ }
135
+ /**
136
+ * Get a session by ID
137
+ */
138
+ getSession(sessionId) {
139
+ const stmt = this.db.prepare('SELECT * FROM sessions WHERE session_id = ?');
140
+ const row = stmt.get(sessionId);
141
+ if (row) {
142
+ return this.rowToSession(row);
143
+ }
144
+ return null;
145
+ }
146
+ /**
147
+ * Get all active sessions
148
+ */
149
+ getActiveSessions() {
150
+ const stmt = this.db.prepare("SELECT * FROM sessions WHERE status = 'active'");
151
+ const rows = stmt.all();
152
+ return rows.map(row => this.rowToSession(row));
153
+ }
154
+ /**
155
+ * Get orphaned sessions (from previous server run)
156
+ */
157
+ getOrphanedSessions(serverId) {
158
+ if (serverId) {
159
+ const stmt = this.db.prepare("SELECT * FROM sessions WHERE status = 'orphaned' AND (server_id = ? OR server_id IS NULL)");
160
+ const rows = stmt.all(serverId);
161
+ return rows.map(row => this.rowToSession(row));
162
+ }
163
+ const stmt = this.db.prepare("SELECT * FROM sessions WHERE status = 'orphaned'");
164
+ const rows = stmt.all();
165
+ return rows.map(row => this.rowToSession(row));
166
+ }
167
+ /**
168
+ * Update heartbeat timestamp
169
+ */
170
+ updateHeartbeat(sessionId) {
171
+ const stmt = this.db.prepare('UPDATE sessions SET last_heartbeat = ? WHERE session_id = ?');
172
+ stmt.run(Date.now() / 1000, sessionId);
173
+ }
174
+ /**
175
+ * Update session status
176
+ */
177
+ updateStatus(sessionId, status) {
178
+ const stmt = this.db.prepare('UPDATE sessions SET status = ? WHERE session_id = ?');
179
+ stmt.run(status, sessionId);
180
+ }
181
+ /**
182
+ * Delete a session
183
+ */
184
+ deleteSession(sessionId) {
185
+ const stmt = this.db.prepare('DELETE FROM sessions WHERE session_id = ?');
186
+ stmt.run(sessionId);
187
+ }
188
+ /**
189
+ * Mark all active sessions as orphaned (called on startup)
190
+ */
191
+ markAllOrphaned(serverId, serverInstanceId) {
192
+ if (serverId && serverInstanceId) {
193
+ const stmt = this.db.prepare("UPDATE sessions SET status = 'orphaned' WHERE status = 'active' AND (server_id = ? OR server_id IS NULL) AND (server_instance_id IS NULL OR server_instance_id != ?)");
194
+ const result = stmt.run(serverId, serverInstanceId);
195
+ return result.changes;
196
+ }
197
+ const stmt = this.db.prepare("UPDATE sessions SET status = 'orphaned' WHERE status = 'active'");
198
+ const result = stmt.run();
199
+ return result.changes;
200
+ }
201
+ /**
202
+ * Get session by file path
203
+ */
204
+ getSessionByFile(filePath) {
205
+ const stmt = this.db.prepare("SELECT * FROM sessions WHERE file_path = ? AND status = 'active'");
206
+ const row = stmt.get(filePath);
207
+ if (row) {
208
+ return this.rowToSession(row);
209
+ }
210
+ return null;
211
+ }
212
+ /**
213
+ * Cleanup old terminated/orphaned sessions
214
+ */
215
+ cleanupOldSessions(maxAgeHours = 24) {
216
+ const cutoff = (Date.now() / 1000) - (maxAgeHours * 3600);
217
+ const stmt = this.db.prepare("DELETE FROM sessions WHERE status IN ('terminated', 'orphaned') AND last_heartbeat < ?");
218
+ const result = stmt.run(cutoff);
219
+ return result.changes;
220
+ }
221
+ /**
222
+ * Get dead sessions (orphaned or terminated) - candidates for cleanup
223
+ */
224
+ getDeadSessions(serverId) {
225
+ if (serverId) {
226
+ const stmt = this.db.prepare("SELECT * FROM sessions WHERE status IN ('orphaned', 'terminated') AND (server_id = ? OR server_id IS NULL)");
227
+ const rows = stmt.all(serverId);
228
+ return rows.map(row => this.rowToSession(row));
229
+ }
230
+ const stmt = this.db.prepare("SELECT * FROM sessions WHERE status IN ('orphaned', 'terminated')");
231
+ const rows = stmt.all();
232
+ return rows.map(row => this.rowToSession(row));
233
+ }
234
+ /**
235
+ * Delete specific sessions by ID
236
+ */
237
+ deleteSessions(sessionIds) {
238
+ if (sessionIds.length === 0)
239
+ return 0;
240
+ const placeholders = sessionIds.map(() => '?').join(',');
241
+ const stmt = this.db.prepare(`DELETE FROM sessions WHERE session_id IN (${placeholders})`);
242
+ const result = stmt.run(...sessionIds);
243
+ return result.changes;
244
+ }
245
+ /**
246
+ * Save kernel preference for a notebook file.
247
+ */
248
+ saveNotebookKernelPreference(filePath, kernelName, serverId) {
249
+ const stmt = this.db.prepare(`
250
+ INSERT OR REPLACE INTO notebook_kernel_prefs
251
+ (file_path, kernel_name, server_id, updated_at)
252
+ VALUES (?, ?, ?, ?)
253
+ `);
254
+ stmt.run(filePath, kernelName, serverId ?? null, Date.now() / 1000);
255
+ }
256
+ /**
257
+ * Get kernel preference for a notebook file.
258
+ */
259
+ getNotebookKernelPreference(filePath) {
260
+ const stmt = this.db.prepare('SELECT kernel_name, server_id, updated_at FROM notebook_kernel_prefs WHERE file_path = ?');
261
+ const row = stmt.get(filePath);
262
+ if (!row)
263
+ return null;
264
+ return {
265
+ kernelName: row.kernel_name,
266
+ serverId: row.server_id ?? null,
267
+ updatedAt: row.updated_at,
268
+ };
269
+ }
270
+ /**
271
+ * Delete kernel preference for a notebook file.
272
+ */
273
+ deleteNotebookKernelPreference(filePath) {
274
+ const stmt = this.db.prepare('DELETE FROM notebook_kernel_prefs WHERE file_path = ?');
275
+ stmt.run(filePath);
276
+ }
277
+ /**
278
+ * Close database connection
279
+ */
280
+ close() {
281
+ this.db.close();
282
+ }
283
+ /**
284
+ * Convert database row to PersistedSession
285
+ */
286
+ rowToSession(row) {
287
+ return {
288
+ sessionId: row.session_id,
289
+ kernelName: row.kernel_name,
290
+ filePath: row.file_path,
291
+ kernelPid: row.kernel_pid,
292
+ serverId: row.server_id,
293
+ serverInstanceId: row.server_instance_id,
294
+ kernelStartTime: row.kernel_start_time,
295
+ status: row.status,
296
+ createdAt: row.created_at,
297
+ lastHeartbeat: row.last_heartbeat,
298
+ connectionFile: row.connection_file,
299
+ connectionConfig: row.connection_config,
300
+ };
301
+ }
302
+ }
303
+ exports.SessionStore = SessionStore;