@threadbase-sh/streamer 1.23.0 → 1.23.1

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.cjs CHANGED
@@ -2566,7 +2566,10 @@ var ConversationCache = class _ConversationCache {
2566
2566
  model = excluded.model,
2567
2567
  account = excluded.account,
2568
2568
  branch = excluded.branch,
2569
- message_count = excluded.message_count,
2569
+ -- message_count is incremented by live tailing but recounted from
2570
+ -- scratch by a scanner rescan; a stale rescan must not carry a live
2571
+ -- session's count backwards, so take the max instead of overwriting.
2572
+ message_count = MAX(conversation_meta.message_count, excluded.message_count),
2570
2573
  last_activity = excluded.last_activity,
2571
2574
  first_message = excluded.first_message,
2572
2575
  last_message = excluded.last_message,
@@ -2843,6 +2846,7 @@ var ConversationCache = class _ConversationCache {
2843
2846
  fileSize = s.size;
2844
2847
  } catch {
2845
2848
  }
2849
+ const seq = ++this.tailSeq;
2846
2850
  this.stmts.upsertFull.run({
2847
2851
  id,
2848
2852
  file_path: m.filePath,
@@ -2857,7 +2861,7 @@ var ConversationCache = class _ConversationCache {
2857
2861
  first_message: m.firstMessage ? JSON.stringify(m.firstMessage) : null,
2858
2862
  last_message: m.lastMessage ? JSON.stringify(m.lastMessage) : null,
2859
2863
  preview: m.preview ?? null,
2860
- updated_at: 0,
2864
+ updated_at: seq,
2861
2865
  mtime_ms: mtimeMs,
2862
2866
  file_size: fileSize,
2863
2867
  provider: m.provider ?? CLAUDE_CODE_PROVIDER
@@ -3074,9 +3078,25 @@ var ConversationCache = class _ConversationCache {
3074
3078
  this.fileIndex.clear();
3075
3079
  }
3076
3080
  }
3077
- invalidateByFilePath(filePath) {
3081
+ /**
3082
+ * Drop the cached row for a file. Two callers with opposite intent:
3083
+ * - a directory-watch "change" event (the file was appended to) — pass
3084
+ * `skipIfTailed: true`. A cached tail means the row is being actively
3085
+ * maintained from the file's real content by the live-tail
3086
+ * (updateFromLines) or warm-up path, fresher than any scanner-derived
3087
+ * view. Both watchers fire on the same append with no ordering guarantee;
3088
+ * without this guard the invalidate can land after the tail write and wipe
3089
+ * the just-cached row, flickering the conversation out of
3090
+ * /api/conversations on nearly every message (CRITICAL #2). The debounced
3091
+ * rescan still re-derives metadata, so skipping the eager drop loses
3092
+ * nothing.
3093
+ * - a genuine unlink (the file is gone) — leave `skipIfTailed` false so the
3094
+ * row is always removed, otherwise a deleted session ghosts in the cache.
3095
+ */
3096
+ invalidateByFilePath(filePath, opts) {
3078
3097
  const row = this.stmts.getIdByFilePath.get(filePath);
3079
3098
  if (!row) return null;
3099
+ if (opts?.skipIfTailed && this.stmts.hasTail.get(row.id)) return null;
3080
3100
  this.invalidate(row.id);
3081
3101
  return row.id;
3082
3102
  }
@@ -4302,7 +4322,7 @@ var StreamerServer = class {
4302
4322
  }
4303
4323
  },
4304
4324
  onConversationChanged: (filePath) => {
4305
- this.cache?.invalidateByFilePath(filePath);
4325
+ this.cache?.invalidateByFilePath(filePath, { skipIfTailed: true });
4306
4326
  this.markScannerStaleDebounced();
4307
4327
  this.log.debug?.(`Scanner invalidated by directory event: ${filePath}`, {
4308
4328
  filePath,