@runtimescope/collector 0.7.0 → 0.7.2

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.
@@ -297,40 +297,84 @@ var EventStore = class {
297
297
 
298
298
  // src/sqlite-store.ts
299
299
  import Database from "better-sqlite3";
300
- var SqliteStore = class {
300
+ import { renameSync, existsSync } from "fs";
301
+ var SqliteStore = class _SqliteStore {
301
302
  db;
302
303
  writeBuffer = [];
303
304
  flushTimer = null;
304
305
  batchSize;
306
+ dbPath;
307
+ static MAX_SNAPSHOTS_PER_SESSION = 50;
305
308
  insertEventStmt;
306
309
  insertSessionStmt;
307
310
  updateSessionDisconnectedStmt;
308
311
  constructor(options) {
309
- this.db = new Database(options.dbPath);
312
+ this.dbPath = options.dbPath;
310
313
  this.batchSize = options.batchSize ?? 50;
311
- if (options.walMode !== false) {
312
- this.db.pragma("journal_mode = WAL");
314
+ this.db = this.openDatabase(options);
315
+ const flushInterval = options.flushIntervalMs ?? 100;
316
+ this.flushTimer = setInterval(() => this.flush(), flushInterval);
317
+ }
318
+ openDatabase(options) {
319
+ try {
320
+ const db = new Database(options.dbPath);
321
+ if (options.walMode !== false) {
322
+ db.pragma("journal_mode = WAL");
323
+ }
324
+ db.pragma("synchronous = NORMAL");
325
+ const check = db.pragma("integrity_check");
326
+ if (check[0]?.integrity_check !== "ok") {
327
+ throw new Error("Integrity check failed");
328
+ }
329
+ this.createSchema(db);
330
+ this.prepareStatements(db);
331
+ return db;
332
+ } catch (err) {
333
+ console.error(
334
+ `[RuntimeScope] SQLite database corrupt or unreadable (${err.message}), recreating...`
335
+ );
336
+ try {
337
+ if (existsSync(options.dbPath)) {
338
+ const backupPath = `${options.dbPath}.corrupt.${Date.now()}`;
339
+ renameSync(options.dbPath, backupPath);
340
+ console.error(`[RuntimeScope] Renamed corrupt DB to ${backupPath}`);
341
+ }
342
+ for (const suffix of ["-wal", "-shm"]) {
343
+ const p = options.dbPath + suffix;
344
+ if (existsSync(p)) {
345
+ renameSync(p, `${p}.corrupt.${Date.now()}`);
346
+ }
347
+ }
348
+ } catch {
349
+ }
350
+ const db = new Database(options.dbPath);
351
+ if (options.walMode !== false) {
352
+ db.pragma("journal_mode = WAL");
353
+ }
354
+ db.pragma("synchronous = NORMAL");
355
+ this.createSchema(db);
356
+ this.prepareStatements(db);
357
+ return db;
313
358
  }
314
- this.db.pragma("synchronous = NORMAL");
315
- this.createSchema();
316
- this.insertEventStmt = this.db.prepare(`
359
+ }
360
+ prepareStatements(db) {
361
+ this.insertEventStmt = db.prepare(`
317
362
  INSERT INTO events (event_id, session_id, project, event_type, timestamp, data)
318
363
  VALUES (?, ?, ?, ?, ?, ?)
319
364
  `);
320
- this.insertSessionStmt = this.db.prepare(`
365
+ this.insertSessionStmt = db.prepare(`
321
366
  INSERT OR REPLACE INTO sessions (
322
367
  session_id, project, app_name, connected_at, sdk_version,
323
368
  event_count, is_connected, build_meta
324
369
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
325
370
  `);
326
- this.updateSessionDisconnectedStmt = this.db.prepare(`
371
+ this.updateSessionDisconnectedStmt = db.prepare(`
327
372
  UPDATE sessions SET is_connected = 0, disconnected_at = ? WHERE session_id = ?
328
373
  `);
329
- const flushInterval = options.flushIntervalMs ?? 100;
330
- this.flushTimer = setInterval(() => this.flush(), flushInterval);
331
374
  }
332
- createSchema() {
333
- this.db.exec(`
375
+ createSchema(db) {
376
+ const d = db ?? this.db;
377
+ d.exec(`
334
378
  CREATE TABLE IF NOT EXISTS events (
335
379
  id INTEGER PRIMARY KEY AUTOINCREMENT,
336
380
  event_id TEXT NOT NULL UNIQUE,
@@ -374,7 +418,7 @@ var SqliteStore = class {
374
418
  CREATE INDEX IF NOT EXISTS idx_snapshots_session ON session_snapshots(session_id);
375
419
  CREATE INDEX IF NOT EXISTS idx_snapshots_project ON session_snapshots(project, created_at);
376
420
  `);
377
- this.migrateSessionMetrics();
421
+ this.migrateSessionMetrics(d);
378
422
  }
379
423
  // --- Write Operations ---
380
424
  addEvent(event, project) {
@@ -427,6 +471,21 @@ var SqliteStore = class {
427
471
  INSERT INTO session_snapshots (session_id, project, label, metrics, created_at)
428
472
  VALUES (?, ?, ?, ?, ?)
429
473
  `).run(sessionId, project, label ?? null, JSON.stringify(metrics), Date.now());
474
+ this.pruneSnapshots(sessionId);
475
+ }
476
+ /** Remove oldest snapshots for a session beyond the retention limit */
477
+ pruneSnapshots(sessionId) {
478
+ const count = this.db.prepare("SELECT COUNT(*) as cnt FROM session_snapshots WHERE session_id = ?").get(sessionId).cnt;
479
+ if (count > _SqliteStore.MAX_SNAPSHOTS_PER_SESSION) {
480
+ this.db.prepare(`
481
+ DELETE FROM session_snapshots WHERE id IN (
482
+ SELECT id FROM session_snapshots
483
+ WHERE session_id = ?
484
+ ORDER BY created_at ASC
485
+ LIMIT ?
486
+ )
487
+ `).run(sessionId, count - _SqliteStore.MAX_SNAPSHOTS_PER_SESSION);
488
+ }
430
489
  }
431
490
  // --- Read Operations ---
432
491
  getEvents(filter) {
@@ -552,17 +611,17 @@ var SqliteStore = class {
552
611
  return rows.map((row) => JSON.parse(row.data));
553
612
  }
554
613
  // --- Migration ---
555
- migrateSessionMetrics() {
556
- const hasOldTable = this.db.prepare(
614
+ migrateSessionMetrics(db) {
615
+ const hasOldTable = db.prepare(
557
616
  "SELECT name FROM sqlite_master WHERE type='table' AND name='session_metrics'"
558
617
  ).get();
559
618
  if (hasOldTable) {
560
- this.db.exec(`
619
+ db.exec(`
561
620
  INSERT OR IGNORE INTO session_snapshots (session_id, project, label, metrics, created_at)
562
621
  SELECT session_id, project, 'auto-disconnect', metrics, created_at
563
622
  FROM session_metrics
564
623
  `);
565
- this.db.exec("DROP TABLE session_metrics");
624
+ db.exec("DROP TABLE session_metrics");
566
625
  }
567
626
  }
568
627
  // --- Maintenance ---
@@ -583,6 +642,26 @@ var SqliteStore = class {
583
642
  }
584
643
  };
585
644
 
645
+ // src/sqlite-check.ts
646
+ import { createRequire } from "module";
647
+ var _checked = false;
648
+ var _available = false;
649
+ function isSqliteAvailable() {
650
+ if (_checked) return _available;
651
+ _checked = true;
652
+ try {
653
+ const require2 = createRequire(import.meta.url);
654
+ require2("better-sqlite3");
655
+ _available = true;
656
+ } catch {
657
+ _available = false;
658
+ console.error(
659
+ "[RuntimeScope] better-sqlite3 is not available \u2014 running in memory-only mode.\n[RuntimeScope] Historical data persistence is disabled. To fix this:\n[RuntimeScope] macOS: xcode-select --install\n[RuntimeScope] Ubuntu: sudo apt-get install build-essential python3\n[RuntimeScope] Windows: npm install --global windows-build-tools\n[RuntimeScope] Then run: npm rebuild better-sqlite3"
660
+ );
661
+ }
662
+ return _available;
663
+ }
664
+
586
665
  // src/rate-limiter.ts
587
666
  var SessionRateLimiter = class {
588
667
  windows = /* @__PURE__ */ new Map();
@@ -707,6 +786,7 @@ var CollectorServer = class {
707
786
  connectCallbacks = [];
708
787
  disconnectCallbacks = [];
709
788
  pruneTimer = null;
789
+ heartbeatTimer = null;
710
790
  tlsConfig = null;
711
791
  constructor(options = {}) {
712
792
  this.store = new EventStore(options.bufferSize ?? 1e4);
@@ -766,6 +846,8 @@ var CollectorServer = class {
766
846
  httpsServer.on("listening", () => {
767
847
  this.wss = wss;
768
848
  this.setupConnectionHandler(wss);
849
+ this.setupPersistentErrorHandler(wss);
850
+ this.startHeartbeat(wss);
769
851
  console.error(`[RuntimeScope] Collector listening on wss://${host}:${port}`);
770
852
  resolve2();
771
853
  });
@@ -779,6 +861,8 @@ var CollectorServer = class {
779
861
  wss.on("listening", () => {
780
862
  this.wss = wss;
781
863
  this.setupConnectionHandler(wss);
864
+ this.setupPersistentErrorHandler(wss);
865
+ this.startHeartbeat(wss);
782
866
  console.error(`[RuntimeScope] Collector listening on ws://${host}:${port}`);
783
867
  resolve2();
784
868
  });
@@ -804,6 +888,7 @@ var CollectorServer = class {
804
888
  }
805
889
  ensureSqliteStore(projectName) {
806
890
  if (!this.projectManager) return null;
891
+ if (!isSqliteAvailable()) return null;
807
892
  let sqliteStore = this.sqliteStores.get(projectName);
808
893
  if (!sqliteStore) {
809
894
  try {
@@ -823,8 +908,33 @@ var CollectorServer = class {
823
908
  }
824
909
  return sqliteStore;
825
910
  }
911
+ /** Catch runtime errors on the WSS so an unhandled error doesn't crash the process */
912
+ setupPersistentErrorHandler(wss) {
913
+ wss.on("error", (err) => {
914
+ console.error("[RuntimeScope] WebSocket server runtime error:", err.message);
915
+ });
916
+ }
917
+ /** Ping all connected clients every 15s — terminate those that don't respond */
918
+ startHeartbeat(wss) {
919
+ this.heartbeatTimer = setInterval(() => {
920
+ for (const ws of wss.clients) {
921
+ const ext = ws;
922
+ if (ext._rsAlive === false) {
923
+ ws.terminate();
924
+ continue;
925
+ }
926
+ ext._rsAlive = false;
927
+ ws.ping();
928
+ }
929
+ }, 15e3);
930
+ }
826
931
  setupConnectionHandler(wss) {
827
932
  wss.on("connection", (ws) => {
933
+ const ext = ws;
934
+ ext._rsAlive = true;
935
+ ws.on("pong", () => {
936
+ ext._rsAlive = true;
937
+ });
828
938
  if (this.authManager?.isEnabled()) {
829
939
  this.pendingHandshakes.add(ws);
830
940
  const authTimeout = setTimeout(() => {
@@ -914,6 +1024,15 @@ var CollectorServer = class {
914
1024
  };
915
1025
  sqliteStore.saveSession(sessionInfo);
916
1026
  }
1027
+ this.store.addEvent({
1028
+ eventId: `session-${payload.sessionId}`,
1029
+ sessionId: payload.sessionId,
1030
+ timestamp: msg.timestamp,
1031
+ eventType: "session",
1032
+ appName: payload.appName,
1033
+ connectedAt: msg.timestamp,
1034
+ sdkVersion: payload.sdkVersion
1035
+ });
917
1036
  console.error(
918
1037
  `[RuntimeScope] Session ${payload.sessionId} connected (${payload.appName} v${payload.sdkVersion})`
919
1038
  );
@@ -1010,10 +1129,27 @@ var CollectorServer = class {
1010
1129
  });
1011
1130
  }
1012
1131
  stop() {
1132
+ if (this.heartbeatTimer) {
1133
+ clearInterval(this.heartbeatTimer);
1134
+ this.heartbeatTimer = null;
1135
+ }
1013
1136
  if (this.pruneTimer) {
1014
1137
  clearInterval(this.pruneTimer);
1015
1138
  this.pruneTimer = null;
1016
1139
  }
1140
+ if (this.wss) {
1141
+ for (const client of this.wss.clients) {
1142
+ if (client.readyState === 1) {
1143
+ try {
1144
+ client.send(JSON.stringify({
1145
+ type: "__server_restart",
1146
+ timestamp: Date.now()
1147
+ }));
1148
+ } catch {
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1017
1153
  for (const [name, sqliteStore] of this.sqliteStores) {
1018
1154
  try {
1019
1155
  sqliteStore.close();
@@ -1031,7 +1167,7 @@ var CollectorServer = class {
1031
1167
  };
1032
1168
 
1033
1169
  // src/project-manager.ts
1034
- import { mkdirSync, readFileSync as readFileSync2, writeFileSync, existsSync, readdirSync } from "fs";
1170
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, readdirSync } from "fs";
1035
1171
  import { join } from "path";
1036
1172
  import { homedir } from "os";
1037
1173
  var DEFAULT_GLOBAL_CONFIG = {
@@ -1063,7 +1199,7 @@ var ProjectManager = class {
1063
1199
  this.mkdirp(this.baseDir);
1064
1200
  this.mkdirp(join(this.baseDir, "projects"));
1065
1201
  const configPath = join(this.baseDir, "config.json");
1066
- if (!existsSync(configPath)) {
1202
+ if (!existsSync2(configPath)) {
1067
1203
  this.writeJson(configPath, DEFAULT_GLOBAL_CONFIG);
1068
1204
  }
1069
1205
  }
@@ -1071,7 +1207,7 @@ var ProjectManager = class {
1071
1207
  const projectDir = this.getProjectDir(projectName);
1072
1208
  this.mkdirp(projectDir);
1073
1209
  const configPath = join(projectDir, "config.json");
1074
- if (!existsSync(configPath)) {
1210
+ if (!existsSync2(configPath)) {
1075
1211
  const config = {
1076
1212
  name: projectName,
1077
1213
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1085,7 +1221,7 @@ var ProjectManager = class {
1085
1221
  // --- Config ---
1086
1222
  getGlobalConfig() {
1087
1223
  const configPath = join(this.baseDir, "config.json");
1088
- if (!existsSync(configPath)) return { ...DEFAULT_GLOBAL_CONFIG };
1224
+ if (!existsSync2(configPath)) return { ...DEFAULT_GLOBAL_CONFIG };
1089
1225
  return { ...DEFAULT_GLOBAL_CONFIG, ...this.readJson(configPath) };
1090
1226
  }
1091
1227
  saveGlobalConfig(config) {
@@ -1093,7 +1229,7 @@ var ProjectManager = class {
1093
1229
  }
1094
1230
  getProjectConfig(projectName) {
1095
1231
  const configPath = join(this.getProjectDir(projectName), "config.json");
1096
- if (!existsSync(configPath)) return null;
1232
+ if (!existsSync2(configPath)) return null;
1097
1233
  return this.readJson(configPath);
1098
1234
  }
1099
1235
  saveProjectConfig(projectName, config) {
@@ -1101,12 +1237,12 @@ var ProjectManager = class {
1101
1237
  }
1102
1238
  getInfrastructureConfig(projectName) {
1103
1239
  const jsonPath = join(this.getProjectDir(projectName), "infrastructure.json");
1104
- if (existsSync(jsonPath)) {
1240
+ if (existsSync2(jsonPath)) {
1105
1241
  const config = this.readJson(jsonPath);
1106
1242
  return this.resolveConfigEnvVars(config);
1107
1243
  }
1108
1244
  const yamlPath = join(this.getProjectDir(projectName), "infrastructure.yaml");
1109
- if (existsSync(yamlPath)) {
1245
+ if (existsSync2(yamlPath)) {
1110
1246
  try {
1111
1247
  const content = readFileSync2(yamlPath, "utf-8");
1112
1248
  return this.resolveConfigEnvVars(this.parseSimpleYaml(content));
@@ -1118,17 +1254,17 @@ var ProjectManager = class {
1118
1254
  }
1119
1255
  getClaudeInstructions(projectName) {
1120
1256
  const filePath = join(this.getProjectDir(projectName), "claude-instructions.md");
1121
- if (!existsSync(filePath)) return null;
1257
+ if (!existsSync2(filePath)) return null;
1122
1258
  return readFileSync2(filePath, "utf-8");
1123
1259
  }
1124
1260
  // --- Discovery ---
1125
1261
  listProjects() {
1126
1262
  const projectsDir = join(this.baseDir, "projects");
1127
- if (!existsSync(projectsDir)) return [];
1263
+ if (!existsSync2(projectsDir)) return [];
1128
1264
  return readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
1129
1265
  }
1130
1266
  projectExists(projectName) {
1131
- return existsSync(this.getProjectDir(projectName));
1267
+ return existsSync2(this.getProjectDir(projectName));
1132
1268
  }
1133
1269
  // --- Environment variable resolution ---
1134
1270
  resolveEnvVars(value) {
@@ -1138,7 +1274,7 @@ var ProjectManager = class {
1138
1274
  }
1139
1275
  // --- Private helpers ---
1140
1276
  mkdirp(dir) {
1141
- if (!existsSync(dir)) {
1277
+ if (!existsSync2(dir)) {
1142
1278
  mkdirSync(dir, { recursive: true });
1143
1279
  }
1144
1280
  }
@@ -1478,14 +1614,14 @@ var SessionManager = class {
1478
1614
  // src/http-server.ts
1479
1615
  import { createServer } from "http";
1480
1616
  import { createServer as createHttpsServer2 } from "https";
1481
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
1617
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
1482
1618
  import { resolve, dirname } from "path";
1483
1619
  import { fileURLToPath } from "url";
1484
1620
  import { WebSocketServer as WebSocketServer2 } from "ws";
1485
1621
 
1486
1622
  // src/pm/pm-routes.ts
1487
1623
  import { readdir, readFile, writeFile, unlink, mkdir } from "fs/promises";
1488
- import { existsSync as existsSync2 } from "fs";
1624
+ import { existsSync as existsSync3 } from "fs";
1489
1625
  import { join as join2 } from "path";
1490
1626
  import { homedir as homedir2 } from "os";
1491
1627
  import { spawn, execSync, execFileSync } from "child_process";
@@ -1732,7 +1868,7 @@ function createPmRouter(pmStore, discovery, helpers, broadcastDevServer) {
1732
1868
  return;
1733
1869
  }
1734
1870
  try {
1735
- await discovery.indexSession(id, session.projectId, session.jsonlPath);
1871
+ await discovery.indexProjectSessions(session.projectId);
1736
1872
  const updated = pmStore.getSession(id);
1737
1873
  helpers.json(res, updated);
1738
1874
  } catch (err) {
@@ -2420,7 +2556,7 @@ function parseGitStatus(porcelain) {
2420
2556
  }
2421
2557
  async function readRuleFile(filePath) {
2422
2558
  try {
2423
- if (existsSync2(filePath)) {
2559
+ if (existsSync3(filePath)) {
2424
2560
  const content = await readFile(filePath, "utf-8");
2425
2561
  return { path: filePath, content, exists: true };
2426
2562
  }
@@ -2445,12 +2581,14 @@ var HttpServer = class {
2445
2581
  sdkBundlePath = null;
2446
2582
  activePort = 9091;
2447
2583
  startedAt = Date.now();
2584
+ connectedSessionsGetter = null;
2448
2585
  constructor(store, processMonitor, options) {
2449
2586
  this.store = store;
2450
2587
  this.processMonitor = processMonitor ?? null;
2451
2588
  this.authManager = options?.authManager ?? null;
2452
2589
  this.allowedOrigins = options?.allowedOrigins ?? null;
2453
2590
  this.rateLimiter = options?.rateLimiter ?? null;
2591
+ this.connectedSessionsGetter = options?.getConnectedSessions ?? null;
2454
2592
  this.registerRoutes();
2455
2593
  if (options?.pmStore && options?.discovery) {
2456
2594
  this.pmRouter = createPmRouter(options.pmStore, options.discovery, {
@@ -2491,6 +2629,24 @@ var HttpServer = class {
2491
2629
  });
2492
2630
  }
2493
2631
  }
2632
+ if (this.connectedSessionsGetter) {
2633
+ for (const cs of this.connectedSessionsGetter()) {
2634
+ const existing = projectMap.get(cs.projectName);
2635
+ if (existing) {
2636
+ if (!existing.sessions.includes(cs.sessionId)) {
2637
+ existing.sessions.push(cs.sessionId);
2638
+ }
2639
+ existing.isConnected = true;
2640
+ } else {
2641
+ projectMap.set(cs.projectName, {
2642
+ appName: cs.projectName,
2643
+ sessions: [cs.sessionId],
2644
+ isConnected: true,
2645
+ eventCount: 0
2646
+ });
2647
+ }
2648
+ }
2649
+ }
2494
2650
  const projects = Array.from(projectMap.values());
2495
2651
  this.json(res, { data: projects, count: projects.length });
2496
2652
  });
@@ -2695,7 +2851,7 @@ var HttpServer = class {
2695
2851
  // npm installed
2696
2852
  ];
2697
2853
  for (const p of candidates) {
2698
- if (existsSync3(p)) {
2854
+ if (existsSync4(p)) {
2699
2855
  this.sdkBundlePath = p;
2700
2856
  return p;
2701
2857
  }
@@ -3105,6 +3261,10 @@ var PmStore = class {
3105
3261
  this.db.exec("ALTER TABLE pm_projects ADD COLUMN sdk_installed INTEGER DEFAULT 0");
3106
3262
  } catch {
3107
3263
  }
3264
+ try {
3265
+ this.db.exec("ALTER TABLE pm_projects ADD COLUMN runtime_apps TEXT DEFAULT NULL");
3266
+ } catch {
3267
+ }
3108
3268
  }
3109
3269
  // ============================================================
3110
3270
  // Projects
@@ -3113,15 +3273,16 @@ var PmStore = class {
3113
3273
  this.db.prepare(`
3114
3274
  INSERT INTO pm_projects (id, name, path, claude_project_key, runtimescope_project,
3115
3275
  phase, management_authorized, probable_to_complete, project_status,
3116
- category, sdk_installed,
3276
+ category, sdk_installed, runtime_apps,
3117
3277
  created_at, updated_at, metadata)
3118
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3278
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3119
3279
  ON CONFLICT(id) DO UPDATE SET
3120
3280
  name = excluded.name,
3121
3281
  path = COALESCE(excluded.path, pm_projects.path),
3122
3282
  claude_project_key = COALESCE(excluded.claude_project_key, pm_projects.claude_project_key),
3123
3283
  runtimescope_project = COALESCE(excluded.runtimescope_project, pm_projects.runtimescope_project),
3124
3284
  sdk_installed = CASE WHEN excluded.sdk_installed = 1 THEN 1 ELSE pm_projects.sdk_installed END,
3285
+ runtime_apps = COALESCE(excluded.runtime_apps, pm_projects.runtime_apps),
3125
3286
  updated_at = excluded.updated_at,
3126
3287
  metadata = COALESCE(excluded.metadata, pm_projects.metadata)
3127
3288
  `).run(
@@ -3136,6 +3297,7 @@ var PmStore = class {
3136
3297
  project.projectStatus,
3137
3298
  project.category ?? null,
3138
3299
  project.sdkInstalled ? 1 : 0,
3300
+ project.runtimeApps?.length ? JSON.stringify(project.runtimeApps) : null,
3139
3301
  project.createdAt,
3140
3302
  project.updatedAt,
3141
3303
  project.metadata ? JSON.stringify(project.metadata) : null
@@ -3180,6 +3342,14 @@ var PmStore = class {
3180
3342
  sets.push("sdk_installed = ?");
3181
3343
  params.push(updates.sdkInstalled ? 1 : 0);
3182
3344
  }
3345
+ if (updates.runtimeApps !== void 0) {
3346
+ sets.push("runtime_apps = ?");
3347
+ params.push(updates.runtimeApps.length ? JSON.stringify(updates.runtimeApps) : null);
3348
+ }
3349
+ if (updates.runtimescopeProject !== void 0) {
3350
+ sets.push("runtimescope_project = ?");
3351
+ params.push(updates.runtimescopeProject ?? null);
3352
+ }
3183
3353
  if (updates.metadata !== void 0) {
3184
3354
  sets.push("metadata = ?");
3185
3355
  params.push(JSON.stringify(updates.metadata));
@@ -3201,6 +3371,7 @@ var PmStore = class {
3201
3371
  path: row.path ?? void 0,
3202
3372
  claudeProjectKey: row.claude_project_key ?? void 0,
3203
3373
  runtimescopeProject: row.runtimescope_project ?? void 0,
3374
+ runtimeApps: row.runtime_apps ? JSON.parse(row.runtime_apps) : void 0,
3204
3375
  phase: row.phase,
3205
3376
  managementAuthorized: row.management_authorized === 1,
3206
3377
  probableToComplete: row.probable_to_complete === 1,
@@ -4036,7 +4207,7 @@ async function parseSessionJsonl(jsonlPath, sessionId, projectId) {
4036
4207
  // src/pm/project-discovery.ts
4037
4208
  import { readdir as readdir2, readFile as readFile2, stat as stat2 } from "fs/promises";
4038
4209
  import { join as join3, basename as basename2 } from "path";
4039
- import { existsSync as existsSync4 } from "fs";
4210
+ import { existsSync as existsSync5 } from "fs";
4040
4211
  import { homedir as homedir3 } from "os";
4041
4212
  var LOG_PREFIX = "[RuntimeScope PM]";
4042
4213
  async function detectSdkInstalled(projectPath) {
@@ -4103,7 +4274,7 @@ function slugifyPath(fsPath) {
4103
4274
  }
4104
4275
  function decodeClaudeKey(key) {
4105
4276
  const naive = "/" + key.slice(1).replace(/-/g, "/");
4106
- if (existsSync4(naive)) return naive;
4277
+ if (existsSync5(naive)) return naive;
4107
4278
  const parts = key.slice(1).split("-");
4108
4279
  return resolvePathSegments(parts);
4109
4280
  }
@@ -4111,16 +4282,16 @@ function resolvePathSegments(parts) {
4111
4282
  if (parts.length === 0) return null;
4112
4283
  function tryResolve(prefix, remaining) {
4113
4284
  if (remaining.length === 0) {
4114
- return existsSync4(prefix) ? prefix : null;
4285
+ return existsSync5(prefix) ? prefix : null;
4115
4286
  }
4116
4287
  for (let count = remaining.length; count >= 1; count--) {
4117
4288
  const segment = remaining.slice(0, count).join("-");
4118
4289
  const candidate = join3(prefix, segment);
4119
4290
  if (count === remaining.length) {
4120
- if (existsSync4(candidate)) return candidate;
4291
+ if (existsSync5(candidate)) return candidate;
4121
4292
  } else {
4122
4293
  try {
4123
- if (existsSync4(candidate)) {
4294
+ if (existsSync5(candidate)) {
4124
4295
  const result = tryResolve(candidate, remaining.slice(count));
4125
4296
  if (result) return result;
4126
4297
  }
@@ -4596,6 +4767,7 @@ export {
4596
4767
  RingBuffer,
4597
4768
  EventStore,
4598
4769
  SqliteStore,
4770
+ isSqliteAvailable,
4599
4771
  SessionRateLimiter,
4600
4772
  loadTlsOptions,
4601
4773
  resolveTlsConfig,
@@ -4613,4 +4785,4 @@ export {
4613
4785
  parseSessionJsonl,
4614
4786
  ProjectDiscovery
4615
4787
  };
4616
- //# sourceMappingURL=chunk-6JZXAFPC.js.map
4788
+ //# sourceMappingURL=chunk-TUFSIGGJ.js.map