@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.d.cts
CHANGED
|
@@ -1195,11 +1195,13 @@ declare class StreamerServer {
|
|
|
1195
1195
|
private selfPtyEndedAt;
|
|
1196
1196
|
private pendingQuestionKey;
|
|
1197
1197
|
private pendingPermission;
|
|
1198
|
+
private pendingPermissionKey;
|
|
1198
1199
|
private scanner;
|
|
1199
1200
|
private scannerPersistenceDisabled;
|
|
1200
1201
|
private allScanners;
|
|
1201
1202
|
private scannerReady;
|
|
1202
1203
|
private scannerStale;
|
|
1204
|
+
private conversationReconcileInFlight;
|
|
1203
1205
|
private refreshInFlight;
|
|
1204
1206
|
private binding;
|
|
1205
1207
|
private activeWarmups;
|
|
@@ -1293,6 +1295,7 @@ declare class StreamerServer {
|
|
|
1293
1295
|
* stale or shouldRefreshProjectsFromHdd detected disk drift.
|
|
1294
1296
|
*/
|
|
1295
1297
|
private reconcileConversationsCacheFromDisk;
|
|
1298
|
+
private startBackgroundConversationReconcile;
|
|
1296
1299
|
private shouldAutoReconcileConversationList;
|
|
1297
1300
|
private handleListConversations;
|
|
1298
1301
|
private handleConversationsCount;
|
package/dist/index.d.ts
CHANGED
|
@@ -1195,11 +1195,13 @@ declare class StreamerServer {
|
|
|
1195
1195
|
private selfPtyEndedAt;
|
|
1196
1196
|
private pendingQuestionKey;
|
|
1197
1197
|
private pendingPermission;
|
|
1198
|
+
private pendingPermissionKey;
|
|
1198
1199
|
private scanner;
|
|
1199
1200
|
private scannerPersistenceDisabled;
|
|
1200
1201
|
private allScanners;
|
|
1201
1202
|
private scannerReady;
|
|
1202
1203
|
private scannerStale;
|
|
1204
|
+
private conversationReconcileInFlight;
|
|
1203
1205
|
private refreshInFlight;
|
|
1204
1206
|
private binding;
|
|
1205
1207
|
private activeWarmups;
|
|
@@ -1293,6 +1295,7 @@ declare class StreamerServer {
|
|
|
1293
1295
|
* stale or shouldRefreshProjectsFromHdd detected disk drift.
|
|
1294
1296
|
*/
|
|
1295
1297
|
private reconcileConversationsCacheFromDisk;
|
|
1298
|
+
private startBackgroundConversationReconcile;
|
|
1296
1299
|
private shouldAutoReconcileConversationList;
|
|
1297
1300
|
private handleListConversations;
|
|
1298
1301
|
private handleConversationsCount;
|
package/dist/index.js
CHANGED
|
@@ -1397,6 +1397,9 @@ import { existsSync as existsSync3 } from "fs";
|
|
|
1397
1397
|
import { basename as basename2 } from "path";
|
|
1398
1398
|
|
|
1399
1399
|
// src/services/questions/detectPermissionGate.ts
|
|
1400
|
+
function permissionContentKey(gate) {
|
|
1401
|
+
return `${gate.prompt ?? ""}::${gate.detail ?? ""}::${gate.options.map((o) => `${o.index}.${o.label}`).join(",")}::${gate.cursor ?? ""}`;
|
|
1402
|
+
}
|
|
1400
1403
|
var OSC_777_PERMISSION_RE = /\x1b\]777;notify;Claude Code;[^\x07\x1b]*needs your permission/;
|
|
1401
1404
|
var OSC_777_WAITING_RE = /\x1b\]777;notify;Claude Code;[^\x07\x1b]*waiting for your input/;
|
|
1402
1405
|
function hasPermissionOsc(rawData) {
|
|
@@ -6848,6 +6851,11 @@ var StreamerServer = class {
|
|
|
6848
6851
|
// to pendingQuestions; mobile answers it by sending the option index via
|
|
6849
6852
|
// /input { keys }. Cleared when the gate closes.
|
|
6850
6853
|
pendingPermission = /* @__PURE__ */ new Map();
|
|
6854
|
+
// Content key (prompt + detail + options + cursor) of the permission gate
|
|
6855
|
+
// currently broadcast for a session — mirrors pendingQuestionKey so a PTY
|
|
6856
|
+
// repaint of the same gate doesn't re-broadcast on every tick. Cleared
|
|
6857
|
+
// alongside pendingPermission.
|
|
6858
|
+
pendingPermissionKey = /* @__PURE__ */ new Map();
|
|
6851
6859
|
scanner = null;
|
|
6852
6860
|
// Set when better-sqlite3 is unusable (e.g. node ABI mismatch made
|
|
6853
6861
|
// ConversationCache.open throw), or when config.scannerPersistent is false
|
|
@@ -6863,6 +6871,10 @@ var StreamerServer = class {
|
|
|
6863
6871
|
// Set by onConversationChanged while a scan is in-flight; getScanner() does
|
|
6864
6872
|
// a single rescan after the current one completes instead of restarting it.
|
|
6865
6873
|
scannerStale = false;
|
|
6874
|
+
// Single-flight guard for the background disk reconcile: a burst of list
|
|
6875
|
+
// polls during active session writes shares one rescan instead of queueing
|
|
6876
|
+
// a full rescan per request.
|
|
6877
|
+
conversationReconcileInFlight = null;
|
|
6866
6878
|
// Single-flight + TTL guard around scanner.refreshFile (see refreshFileGuarded).
|
|
6867
6879
|
// A live file's mtime is always newer than the snapshot, so an unguarded
|
|
6868
6880
|
// refresh fires on every request and re-parses the whole file from byte 0.
|
|
@@ -7141,6 +7153,7 @@ var StreamerServer = class {
|
|
|
7141
7153
|
this.cancelPendingQuestion(session.id);
|
|
7142
7154
|
}
|
|
7143
7155
|
this.pendingPermission.delete(session.id);
|
|
7156
|
+
this.pendingPermissionKey.delete(session.id);
|
|
7144
7157
|
this.contendedSessions.delete(session.id);
|
|
7145
7158
|
this.rememberSelfPtyEnded(session.id);
|
|
7146
7159
|
}
|
|
@@ -7880,9 +7893,9 @@ var StreamerServer = class {
|
|
|
7880
7893
|
* the automatic freshness path when the directory watcher marked the scanner
|
|
7881
7894
|
* stale or shouldRefreshProjectsFromHdd detected disk drift.
|
|
7882
7895
|
*/
|
|
7883
|
-
async reconcileConversationsCacheFromDisk() {
|
|
7896
|
+
async reconcileConversationsCacheFromDisk(onProgress) {
|
|
7884
7897
|
if (!this.cache) return;
|
|
7885
|
-
const scanner = await this.rescanForRefresh();
|
|
7898
|
+
const scanner = await this.rescanForRefresh(onProgress);
|
|
7886
7899
|
const metas = [...scanner.getMetadataCache().values()];
|
|
7887
7900
|
try {
|
|
7888
7901
|
this.cache.upsertFromScannerMeta(metas);
|
|
@@ -7910,6 +7923,18 @@ var StreamerServer = class {
|
|
|
7910
7923
|
);
|
|
7911
7924
|
}
|
|
7912
7925
|
}
|
|
7926
|
+
// Reconcile the cache from disk without blocking the caller. Single-flighted
|
|
7927
|
+
// so a burst of list polls during active session writes shares one rescan
|
|
7928
|
+
// rather than queueing a full rescan each; tracked so close() awaits the
|
|
7929
|
+
// in-flight cache write before shutting the DB.
|
|
7930
|
+
startBackgroundConversationReconcile() {
|
|
7931
|
+
if (this.conversationReconcileInFlight) return;
|
|
7932
|
+
const task = this.reconcileConversationsCacheFromDisk().finally(() => {
|
|
7933
|
+
this.conversationReconcileInFlight = null;
|
|
7934
|
+
});
|
|
7935
|
+
this.conversationReconcileInFlight = task;
|
|
7936
|
+
this.trackCacheWrite(task);
|
|
7937
|
+
}
|
|
7913
7938
|
shouldAutoReconcileConversationList() {
|
|
7914
7939
|
if (!this.cache) return false;
|
|
7915
7940
|
if (this.scannerStale) return true;
|
|
@@ -7927,13 +7952,19 @@ var StreamerServer = class {
|
|
|
7927
7952
|
const providerFilter = url.searchParams.get("provider") ?? void 0;
|
|
7928
7953
|
const bustCache = url.searchParams.get("refresh") === "1";
|
|
7929
7954
|
if (this.cache && (bustCache || this.shouldAutoReconcileConversationList())) {
|
|
7930
|
-
|
|
7955
|
+
const canServeStale = !bustCache && this.cache.listConversations({ limit: 0, offset: 0 }).total > 0;
|
|
7956
|
+
if (canServeStale) {
|
|
7957
|
+
this.startBackgroundConversationReconcile();
|
|
7958
|
+
} else {
|
|
7959
|
+
const shouldEmitProgress = createScanProgressThrottle();
|
|
7931
7960
|
await this.withWarmup(
|
|
7932
7961
|
"conversation_refresh",
|
|
7933
|
-
() => this.reconcileConversationsCacheFromDisk()
|
|
7962
|
+
() => this.reconcileConversationsCacheFromDisk((scanned, total2) => {
|
|
7963
|
+
if (shouldEmitProgress(scanned, total2)) {
|
|
7964
|
+
this.wsHub.broadcast({ type: "scan_progress", scanned, total: total2 });
|
|
7965
|
+
}
|
|
7966
|
+
})
|
|
7934
7967
|
);
|
|
7935
|
-
} else {
|
|
7936
|
-
await this.reconcileConversationsCacheFromDisk();
|
|
7937
7968
|
}
|
|
7938
7969
|
}
|
|
7939
7970
|
if (this.cache) {
|
|
@@ -8169,7 +8200,7 @@ var StreamerServer = class {
|
|
|
8169
8200
|
// getFreshScanner() this does NOT discard the warm scanner. scannerReady is
|
|
8170
8201
|
// only ever reassigned to a live scan promise (never nulled mid-scan), so the
|
|
8171
8202
|
// getScanner() anti-infinite-loop guard is preserved.
|
|
8172
|
-
async rescanForRefresh() {
|
|
8203
|
+
async rescanForRefresh(onProgress) {
|
|
8173
8204
|
if (this.scannerReady) await this.scannerReady;
|
|
8174
8205
|
this.scannerStale = false;
|
|
8175
8206
|
if (!this.scanner) {
|
|
@@ -8180,7 +8211,8 @@ var StreamerServer = class {
|
|
|
8180
8211
|
this.scannerReady = scanner.scan({
|
|
8181
8212
|
...this.scanProfiles ? { profiles: this.scanProfiles } : {},
|
|
8182
8213
|
...this.codexScanOpts(),
|
|
8183
|
-
fullRescan: true
|
|
8214
|
+
fullRescan: true,
|
|
8215
|
+
...onProgress ? { onProgress } : {}
|
|
8184
8216
|
});
|
|
8185
8217
|
await this.scannerReady;
|
|
8186
8218
|
return scanner;
|
|
@@ -9158,10 +9190,14 @@ var StreamerServer = class {
|
|
|
9158
9190
|
if (gate === null) {
|
|
9159
9191
|
if (!this.pendingPermission.has(sessionId)) return;
|
|
9160
9192
|
this.pendingPermission.delete(sessionId);
|
|
9193
|
+
this.pendingPermissionKey.delete(sessionId);
|
|
9161
9194
|
this.wsHub.broadcast({ type: "permission_cancelled", sessionId });
|
|
9162
9195
|
return;
|
|
9163
9196
|
}
|
|
9197
|
+
const key = permissionContentKey(gate);
|
|
9198
|
+
if (this.pendingPermissionKey.get(sessionId) === key) return;
|
|
9164
9199
|
this.pendingPermission.set(sessionId, gate);
|
|
9200
|
+
this.pendingPermissionKey.set(sessionId, key);
|
|
9165
9201
|
const subscriberCount = this.sessionSubscribers.get(sessionId)?.size ?? 0;
|
|
9166
9202
|
this.log.info(
|
|
9167
9203
|
`[ws.broadcast_permission] ${sessionId.slice(0, 8)} subscribers=${subscriberCount}`,
|