@threadbase-sh/streamer 1.36.2 → 1.36.4
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 +44 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +44 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +44 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1449,6 +1449,9 @@ var import_fs6 = require("fs");
|
|
|
1449
1449
|
var import_path6 = require("path");
|
|
1450
1450
|
|
|
1451
1451
|
// src/services/questions/detectPermissionGate.ts
|
|
1452
|
+
function permissionContentKey(gate) {
|
|
1453
|
+
return `${gate.prompt ?? ""}::${gate.detail ?? ""}::${gate.options.map((o) => `${o.index}.${o.label}`).join(",")}::${gate.cursor ?? ""}`;
|
|
1454
|
+
}
|
|
1452
1455
|
var OSC_777_PERMISSION_RE = /\x1b\]777;notify;Claude Code;[^\x07\x1b]*needs your permission/;
|
|
1453
1456
|
var OSC_777_WAITING_RE = /\x1b\]777;notify;Claude Code;[^\x07\x1b]*waiting for your input/;
|
|
1454
1457
|
function hasPermissionOsc(rawData) {
|
|
@@ -6884,6 +6887,11 @@ var StreamerServer = class {
|
|
|
6884
6887
|
// to pendingQuestions; mobile answers it by sending the option index via
|
|
6885
6888
|
// /input { keys }. Cleared when the gate closes.
|
|
6886
6889
|
pendingPermission = /* @__PURE__ */ new Map();
|
|
6890
|
+
// Content key (prompt + detail + options + cursor) of the permission gate
|
|
6891
|
+
// currently broadcast for a session — mirrors pendingQuestionKey so a PTY
|
|
6892
|
+
// repaint of the same gate doesn't re-broadcast on every tick. Cleared
|
|
6893
|
+
// alongside pendingPermission.
|
|
6894
|
+
pendingPermissionKey = /* @__PURE__ */ new Map();
|
|
6887
6895
|
scanner = null;
|
|
6888
6896
|
// Set when better-sqlite3 is unusable (e.g. node ABI mismatch made
|
|
6889
6897
|
// ConversationCache.open throw), or when config.scannerPersistent is false
|
|
@@ -6899,6 +6907,10 @@ var StreamerServer = class {
|
|
|
6899
6907
|
// Set by onConversationChanged while a scan is in-flight; getScanner() does
|
|
6900
6908
|
// a single rescan after the current one completes instead of restarting it.
|
|
6901
6909
|
scannerStale = false;
|
|
6910
|
+
// Single-flight guard for the background disk reconcile: a burst of list
|
|
6911
|
+
// polls during active session writes shares one rescan instead of queueing
|
|
6912
|
+
// a full rescan per request.
|
|
6913
|
+
conversationReconcileInFlight = null;
|
|
6902
6914
|
// Single-flight + TTL guard around scanner.refreshFile (see refreshFileGuarded).
|
|
6903
6915
|
// A live file's mtime is always newer than the snapshot, so an unguarded
|
|
6904
6916
|
// refresh fires on every request and re-parses the whole file from byte 0.
|
|
@@ -7177,6 +7189,7 @@ var StreamerServer = class {
|
|
|
7177
7189
|
this.cancelPendingQuestion(session.id);
|
|
7178
7190
|
}
|
|
7179
7191
|
this.pendingPermission.delete(session.id);
|
|
7192
|
+
this.pendingPermissionKey.delete(session.id);
|
|
7180
7193
|
this.contendedSessions.delete(session.id);
|
|
7181
7194
|
this.rememberSelfPtyEnded(session.id);
|
|
7182
7195
|
}
|
|
@@ -7916,9 +7929,9 @@ var StreamerServer = class {
|
|
|
7916
7929
|
* the automatic freshness path when the directory watcher marked the scanner
|
|
7917
7930
|
* stale or shouldRefreshProjectsFromHdd detected disk drift.
|
|
7918
7931
|
*/
|
|
7919
|
-
async reconcileConversationsCacheFromDisk() {
|
|
7932
|
+
async reconcileConversationsCacheFromDisk(onProgress) {
|
|
7920
7933
|
if (!this.cache) return;
|
|
7921
|
-
const scanner = await this.rescanForRefresh();
|
|
7934
|
+
const scanner = await this.rescanForRefresh(onProgress);
|
|
7922
7935
|
const metas = [...scanner.getMetadataCache().values()];
|
|
7923
7936
|
try {
|
|
7924
7937
|
this.cache.upsertFromScannerMeta(metas);
|
|
@@ -7946,6 +7959,18 @@ var StreamerServer = class {
|
|
|
7946
7959
|
);
|
|
7947
7960
|
}
|
|
7948
7961
|
}
|
|
7962
|
+
// Reconcile the cache from disk without blocking the caller. Single-flighted
|
|
7963
|
+
// so a burst of list polls during active session writes shares one rescan
|
|
7964
|
+
// rather than queueing a full rescan each; tracked so close() awaits the
|
|
7965
|
+
// in-flight cache write before shutting the DB.
|
|
7966
|
+
startBackgroundConversationReconcile() {
|
|
7967
|
+
if (this.conversationReconcileInFlight) return;
|
|
7968
|
+
const task = this.reconcileConversationsCacheFromDisk().finally(() => {
|
|
7969
|
+
this.conversationReconcileInFlight = null;
|
|
7970
|
+
});
|
|
7971
|
+
this.conversationReconcileInFlight = task;
|
|
7972
|
+
this.trackCacheWrite(task);
|
|
7973
|
+
}
|
|
7949
7974
|
shouldAutoReconcileConversationList() {
|
|
7950
7975
|
if (!this.cache) return false;
|
|
7951
7976
|
if (this.scannerStale) return true;
|
|
@@ -7963,13 +7988,19 @@ var StreamerServer = class {
|
|
|
7963
7988
|
const providerFilter = url.searchParams.get("provider") ?? void 0;
|
|
7964
7989
|
const bustCache = url.searchParams.get("refresh") === "1";
|
|
7965
7990
|
if (this.cache && (bustCache || this.shouldAutoReconcileConversationList())) {
|
|
7966
|
-
|
|
7991
|
+
const canServeStale = !bustCache && this.cache.listConversations({ limit: 0, offset: 0 }).total > 0;
|
|
7992
|
+
if (canServeStale) {
|
|
7993
|
+
this.startBackgroundConversationReconcile();
|
|
7994
|
+
} else {
|
|
7995
|
+
const shouldEmitProgress = createScanProgressThrottle();
|
|
7967
7996
|
await this.withWarmup(
|
|
7968
7997
|
"conversation_refresh",
|
|
7969
|
-
() => this.reconcileConversationsCacheFromDisk()
|
|
7998
|
+
() => this.reconcileConversationsCacheFromDisk((scanned, total2) => {
|
|
7999
|
+
if (shouldEmitProgress(scanned, total2)) {
|
|
8000
|
+
this.wsHub.broadcast({ type: "scan_progress", scanned, total: total2 });
|
|
8001
|
+
}
|
|
8002
|
+
})
|
|
7970
8003
|
);
|
|
7971
|
-
} else {
|
|
7972
|
-
await this.reconcileConversationsCacheFromDisk();
|
|
7973
8004
|
}
|
|
7974
8005
|
}
|
|
7975
8006
|
if (this.cache) {
|
|
@@ -8205,7 +8236,7 @@ var StreamerServer = class {
|
|
|
8205
8236
|
// getFreshScanner() this does NOT discard the warm scanner. scannerReady is
|
|
8206
8237
|
// only ever reassigned to a live scan promise (never nulled mid-scan), so the
|
|
8207
8238
|
// getScanner() anti-infinite-loop guard is preserved.
|
|
8208
|
-
async rescanForRefresh() {
|
|
8239
|
+
async rescanForRefresh(onProgress) {
|
|
8209
8240
|
if (this.scannerReady) await this.scannerReady;
|
|
8210
8241
|
this.scannerStale = false;
|
|
8211
8242
|
if (!this.scanner) {
|
|
@@ -8216,7 +8247,8 @@ var StreamerServer = class {
|
|
|
8216
8247
|
this.scannerReady = scanner.scan({
|
|
8217
8248
|
...this.scanProfiles ? { profiles: this.scanProfiles } : {},
|
|
8218
8249
|
...this.codexScanOpts(),
|
|
8219
|
-
fullRescan: true
|
|
8250
|
+
fullRescan: true,
|
|
8251
|
+
...onProgress ? { onProgress } : {}
|
|
8220
8252
|
});
|
|
8221
8253
|
await this.scannerReady;
|
|
8222
8254
|
return scanner;
|
|
@@ -9194,10 +9226,14 @@ var StreamerServer = class {
|
|
|
9194
9226
|
if (gate === null) {
|
|
9195
9227
|
if (!this.pendingPermission.has(sessionId)) return;
|
|
9196
9228
|
this.pendingPermission.delete(sessionId);
|
|
9229
|
+
this.pendingPermissionKey.delete(sessionId);
|
|
9197
9230
|
this.wsHub.broadcast({ type: "permission_cancelled", sessionId });
|
|
9198
9231
|
return;
|
|
9199
9232
|
}
|
|
9233
|
+
const key = permissionContentKey(gate);
|
|
9234
|
+
if (this.pendingPermissionKey.get(sessionId) === key) return;
|
|
9200
9235
|
this.pendingPermission.set(sessionId, gate);
|
|
9236
|
+
this.pendingPermissionKey.set(sessionId, key);
|
|
9201
9237
|
const subscriberCount = this.sessionSubscribers.get(sessionId)?.size ?? 0;
|
|
9202
9238
|
this.log.info(
|
|
9203
9239
|
`[ws.broadcast_permission] ${sessionId.slice(0, 8)} subscribers=${subscriberCount}`,
|