@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.
- package/dist/{chunk-6JZXAFPC.js → chunk-TUFSIGGJ.js} +214 -42
- package/dist/chunk-TUFSIGGJ.js.map +1 -0
- package/dist/index.d.ts +32 -3
- package/dist/index.js +15 -2
- package/dist/index.js.map +1 -1
- package/dist/standalone.js +30 -22
- package/dist/standalone.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-6JZXAFPC.js.map +0 -1
|
@@ -297,40 +297,84 @@ var EventStore = class {
|
|
|
297
297
|
|
|
298
298
|
// src/sqlite-store.ts
|
|
299
299
|
import Database from "better-sqlite3";
|
|
300
|
-
|
|
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.
|
|
312
|
+
this.dbPath = options.dbPath;
|
|
310
313
|
this.batchSize = options.batchSize ?? 50;
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
this.insertEventStmt =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
|
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.
|
|
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 (
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
4291
|
+
if (existsSync5(candidate)) return candidate;
|
|
4121
4292
|
} else {
|
|
4122
4293
|
try {
|
|
4123
|
-
if (
|
|
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-
|
|
4788
|
+
//# sourceMappingURL=chunk-TUFSIGGJ.js.map
|