@threadbase-sh/streamer 1.27.3 → 1.28.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/cli.cjs CHANGED
@@ -30931,7 +30931,7 @@ var require_websocket = __commonJS({
30931
30931
  var http = require("http");
30932
30932
  var net = require("net");
30933
30933
  var tls = require("tls");
30934
- var { randomBytes: randomBytes4, createHash: createHash4 } = require("crypto");
30934
+ var { randomBytes: randomBytes4, createHash: createHash5 } = require("crypto");
30935
30935
  var { Duplex, Readable: Readable3 } = require("stream");
30936
30936
  var { URL: URL2 } = require("url");
30937
30937
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -31599,7 +31599,7 @@ var require_websocket = __commonJS({
31599
31599
  abortHandshake(websocket, socket, "Invalid Upgrade header");
31600
31600
  return;
31601
31601
  }
31602
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
31602
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
31603
31603
  if (res.headers["sec-websocket-accept"] !== digest) {
31604
31604
  abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
31605
31605
  return;
@@ -31968,7 +31968,7 @@ var require_websocket_server = __commonJS({
31968
31968
  var EventEmitter6 = require("events");
31969
31969
  var http = require("http");
31970
31970
  var { Duplex } = require("stream");
31971
- var { createHash: createHash4 } = require("crypto");
31971
+ var { createHash: createHash5 } = require("crypto");
31972
31972
  var extension2 = require_extension();
31973
31973
  var PerMessageDeflate2 = require_permessage_deflate();
31974
31974
  var subprotocol2 = require_subprotocol();
@@ -32275,7 +32275,7 @@ var require_websocket_server = __commonJS({
32275
32275
  );
32276
32276
  }
32277
32277
  if (this._state > RUNNING) return abortHandshake(socket, 503);
32278
- const digest = createHash4("sha1").update(key + GUID).digest("base64");
32278
+ const digest = createHash5("sha1").update(key + GUID).digest("base64");
32279
32279
  const headers = [
32280
32280
  "HTTP/1.1 101 Switching Protocols",
32281
32281
  "Upgrade: websocket",
@@ -123750,14 +123750,14 @@ __export(prompt_exports, {
123750
123750
  interactivePermissionModePrompt: () => interactivePermissionModePrompt,
123751
123751
  interactivePrompt: () => interactivePrompt
123752
123752
  });
123753
- var import_node_process2, import_promises20, interactivePrompt, interactivePermissionModePrompt;
123753
+ var import_node_process2, import_promises22, interactivePrompt, interactivePermissionModePrompt;
123754
123754
  var init_prompt = __esm({
123755
123755
  "src/lifecycle/prompt.ts"() {
123756
123756
  "use strict";
123757
123757
  import_node_process2 = require("process");
123758
- import_promises20 = require("readline/promises");
123758
+ import_promises22 = require("readline/promises");
123759
123759
  interactivePrompt = async ({ prodPort, suggestedAltPort, prodActive }) => {
123760
- const rl = (0, import_promises20.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
123760
+ const rl = (0, import_promises22.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
123761
123761
  try {
123762
123762
  if (!prodActive) {
123763
123763
  import_node_process2.stdout.write(
@@ -123792,7 +123792,7 @@ The supervised prod streamer is already holding port ${prodPort}.
123792
123792
  }
123793
123793
  };
123794
123794
  interactivePermissionModePrompt = async () => {
123795
- const rl = (0, import_promises20.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
123795
+ const rl = (0, import_promises22.createInterface)({ input: import_node_process2.stdin, output: import_node_process2.stdout });
123796
123796
  try {
123797
123797
  import_node_process2.stdout.write(
123798
123798
  "\nWhich permission mode should spawned Claude Code sessions use?\n [a] acceptEdits \u2014 auto-approve file edits, still prompt for shell commands (default)\n [m] manual \u2014 prompt for every edit and command\n"
@@ -130940,6 +130940,17 @@ function reduceConvLine(state, entry) {
130940
130940
  attachment: entry.attachment !== void 0 ? entry.attachment : void 0
130941
130941
  };
130942
130942
  }
130943
+ function parseJsonlLine(line, state = initialConvState()) {
130944
+ const text = line.trimEnd();
130945
+ if (text.trim().length === 0) return null;
130946
+ let entry;
130947
+ try {
130948
+ entry = JSON.parse(text);
130949
+ } catch {
130950
+ return null;
130951
+ }
130952
+ return reduceConvLine(state, entry);
130953
+ }
130943
130954
  function applyTeamInfo(messages, state) {
130944
130955
  if (Object.keys(state.teamInfo).length === 0) return;
130945
130956
  for (const m2 of messages) {
@@ -133520,7 +133531,7 @@ async function search(query, options, scanner) {
133520
133531
  // src/server.ts
133521
133532
  var import_events3 = require("events");
133522
133533
  var import_fs25 = require("fs");
133523
- var import_promises17 = require("fs/promises");
133534
+ var import_promises19 = require("fs/promises");
133524
133535
  var import_http = require("http");
133525
133536
  var import_os10 = require("os");
133526
133537
  var import_path22 = require("path");
@@ -136666,7 +136677,9 @@ async function createDirectory(parentAbsolutePath, name) {
136666
136677
  // src/conversation-cache.ts
136667
136678
  var import_better_sqlite32 = __toESM(require("better-sqlite3"), 1);
136668
136679
  var import_fs17 = require("fs");
136680
+ var import_promises12 = require("fs/promises");
136669
136681
  var import_path13 = require("path");
136682
+ var import_promises13 = require("timers/promises");
136670
136683
 
136671
136684
  // src/db/sqlite-migrate.ts
136672
136685
  var import_fs15 = require("fs");
@@ -136792,6 +136805,33 @@ function parseAgentEntrypointsEnv(raw2) {
136792
136805
  return new Set(parts);
136793
136806
  }
136794
136807
 
136808
+ // src/utils/fileIdentity.ts
136809
+ var import_crypto5 = require("crypto");
136810
+ function fileIdentity(stat10, headBytes) {
136811
+ if (stat10.ino && stat10.ino > 0) return `inode:${stat10.dev}:${stat10.ino}`;
136812
+ const head = headBytes ?? Buffer.alloc(0);
136813
+ return `fp:${(0, import_crypto5.createHash)("sha1").update(head).digest("hex")}`;
136814
+ }
136815
+ function splitCompleteLines(buf, baseOffset) {
136816
+ const spans = [];
136817
+ let lineStart = 0;
136818
+ let consumed = 0;
136819
+ for (let i = 0; i < buf.length; i++) {
136820
+ if (buf[i] !== 10) continue;
136821
+ const lineLen = i - lineStart;
136822
+ if (lineLen > 0) {
136823
+ spans.push({
136824
+ byteOffset: baseOffset + lineStart,
136825
+ byteLength: lineLen,
136826
+ text: buf.toString("utf-8", lineStart, i)
136827
+ });
136828
+ }
136829
+ lineStart = i + 1;
136830
+ consumed = lineStart;
136831
+ }
136832
+ return { spans, consumed };
136833
+ }
136834
+
136795
136835
  // src/conversation-cache.ts
136796
136836
  function shortProjectName(fullPath) {
136797
136837
  const parts = fullPath.split(/[/\\]/).filter(Boolean);
@@ -136840,6 +136880,14 @@ var ConversationCache = class _ConversationCache {
136840
136880
  tailSize;
136841
136881
  fileIndex = /* @__PURE__ */ new Map();
136842
136882
  fileIndexLoaded = false;
136883
+ // Per-file scanner parse state for the incremental offset-index writer. The
136884
+ // reducer is stateful across lines (pending tool_uses, latest timestamp), so
136885
+ // it must persist between watcher reads of the same file. Cleared on
136886
+ // truncation/backfill.
136887
+ indexParseState = /* @__PURE__ */ new Map();
136888
+ // Single-flight guard for backfillIndex — concurrent detail requests for the
136889
+ // same cold file await one walk, not N. Entry dropped on settle.
136890
+ backfillInFlight = /* @__PURE__ */ new Map();
136843
136891
  // Monotonically increasing counter for tail updated_at — guarantees strict
136844
136892
  // ordering even when multiple updateFromLine() calls land within the same ms.
136845
136893
  tailSeq = Date.now();
@@ -137003,6 +137051,42 @@ var ConversationCache = class _ConversationCache {
137003
137051
  GROUP BY project_path
137004
137052
  ORDER BY cnt DESC
137005
137053
  LIMIT ?`
137054
+ ),
137055
+ getFileState: db.prepare("SELECT * FROM conversation_file_state WHERE path = ?"),
137056
+ upsertFileState: db.prepare(
137057
+ `INSERT INTO conversation_file_state
137058
+ (path, identity, size, mtime_ms, byte_offset, last_message_index)
137059
+ VALUES (@path, @identity, @size, @mtime_ms, @byte_offset, @last_message_index)
137060
+ ON CONFLICT(path) DO UPDATE SET
137061
+ identity = excluded.identity,
137062
+ size = excluded.size,
137063
+ mtime_ms = excluded.mtime_ms,
137064
+ byte_offset = excluded.byte_offset,
137065
+ last_message_index = excluded.last_message_index`
137066
+ ),
137067
+ deleteFileState: db.prepare("DELETE FROM conversation_file_state WHERE path = ?"),
137068
+ deleteMessageIndex: db.prepare(
137069
+ "DELETE FROM conversation_message_index WHERE conversation_id = ?"
137070
+ ),
137071
+ insertMessageIndexRow: db.prepare(
137072
+ `INSERT INTO conversation_message_index
137073
+ (conversation_id, message_index, byte_offset, byte_length, uuid, role, ts)
137074
+ VALUES (@conversation_id, @message_index, @byte_offset, @byte_length, @uuid, @role, @ts)
137075
+ ON CONFLICT(conversation_id, message_index) DO UPDATE SET
137076
+ byte_offset = excluded.byte_offset,
137077
+ byte_length = excluded.byte_length,
137078
+ uuid = excluded.uuid,
137079
+ role = excluded.role,
137080
+ ts = excluded.ts`
137081
+ ),
137082
+ getMessageIndexWindow: db.prepare(
137083
+ `SELECT message_index, byte_offset, byte_length, uuid, role, ts
137084
+ FROM conversation_message_index
137085
+ WHERE conversation_id = ? AND message_index >= ? AND message_index < ?
137086
+ ORDER BY message_index ASC`
137087
+ ),
137088
+ getIndexedMessageCount: db.prepare(
137089
+ "SELECT COUNT(*) as cnt FROM conversation_message_index WHERE conversation_id = ?"
137006
137090
  )
137007
137091
  };
137008
137092
  }
@@ -137013,6 +137097,273 @@ var ConversationCache = class _ConversationCache {
137013
137097
  getDatabase() {
137014
137098
  return this.db;
137015
137099
  }
137100
+ // ── Offset index (design 1b) ────────────────────────────────────────────
137101
+ // conversation_file_state + conversation_message_index back the windowed
137102
+ // detail read path (SQL window select + pread of byte ranges). All methods
137103
+ // are thin wrappers over the prepared statements above.
137104
+ getFileState(path2) {
137105
+ return this.stmts.getFileState.get(path2) ?? null;
137106
+ }
137107
+ upsertFileState(row) {
137108
+ this.stmts.upsertFileState.run(row);
137109
+ }
137110
+ /** Drop a file's index rows + file_state (truncation / identity change). */
137111
+ deleteFileIndex(path2, conversationId) {
137112
+ const tx = this.db.transaction(() => {
137113
+ this.stmts.deleteMessageIndex.run(conversationId);
137114
+ this.stmts.deleteFileState.run(path2);
137115
+ });
137116
+ tx();
137117
+ this.indexParseState.delete(path2);
137118
+ }
137119
+ /** Append/replace index rows in one transaction. */
137120
+ appendMessageIndexRows(rows) {
137121
+ const tx = this.db.transaction((batch) => {
137122
+ for (const r of batch) this.stmts.insertMessageIndexRow.run(r);
137123
+ });
137124
+ tx(rows);
137125
+ }
137126
+ /** Rows for message_index in [fromIndex, toIndex), ordered ascending. */
137127
+ getMessageIndexWindow(conversationId, fromIndex, toIndex) {
137128
+ return this.stmts.getMessageIndexWindow.all(
137129
+ conversationId,
137130
+ fromIndex,
137131
+ toIndex
137132
+ );
137133
+ }
137134
+ getIndexedMessageCount(conversationId) {
137135
+ return this.stmts.getIndexedMessageCount.get(conversationId).cnt;
137136
+ }
137137
+ /**
137138
+ * Conversation id for a JSONL path — the filename stem (matches the pseudo-id
137139
+ * updateFromLine derives and the uuid the detail read path resolves). The
137140
+ * offset index keys on this so the window select and the cursor agree.
137141
+ */
137142
+ static conversationIdForFile(filePath) {
137143
+ return filePath.split(/[/\\]/).pop()?.replace(/\.jsonl$/, "") ?? filePath;
137144
+ }
137145
+ // The offset index understands only claude-code JSONL: parseJsonlLine is the
137146
+ // claude-code reducer, so a codex file "indexes" as zero messages and the
137147
+ // resulting file_state serves empty windows for a real conversation (the
137148
+ // silent-wrong-data bug hotfixed after 1.28.0). Non-claude providers are
137149
+ // excluded from the index entirely; the scanner serves them (it routes each
137150
+ // provider to its own parser).
137151
+ isIndexableFile(filePath) {
137152
+ const meta3 = this.getMetaById(_ConversationCache.conversationIdForFile(filePath));
137153
+ return (meta3?.provider ?? CLAUDE_CODE_PROVIDER2) === CLAUDE_CODE_PROVIDER2;
137154
+ }
137155
+ /**
137156
+ * Incremental offset-index writer: extend the index for a burst of appended
137157
+ * lines (one watcher read) using their byte spans. Each line is classified
137158
+ * with the scanner's parseJsonlLine (a running per-file reducer state), so the
137159
+ * message ordering can never drift from the scanner's. Message lines get an
137160
+ * index row at the next message_index; non-message lines (summary/sidecar)
137161
+ * get no row but still advance byte_offset. file_state is updated to the end
137162
+ * of the last consumed span.
137163
+ *
137164
+ * Requires an up-to-date `stat` (identity/size/mtime) for the file so the read
137165
+ * path can detect truncation/replacement.
137166
+ *
137167
+ * `readFrom` is the absolute byte offset the watcher read started at, and
137168
+ * `endOffset` is where it ended (readFrom + consumed, i.e. the watcher's new
137169
+ * entry.offset). CONTIGUITY GUARD: the read must begin exactly where the index
137170
+ * left off (`readFrom === existing.byte_offset`, or 0 with no state). If it
137171
+ * doesn't — the watcher attached at EOF after the server was down, or an
137172
+ * append raced an in-flight backfill — extending would assign wrong
137173
+ * message_index values over a hole. In that case this writes nothing and
137174
+ * returns null so the caller drops the index and backfills.
137175
+ *
137176
+ * On success returns the message_index assigned to each input span (null for a
137177
+ * non-message line) so the caller can stamp WS `seq`. Empty array when spans
137178
+ * is empty. `endOffset` is stored verbatim as byte_offset so the watcher's
137179
+ * offset and file_state.byte_offset are the same number by construction.
137180
+ */
137181
+ extendMessageIndex(filePath, spans, stat10, readFrom, endOffset) {
137182
+ if (!this.isIndexableFile(filePath)) return spans.map(() => null);
137183
+ const existing = this.getFileState(filePath);
137184
+ const expectedStart = existing?.byte_offset ?? 0;
137185
+ if (readFrom !== expectedStart) return null;
137186
+ if (spans.length === 0) return [];
137187
+ const convId = _ConversationCache.conversationIdForFile(filePath);
137188
+ let state = this.indexParseState.get(filePath);
137189
+ if (!state) {
137190
+ state = initialConvState();
137191
+ this.indexParseState.set(filePath, state);
137192
+ }
137193
+ let nextIndex = existing ? existing.last_message_index + 1 : 0;
137194
+ const rows = [];
137195
+ const seqs = [];
137196
+ for (const span of spans) {
137197
+ const msg = parseJsonlLine(span.text, state);
137198
+ if (!msg) {
137199
+ seqs.push(null);
137200
+ continue;
137201
+ }
137202
+ rows.push({
137203
+ conversation_id: convId,
137204
+ message_index: nextIndex,
137205
+ byte_offset: span.byteOffset,
137206
+ byte_length: span.byteLength,
137207
+ uuid: msg.uuid ?? null,
137208
+ role: msg.role ?? null,
137209
+ ts: msg.timestamp ? Date.parse(msg.timestamp) || null : null
137210
+ });
137211
+ seqs.push(nextIndex);
137212
+ nextIndex++;
137213
+ }
137214
+ const tx = this.db.transaction(() => {
137215
+ for (const r of rows) this.stmts.insertMessageIndexRow.run(r);
137216
+ this.stmts.upsertFileState.run({
137217
+ path: filePath,
137218
+ identity: fileIdentity(stat10),
137219
+ size: stat10.size,
137220
+ mtime_ms: Math.round(stat10.mtimeMs),
137221
+ // Store the watcher's end offset verbatim — same number as entry.offset,
137222
+ // so the next read's contiguity check compares like-for-like (never
137223
+ // false-positive on a read that ended in trailing empty lines).
137224
+ byte_offset: endOffset,
137225
+ last_message_index: nextIndex - 1
137226
+ });
137227
+ });
137228
+ tx();
137229
+ return seqs;
137230
+ }
137231
+ clearIndexParseState(filePath) {
137232
+ this.indexParseState.delete(filePath);
137233
+ }
137234
+ /**
137235
+ * On-demand full backfill of the offset index for a file with no/stale
137236
+ * file_state (cold conversation, or after a truncation/replacement). Rebuilds
137237
+ * from byte 0: drops any existing rows, walks the whole file in chunks with a
137238
+ * running parse state, yields to the event loop every ~1000 lines so a large
137239
+ * file never blocks, and writes index rows + file_state.
137240
+ *
137241
+ * Single-flighted per path: concurrent callers await the same walk. The
137242
+ * triggering detail request is served by the scanner fallback while this runs.
137243
+ */
137244
+ backfillIndex(filePath) {
137245
+ const inFlight = this.backfillInFlight.get(filePath);
137246
+ if (inFlight) return inFlight;
137247
+ const walk = this.runBackfill(filePath).finally(() => {
137248
+ this.backfillInFlight.delete(filePath);
137249
+ });
137250
+ this.backfillInFlight.set(filePath, walk);
137251
+ return walk;
137252
+ }
137253
+ async runBackfill(filePath) {
137254
+ const convId = _ConversationCache.conversationIdForFile(filePath);
137255
+ this.deleteFileIndex(filePath, convId);
137256
+ this.indexParseState.delete(filePath);
137257
+ if (!this.isIndexableFile(filePath)) return;
137258
+ const CHUNK = 256 * 1024;
137259
+ const YIELD_EVERY = 1e3;
137260
+ const state = initialConvState();
137261
+ const fh = await (0, import_promises12.open)(filePath, "r");
137262
+ let fileOffset = 0;
137263
+ let carry = Buffer.alloc(0);
137264
+ let nextIndex = 0;
137265
+ let linesSinceYield = 0;
137266
+ let lastConsumedEnd = 0;
137267
+ let stat10;
137268
+ try {
137269
+ stat10 = await fh.stat();
137270
+ const buf = Buffer.alloc(CHUNK);
137271
+ for (; ; ) {
137272
+ const { bytesRead } = await fh.read(buf, 0, CHUNK, null);
137273
+ if (bytesRead === 0) break;
137274
+ const combined = carry.length > 0 ? Buffer.concat([carry, buf.subarray(0, bytesRead)]) : buf.subarray(0, bytesRead);
137275
+ const { spans, consumed } = splitCompleteLines(combined, fileOffset);
137276
+ const rows = [];
137277
+ for (const span of spans) {
137278
+ const msg = parseJsonlLine(span.text, state);
137279
+ linesSinceYield++;
137280
+ if (msg) {
137281
+ rows.push({
137282
+ conversation_id: convId,
137283
+ message_index: nextIndex,
137284
+ byte_offset: span.byteOffset,
137285
+ byte_length: span.byteLength,
137286
+ uuid: msg.uuid ?? null,
137287
+ role: msg.role ?? null,
137288
+ ts: msg.timestamp ? Date.parse(msg.timestamp) || null : null
137289
+ });
137290
+ nextIndex++;
137291
+ }
137292
+ if (linesSinceYield >= YIELD_EVERY) {
137293
+ linesSinceYield = 0;
137294
+ await (0, import_promises13.setImmediate)();
137295
+ }
137296
+ }
137297
+ if (rows.length > 0) this.appendMessageIndexRows(rows);
137298
+ lastConsumedEnd = fileOffset + consumed;
137299
+ carry = Buffer.from(combined.subarray(consumed));
137300
+ fileOffset += consumed;
137301
+ }
137302
+ } finally {
137303
+ await fh.close();
137304
+ }
137305
+ this.upsertFileState({
137306
+ path: filePath,
137307
+ identity: fileIdentity(stat10),
137308
+ size: stat10.size,
137309
+ mtime_ms: Math.round(stat10.mtimeMs),
137310
+ byte_offset: lastConsumedEnd,
137311
+ last_message_index: nextIndex - 1
137312
+ });
137313
+ this.indexParseState.set(filePath, state);
137314
+ }
137315
+ /**
137316
+ * Windowed detail read straight from the offset index — the hot path.
137317
+ * Returns the parsed messages for message_index in [fromIndex, toIndex) plus
137318
+ * the total indexed count, or null when the index can't serve this file (no
137319
+ * file_state, identity/size mismatch = truncation/replacement, or cold index)
137320
+ * so the caller falls back to the scanner and enqueues a backfill.
137321
+ *
137322
+ * On a match it SQL-selects the window's byte ranges and preads exactly those
137323
+ * ranges from the JSONL (never the whole file), parsing only the sliced lines.
137324
+ * Returns messages in the same ConversationMessage shape parseJsonlLine
137325
+ * produces during a scan, so the payload is identical to the scanner path.
137326
+ */
137327
+ readMessageWindow(filePath, fromIndex, toIndex) {
137328
+ const fileState = this.getFileState(filePath);
137329
+ if (!fileState) return null;
137330
+ let stat10;
137331
+ try {
137332
+ stat10 = (0, import_fs17.statSync)(filePath);
137333
+ } catch {
137334
+ return null;
137335
+ }
137336
+ if (fileIdentity(stat10) !== fileState.identity || stat10.size !== fileState.byte_offset) {
137337
+ return null;
137338
+ }
137339
+ if (fileState.last_message_index < 0 || !this.isIndexableFile(filePath)) {
137340
+ return null;
137341
+ }
137342
+ const total = fileState.last_message_index + 1;
137343
+ const from = Math.max(0, fromIndex);
137344
+ const to2 = Math.min(toIndex, total);
137345
+ if (to2 <= from) return { messages: [], total, fromIndex: from };
137346
+ const rows = this.getMessageIndexWindow(
137347
+ _ConversationCache.conversationIdForFile(filePath),
137348
+ from,
137349
+ to2
137350
+ );
137351
+ if (rows.length === 0) return { messages: [], total, fromIndex: from };
137352
+ const messages = [];
137353
+ const fd = (0, import_fs17.openSync)(filePath, "r");
137354
+ try {
137355
+ const state = initialConvState();
137356
+ for (const row of rows) {
137357
+ const buf = Buffer.alloc(row.byte_length);
137358
+ (0, import_fs17.readSync)(fd, buf, 0, row.byte_length, row.byte_offset);
137359
+ const msg = parseJsonlLine(buf.toString("utf-8"), state);
137360
+ if (msg) messages.push(msg);
137361
+ }
137362
+ } finally {
137363
+ (0, import_fs17.closeSync)(fd);
137364
+ }
137365
+ return { messages, total, fromIndex: from };
137366
+ }
137016
137367
  agentEntrypointsKey() {
137017
137368
  return [...this.agentEntrypoints].sort().join(",");
137018
137369
  }
@@ -137736,7 +138087,7 @@ var ConversationsRepository = class {
137736
138087
  };
137737
138088
 
137738
138089
  // src/db/repositories/projects.repository.ts
137739
- var import_crypto5 = require("crypto");
138090
+ var import_crypto6 = require("crypto");
137740
138091
 
137741
138092
  // src/utils/canonicalizeProjectPath.ts
137742
138093
  function canonicalizeProjectPath(projectPath) {
@@ -137829,7 +138180,7 @@ var ProjectsRepository = class {
137829
138180
  });
137830
138181
  return rowToProject(this.getById.get(existing.id));
137831
138182
  }
137832
- const id = (0, import_crypto5.randomUUID)();
138183
+ const id = (0, import_crypto6.randomUUID)();
137833
138184
  this.insert.run({
137834
138185
  id,
137835
138186
  path: path2,
@@ -137924,7 +138275,7 @@ var import_path19 = require("path");
137924
138275
 
137925
138276
  // src/codex-pty-runner.ts
137926
138277
  var import_headless = __toESM(require_xterm_headless(), 1);
137927
- var import_crypto6 = require("crypto");
138278
+ var import_crypto7 = require("crypto");
137928
138279
  var import_fs21 = require("fs");
137929
138280
  var import_path17 = require("path");
137930
138281
  init_logger();
@@ -138179,7 +138530,7 @@ var CodexPtyRunner = class {
138179
138530
  // ManagedSession handle only.
138180
138531
  async startFresh(options) {
138181
138532
  const nodePty = await loadPty();
138182
- const sessionId = (0, import_crypto6.randomUUID)();
138533
+ const sessionId = (0, import_crypto7.randomUUID)();
138183
138534
  const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138184
138535
  const args = ["--cd", options.projectPath, "--no-alt-screen"];
138185
138536
  if (options.systemPrompt) {
@@ -138508,7 +138859,7 @@ function stripAnsi(str) {
138508
138859
 
138509
138860
  // src/pty-manager.ts
138510
138861
  var import_headless2 = __toESM(require_xterm_headless(), 1);
138511
- var import_crypto7 = require("crypto");
138862
+ var import_crypto8 = require("crypto");
138512
138863
  var import_fs22 = require("fs");
138513
138864
  var import_path18 = require("path");
138514
138865
  init_logger();
@@ -138912,7 +139263,7 @@ var PTYManager = class {
138912
139263
  // onReady fires once Claude reaches its first prompt (waiting_input).
138913
139264
  async startFresh(options) {
138914
139265
  const nodePty = await loadPty2();
138915
- const sessionId = (0, import_crypto7.randomUUID)();
139266
+ const sessionId = (0, import_crypto8.randomUUID)();
138916
139267
  const projectName = options.projectName ?? (0, import_path18.basename)(options.projectPath);
138917
139268
  const args = [
138918
139269
  "--permission-mode",
@@ -139496,7 +139847,7 @@ var LiveSessionManager = class {
139496
139847
  init_logger();
139497
139848
 
139498
139849
  // src/pair-store.ts
139499
- var import_crypto8 = require("crypto");
139850
+ var import_crypto9 = require("crypto");
139500
139851
  var DEFAULT_TTL_SECONDS = 180;
139501
139852
  var SWEEP_INTERVAL_MS = 6e4;
139502
139853
  var PairTokenStore = class {
@@ -139511,7 +139862,7 @@ var PairTokenStore = class {
139511
139862
  }
139512
139863
  }
139513
139864
  mint() {
139514
- const token = `pt_${(0, import_crypto8.randomBytes)(16).toString("hex")}`;
139865
+ const token = `pt_${(0, import_crypto9.randomBytes)(16).toString("hex")}`;
139515
139866
  const expiresAt = Date.now() + this.ttlMs;
139516
139867
  this.current = { token, expiresAt, used: false };
139517
139868
  return {
@@ -139716,11 +140067,11 @@ function seal(plaintext, recipientPublicKeyBase64) {
139716
140067
  // node_modules/chokidar/index.js
139717
140068
  var import_node_events2 = require("events");
139718
140069
  var import_node_fs7 = require("fs");
139719
- var import_promises14 = require("fs/promises");
140070
+ var import_promises16 = require("fs/promises");
139720
140071
  var sp2 = __toESM(require("path"), 1);
139721
140072
 
139722
140073
  // node_modules/readdirp/index.js
139723
- var import_promises12 = require("fs/promises");
140074
+ var import_promises14 = require("fs/promises");
139724
140075
  var import_node_path9 = require("path");
139725
140076
  var import_node_stream2 = require("stream");
139726
140077
  var EntryTypes2 = {
@@ -139801,7 +140152,7 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139801
140152
  const { root, type } = opts;
139802
140153
  this._fileFilter = normalizeFilter2(opts.fileFilter);
139803
140154
  this._directoryFilter = normalizeFilter2(opts.directoryFilter);
139804
- const statMethod = opts.lstat ? import_promises12.lstat : import_promises12.stat;
140155
+ const statMethod = opts.lstat ? import_promises14.lstat : import_promises14.stat;
139805
140156
  if (wantBigintFsStats2) {
139806
140157
  this._stat = (path2) => statMethod(path2, { bigint: true });
139807
140158
  } else {
@@ -139872,7 +140223,7 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139872
140223
  async _exploreDir(path2, depth) {
139873
140224
  let files;
139874
140225
  try {
139875
- files = await (0, import_promises12.readdir)(path2, this._rdOptions);
140226
+ files = await (0, import_promises14.readdir)(path2, this._rdOptions);
139876
140227
  } catch (error51) {
139877
140228
  this._onError(error51);
139878
140229
  }
@@ -139910,8 +140261,8 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139910
140261
  if (stats && stats.isSymbolicLink()) {
139911
140262
  const full = entry.fullPath;
139912
140263
  try {
139913
- const entryRealPath = await (0, import_promises12.realpath)(full);
139914
- const entryRealPathStats = await (0, import_promises12.lstat)(entryRealPath);
140264
+ const entryRealPath = await (0, import_promises14.realpath)(full);
140265
+ const entryRealPathStats = await (0, import_promises14.lstat)(entryRealPath);
139915
140266
  if (entryRealPathStats.isFile()) {
139916
140267
  return "file";
139917
140268
  }
@@ -139954,7 +140305,7 @@ function readdirp2(root, options = {}) {
139954
140305
 
139955
140306
  // node_modules/chokidar/handler.js
139956
140307
  var import_node_fs6 = require("fs");
139957
- var import_promises13 = require("fs/promises");
140308
+ var import_promises15 = require("fs/promises");
139958
140309
  var import_node_os3 = require("os");
139959
140310
  var sp = __toESM(require("path"), 1);
139960
140311
  var STR_DATA2 = "data";
@@ -139981,7 +140332,7 @@ var EVENTS2 = {
139981
140332
  };
139982
140333
  var EV2 = EVENTS2;
139983
140334
  var THROTTLE_MODE_WATCH2 = "watch";
139984
- var statMethods2 = { lstat: import_promises13.lstat, stat: import_promises13.stat };
140335
+ var statMethods2 = { lstat: import_promises15.lstat, stat: import_promises15.stat };
139985
140336
  var KEY_LISTENERS2 = "listeners";
139986
140337
  var KEY_ERR2 = "errHandlers";
139987
140338
  var KEY_RAW2 = "rawEmitters";
@@ -140338,7 +140689,7 @@ var setFsWatchListener2 = (path2, fullPath, options, handlers) => {
140338
140689
  cont.watcherUnusable = true;
140339
140690
  if (isWindows3 && error51.code === "EPERM") {
140340
140691
  try {
140341
- const fd = await (0, import_promises13.open)(path2, "r");
140692
+ const fd = await (0, import_promises15.open)(path2, "r");
140342
140693
  await fd.close();
140343
140694
  broadcastErr(error51);
140344
140695
  } catch (err) {
@@ -140469,7 +140820,7 @@ var NodeFsHandler2 = class {
140469
140820
  return;
140470
140821
  if (!newStats || newStats.mtimeMs === 0) {
140471
140822
  try {
140472
- const newStats2 = await (0, import_promises13.stat)(file2);
140823
+ const newStats2 = await (0, import_promises15.stat)(file2);
140473
140824
  if (this.fsw.closed)
140474
140825
  return;
140475
140826
  const at2 = newStats2.atimeMs;
@@ -140524,7 +140875,7 @@ var NodeFsHandler2 = class {
140524
140875
  this.fsw._incrReadyCount();
140525
140876
  let linkPath;
140526
140877
  try {
140527
- linkPath = await (0, import_promises13.realpath)(path2);
140878
+ linkPath = await (0, import_promises15.realpath)(path2);
140528
140879
  } catch (e) {
140529
140880
  this.fsw._emitReady();
140530
140881
  return true;
@@ -140673,7 +141024,7 @@ var NodeFsHandler2 = class {
140673
141024
  let closer;
140674
141025
  if (stats.isDirectory()) {
140675
141026
  const absPath = sp.resolve(path2);
140676
- const targetPath = follow ? await (0, import_promises13.realpath)(path2) : path2;
141027
+ const targetPath = follow ? await (0, import_promises15.realpath)(path2) : path2;
140677
141028
  if (this.fsw.closed)
140678
141029
  return;
140679
141030
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -140683,7 +141034,7 @@ var NodeFsHandler2 = class {
140683
141034
  this.fsw._symlinkPaths.set(absPath, targetPath);
140684
141035
  }
140685
141036
  } else if (stats.isSymbolicLink()) {
140686
- const targetPath = follow ? await (0, import_promises13.realpath)(path2) : path2;
141037
+ const targetPath = follow ? await (0, import_promises15.realpath)(path2) : path2;
140687
141038
  if (this.fsw.closed)
140688
141039
  return;
140689
141040
  const parent = sp.dirname(wh.watchPath);
@@ -140843,7 +141194,7 @@ var DirEntry2 = class {
140843
141194
  return;
140844
141195
  const dir = this.path;
140845
141196
  try {
140846
- await (0, import_promises14.readdir)(dir);
141197
+ await (0, import_promises16.readdir)(dir);
140847
141198
  } catch (err) {
140848
141199
  if (this._removeWatcher) {
140849
141200
  this._removeWatcher(sp2.dirname(dir), sp2.basename(dir));
@@ -141193,7 +141544,7 @@ var FSWatcher2 = class extends import_node_events2.EventEmitter {
141193
141544
  const fullPath = opts.cwd ? sp2.join(opts.cwd, path2) : path2;
141194
141545
  let stats2;
141195
141546
  try {
141196
- stats2 = await (0, import_promises14.stat)(fullPath);
141547
+ stats2 = await (0, import_promises16.stat)(fullPath);
141197
141548
  } catch (err) {
141198
141549
  }
141199
141550
  if (!stats2 || this.closed)
@@ -141447,18 +141798,20 @@ var chokidar_default = { watch: watch2, FSWatcher: FSWatcher2 };
141447
141798
 
141448
141799
  // src/services/conversations/conversationWatcher.ts
141449
141800
  var import_fs23 = require("fs");
141450
- var import_promises15 = require("fs/promises");
141801
+ var import_promises17 = require("fs/promises");
141451
141802
  var ConversationWatcher = class {
141452
141803
  files = /* @__PURE__ */ new Map();
141453
141804
  directories = /* @__PURE__ */ new Map();
141454
141805
  onNewLine;
141455
141806
  onNewLines;
141807
+ onNewLineSpans;
141456
141808
  onConversationChanged;
141457
141809
  onFileDeleted;
141458
141810
  onError;
141459
141811
  constructor(events = {}) {
141460
141812
  this.onNewLine = events.onNewLine;
141461
141813
  this.onNewLines = events.onNewLines;
141814
+ this.onNewLineSpans = events.onNewLineSpans;
141462
141815
  this.onConversationChanged = events.onConversationChanged;
141463
141816
  this.onFileDeleted = events.onFileDeleted;
141464
141817
  this.onError = events.onError;
@@ -141551,20 +141904,24 @@ var ConversationWatcher = class {
141551
141904
  entry.reading = true;
141552
141905
  try {
141553
141906
  for (; ; ) {
141554
- const st2 = await (0, import_promises15.stat)(filePath);
141907
+ const st2 = await (0, import_promises17.stat)(filePath);
141555
141908
  if (st2.size <= entry.offset) break;
141556
141909
  const readFrom = entry.offset;
141557
141910
  const bytesToRead = st2.size - readFrom;
141558
141911
  const buf = Buffer.alloc(bytesToRead);
141559
- const fh = await (0, import_promises15.open)(filePath, "r");
141912
+ const fh = await (0, import_promises17.open)(filePath, "r");
141560
141913
  try {
141561
141914
  await fh.read(buf, 0, bytesToRead, readFrom);
141562
141915
  } finally {
141563
141916
  await fh.close();
141564
141917
  }
141565
- entry.offset = readFrom + bytesToRead;
141918
+ const { spans, consumed } = splitCompleteLines(buf, readFrom);
141919
+ entry.offset = readFrom + consumed;
141566
141920
  if (!this.files.has(filePath)) return;
141567
- const lines = buf.toString("utf-8").split("\n").filter(Boolean);
141921
+ const lines = spans.map((s3) => s3.text);
141922
+ if (spans.length > 0) {
141923
+ this.onNewLineSpans?.(filePath, spans, readFrom, entry.offset);
141924
+ }
141568
141925
  if (this.onNewLines) {
141569
141926
  this.onNewLines(filePath, lines);
141570
141927
  } else {
@@ -142004,8 +142361,8 @@ function discoveredToResponse(d, conversationId) {
142004
142361
  }
142005
142362
 
142006
142363
  // src/uploads.ts
142007
- var import_crypto9 = require("crypto");
142008
- var import_promises16 = require("fs/promises");
142364
+ var import_crypto10 = require("crypto");
142365
+ var import_promises18 = require("fs/promises");
142009
142366
  var import_heic_convert = __toESM(require_heic_convert(), 1);
142010
142367
  var import_path21 = require("path");
142011
142368
  var UPLOAD_DIR_NAME = ".threadbase-uploads";
@@ -142038,12 +142395,12 @@ async function saveUploadFile(input) {
142038
142395
  mimeType = "image/jpeg";
142039
142396
  originalName = originalName.replace(/\.(heic|heif)$/i, ".jpg");
142040
142397
  }
142041
- const id = `up_${(0, import_crypto9.randomBytes)(8).toString("hex")}`;
142398
+ const id = `up_${(0, import_crypto10.randomBytes)(8).toString("hex")}`;
142042
142399
  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
142043
142400
  const dir = (0, import_path21.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
142044
- await (0, import_promises16.mkdir)(dir, { recursive: true });
142401
+ await (0, import_promises18.mkdir)(dir, { recursive: true });
142045
142402
  const filePath = (0, import_path21.join)(dir, `${Date.now()}-${id}-${safeName}`);
142046
- await (0, import_promises16.writeFile)(filePath, buffer);
142403
+ await (0, import_promises18.writeFile)(filePath, buffer);
142047
142404
  return {
142048
142405
  id,
142049
142406
  filePath,
@@ -142442,6 +142799,10 @@ var StreamerServer = class {
142442
142799
  fileWatcher;
142443
142800
  sessionFileMap = /* @__PURE__ */ new Map();
142444
142801
  // sessionId → JSONL filePath
142802
+ // Per-file seq assignments from the most recent onNewLineSpans (offset index),
142803
+ // handed to the immediately-following onNewLines so it can stamp WS `seq` on
142804
+ // the matching conversation_events entries. Same read → same lines order.
142805
+ pendingLineSeqs = /* @__PURE__ */ new Map();
142445
142806
  pendingQuestions = /* @__PURE__ */ new Map();
142446
142807
  // Content key of the AskUserQuestion currently broadcast for a session (from
142447
142808
  // either the rendered screen or JSONL), used to de-dupe the two paths: when
@@ -142565,7 +142926,7 @@ var StreamerServer = class {
142565
142926
  this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
142566
142927
  const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config2.browseRoot;
142567
142928
  if (rawRoot) {
142568
- (0, import_promises17.realpath)(rawRoot).then((resolved) => {
142929
+ (0, import_promises19.realpath)(rawRoot).then((resolved) => {
142569
142930
  this.browseRoot = resolved;
142570
142931
  if (this.verbose) this.log.info(`Browse root: ${resolved}`, { browseRoot: resolved });
142571
142932
  }).catch(() => {
@@ -142587,6 +142948,42 @@ var StreamerServer = class {
142587
142948
  this.sessionStore = new SessionStore();
142588
142949
  this.wsHub = new WSHub();
142589
142950
  this.fileWatcher = new ConversationWatcher({
142951
+ onNewLineSpans: (filePath, spans, readFrom, endOffset) => {
142952
+ if (!this.cache) return;
142953
+ const cache = this.cache;
142954
+ this.pendingLineSeqs.delete(filePath);
142955
+ try {
142956
+ const seqs = cache.extendMessageIndex(
142957
+ filePath,
142958
+ spans,
142959
+ (0, import_fs25.statSync)(filePath),
142960
+ readFrom,
142961
+ endOffset
142962
+ );
142963
+ if (seqs === null) {
142964
+ cache.deleteFileIndex(filePath, ConversationCache.conversationIdForFile(filePath));
142965
+ cache.clearIndexParseState(filePath);
142966
+ this.trackCacheWrite(
142967
+ cache.backfillIndex(filePath).catch((err) => {
142968
+ this.log.warn("offset-index.backfill_failed", {
142969
+ event: "offset_index.backfill_failed",
142970
+ filePath,
142971
+ trigger: "noncontiguous-append",
142972
+ err
142973
+ });
142974
+ })
142975
+ );
142976
+ return;
142977
+ }
142978
+ this.pendingLineSeqs.set(filePath, seqs);
142979
+ } catch (err) {
142980
+ this.log.warn("offset-index.extend_failed", {
142981
+ event: "offset_index.extend_failed",
142982
+ filePath,
142983
+ err
142984
+ });
142985
+ }
142986
+ },
142590
142987
  onNewLines: (filePath, lines) => {
142591
142988
  this.cache?.updateFromLines(filePath, lines);
142592
142989
  for (const [sessionId, watchedPath] of this.sessionFileMap) {
@@ -142613,13 +143010,20 @@ var StreamerServer = class {
142613
143010
  this.pendingQuestionKey.set(sessionId, key);
142614
143011
  if (broadcast) this.wsHub.broadcast(m2);
142615
143012
  }
142616
- this.wsHub.broadcast({ type: "conversation_events", sessionId, lines });
143013
+ const seqs = this.pendingLineSeqs.get(filePath);
143014
+ this.wsHub.broadcast({
143015
+ type: "conversation_events",
143016
+ sessionId,
143017
+ lines,
143018
+ ...seqs && seqs.length === lines.length ? { seqs } : {}
143019
+ });
142617
143020
  for (const line of lines) {
142618
143021
  this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
142619
143022
  }
142620
143023
  break;
142621
143024
  }
142622
143025
  }
143026
+ this.pendingLineSeqs.delete(filePath);
142623
143027
  },
142624
143028
  onConversationChanged: (filePath) => {
142625
143029
  this.fileWatcher.poke(filePath);
@@ -143703,9 +144107,13 @@ var StreamerServer = class {
143703
144107
  return;
143704
144108
  }
143705
144109
  const etagSource = conversation;
144110
+ const indexedCount = etagSource.filePath && this.cache ? this.cache.getIndexedMessageCount(
144111
+ ConversationCache.conversationIdForFile(etagSource.filePath)
144112
+ ) : 0;
144113
+ const etagMessageCount = Math.max(etagSource.messageCount, indexedCount);
143706
144114
  const etag = computeConversationEtag({
143707
144115
  filePath: etagSource.filePath,
143708
- messageCount: etagSource.messageCount,
144116
+ messageCount: etagMessageCount,
143709
144117
  timestamp: etagSource.timestamp
143710
144118
  });
143711
144119
  const isFirstPage = !url2.searchParams.has("before_index") && !url2.searchParams.has("anchor_index") && !url2.searchParams.has("after_index");
@@ -143722,12 +144130,14 @@ var StreamerServer = class {
143722
144130
  let slice = filtered;
143723
144131
  let fromIdx = 0;
143724
144132
  let messagePagination;
144133
+ let indexTotal = null;
143725
144134
  if (usePaging) {
143726
144135
  const limit = Math.min(Math.max(intParam(url2, "msg_limit", 80), 1), 500);
143727
144136
  let beforeIndex = total;
143728
144137
  let scanLimit = limit;
143729
144138
  let anchorIndex = null;
143730
144139
  let newerPaging = false;
144140
+ let usedAfterIndex = false;
143731
144141
  if (url2.searchParams.has("before_index")) {
143732
144142
  beforeIndex = intParam(url2, "before_index", total);
143733
144143
  beforeIndex = Math.min(Math.max(beforeIndex, 0), total);
@@ -143736,6 +144146,7 @@ var StreamerServer = class {
143736
144146
  beforeIndex = Math.min(total, from + limit);
143737
144147
  scanLimit = beforeIndex - from;
143738
144148
  newerPaging = true;
144149
+ usedAfterIndex = true;
143739
144150
  } else if (hasAnchor) {
143740
144151
  anchorIndex = Math.min(
143741
144152
  Math.max(intParam(url2, "anchor_index", 0), 0),
@@ -143745,9 +144156,34 @@ var StreamerServer = class {
143745
144156
  beforeIndex = Math.min(total, from + limit);
143746
144157
  newerPaging = true;
143747
144158
  }
143748
- const pagedScanner = this.scannerReady ? await this.getScanner(true) : null;
143749
- const page = scanLimit > 0 && pagedScanner && typeof pagedScanner.getConversationPage === "function" ? await pagedScanner.getConversationPage(id, { beforeIndex, limit: scanLimit }) : null;
143750
- const start = page?.fromIndex ?? Math.max(0, beforeIndex - scanLimit);
144159
+ const isTailRequest = !url2.searchParams.has("before_index") && !hasAfter && !hasAnchor;
144160
+ const indexFilePath = conversation.filePath;
144161
+ if (isTailRequest && indexFilePath && this.cache) {
144162
+ const indexed = this.cache.getIndexedMessageCount(
144163
+ ConversationCache.conversationIdForFile(indexFilePath)
144164
+ );
144165
+ if (indexed > beforeIndex) {
144166
+ beforeIndex = indexed;
144167
+ }
144168
+ }
144169
+ const windowStart = Math.max(0, beforeIndex - scanLimit);
144170
+ const indexWindow = scanLimit > 0 && !hasAnchor && indexFilePath && this.cache ? this.cache.readMessageWindow(indexFilePath, windowStart, beforeIndex) : null;
144171
+ if (!indexWindow && indexFilePath && this.cache && !hasAnchor) {
144172
+ this.trackCacheWrite(
144173
+ this.cache.backfillIndex(indexFilePath).catch((err) => {
144174
+ this.log.warn("offset-index.backfill_failed", {
144175
+ event: "offset_index.backfill_failed",
144176
+ conversationId: id,
144177
+ filePath: indexFilePath,
144178
+ err
144179
+ });
144180
+ })
144181
+ );
144182
+ }
144183
+ const pagedScanner = !indexWindow && this.scannerReady ? await this.getScanner(true) : null;
144184
+ const page = indexWindow ?? (scanLimit > 0 && pagedScanner && typeof pagedScanner.getConversationPage === "function" ? await pagedScanner.getConversationPage(id, { beforeIndex, limit: scanLimit }) : null);
144185
+ if (indexWindow) indexTotal = indexWindow.total;
144186
+ const start = page?.fromIndex ?? windowStart;
143751
144187
  slice = page?.messages ?? filtered.slice(start, beforeIndex);
143752
144188
  fromIdx = start;
143753
144189
  const effectiveTotal = page?.total ?? total;
@@ -143763,6 +144199,9 @@ var StreamerServer = class {
143763
144199
  messagePagination.has_more_newer = beforeIndex < effectiveTotal;
143764
144200
  messagePagination.next_after_index = beforeIndex < effectiveTotal ? beforeIndex : null;
143765
144201
  }
144202
+ if (usedAfterIndex) {
144203
+ messagePagination.etag = etag;
144204
+ }
143766
144205
  }
143767
144206
  const messagesPayload = slice.map((m2, localIdx) => {
143768
144207
  const content = [];
@@ -143804,6 +144243,8 @@ var StreamerServer = class {
143804
144243
  const cachedConvMeta = this.cache?.getMetaById(id);
143805
144244
  const convProvider = coerceProviderForRunner(conv.provider ?? cachedConvMeta?.provider);
143806
144245
  const availability = classifyResumability(conv.projectPath);
144246
+ const metaMessageCount = indexTotal != null && indexTotal > conv.messageCount ? indexTotal : conv.messageCount;
144247
+ const metaLastUpdatedAt = indexTotal != null && indexTotal > conv.messageCount ? slice.at(-1)?.timestamp ?? conv.timestamp : conv.timestamp;
143807
144248
  const body = {
143808
144249
  meta: {
143809
144250
  id,
@@ -143811,8 +144252,8 @@ var StreamerServer = class {
143811
144252
  project_name: conv.projectName,
143812
144253
  project_path: conv.projectPath,
143813
144254
  file_path: conv.filePath,
143814
- last_updated_at: conv.timestamp,
143815
- message_count: conv.messageCount,
144255
+ last_updated_at: metaLastUpdatedAt,
144256
+ message_count: metaMessageCount,
143816
144257
  last_prompt: conv.lastPrompt ?? void 0,
143817
144258
  provider: convProvider,
143818
144259
  resumable: isProviderResumable(convProvider, availability.resumable),
@@ -145094,7 +145535,7 @@ function isBrewInstall(scriptPath = process.argv[1] ?? "") {
145094
145535
  var import_node_crypto4 = require("crypto");
145095
145536
  var import_node_fs8 = require("fs");
145096
145537
  var import_node_path10 = require("path");
145097
- var import_promises18 = require("stream/promises");
145538
+ var import_promises20 = require("stream/promises");
145098
145539
 
145099
145540
  // src/updater/manifest.ts
145100
145541
  init_zod();
@@ -145169,7 +145610,7 @@ async function downloadAndVerify(opts) {
145169
145610
  }
145170
145611
  });
145171
145612
  try {
145172
- await (0, import_promises18.pipeline)(res.body.pipeThrough(measured), out);
145613
+ await (0, import_promises20.pipeline)(res.body.pipeThrough(measured), out);
145173
145614
  } catch (err) {
145174
145615
  (0, import_node_fs8.rmSync)(targetPath, { force: true });
145175
145616
  throw err;
@@ -145383,7 +145824,7 @@ var import_fs29 = __toESM(require("fs"), 1);
145383
145824
  var import_node_fs14 = __toESM(require("fs"), 1);
145384
145825
  var import_node_path19 = __toESM(require("path"), 1);
145385
145826
  var import_node_fs15 = __toESM(require("fs"), 1);
145386
- var import_promises19 = __toESM(require("fs/promises"), 1);
145827
+ var import_promises21 = __toESM(require("fs/promises"), 1);
145387
145828
  var import_node_path20 = __toESM(require("path"), 1);
145388
145829
  var import_node_path21 = require("path");
145389
145830
  var import_node_fs16 = __toESM(require("fs"), 1);
@@ -147615,7 +148056,7 @@ var gr = (s3, t2, e) => {
147615
148056
  E2 ? e(E2) : x2 && a ? Es(x2, o, h, (Le) => S2(Le)) : n ? import_node_fs15.default.chmod(s3, r, e) : e();
147616
148057
  };
147617
148058
  if (s3 === d) return no(s3, S2);
147618
- if (l) return import_promises19.default.mkdir(s3, { mode: r, recursive: true }).then((E2) => S2(null, E2 ?? void 0), S2);
148059
+ if (l) return import_promises21.default.mkdir(s3, { mode: r, recursive: true }).then((E2) => S2(null, E2 ?? void 0), S2);
147619
148060
  let D2 = f(import_node_path20.default.relative(d, s3)).split("/");
147620
148061
  Ss(d, D2, r, c, d, void 0, S2);
147621
148062
  };