@threadbase-sh/streamer 1.27.3 → 1.28.0

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,258 @@ 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
+ /**
137146
+ * Incremental offset-index writer: extend the index for a burst of appended
137147
+ * lines (one watcher read) using their byte spans. Each line is classified
137148
+ * with the scanner's parseJsonlLine (a running per-file reducer state), so the
137149
+ * message ordering can never drift from the scanner's. Message lines get an
137150
+ * index row at the next message_index; non-message lines (summary/sidecar)
137151
+ * get no row but still advance byte_offset. file_state is updated to the end
137152
+ * of the last consumed span.
137153
+ *
137154
+ * Requires an up-to-date `stat` (identity/size/mtime) for the file so the read
137155
+ * path can detect truncation/replacement.
137156
+ *
137157
+ * `readFrom` is the absolute byte offset the watcher read started at, and
137158
+ * `endOffset` is where it ended (readFrom + consumed, i.e. the watcher's new
137159
+ * entry.offset). CONTIGUITY GUARD: the read must begin exactly where the index
137160
+ * left off (`readFrom === existing.byte_offset`, or 0 with no state). If it
137161
+ * doesn't — the watcher attached at EOF after the server was down, or an
137162
+ * append raced an in-flight backfill — extending would assign wrong
137163
+ * message_index values over a hole. In that case this writes nothing and
137164
+ * returns null so the caller drops the index and backfills.
137165
+ *
137166
+ * On success returns the message_index assigned to each input span (null for a
137167
+ * non-message line) so the caller can stamp WS `seq`. Empty array when spans
137168
+ * is empty. `endOffset` is stored verbatim as byte_offset so the watcher's
137169
+ * offset and file_state.byte_offset are the same number by construction.
137170
+ */
137171
+ extendMessageIndex(filePath, spans, stat10, readFrom, endOffset) {
137172
+ const existing = this.getFileState(filePath);
137173
+ const expectedStart = existing?.byte_offset ?? 0;
137174
+ if (readFrom !== expectedStart) return null;
137175
+ if (spans.length === 0) return [];
137176
+ const convId = _ConversationCache.conversationIdForFile(filePath);
137177
+ let state = this.indexParseState.get(filePath);
137178
+ if (!state) {
137179
+ state = initialConvState();
137180
+ this.indexParseState.set(filePath, state);
137181
+ }
137182
+ let nextIndex = existing ? existing.last_message_index + 1 : 0;
137183
+ const rows = [];
137184
+ const seqs = [];
137185
+ for (const span of spans) {
137186
+ const msg = parseJsonlLine(span.text, state);
137187
+ if (!msg) {
137188
+ seqs.push(null);
137189
+ continue;
137190
+ }
137191
+ rows.push({
137192
+ conversation_id: convId,
137193
+ message_index: nextIndex,
137194
+ byte_offset: span.byteOffset,
137195
+ byte_length: span.byteLength,
137196
+ uuid: msg.uuid ?? null,
137197
+ role: msg.role ?? null,
137198
+ ts: msg.timestamp ? Date.parse(msg.timestamp) || null : null
137199
+ });
137200
+ seqs.push(nextIndex);
137201
+ nextIndex++;
137202
+ }
137203
+ const tx = this.db.transaction(() => {
137204
+ for (const r of rows) this.stmts.insertMessageIndexRow.run(r);
137205
+ this.stmts.upsertFileState.run({
137206
+ path: filePath,
137207
+ identity: fileIdentity(stat10),
137208
+ size: stat10.size,
137209
+ mtime_ms: Math.round(stat10.mtimeMs),
137210
+ // Store the watcher's end offset verbatim — same number as entry.offset,
137211
+ // so the next read's contiguity check compares like-for-like (never
137212
+ // false-positive on a read that ended in trailing empty lines).
137213
+ byte_offset: endOffset,
137214
+ last_message_index: nextIndex - 1
137215
+ });
137216
+ });
137217
+ tx();
137218
+ return seqs;
137219
+ }
137220
+ clearIndexParseState(filePath) {
137221
+ this.indexParseState.delete(filePath);
137222
+ }
137223
+ /**
137224
+ * On-demand full backfill of the offset index for a file with no/stale
137225
+ * file_state (cold conversation, or after a truncation/replacement). Rebuilds
137226
+ * from byte 0: drops any existing rows, walks the whole file in chunks with a
137227
+ * running parse state, yields to the event loop every ~1000 lines so a large
137228
+ * file never blocks, and writes index rows + file_state.
137229
+ *
137230
+ * Single-flighted per path: concurrent callers await the same walk. The
137231
+ * triggering detail request is served by the scanner fallback while this runs.
137232
+ */
137233
+ backfillIndex(filePath) {
137234
+ const inFlight = this.backfillInFlight.get(filePath);
137235
+ if (inFlight) return inFlight;
137236
+ const walk = this.runBackfill(filePath).finally(() => {
137237
+ this.backfillInFlight.delete(filePath);
137238
+ });
137239
+ this.backfillInFlight.set(filePath, walk);
137240
+ return walk;
137241
+ }
137242
+ async runBackfill(filePath) {
137243
+ const convId = _ConversationCache.conversationIdForFile(filePath);
137244
+ this.deleteFileIndex(filePath, convId);
137245
+ this.indexParseState.delete(filePath);
137246
+ const CHUNK = 256 * 1024;
137247
+ const YIELD_EVERY = 1e3;
137248
+ const state = initialConvState();
137249
+ const fh = await (0, import_promises12.open)(filePath, "r");
137250
+ let fileOffset = 0;
137251
+ let carry = Buffer.alloc(0);
137252
+ let nextIndex = 0;
137253
+ let linesSinceYield = 0;
137254
+ let lastConsumedEnd = 0;
137255
+ let stat10;
137256
+ try {
137257
+ stat10 = await fh.stat();
137258
+ const buf = Buffer.alloc(CHUNK);
137259
+ for (; ; ) {
137260
+ const { bytesRead } = await fh.read(buf, 0, CHUNK, null);
137261
+ if (bytesRead === 0) break;
137262
+ const combined = carry.length > 0 ? Buffer.concat([carry, buf.subarray(0, bytesRead)]) : buf.subarray(0, bytesRead);
137263
+ const { spans, consumed } = splitCompleteLines(combined, fileOffset);
137264
+ const rows = [];
137265
+ for (const span of spans) {
137266
+ const msg = parseJsonlLine(span.text, state);
137267
+ linesSinceYield++;
137268
+ if (msg) {
137269
+ rows.push({
137270
+ conversation_id: convId,
137271
+ message_index: nextIndex,
137272
+ byte_offset: span.byteOffset,
137273
+ byte_length: span.byteLength,
137274
+ uuid: msg.uuid ?? null,
137275
+ role: msg.role ?? null,
137276
+ ts: msg.timestamp ? Date.parse(msg.timestamp) || null : null
137277
+ });
137278
+ nextIndex++;
137279
+ }
137280
+ if (linesSinceYield >= YIELD_EVERY) {
137281
+ linesSinceYield = 0;
137282
+ await (0, import_promises13.setImmediate)();
137283
+ }
137284
+ }
137285
+ if (rows.length > 0) this.appendMessageIndexRows(rows);
137286
+ lastConsumedEnd = fileOffset + consumed;
137287
+ carry = Buffer.from(combined.subarray(consumed));
137288
+ fileOffset += consumed;
137289
+ }
137290
+ } finally {
137291
+ await fh.close();
137292
+ }
137293
+ this.upsertFileState({
137294
+ path: filePath,
137295
+ identity: fileIdentity(stat10),
137296
+ size: stat10.size,
137297
+ mtime_ms: Math.round(stat10.mtimeMs),
137298
+ byte_offset: lastConsumedEnd,
137299
+ last_message_index: nextIndex - 1
137300
+ });
137301
+ this.indexParseState.set(filePath, state);
137302
+ }
137303
+ /**
137304
+ * Windowed detail read straight from the offset index — the hot path.
137305
+ * Returns the parsed messages for message_index in [fromIndex, toIndex) plus
137306
+ * the total indexed count, or null when the index can't serve this file (no
137307
+ * file_state, identity/size mismatch = truncation/replacement, or cold index)
137308
+ * so the caller falls back to the scanner and enqueues a backfill.
137309
+ *
137310
+ * On a match it SQL-selects the window's byte ranges and preads exactly those
137311
+ * ranges from the JSONL (never the whole file), parsing only the sliced lines.
137312
+ * Returns messages in the same ConversationMessage shape parseJsonlLine
137313
+ * produces during a scan, so the payload is identical to the scanner path.
137314
+ */
137315
+ readMessageWindow(filePath, fromIndex, toIndex) {
137316
+ const fileState = this.getFileState(filePath);
137317
+ if (!fileState) return null;
137318
+ let stat10;
137319
+ try {
137320
+ stat10 = (0, import_fs17.statSync)(filePath);
137321
+ } catch {
137322
+ return null;
137323
+ }
137324
+ if (fileIdentity(stat10) !== fileState.identity || stat10.size !== fileState.byte_offset) {
137325
+ return null;
137326
+ }
137327
+ const total = fileState.last_message_index + 1;
137328
+ const from = Math.max(0, fromIndex);
137329
+ const to2 = Math.min(toIndex, total);
137330
+ if (to2 <= from) return { messages: [], total, fromIndex: from };
137331
+ const rows = this.getMessageIndexWindow(
137332
+ _ConversationCache.conversationIdForFile(filePath),
137333
+ from,
137334
+ to2
137335
+ );
137336
+ if (rows.length === 0) return { messages: [], total, fromIndex: from };
137337
+ const messages = [];
137338
+ const fd = (0, import_fs17.openSync)(filePath, "r");
137339
+ try {
137340
+ const state = initialConvState();
137341
+ for (const row of rows) {
137342
+ const buf = Buffer.alloc(row.byte_length);
137343
+ (0, import_fs17.readSync)(fd, buf, 0, row.byte_length, row.byte_offset);
137344
+ const msg = parseJsonlLine(buf.toString("utf-8"), state);
137345
+ if (msg) messages.push(msg);
137346
+ }
137347
+ } finally {
137348
+ (0, import_fs17.closeSync)(fd);
137349
+ }
137350
+ return { messages, total, fromIndex: from };
137351
+ }
137016
137352
  agentEntrypointsKey() {
137017
137353
  return [...this.agentEntrypoints].sort().join(",");
137018
137354
  }
@@ -137736,7 +138072,7 @@ var ConversationsRepository = class {
137736
138072
  };
137737
138073
 
137738
138074
  // src/db/repositories/projects.repository.ts
137739
- var import_crypto5 = require("crypto");
138075
+ var import_crypto6 = require("crypto");
137740
138076
 
137741
138077
  // src/utils/canonicalizeProjectPath.ts
137742
138078
  function canonicalizeProjectPath(projectPath) {
@@ -137829,7 +138165,7 @@ var ProjectsRepository = class {
137829
138165
  });
137830
138166
  return rowToProject(this.getById.get(existing.id));
137831
138167
  }
137832
- const id = (0, import_crypto5.randomUUID)();
138168
+ const id = (0, import_crypto6.randomUUID)();
137833
138169
  this.insert.run({
137834
138170
  id,
137835
138171
  path: path2,
@@ -137924,7 +138260,7 @@ var import_path19 = require("path");
137924
138260
 
137925
138261
  // src/codex-pty-runner.ts
137926
138262
  var import_headless = __toESM(require_xterm_headless(), 1);
137927
- var import_crypto6 = require("crypto");
138263
+ var import_crypto7 = require("crypto");
137928
138264
  var import_fs21 = require("fs");
137929
138265
  var import_path17 = require("path");
137930
138266
  init_logger();
@@ -138179,7 +138515,7 @@ var CodexPtyRunner = class {
138179
138515
  // ManagedSession handle only.
138180
138516
  async startFresh(options) {
138181
138517
  const nodePty = await loadPty();
138182
- const sessionId = (0, import_crypto6.randomUUID)();
138518
+ const sessionId = (0, import_crypto7.randomUUID)();
138183
138519
  const projectName = options.projectName ?? (0, import_path17.basename)(options.projectPath);
138184
138520
  const args = ["--cd", options.projectPath, "--no-alt-screen"];
138185
138521
  if (options.systemPrompt) {
@@ -138508,7 +138844,7 @@ function stripAnsi(str) {
138508
138844
 
138509
138845
  // src/pty-manager.ts
138510
138846
  var import_headless2 = __toESM(require_xterm_headless(), 1);
138511
- var import_crypto7 = require("crypto");
138847
+ var import_crypto8 = require("crypto");
138512
138848
  var import_fs22 = require("fs");
138513
138849
  var import_path18 = require("path");
138514
138850
  init_logger();
@@ -138912,7 +139248,7 @@ var PTYManager = class {
138912
139248
  // onReady fires once Claude reaches its first prompt (waiting_input).
138913
139249
  async startFresh(options) {
138914
139250
  const nodePty = await loadPty2();
138915
- const sessionId = (0, import_crypto7.randomUUID)();
139251
+ const sessionId = (0, import_crypto8.randomUUID)();
138916
139252
  const projectName = options.projectName ?? (0, import_path18.basename)(options.projectPath);
138917
139253
  const args = [
138918
139254
  "--permission-mode",
@@ -139496,7 +139832,7 @@ var LiveSessionManager = class {
139496
139832
  init_logger();
139497
139833
 
139498
139834
  // src/pair-store.ts
139499
- var import_crypto8 = require("crypto");
139835
+ var import_crypto9 = require("crypto");
139500
139836
  var DEFAULT_TTL_SECONDS = 180;
139501
139837
  var SWEEP_INTERVAL_MS = 6e4;
139502
139838
  var PairTokenStore = class {
@@ -139511,7 +139847,7 @@ var PairTokenStore = class {
139511
139847
  }
139512
139848
  }
139513
139849
  mint() {
139514
- const token = `pt_${(0, import_crypto8.randomBytes)(16).toString("hex")}`;
139850
+ const token = `pt_${(0, import_crypto9.randomBytes)(16).toString("hex")}`;
139515
139851
  const expiresAt = Date.now() + this.ttlMs;
139516
139852
  this.current = { token, expiresAt, used: false };
139517
139853
  return {
@@ -139716,11 +140052,11 @@ function seal(plaintext, recipientPublicKeyBase64) {
139716
140052
  // node_modules/chokidar/index.js
139717
140053
  var import_node_events2 = require("events");
139718
140054
  var import_node_fs7 = require("fs");
139719
- var import_promises14 = require("fs/promises");
140055
+ var import_promises16 = require("fs/promises");
139720
140056
  var sp2 = __toESM(require("path"), 1);
139721
140057
 
139722
140058
  // node_modules/readdirp/index.js
139723
- var import_promises12 = require("fs/promises");
140059
+ var import_promises14 = require("fs/promises");
139724
140060
  var import_node_path9 = require("path");
139725
140061
  var import_node_stream2 = require("stream");
139726
140062
  var EntryTypes2 = {
@@ -139801,7 +140137,7 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139801
140137
  const { root, type } = opts;
139802
140138
  this._fileFilter = normalizeFilter2(opts.fileFilter);
139803
140139
  this._directoryFilter = normalizeFilter2(opts.directoryFilter);
139804
- const statMethod = opts.lstat ? import_promises12.lstat : import_promises12.stat;
140140
+ const statMethod = opts.lstat ? import_promises14.lstat : import_promises14.stat;
139805
140141
  if (wantBigintFsStats2) {
139806
140142
  this._stat = (path2) => statMethod(path2, { bigint: true });
139807
140143
  } else {
@@ -139872,7 +140208,7 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139872
140208
  async _exploreDir(path2, depth) {
139873
140209
  let files;
139874
140210
  try {
139875
- files = await (0, import_promises12.readdir)(path2, this._rdOptions);
140211
+ files = await (0, import_promises14.readdir)(path2, this._rdOptions);
139876
140212
  } catch (error51) {
139877
140213
  this._onError(error51);
139878
140214
  }
@@ -139910,8 +140246,8 @@ var ReaddirpStream2 = class extends import_node_stream2.Readable {
139910
140246
  if (stats && stats.isSymbolicLink()) {
139911
140247
  const full = entry.fullPath;
139912
140248
  try {
139913
- const entryRealPath = await (0, import_promises12.realpath)(full);
139914
- const entryRealPathStats = await (0, import_promises12.lstat)(entryRealPath);
140249
+ const entryRealPath = await (0, import_promises14.realpath)(full);
140250
+ const entryRealPathStats = await (0, import_promises14.lstat)(entryRealPath);
139915
140251
  if (entryRealPathStats.isFile()) {
139916
140252
  return "file";
139917
140253
  }
@@ -139954,7 +140290,7 @@ function readdirp2(root, options = {}) {
139954
140290
 
139955
140291
  // node_modules/chokidar/handler.js
139956
140292
  var import_node_fs6 = require("fs");
139957
- var import_promises13 = require("fs/promises");
140293
+ var import_promises15 = require("fs/promises");
139958
140294
  var import_node_os3 = require("os");
139959
140295
  var sp = __toESM(require("path"), 1);
139960
140296
  var STR_DATA2 = "data";
@@ -139981,7 +140317,7 @@ var EVENTS2 = {
139981
140317
  };
139982
140318
  var EV2 = EVENTS2;
139983
140319
  var THROTTLE_MODE_WATCH2 = "watch";
139984
- var statMethods2 = { lstat: import_promises13.lstat, stat: import_promises13.stat };
140320
+ var statMethods2 = { lstat: import_promises15.lstat, stat: import_promises15.stat };
139985
140321
  var KEY_LISTENERS2 = "listeners";
139986
140322
  var KEY_ERR2 = "errHandlers";
139987
140323
  var KEY_RAW2 = "rawEmitters";
@@ -140338,7 +140674,7 @@ var setFsWatchListener2 = (path2, fullPath, options, handlers) => {
140338
140674
  cont.watcherUnusable = true;
140339
140675
  if (isWindows3 && error51.code === "EPERM") {
140340
140676
  try {
140341
- const fd = await (0, import_promises13.open)(path2, "r");
140677
+ const fd = await (0, import_promises15.open)(path2, "r");
140342
140678
  await fd.close();
140343
140679
  broadcastErr(error51);
140344
140680
  } catch (err) {
@@ -140469,7 +140805,7 @@ var NodeFsHandler2 = class {
140469
140805
  return;
140470
140806
  if (!newStats || newStats.mtimeMs === 0) {
140471
140807
  try {
140472
- const newStats2 = await (0, import_promises13.stat)(file2);
140808
+ const newStats2 = await (0, import_promises15.stat)(file2);
140473
140809
  if (this.fsw.closed)
140474
140810
  return;
140475
140811
  const at2 = newStats2.atimeMs;
@@ -140524,7 +140860,7 @@ var NodeFsHandler2 = class {
140524
140860
  this.fsw._incrReadyCount();
140525
140861
  let linkPath;
140526
140862
  try {
140527
- linkPath = await (0, import_promises13.realpath)(path2);
140863
+ linkPath = await (0, import_promises15.realpath)(path2);
140528
140864
  } catch (e) {
140529
140865
  this.fsw._emitReady();
140530
140866
  return true;
@@ -140673,7 +141009,7 @@ var NodeFsHandler2 = class {
140673
141009
  let closer;
140674
141010
  if (stats.isDirectory()) {
140675
141011
  const absPath = sp.resolve(path2);
140676
- const targetPath = follow ? await (0, import_promises13.realpath)(path2) : path2;
141012
+ const targetPath = follow ? await (0, import_promises15.realpath)(path2) : path2;
140677
141013
  if (this.fsw.closed)
140678
141014
  return;
140679
141015
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -140683,7 +141019,7 @@ var NodeFsHandler2 = class {
140683
141019
  this.fsw._symlinkPaths.set(absPath, targetPath);
140684
141020
  }
140685
141021
  } else if (stats.isSymbolicLink()) {
140686
- const targetPath = follow ? await (0, import_promises13.realpath)(path2) : path2;
141022
+ const targetPath = follow ? await (0, import_promises15.realpath)(path2) : path2;
140687
141023
  if (this.fsw.closed)
140688
141024
  return;
140689
141025
  const parent = sp.dirname(wh.watchPath);
@@ -140843,7 +141179,7 @@ var DirEntry2 = class {
140843
141179
  return;
140844
141180
  const dir = this.path;
140845
141181
  try {
140846
- await (0, import_promises14.readdir)(dir);
141182
+ await (0, import_promises16.readdir)(dir);
140847
141183
  } catch (err) {
140848
141184
  if (this._removeWatcher) {
140849
141185
  this._removeWatcher(sp2.dirname(dir), sp2.basename(dir));
@@ -141193,7 +141529,7 @@ var FSWatcher2 = class extends import_node_events2.EventEmitter {
141193
141529
  const fullPath = opts.cwd ? sp2.join(opts.cwd, path2) : path2;
141194
141530
  let stats2;
141195
141531
  try {
141196
- stats2 = await (0, import_promises14.stat)(fullPath);
141532
+ stats2 = await (0, import_promises16.stat)(fullPath);
141197
141533
  } catch (err) {
141198
141534
  }
141199
141535
  if (!stats2 || this.closed)
@@ -141447,18 +141783,20 @@ var chokidar_default = { watch: watch2, FSWatcher: FSWatcher2 };
141447
141783
 
141448
141784
  // src/services/conversations/conversationWatcher.ts
141449
141785
  var import_fs23 = require("fs");
141450
- var import_promises15 = require("fs/promises");
141786
+ var import_promises17 = require("fs/promises");
141451
141787
  var ConversationWatcher = class {
141452
141788
  files = /* @__PURE__ */ new Map();
141453
141789
  directories = /* @__PURE__ */ new Map();
141454
141790
  onNewLine;
141455
141791
  onNewLines;
141792
+ onNewLineSpans;
141456
141793
  onConversationChanged;
141457
141794
  onFileDeleted;
141458
141795
  onError;
141459
141796
  constructor(events = {}) {
141460
141797
  this.onNewLine = events.onNewLine;
141461
141798
  this.onNewLines = events.onNewLines;
141799
+ this.onNewLineSpans = events.onNewLineSpans;
141462
141800
  this.onConversationChanged = events.onConversationChanged;
141463
141801
  this.onFileDeleted = events.onFileDeleted;
141464
141802
  this.onError = events.onError;
@@ -141551,20 +141889,24 @@ var ConversationWatcher = class {
141551
141889
  entry.reading = true;
141552
141890
  try {
141553
141891
  for (; ; ) {
141554
- const st2 = await (0, import_promises15.stat)(filePath);
141892
+ const st2 = await (0, import_promises17.stat)(filePath);
141555
141893
  if (st2.size <= entry.offset) break;
141556
141894
  const readFrom = entry.offset;
141557
141895
  const bytesToRead = st2.size - readFrom;
141558
141896
  const buf = Buffer.alloc(bytesToRead);
141559
- const fh = await (0, import_promises15.open)(filePath, "r");
141897
+ const fh = await (0, import_promises17.open)(filePath, "r");
141560
141898
  try {
141561
141899
  await fh.read(buf, 0, bytesToRead, readFrom);
141562
141900
  } finally {
141563
141901
  await fh.close();
141564
141902
  }
141565
- entry.offset = readFrom + bytesToRead;
141903
+ const { spans, consumed } = splitCompleteLines(buf, readFrom);
141904
+ entry.offset = readFrom + consumed;
141566
141905
  if (!this.files.has(filePath)) return;
141567
- const lines = buf.toString("utf-8").split("\n").filter(Boolean);
141906
+ const lines = spans.map((s3) => s3.text);
141907
+ if (spans.length > 0) {
141908
+ this.onNewLineSpans?.(filePath, spans, readFrom, entry.offset);
141909
+ }
141568
141910
  if (this.onNewLines) {
141569
141911
  this.onNewLines(filePath, lines);
141570
141912
  } else {
@@ -142004,8 +142346,8 @@ function discoveredToResponse(d, conversationId) {
142004
142346
  }
142005
142347
 
142006
142348
  // src/uploads.ts
142007
- var import_crypto9 = require("crypto");
142008
- var import_promises16 = require("fs/promises");
142349
+ var import_crypto10 = require("crypto");
142350
+ var import_promises18 = require("fs/promises");
142009
142351
  var import_heic_convert = __toESM(require_heic_convert(), 1);
142010
142352
  var import_path21 = require("path");
142011
142353
  var UPLOAD_DIR_NAME = ".threadbase-uploads";
@@ -142038,12 +142380,12 @@ async function saveUploadFile(input) {
142038
142380
  mimeType = "image/jpeg";
142039
142381
  originalName = originalName.replace(/\.(heic|heif)$/i, ".jpg");
142040
142382
  }
142041
- const id = `up_${(0, import_crypto9.randomBytes)(8).toString("hex")}`;
142383
+ const id = `up_${(0, import_crypto10.randomBytes)(8).toString("hex")}`;
142042
142384
  const safeName = sanitizeFilename(originalName) || `file${MIME_TO_EXT[mimeType] ?? ""}`;
142043
142385
  const dir = (0, import_path21.join)(input.projectPath, UPLOAD_DIR_NAME, input.sessionId);
142044
- await (0, import_promises16.mkdir)(dir, { recursive: true });
142386
+ await (0, import_promises18.mkdir)(dir, { recursive: true });
142045
142387
  const filePath = (0, import_path21.join)(dir, `${Date.now()}-${id}-${safeName}`);
142046
- await (0, import_promises16.writeFile)(filePath, buffer);
142388
+ await (0, import_promises18.writeFile)(filePath, buffer);
142047
142389
  return {
142048
142390
  id,
142049
142391
  filePath,
@@ -142442,6 +142784,10 @@ var StreamerServer = class {
142442
142784
  fileWatcher;
142443
142785
  sessionFileMap = /* @__PURE__ */ new Map();
142444
142786
  // sessionId → JSONL filePath
142787
+ // Per-file seq assignments from the most recent onNewLineSpans (offset index),
142788
+ // handed to the immediately-following onNewLines so it can stamp WS `seq` on
142789
+ // the matching conversation_events entries. Same read → same lines order.
142790
+ pendingLineSeqs = /* @__PURE__ */ new Map();
142445
142791
  pendingQuestions = /* @__PURE__ */ new Map();
142446
142792
  // Content key of the AskUserQuestion currently broadcast for a session (from
142447
142793
  // either the rendered screen or JSONL), used to de-dupe the two paths: when
@@ -142565,7 +142911,7 @@ var StreamerServer = class {
142565
142911
  this.agentEntrypoints = parseAgentEntrypointsEnv(process.env.THREADBASE_AGENT_ENTRYPOINTS);
142566
142912
  const rawRoot = process.env.THREADBASE_BROWSE_ROOT ?? loadBrowseRoot() ?? config2.browseRoot;
142567
142913
  if (rawRoot) {
142568
- (0, import_promises17.realpath)(rawRoot).then((resolved) => {
142914
+ (0, import_promises19.realpath)(rawRoot).then((resolved) => {
142569
142915
  this.browseRoot = resolved;
142570
142916
  if (this.verbose) this.log.info(`Browse root: ${resolved}`, { browseRoot: resolved });
142571
142917
  }).catch(() => {
@@ -142587,6 +142933,42 @@ var StreamerServer = class {
142587
142933
  this.sessionStore = new SessionStore();
142588
142934
  this.wsHub = new WSHub();
142589
142935
  this.fileWatcher = new ConversationWatcher({
142936
+ onNewLineSpans: (filePath, spans, readFrom, endOffset) => {
142937
+ if (!this.cache) return;
142938
+ const cache = this.cache;
142939
+ this.pendingLineSeqs.delete(filePath);
142940
+ try {
142941
+ const seqs = cache.extendMessageIndex(
142942
+ filePath,
142943
+ spans,
142944
+ (0, import_fs25.statSync)(filePath),
142945
+ readFrom,
142946
+ endOffset
142947
+ );
142948
+ if (seqs === null) {
142949
+ cache.deleteFileIndex(filePath, ConversationCache.conversationIdForFile(filePath));
142950
+ cache.clearIndexParseState(filePath);
142951
+ this.trackCacheWrite(
142952
+ cache.backfillIndex(filePath).catch((err) => {
142953
+ this.log.warn("offset-index.backfill_failed", {
142954
+ event: "offset_index.backfill_failed",
142955
+ filePath,
142956
+ trigger: "noncontiguous-append",
142957
+ err
142958
+ });
142959
+ })
142960
+ );
142961
+ return;
142962
+ }
142963
+ this.pendingLineSeqs.set(filePath, seqs);
142964
+ } catch (err) {
142965
+ this.log.warn("offset-index.extend_failed", {
142966
+ event: "offset_index.extend_failed",
142967
+ filePath,
142968
+ err
142969
+ });
142970
+ }
142971
+ },
142590
142972
  onNewLines: (filePath, lines) => {
142591
142973
  this.cache?.updateFromLines(filePath, lines);
142592
142974
  for (const [sessionId, watchedPath] of this.sessionFileMap) {
@@ -142613,13 +142995,20 @@ var StreamerServer = class {
142613
142995
  this.pendingQuestionKey.set(sessionId, key);
142614
142996
  if (broadcast) this.wsHub.broadcast(m2);
142615
142997
  }
142616
- this.wsHub.broadcast({ type: "conversation_events", sessionId, lines });
142998
+ const seqs = this.pendingLineSeqs.get(filePath);
142999
+ this.wsHub.broadcast({
143000
+ type: "conversation_events",
143001
+ sessionId,
143002
+ lines,
143003
+ ...seqs && seqs.length === lines.length ? { seqs } : {}
143004
+ });
142617
143005
  for (const line of lines) {
142618
143006
  this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
142619
143007
  }
142620
143008
  break;
142621
143009
  }
142622
143010
  }
143011
+ this.pendingLineSeqs.delete(filePath);
142623
143012
  },
142624
143013
  onConversationChanged: (filePath) => {
142625
143014
  this.fileWatcher.poke(filePath);
@@ -143703,9 +144092,13 @@ var StreamerServer = class {
143703
144092
  return;
143704
144093
  }
143705
144094
  const etagSource = conversation;
144095
+ const indexedCount = etagSource.filePath && this.cache ? this.cache.getIndexedMessageCount(
144096
+ ConversationCache.conversationIdForFile(etagSource.filePath)
144097
+ ) : 0;
144098
+ const etagMessageCount = Math.max(etagSource.messageCount, indexedCount);
143706
144099
  const etag = computeConversationEtag({
143707
144100
  filePath: etagSource.filePath,
143708
- messageCount: etagSource.messageCount,
144101
+ messageCount: etagMessageCount,
143709
144102
  timestamp: etagSource.timestamp
143710
144103
  });
143711
144104
  const isFirstPage = !url2.searchParams.has("before_index") && !url2.searchParams.has("anchor_index") && !url2.searchParams.has("after_index");
@@ -143722,12 +144115,14 @@ var StreamerServer = class {
143722
144115
  let slice = filtered;
143723
144116
  let fromIdx = 0;
143724
144117
  let messagePagination;
144118
+ let indexTotal = null;
143725
144119
  if (usePaging) {
143726
144120
  const limit = Math.min(Math.max(intParam(url2, "msg_limit", 80), 1), 500);
143727
144121
  let beforeIndex = total;
143728
144122
  let scanLimit = limit;
143729
144123
  let anchorIndex = null;
143730
144124
  let newerPaging = false;
144125
+ let usedAfterIndex = false;
143731
144126
  if (url2.searchParams.has("before_index")) {
143732
144127
  beforeIndex = intParam(url2, "before_index", total);
143733
144128
  beforeIndex = Math.min(Math.max(beforeIndex, 0), total);
@@ -143736,6 +144131,7 @@ var StreamerServer = class {
143736
144131
  beforeIndex = Math.min(total, from + limit);
143737
144132
  scanLimit = beforeIndex - from;
143738
144133
  newerPaging = true;
144134
+ usedAfterIndex = true;
143739
144135
  } else if (hasAnchor) {
143740
144136
  anchorIndex = Math.min(
143741
144137
  Math.max(intParam(url2, "anchor_index", 0), 0),
@@ -143745,9 +144141,34 @@ var StreamerServer = class {
143745
144141
  beforeIndex = Math.min(total, from + limit);
143746
144142
  newerPaging = true;
143747
144143
  }
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);
144144
+ const isTailRequest = !url2.searchParams.has("before_index") && !hasAfter && !hasAnchor;
144145
+ const indexFilePath = conversation.filePath;
144146
+ if (isTailRequest && indexFilePath && this.cache) {
144147
+ const indexed = this.cache.getIndexedMessageCount(
144148
+ ConversationCache.conversationIdForFile(indexFilePath)
144149
+ );
144150
+ if (indexed > beforeIndex) {
144151
+ beforeIndex = indexed;
144152
+ }
144153
+ }
144154
+ const windowStart = Math.max(0, beforeIndex - scanLimit);
144155
+ const indexWindow = scanLimit > 0 && !hasAnchor && indexFilePath && this.cache ? this.cache.readMessageWindow(indexFilePath, windowStart, beforeIndex) : null;
144156
+ if (!indexWindow && indexFilePath && this.cache && !hasAnchor) {
144157
+ this.trackCacheWrite(
144158
+ this.cache.backfillIndex(indexFilePath).catch((err) => {
144159
+ this.log.warn("offset-index.backfill_failed", {
144160
+ event: "offset_index.backfill_failed",
144161
+ conversationId: id,
144162
+ filePath: indexFilePath,
144163
+ err
144164
+ });
144165
+ })
144166
+ );
144167
+ }
144168
+ const pagedScanner = !indexWindow && this.scannerReady ? await this.getScanner(true) : null;
144169
+ const page = indexWindow ?? (scanLimit > 0 && pagedScanner && typeof pagedScanner.getConversationPage === "function" ? await pagedScanner.getConversationPage(id, { beforeIndex, limit: scanLimit }) : null);
144170
+ if (indexWindow) indexTotal = indexWindow.total;
144171
+ const start = page?.fromIndex ?? windowStart;
143751
144172
  slice = page?.messages ?? filtered.slice(start, beforeIndex);
143752
144173
  fromIdx = start;
143753
144174
  const effectiveTotal = page?.total ?? total;
@@ -143763,6 +144184,9 @@ var StreamerServer = class {
143763
144184
  messagePagination.has_more_newer = beforeIndex < effectiveTotal;
143764
144185
  messagePagination.next_after_index = beforeIndex < effectiveTotal ? beforeIndex : null;
143765
144186
  }
144187
+ if (usedAfterIndex) {
144188
+ messagePagination.etag = etag;
144189
+ }
143766
144190
  }
143767
144191
  const messagesPayload = slice.map((m2, localIdx) => {
143768
144192
  const content = [];
@@ -143804,6 +144228,8 @@ var StreamerServer = class {
143804
144228
  const cachedConvMeta = this.cache?.getMetaById(id);
143805
144229
  const convProvider = coerceProviderForRunner(conv.provider ?? cachedConvMeta?.provider);
143806
144230
  const availability = classifyResumability(conv.projectPath);
144231
+ const metaMessageCount = indexTotal != null && indexTotal > conv.messageCount ? indexTotal : conv.messageCount;
144232
+ const metaLastUpdatedAt = indexTotal != null && indexTotal > conv.messageCount ? slice.at(-1)?.timestamp ?? conv.timestamp : conv.timestamp;
143807
144233
  const body = {
143808
144234
  meta: {
143809
144235
  id,
@@ -143811,8 +144237,8 @@ var StreamerServer = class {
143811
144237
  project_name: conv.projectName,
143812
144238
  project_path: conv.projectPath,
143813
144239
  file_path: conv.filePath,
143814
- last_updated_at: conv.timestamp,
143815
- message_count: conv.messageCount,
144240
+ last_updated_at: metaLastUpdatedAt,
144241
+ message_count: metaMessageCount,
143816
144242
  last_prompt: conv.lastPrompt ?? void 0,
143817
144243
  provider: convProvider,
143818
144244
  resumable: isProviderResumable(convProvider, availability.resumable),
@@ -145094,7 +145520,7 @@ function isBrewInstall(scriptPath = process.argv[1] ?? "") {
145094
145520
  var import_node_crypto4 = require("crypto");
145095
145521
  var import_node_fs8 = require("fs");
145096
145522
  var import_node_path10 = require("path");
145097
- var import_promises18 = require("stream/promises");
145523
+ var import_promises20 = require("stream/promises");
145098
145524
 
145099
145525
  // src/updater/manifest.ts
145100
145526
  init_zod();
@@ -145169,7 +145595,7 @@ async function downloadAndVerify(opts) {
145169
145595
  }
145170
145596
  });
145171
145597
  try {
145172
- await (0, import_promises18.pipeline)(res.body.pipeThrough(measured), out);
145598
+ await (0, import_promises20.pipeline)(res.body.pipeThrough(measured), out);
145173
145599
  } catch (err) {
145174
145600
  (0, import_node_fs8.rmSync)(targetPath, { force: true });
145175
145601
  throw err;
@@ -145383,7 +145809,7 @@ var import_fs29 = __toESM(require("fs"), 1);
145383
145809
  var import_node_fs14 = __toESM(require("fs"), 1);
145384
145810
  var import_node_path19 = __toESM(require("path"), 1);
145385
145811
  var import_node_fs15 = __toESM(require("fs"), 1);
145386
- var import_promises19 = __toESM(require("fs/promises"), 1);
145812
+ var import_promises21 = __toESM(require("fs/promises"), 1);
145387
145813
  var import_node_path20 = __toESM(require("path"), 1);
145388
145814
  var import_node_path21 = require("path");
145389
145815
  var import_node_fs16 = __toESM(require("fs"), 1);
@@ -147615,7 +148041,7 @@ var gr = (s3, t2, e) => {
147615
148041
  E2 ? e(E2) : x2 && a ? Es(x2, o, h, (Le) => S2(Le)) : n ? import_node_fs15.default.chmod(s3, r, e) : e();
147616
148042
  };
147617
148043
  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);
148044
+ if (l) return import_promises21.default.mkdir(s3, { mode: r, recursive: true }).then((E2) => S2(null, E2 ?? void 0), S2);
147619
148045
  let D2 = f(import_node_path20.default.relative(d, s3)).split("/");
147620
148046
  Ss(d, D2, r, c, d, void 0, S2);
147621
148047
  };