@threadbase-sh/streamer 1.34.0 → 1.35.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.
- package/dist/cli.cjs +92 -18
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +92 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -3
- package/dist/index.d.ts +16 -3
- package/dist/index.js +92 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -142454,12 +142454,13 @@ function fingerprintOf(ids) {
|
|
|
142454
142454
|
return `sha256:${(0, import_crypto10.createHash)("sha256").update(sorted.join("\n")).digest("hex")}`;
|
|
142455
142455
|
}
|
|
142456
142456
|
var CacheIntegrityMonitor = class {
|
|
142457
|
-
constructor(cache, wsHub, log8, cacheDir, rescan) {
|
|
142457
|
+
constructor(cache, wsHub, log8, cacheDir, rescan, runDuringReset) {
|
|
142458
142458
|
this.cache = cache;
|
|
142459
142459
|
this.wsHub = wsHub;
|
|
142460
142460
|
this.log = log8;
|
|
142461
142461
|
this.cacheDir = cacheDir;
|
|
142462
142462
|
this.rescan = rescan;
|
|
142463
|
+
this.runDuringReset = runDuringReset;
|
|
142463
142464
|
const state = loadAlertState();
|
|
142464
142465
|
this._pending = state.pending ?? null;
|
|
142465
142466
|
this.ignoredIds = new Set(state.ignoredIds ?? []);
|
|
@@ -142469,6 +142470,7 @@ var CacheIntegrityMonitor = class {
|
|
|
142469
142470
|
log;
|
|
142470
142471
|
cacheDir;
|
|
142471
142472
|
rescan;
|
|
142473
|
+
runDuringReset;
|
|
142472
142474
|
_pending;
|
|
142473
142475
|
ignoredIds;
|
|
142474
142476
|
deferredUnlinks = [];
|
|
@@ -142672,18 +142674,20 @@ var CacheIntegrityMonitor = class {
|
|
|
142672
142674
|
}
|
|
142673
142675
|
case "reset_rescan": {
|
|
142674
142676
|
const backupPath = await this.ensureBackup(pending);
|
|
142675
|
-
|
|
142676
|
-
|
|
142677
|
-
|
|
142677
|
+
const reset = async () => {
|
|
142678
|
+
this.cache.clearAll();
|
|
142679
|
+
if (this.rescan) {
|
|
142678
142680
|
const metas = await this.rescan();
|
|
142679
142681
|
this.cache.upsertFromScannerMeta(metas);
|
|
142680
|
-
} catch (err) {
|
|
142681
|
-
this.log.error("cache-integrity reset rescan failed", {
|
|
142682
|
-
event: "cache_integrity.reset_rescan_failed",
|
|
142683
|
-
error: err instanceof Error ? err.message : String(err)
|
|
142684
|
-
});
|
|
142685
142682
|
}
|
|
142686
|
-
}
|
|
142683
|
+
};
|
|
142684
|
+
const resetPromise = this.runDuringReset ? this.runDuringReset(reset) : reset();
|
|
142685
|
+
void resetPromise.catch((err) => {
|
|
142686
|
+
this.log.error("cache-integrity reset rescan failed", {
|
|
142687
|
+
event: "cache_integrity.reset_rescan_failed",
|
|
142688
|
+
error: err instanceof Error ? err.message : String(err)
|
|
142689
|
+
});
|
|
142690
|
+
});
|
|
142687
142691
|
this.clearPending();
|
|
142688
142692
|
this.broadcastResolved(fingerprint2, action);
|
|
142689
142693
|
return { ok: true, action, backupPath };
|
|
@@ -143911,7 +143915,8 @@ var StreamerServer = class {
|
|
|
143911
143915
|
// listener-level 'error' handler demotes EADDRINUSE to debug during this
|
|
143912
143916
|
// window so the self-healing kickstart-relaunch race doesn't spam warn.
|
|
143913
143917
|
binding = false;
|
|
143914
|
-
|
|
143918
|
+
activeWarmups = /* @__PURE__ */ new Map([[0, "startup"]]);
|
|
143919
|
+
nextWarmupId = 1;
|
|
143915
143920
|
// Every fire-and-forget task that runs a scan and then writes to this.cache
|
|
143916
143921
|
// in an async continuation (startup warm-up, background count refresh, …).
|
|
143917
143922
|
// close() awaits all of them before closing this.cache, so a scan's post-scan
|
|
@@ -144261,7 +144266,7 @@ var StreamerServer = class {
|
|
|
144261
144266
|
this.wsHub.addClient(ws2);
|
|
144262
144267
|
const sessions = this.sessionStore.list(this.ptyAttachedIds());
|
|
144263
144268
|
ws2.send(JSON.stringify({ type: "session_list", sessions }));
|
|
144264
|
-
if (this.
|
|
144269
|
+
if (!this.currentWarmupState()) {
|
|
144265
144270
|
ws2.send(JSON.stringify({ type: "cache_ready" }));
|
|
144266
144271
|
}
|
|
144267
144272
|
const alertMsg = this.cacheMonitor?.wsMessage();
|
|
@@ -144470,6 +144475,39 @@ var StreamerServer = class {
|
|
|
144470
144475
|
const addr = this.httpServer.address();
|
|
144471
144476
|
return typeof addr === "object" && addr ? addr.port : 0;
|
|
144472
144477
|
}
|
|
144478
|
+
currentWarmupState() {
|
|
144479
|
+
let current = null;
|
|
144480
|
+
for (const state of this.activeWarmups.values()) current = state;
|
|
144481
|
+
return current;
|
|
144482
|
+
}
|
|
144483
|
+
beginWarmup(state) {
|
|
144484
|
+
const id = this.nextWarmupId++;
|
|
144485
|
+
this.activeWarmups.set(id, state);
|
|
144486
|
+
return id;
|
|
144487
|
+
}
|
|
144488
|
+
finishWarmup(id) {
|
|
144489
|
+
if (!this.activeWarmups.delete(id) || this.activeWarmups.size > 0) return;
|
|
144490
|
+
this.wsHub.broadcast({ type: "cache_ready" });
|
|
144491
|
+
}
|
|
144492
|
+
async withWarmup(state, operation) {
|
|
144493
|
+
const id = this.beginWarmup(state);
|
|
144494
|
+
try {
|
|
144495
|
+
return await operation();
|
|
144496
|
+
} finally {
|
|
144497
|
+
this.finishWarmup(id);
|
|
144498
|
+
}
|
|
144499
|
+
}
|
|
144500
|
+
rejectIfWarmingUp(res) {
|
|
144501
|
+
const warmupState = this.currentWarmupState();
|
|
144502
|
+
if (!warmupState) return false;
|
|
144503
|
+
const body = {
|
|
144504
|
+
error: "Server is warming up",
|
|
144505
|
+
code: "SERVER_WARMING_UP",
|
|
144506
|
+
warmupState
|
|
144507
|
+
};
|
|
144508
|
+
json2(res, 503, body);
|
|
144509
|
+
return true;
|
|
144510
|
+
}
|
|
144473
144511
|
async listen(port, opts) {
|
|
144474
144512
|
const dbConfig = this.disableDb ? null : getDbConfig();
|
|
144475
144513
|
if (dbConfig) {
|
|
@@ -144527,6 +144565,11 @@ var StreamerServer = class {
|
|
|
144527
144565
|
async () => {
|
|
144528
144566
|
const scanner = await this.rescanForRefresh();
|
|
144529
144567
|
return [...scanner.getMetadataCache().values()];
|
|
144568
|
+
},
|
|
144569
|
+
(operation) => {
|
|
144570
|
+
const reset = this.withWarmup("cache_reset", operation);
|
|
144571
|
+
this.trackCacheWrite(reset);
|
|
144572
|
+
return reset;
|
|
144530
144573
|
}
|
|
144531
144574
|
);
|
|
144532
144575
|
for (const dir of this.projectsDirs()) {
|
|
@@ -144622,8 +144665,7 @@ var StreamerServer = class {
|
|
|
144622
144665
|
event: "cache.warmup_failed"
|
|
144623
144666
|
});
|
|
144624
144667
|
}).finally(() => {
|
|
144625
|
-
this.
|
|
144626
|
-
this.wsHub.broadcast({ type: "cache_ready" });
|
|
144668
|
+
this.finishWarmup(0);
|
|
144627
144669
|
resolveWarm();
|
|
144628
144670
|
});
|
|
144629
144671
|
}
|
|
@@ -144855,6 +144897,7 @@ var StreamerServer = class {
|
|
|
144855
144897
|
return this.checkRateLimit(this.sessionInputAttempts, sessionId, 500, 6e4);
|
|
144856
144898
|
}
|
|
144857
144899
|
async handleListConversations(url2, res) {
|
|
144900
|
+
if (this.rejectIfWarmingUp(res)) return;
|
|
144858
144901
|
const limit = intParam(url2, "limit", 50);
|
|
144859
144902
|
const offset = intParam(url2, "offset", 0);
|
|
144860
144903
|
const sort = url2.searchParams.get("sort") ?? "recent";
|
|
@@ -144862,7 +144905,7 @@ var StreamerServer = class {
|
|
|
144862
144905
|
const providerFilter = url2.searchParams.get("provider") ?? void 0;
|
|
144863
144906
|
const bustCache = url2.searchParams.get("refresh") === "1";
|
|
144864
144907
|
if (bustCache && this.cache) {
|
|
144865
|
-
const scanner2 = await this.rescanForRefresh();
|
|
144908
|
+
const scanner2 = await this.withWarmup("conversation_refresh", () => this.rescanForRefresh());
|
|
144866
144909
|
const metas2 = [...scanner2.getMetadataCache().values()];
|
|
144867
144910
|
try {
|
|
144868
144911
|
this.cache.upsertFromScannerMeta(metas2);
|
|
@@ -144943,6 +144986,7 @@ var StreamerServer = class {
|
|
|
144943
144986
|
json2(res, 200, { conversations: adapted, hasMore: offset + limit < total, offset, total });
|
|
144944
144987
|
}
|
|
144945
144988
|
async handleConversationsCount(url2, res) {
|
|
144989
|
+
if (this.rejectIfWarmingUp(res)) return;
|
|
144946
144990
|
const project = url2.searchParams.get("project") ?? void 0;
|
|
144947
144991
|
const providerFilter = url2.searchParams.get("provider") ?? void 0;
|
|
144948
144992
|
const bustCache = url2.searchParams.get("refresh") === "1";
|
|
@@ -144970,7 +145014,7 @@ var StreamerServer = class {
|
|
|
144970
145014
|
// path — refresh=1 returns the cached total synchronously and this catches up.
|
|
144971
145015
|
refreshCountInBackground() {
|
|
144972
145016
|
this.trackCacheWrite(
|
|
144973
|
-
(async () => {
|
|
145017
|
+
this.withWarmup("conversation_refresh", async () => {
|
|
144974
145018
|
try {
|
|
144975
145019
|
const scanner = await this.getFreshScanner();
|
|
144976
145020
|
if (this.cache) {
|
|
@@ -144982,13 +145026,15 @@ var StreamerServer = class {
|
|
|
144982
145026
|
{ event: "count.refresh_failed" }
|
|
144983
145027
|
);
|
|
144984
145028
|
}
|
|
144985
|
-
})
|
|
145029
|
+
})
|
|
144986
145030
|
);
|
|
144987
145031
|
}
|
|
144988
145032
|
handleSessionsCount(res) {
|
|
145033
|
+
if (this.rejectIfWarmingUp(res)) return;
|
|
144989
145034
|
json2(res, 200, { total: this.sessionStore.list(this.ptyAttachedIds()).length });
|
|
144990
145035
|
}
|
|
144991
145036
|
handleGetRecentSessions(url2, res) {
|
|
145037
|
+
if (this.rejectIfWarmingUp(res)) return;
|
|
144992
145038
|
const limit = intParam(url2, "limit", 20);
|
|
144993
145039
|
if (!this.cache) {
|
|
144994
145040
|
json2(res, 200, { sessions: [], total: 0 });
|
|
@@ -145436,6 +145482,7 @@ var StreamerServer = class {
|
|
|
145436
145482
|
return isScannedSnapshotStale(conv.timestamp, mtimeMs);
|
|
145437
145483
|
}
|
|
145438
145484
|
async handleGetConversation(id, url2, res, ifNoneMatch) {
|
|
145485
|
+
if (this.rejectIfWarmingUp(res)) return;
|
|
145439
145486
|
const conversation = await this.findConversationByUuid(id);
|
|
145440
145487
|
if (!conversation && this.cache) {
|
|
145441
145488
|
const isFirstLoad = !url2.searchParams.has("before_index");
|
|
@@ -145763,6 +145810,7 @@ var StreamerServer = class {
|
|
|
145763
145810
|
});
|
|
145764
145811
|
}
|
|
145765
145812
|
async handleListSessions(url2, res) {
|
|
145813
|
+
if (this.rejectIfWarmingUp(res)) return;
|
|
145766
145814
|
const now = Date.now();
|
|
145767
145815
|
if (!this.discoveryCache || now - this.discoveryCache.fetchedAt >= DISCOVERY_TTL_MS) {
|
|
145768
145816
|
try {
|
|
@@ -145795,6 +145843,7 @@ var StreamerServer = class {
|
|
|
145795
145843
|
}
|
|
145796
145844
|
}
|
|
145797
145845
|
handleGetSession(sessionId, res) {
|
|
145846
|
+
if (this.rejectIfWarmingUp(res)) return;
|
|
145798
145847
|
const session = this.sessionStore.get(sessionId, this.ptyAttachedIds());
|
|
145799
145848
|
if (session) {
|
|
145800
145849
|
if (!(0, import_fs28.existsSync)(session.projectPath)) {
|
|
@@ -146246,12 +146295,21 @@ var StreamerServer = class {
|
|
|
146246
146295
|
json2(res, 404, { error: "Discovered session not found" });
|
|
146247
146296
|
return;
|
|
146248
146297
|
}
|
|
146249
|
-
const {
|
|
146298
|
+
const { branch } = discSession;
|
|
146299
|
+
let { projectPath, projectName } = discSession;
|
|
146250
146300
|
const convId = discSession.id;
|
|
146251
146301
|
if (discSession.pid == null) {
|
|
146252
146302
|
json2(res, 400, { error: "Session has no known PID" });
|
|
146253
146303
|
return;
|
|
146254
146304
|
}
|
|
146305
|
+
if (!projectPath) {
|
|
146306
|
+
const jsonlPath = this.findJsonlPath(convId);
|
|
146307
|
+
const jsonlCwd = jsonlPath ? await this.readCwdFromJsonl(jsonlPath) : null;
|
|
146308
|
+
if (jsonlCwd) {
|
|
146309
|
+
projectPath = jsonlCwd;
|
|
146310
|
+
projectName = projectName || (0, import_path28.basename)(jsonlCwd);
|
|
146311
|
+
}
|
|
146312
|
+
}
|
|
146255
146313
|
if (!projectPath) {
|
|
146256
146314
|
this.log.warn("adopt: refusing, working directory unknown", {
|
|
146257
146315
|
event: "adopt.no_project_path",
|
|
@@ -146264,6 +146322,22 @@ var StreamerServer = class {
|
|
|
146264
146322
|
});
|
|
146265
146323
|
return;
|
|
146266
146324
|
}
|
|
146325
|
+
const availability = classifyResumability(projectPath);
|
|
146326
|
+
if (!availability.resumable) {
|
|
146327
|
+
this.log.warn("adopt: refusing, project directory no longer exists", {
|
|
146328
|
+
event: "adopt.project_path_missing",
|
|
146329
|
+
sessionId,
|
|
146330
|
+
pid: discSession.pid,
|
|
146331
|
+
projectPath,
|
|
146332
|
+
reason: availability.unavailable_reason
|
|
146333
|
+
});
|
|
146334
|
+
json2(res, 400, {
|
|
146335
|
+
error: "Cannot take over this session: its project directory no longer exists",
|
|
146336
|
+
code: "ADOPT_PROJECT_PATH_MISSING",
|
|
146337
|
+
reason: availability.unavailable_reason
|
|
146338
|
+
});
|
|
146339
|
+
return;
|
|
146340
|
+
}
|
|
146267
146341
|
this.ptyManager.killPid(discSession.pid);
|
|
146268
146342
|
const exited = await waitForProcessExit(discSession.pid, ADOPT_KILL_TIMEOUT_MS);
|
|
146269
146343
|
if (!exited) {
|