@threadbase-sh/streamer 1.67.0 → 1.67.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6477,7 +6477,7 @@ import {
6477
6477
  search
6478
6478
  } from "@threadbase-sh/scanner";
6479
6479
  import { createReadStream, existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
6480
- import { join as join12 } from "path";
6480
+ import { basename as basename5, join as join12 } from "path";
6481
6481
  import { createInterface } from "readline";
6482
6482
 
6483
6483
  // src/conversation-cache.ts
@@ -6769,6 +6769,7 @@ CREATE TABLE IF NOT EXISTS session_names (
6769
6769
  );
6770
6770
  `;
6771
6771
  var cacheLog = getLogger("cache");
6772
+ var PARSE_STATE_LOOKBACK = 8;
6772
6773
  var ConversationCache = class _ConversationCache {
6773
6774
  db;
6774
6775
  tailSize;
@@ -7285,7 +7286,7 @@ var ConversationCache = class _ConversationCache {
7285
7286
  if (to <= from) return { messages: [], total, fromIndex: from };
7286
7287
  const rows = this.getMessageIndexWindow(
7287
7288
  _ConversationCache.conversationIdForFile(filePath),
7288
- from,
7289
+ Math.max(0, from - PARSE_STATE_LOOKBACK),
7289
7290
  to
7290
7291
  );
7291
7292
  if (rows.length === 0) return { messages: [], total, fromIndex: from };
@@ -7297,7 +7298,7 @@ var ConversationCache = class _ConversationCache {
7297
7298
  const buf = Buffer.alloc(row.byte_length);
7298
7299
  readSync3(fd, buf, 0, row.byte_length, row.byte_offset);
7299
7300
  const msg = parseJsonlLine(buf.toString("utf-8"), state);
7300
- if (msg) messages.push(msg);
7301
+ if (msg && row.message_index >= from) messages.push(msg);
7301
7302
  }
7302
7303
  } finally {
7303
7304
  closeSync3(fd);
@@ -8644,6 +8645,64 @@ var ConversationHandlers = class {
8644
8645
  }
8645
8646
  return null;
8646
8647
  }
8648
+ /**
8649
+ * Resolve a conversation id to a JSONL path, in order of authority.
8650
+ *
8651
+ * `findJsonlPath` alone answers 64.0% of this machine's 961 conversations and
8652
+ * 0 of 343 Codex ones — it reconstructs `<projectsDir>/<dir>/<uuid>.jsonl`,
8653
+ * which is Claude Code's layout, and a Codex rollout is
8654
+ * `rollout-<ts>-<uuid>.jsonl` under a date path, so that walk cannot match one
8655
+ * by construction. The ladder below answers 99.7%, leaving only the three
8656
+ * whose file is genuinely gone.
8657
+ */
8658
+ async locateJsonlPath(uuid, lookupId) {
8659
+ const live = this.deps.findLiveSessionFilePath(uuid) ?? this.deps.findLiveSessionFilePath(lookupId);
8660
+ if (live) return live;
8661
+ const cached3 = this.cache?.getMetaById(lookupId)?.filePath;
8662
+ if (cached3 && await this.isJsonlPathFor(cached3, lookupId)) return cached3;
8663
+ return this.findJsonlPath(lookupId);
8664
+ }
8665
+ /**
8666
+ * Does `filePath` actually hold the conversation `requestedId` names?
8667
+ *
8668
+ * The filename settles it for both providers: Claude writes `<uuid>.jsonl`
8669
+ * (or `agent-<agentId>.jsonl`), Codex writes `rollout-<ts>-<uuid>.jsonl`.
8670
+ * Only when the name says nothing do we open the file — and there the naive
8671
+ * rule is wrong, because **a Claude subagent transcript carries the PARENT's
8672
+ * `sessionId`**. Matching on `sessionId` alone therefore verifies exactly the
8673
+ * file this check exists to reject, so a sidechain is refused outright unless
8674
+ * it was asked for by its own `agent-<agentId>` name, which the filename
8675
+ * branch above already covers.
8676
+ */
8677
+ async isJsonlPathFor(filePath, requestedId) {
8678
+ if (!existsSync8(filePath)) return false;
8679
+ const stem = basename5(filePath).replace(/\.jsonl$/, "");
8680
+ if (stem === requestedId || stem.includes(requestedId)) return true;
8681
+ const first = await this.readFirstJsonlEntry(filePath);
8682
+ if (!first || first.isSidechain === true) return false;
8683
+ return first.sessionId === requestedId;
8684
+ }
8685
+ /** First parseable JSONL line, for identity checks. Null on an empty or unreadable file. */
8686
+ async readFirstJsonlEntry(filePath) {
8687
+ return new Promise((resolve2) => {
8688
+ const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });
8689
+ let done = false;
8690
+ rl.on("line", (line) => {
8691
+ if (done) return;
8692
+ try {
8693
+ const entry = JSON.parse(line);
8694
+ done = true;
8695
+ rl.close();
8696
+ resolve2(entry);
8697
+ } catch {
8698
+ }
8699
+ });
8700
+ rl.on("close", () => {
8701
+ if (!done) resolve2(null);
8702
+ });
8703
+ rl.on("error", () => resolve2(null));
8704
+ });
8705
+ }
8647
8706
  async readCwdFromJsonl(filePath) {
8648
8707
  return new Promise((resolve2) => {
8649
8708
  const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });
@@ -8669,7 +8728,7 @@ var ConversationHandlers = class {
8669
8728
  async findConversationByUuid(uuid) {
8670
8729
  const lookupId = this.deps.resolveConversationLookupId(uuid);
8671
8730
  if (!this.scannerManager.ready && !this.scanProfiles) {
8672
- const filePath2 = this.findJsonlPath(lookupId) ?? this.deps.findLiveSessionFilePath(uuid) ?? this.deps.findLiveSessionFilePath(lookupId);
8731
+ const filePath2 = await this.locateJsonlPath(uuid, lookupId);
8673
8732
  if (filePath2) {
8674
8733
  const account = this.cache?.getMetaById(lookupId)?.account ?? void 0;
8675
8734
  const coldScanner = this.scannerManager.current ?? this.scannerManager.newScanner();
@@ -8721,7 +8780,7 @@ var ConversationHandlers = class {
8721
8780
  return fromIndex;
8722
8781
  }
8723
8782
  if (this.scanProfiles) return null;
8724
- const filePath = this.findJsonlPath(lookupId) ?? this.deps.findLiveSessionFilePath(uuid) ?? this.deps.findLiveSessionFilePath(lookupId);
8783
+ const filePath = await this.locateJsonlPath(uuid, lookupId);
8725
8784
  if (!filePath) return null;
8726
8785
  if (this.scannerManager.ready) {
8727
8786
  const account = this.cache?.getMetaById(lookupId)?.account ?? void 0;
@@ -9135,7 +9194,7 @@ var ConversationHandlers = class {
9135
9194
 
9136
9195
  // src/api/handlers/sessions.handlers.ts
9137
9196
  import { existsSync as existsSync10 } from "fs";
9138
- import { basename as basename5, dirname as dirname9, join as join16 } from "path";
9197
+ import { basename as basename6, dirname as dirname9, join as join16 } from "path";
9139
9198
 
9140
9199
  // node_modules/nanoid/index.js
9141
9200
  import crypto2 from "crypto";
@@ -10659,7 +10718,7 @@ var SessionHandlers = class {
10659
10718
  const jsonlCwd = jsonlPath ? await this.deps.readCwdFromJsonl(jsonlPath) : null;
10660
10719
  if (jsonlCwd) {
10661
10720
  projectPath = jsonlCwd;
10662
- projectName = projectName || basename5(jsonlCwd);
10721
+ projectName = projectName || basename6(jsonlCwd);
10663
10722
  }
10664
10723
  }
10665
10724
  if (!projectPath) {
@@ -15256,7 +15315,7 @@ function discoveredToResponse(d, conversationId) {
15256
15315
  // src/session-watchers.ts
15257
15316
  import { existsSync as existsSync15, watch as fsWatch, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync13 } from "fs";
15258
15317
  import { homedir as homedir13 } from "os";
15259
- import { basename as basename6, join as join24 } from "path";
15318
+ import { basename as basename7, join as join24 } from "path";
15260
15319
  var SessionWatchers = class {
15261
15320
  constructor(deps) {
15262
15321
  this.deps = deps;
@@ -15362,7 +15421,7 @@ var SessionWatchers = class {
15362
15421
  try {
15363
15422
  const now = Date.now();
15364
15423
  const match = readdirSync7(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: statSync13(join24(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
15365
- ({ f }) => basename6(f, ".jsonl") === sessionId || this.readFirstLineSessionId(join24(projectsDir, f)) === sessionId
15424
+ ({ f }) => basename7(f, ".jsonl") === sessionId || this.readFirstLineSessionId(join24(projectsDir, f)) === sessionId
15366
15425
  ).sort((a, b) => b.mtime - a.mtime)[0];
15367
15426
  if (match) resolvedFilePath = join24(projectsDir, match.f);
15368
15427
  } catch {