@threadbase-sh/streamer 1.27.1 → 1.27.3

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/index.d.cts CHANGED
@@ -882,6 +882,7 @@ declare class StreamerServer {
882
882
  private allScanners;
883
883
  private scannerReady;
884
884
  private scannerStale;
885
+ private refreshInFlight;
885
886
  private binding;
886
887
  private cacheReady;
887
888
  private inFlightCacheWrites;
@@ -945,6 +946,7 @@ declare class StreamerServer {
945
946
  private bindWithRetry;
946
947
  private bindWithRetryLoop;
947
948
  private trackCacheWrite;
949
+ private refreshFileGuarded;
948
950
  close(): Promise<void>;
949
951
  private handleRequest;
950
952
  private handlePairStart;
package/dist/index.d.ts CHANGED
@@ -882,6 +882,7 @@ declare class StreamerServer {
882
882
  private allScanners;
883
883
  private scannerReady;
884
884
  private scannerStale;
885
+ private refreshInFlight;
885
886
  private binding;
886
887
  private cacheReady;
887
888
  private inFlightCacheWrites;
@@ -945,6 +946,7 @@ declare class StreamerServer {
945
946
  private bindWithRetry;
946
947
  private bindWithRetryLoop;
947
948
  private trackCacheWrite;
949
+ private refreshFileGuarded;
948
950
  close(): Promise<void>;
949
951
  private handleRequest;
950
952
  private handlePairStart;
package/dist/index.js CHANGED
@@ -5095,6 +5095,7 @@ var WSHub = class {
5095
5095
  var BROWSE_SYSTEM_PROMPT = (browseRoot) => `You are working within the project boundary: ${browseRoot}. Do not read, write, or execute commands that access files or directories outside this boundary.`;
5096
5096
  var DEFAULT_SYSTEM_PROMPT = "When presenting options or choices to the user, limit the options to at most 3.";
5097
5097
  var DEFAULT_PTY_GRACE_PERIOD_MS = 27e4;
5098
+ var REFRESH_TTL_MS = 2e3;
5098
5099
  var START_READY_TIMEOUT_MS = 15e3;
5099
5100
  function parseIncludeAgentsEnv(raw) {
5100
5101
  if (raw === void 0) return false;
@@ -5134,6 +5135,12 @@ var StreamerServer = class {
5134
5135
  // Set by onConversationChanged while a scan is in-flight; getScanner() does
5135
5136
  // a single rescan after the current one completes instead of restarting it.
5136
5137
  scannerStale = false;
5138
+ // Single-flight + TTL guard around scanner.refreshFile (see refreshFileGuarded).
5139
+ // A live file's mtime is always newer than the snapshot, so an unguarded
5140
+ // refresh fires on every request and re-parses the whole file from byte 0.
5141
+ // Keyed by filePath; entries drop on settle + TTL expiry, so the map stays
5142
+ // bounded by the active-file set.
5143
+ refreshInFlight = /* @__PURE__ */ new Map();
5137
5144
  // True only while bindWithRetry is actively retrying. The persistent
5138
5145
  // listener-level 'error' handler demotes EADDRINUSE to debug during this
5139
5146
  // window so the self-healing kickstart-relaunch race doesn't spam warn.
@@ -5332,7 +5339,7 @@ var StreamerServer = class {
5332
5339
  if (session.status === "waiting_input" || session.status === "idle") {
5333
5340
  const filePath = this.sessionFileMap.get(session.id);
5334
5341
  if (filePath) {
5335
- this.getScanner().then((scanner) => scanner.refreshFile(filePath)).then((meta) => {
5342
+ this.getScanner().then((scanner) => this.refreshFileGuarded(scanner, filePath)).then((meta) => {
5336
5343
  this.log.info("scanner.refreshFile: ok", {
5337
5344
  event: "scanner.refresh",
5338
5345
  sessionId: session.id,
@@ -5781,6 +5788,34 @@ var StreamerServer = class {
5781
5788
  this.inFlightCacheWrites.delete(guarded);
5782
5789
  });
5783
5790
  }
5791
+ // Single-flight + TTL wrapper for scanner.refreshFile. Both call sites (the
5792
+ // detail-stale branch and the per-turn refresh) route through here so that
5793
+ // N stacked retries on a live, actively-appended file cost one parse, not N:
5794
+ // - a refresh already in flight for the path → await the same promise;
5795
+ // - a refresh that settled within REFRESH_TTL_MS → skip, return null (the
5796
+ // caller keeps serving the current snapshot);
5797
+ // - otherwise start one, cache the promise, and stamp completedAt on settle.
5798
+ // The map is bounded by the active-file set (entries only live while a
5799
+ // refresh is in flight or within its TTL and are overwritten on the next
5800
+ // refresh of the same path).
5801
+ refreshFileGuarded(scanner, filePath) {
5802
+ const existing = this.refreshInFlight.get(filePath);
5803
+ if (existing) {
5804
+ const settled = existing.completedAt > 0;
5805
+ if (!settled) {
5806
+ return existing.promise;
5807
+ }
5808
+ if (Date.now() - existing.completedAt < REFRESH_TTL_MS) {
5809
+ return Promise.resolve(null);
5810
+ }
5811
+ }
5812
+ const entry = { promise: Promise.resolve(null), completedAt: 0 };
5813
+ entry.promise = scanner.refreshFile(filePath).finally(() => {
5814
+ entry.completedAt = Date.now();
5815
+ });
5816
+ this.refreshInFlight.set(filePath, entry);
5817
+ return entry.promise;
5818
+ }
5784
5819
  async close() {
5785
5820
  for (const timer of this.ptyGraceTimers.values()) clearTimeout(timer);
5786
5821
  this.ptyGraceTimers.clear();
@@ -6245,10 +6280,22 @@ var StreamerServer = class {
6245
6280
  const scanner = await this.getScanner(true);
6246
6281
  const fromIndex = await scanner.getConversation(uuid);
6247
6282
  if (fromIndex) {
6283
+ if (this.ptyManager.hasSession(uuid)) {
6284
+ return fromIndex;
6285
+ }
6248
6286
  if (fromIndex.filePath && this.isConversationSnapshotStale(fromIndex)) {
6249
- const refreshedMeta = await scanner.refreshFile(fromIndex.filePath);
6250
- if (!refreshedMeta) return null;
6251
- return await scanner.getConversation(uuid) ?? fromIndex;
6287
+ const filePath2 = fromIndex.filePath;
6288
+ this.trackCacheWrite(
6289
+ this.refreshFileGuarded(scanner, filePath2).catch((err) => {
6290
+ this.log.warn("scanner.refreshFile: failed", {
6291
+ event: "scanner.refresh_failed",
6292
+ conversationId: uuid,
6293
+ filePath: filePath2,
6294
+ trigger: "detail-swr",
6295
+ err
6296
+ });
6297
+ })
6298
+ );
6252
6299
  }
6253
6300
  return fromIndex;
6254
6301
  }