@threadbase-sh/streamer 1.66.0 → 1.67.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.
@@ -60,6 +60,35 @@ export declare class ConversationHandlers {
60
60
  handleGetPopularProjects(url: URL, res: ServerResponse): void;
61
61
  handleGetProjectSummaries(url: URL, res: ServerResponse): void;
62
62
  findJsonlPath(uuid: string): string | null;
63
+ /**
64
+ * Resolve a conversation id to a JSONL path, in order of authority.
65
+ *
66
+ * `findJsonlPath` alone answers 64.0% of this machine's 961 conversations and
67
+ * 0 of 343 Codex ones — it reconstructs `<projectsDir>/<dir>/<uuid>.jsonl`,
68
+ * which is Claude Code's layout, and a Codex rollout is
69
+ * `rollout-<ts>-<uuid>.jsonl` under a date path, so that walk cannot match one
70
+ * by construction. The ladder below answers 99.7%, leaving only the three
71
+ * whose file is genuinely gone.
72
+ */
73
+ locateJsonlPath(uuid: string, lookupId: string): Promise<string | null>;
74
+ /**
75
+ * Does `filePath` actually hold the conversation `requestedId` names?
76
+ *
77
+ * The filename settles it for both providers: Claude writes `<uuid>.jsonl`
78
+ * (or `agent-<agentId>.jsonl`), Codex writes `rollout-<ts>-<uuid>.jsonl`.
79
+ * Only when the name says nothing do we open the file — and there the naive
80
+ * rule is wrong, because **a Claude subagent transcript carries the PARENT's
81
+ * `sessionId`**. Matching on `sessionId` alone therefore verifies exactly the
82
+ * file this check exists to reject, so a sidechain is refused outright unless
83
+ * it was asked for by its own `agent-<agentId>` name, which the filename
84
+ * branch above already covers.
85
+ */
86
+ isJsonlPathFor(filePath: string, requestedId: string): Promise<boolean>;
87
+ /** First parseable JSONL line, for identity checks. Null on an empty or unreadable file. */
88
+ readFirstJsonlEntry(filePath: string): Promise<{
89
+ sessionId?: string;
90
+ isSidechain?: boolean;
91
+ } | null>;
63
92
  readCwdFromJsonl(filePath: string): Promise<string | null>;
64
93
  findConversationByUuid(uuid: string): Promise<Conversation | null>;
65
94
  handleGetConversation(id: string, url: URL, res: ServerResponse, ifNoneMatch?: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"conversations.handlers.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/conversations.handlers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"conversations.handlers.d.ts","sourceRoot":"","sources":["../../../src/api/handlers/conversations.handlers.ts"],"names":[],"mappings":"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"}
package/dist/cli.cjs CHANGED
@@ -99746,8 +99746,8 @@ var require_pattern = __commonJS({
99746
99746
  }
99747
99747
  exports2.endsWithSlashGlobStar = endsWithSlashGlobStar;
99748
99748
  function isAffectDepthOfReadingPattern(pattern) {
99749
- const basename12 = path2.basename(pattern);
99750
- return endsWithSlashGlobStar(pattern) || isStaticPattern(basename12);
99749
+ const basename13 = path2.basename(pattern);
99750
+ return endsWithSlashGlobStar(pattern) || isStaticPattern(basename13);
99751
99751
  }
99752
99752
  exports2.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
99753
99753
  function expandPatternsWithBraceExpansion(patterns2) {
@@ -139150,10 +139150,10 @@ var ReaddirpStream = class extends import_node_stream.Readable {
139150
139150
  }
139151
139151
  async _formatEntry(dirent, path2) {
139152
139152
  let entry;
139153
- const basename12 = this._isDirent ? dirent.name : dirent;
139153
+ const basename13 = this._isDirent ? dirent.name : dirent;
139154
139154
  try {
139155
- const fullPath = (0, import_node_path8.resolve)((0, import_node_path8.join)(path2, basename12));
139156
- entry = { path: (0, import_node_path8.relative)(this._root, fullPath), fullPath, basename: basename12 };
139155
+ const fullPath = (0, import_node_path8.resolve)((0, import_node_path8.join)(path2, basename13));
139156
+ entry = { path: (0, import_node_path8.relative)(this._root, fullPath), fullPath, basename: basename13 };
139157
139157
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
139158
139158
  } catch (err) {
139159
139159
  this._onError(err);
@@ -139694,9 +139694,9 @@ var NodeFsHandler = class {
139694
139694
  _watchWithNodeFs(path2, listener) {
139695
139695
  const opts = this.fsw.options;
139696
139696
  const directory = sp.dirname(path2);
139697
- const basename12 = sp.basename(path2);
139697
+ const basename13 = sp.basename(path2);
139698
139698
  const parent = this.fsw._getWatchedDir(directory);
139699
- parent.add(basename12);
139699
+ parent.add(basename13);
139700
139700
  const absolutePath = sp.resolve(path2);
139701
139701
  const options = {
139702
139702
  persistent: opts.persistent
@@ -139706,7 +139706,7 @@ var NodeFsHandler = class {
139706
139706
  let closer;
139707
139707
  if (opts.usePolling) {
139708
139708
  const enableBin = opts.interval !== opts.binaryInterval;
139709
- options.interval = enableBin && isBinaryPath(basename12) ? opts.binaryInterval : opts.interval;
139709
+ options.interval = enableBin && isBinaryPath(basename13) ? opts.binaryInterval : opts.interval;
139710
139710
  closer = setFsWatchFileListener(path2, absolutePath, options, {
139711
139711
  listener,
139712
139712
  rawEmitter: this.fsw._emitRaw
@@ -139729,10 +139729,10 @@ var NodeFsHandler = class {
139729
139729
  return;
139730
139730
  }
139731
139731
  const dirname18 = sp.dirname(file2);
139732
- const basename12 = sp.basename(file2);
139732
+ const basename13 = sp.basename(file2);
139733
139733
  const parent = this.fsw._getWatchedDir(dirname18);
139734
139734
  let prevStats = stats;
139735
- if (parent.has(basename12))
139735
+ if (parent.has(basename13))
139736
139736
  return;
139737
139737
  const listener = async (path2, newStats) => {
139738
139738
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file2, 5))
@@ -139757,9 +139757,9 @@ var NodeFsHandler = class {
139757
139757
  prevStats = newStats2;
139758
139758
  }
139759
139759
  } catch (error51) {
139760
- this.fsw._remove(dirname18, basename12);
139760
+ this.fsw._remove(dirname18, basename13);
139761
139761
  }
139762
- } else if (parent.has(basename12)) {
139762
+ } else if (parent.has(basename13)) {
139763
139763
  const at2 = newStats.atimeMs;
139764
139764
  const mt2 = newStats.mtimeMs;
139765
139765
  if (!at2 || at2 <= mt2 || mt2 !== prevStats.mtimeMs) {
@@ -143610,8 +143610,18 @@ var IndexQueue = class {
143610
143610
  };
143611
143611
  var BATCH_SIZE2 = 12;
143612
143612
  var DEFAULT_CONFIG_PATH2 = "~/.config/threadbase-scanner";
143613
+ function isTestProcess() {
143614
+ return Boolean(process.env.VITEST) || Boolean(process.env.JEST_WORKER_ID) || process.env.NODE_ENV === "test";
143615
+ }
143613
143616
  function defaultDbPath() {
143614
- return process.env.TB_SCANNER_DB ?? (0, import_path10.join)((0, import_os8.homedir)(), ".config", "threadbase-scanner", "index.db");
143617
+ const override = process.env.TB_SCANNER_DB;
143618
+ if (override) return override;
143619
+ if (isTestProcess()) {
143620
+ throw new Error(
143621
+ "TB_SCANNER_DB must be set under a test runner. Refusing to open the default index at ~/.config/threadbase-scanner/index.db, because test fixtures written there are indistinguishable from real conversations and corrupt every measurement of the index. Point it at a temp directory in your test setup, or pass persistent.dbPath explicitly."
143622
+ );
143623
+ }
143624
+ return (0, import_path10.join)((0, import_os8.homedir)(), ".config", "threadbase-scanner", "index.db");
143615
143625
  }
143616
143626
  function capForMemory(doc, max) {
143617
143627
  const combined = combineSearchContent(doc);
@@ -146482,6 +146492,64 @@ var ConversationHandlers = class {
146482
146492
  }
146483
146493
  return null;
146484
146494
  }
146495
+ /**
146496
+ * Resolve a conversation id to a JSONL path, in order of authority.
146497
+ *
146498
+ * `findJsonlPath` alone answers 64.0% of this machine's 961 conversations and
146499
+ * 0 of 343 Codex ones — it reconstructs `<projectsDir>/<dir>/<uuid>.jsonl`,
146500
+ * which is Claude Code's layout, and a Codex rollout is
146501
+ * `rollout-<ts>-<uuid>.jsonl` under a date path, so that walk cannot match one
146502
+ * by construction. The ladder below answers 99.7%, leaving only the three
146503
+ * whose file is genuinely gone.
146504
+ */
146505
+ async locateJsonlPath(uuid3, lookupId) {
146506
+ const live = this.deps.findLiveSessionFilePath(uuid3) ?? this.deps.findLiveSessionFilePath(lookupId);
146507
+ if (live) return live;
146508
+ const cached4 = this.cache?.getMetaById(lookupId)?.filePath;
146509
+ if (cached4 && await this.isJsonlPathFor(cached4, lookupId)) return cached4;
146510
+ return this.findJsonlPath(lookupId);
146511
+ }
146512
+ /**
146513
+ * Does `filePath` actually hold the conversation `requestedId` names?
146514
+ *
146515
+ * The filename settles it for both providers: Claude writes `<uuid>.jsonl`
146516
+ * (or `agent-<agentId>.jsonl`), Codex writes `rollout-<ts>-<uuid>.jsonl`.
146517
+ * Only when the name says nothing do we open the file — and there the naive
146518
+ * rule is wrong, because **a Claude subagent transcript carries the PARENT's
146519
+ * `sessionId`**. Matching on `sessionId` alone therefore verifies exactly the
146520
+ * file this check exists to reject, so a sidechain is refused outright unless
146521
+ * it was asked for by its own `agent-<agentId>` name, which the filename
146522
+ * branch above already covers.
146523
+ */
146524
+ async isJsonlPathFor(filePath, requestedId) {
146525
+ if (!(0, import_fs20.existsSync)(filePath)) return false;
146526
+ const stem = (0, import_path18.basename)(filePath).replace(/\.jsonl$/, "");
146527
+ if (stem === requestedId || stem.includes(requestedId)) return true;
146528
+ const first = await this.readFirstJsonlEntry(filePath);
146529
+ if (!first || first.isSidechain === true) return false;
146530
+ return first.sessionId === requestedId;
146531
+ }
146532
+ /** First parseable JSONL line, for identity checks. Null on an empty or unreadable file. */
146533
+ async readFirstJsonlEntry(filePath) {
146534
+ return new Promise((resolve4) => {
146535
+ const rl = (0, import_readline4.createInterface)({ input: (0, import_fs20.createReadStream)(filePath), crlfDelay: Infinity });
146536
+ let done = false;
146537
+ rl.on("line", (line) => {
146538
+ if (done) return;
146539
+ try {
146540
+ const entry = JSON.parse(line);
146541
+ done = true;
146542
+ rl.close();
146543
+ resolve4(entry);
146544
+ } catch {
146545
+ }
146546
+ });
146547
+ rl.on("close", () => {
146548
+ if (!done) resolve4(null);
146549
+ });
146550
+ rl.on("error", () => resolve4(null));
146551
+ });
146552
+ }
146485
146553
  async readCwdFromJsonl(filePath) {
146486
146554
  return new Promise((resolve4) => {
146487
146555
  const rl = (0, import_readline4.createInterface)({ input: (0, import_fs20.createReadStream)(filePath), crlfDelay: Infinity });
@@ -146507,7 +146575,7 @@ var ConversationHandlers = class {
146507
146575
  async findConversationByUuid(uuid3) {
146508
146576
  const lookupId = this.deps.resolveConversationLookupId(uuid3);
146509
146577
  if (!this.scannerManager.ready && !this.scanProfiles) {
146510
- const filePath2 = this.findJsonlPath(lookupId) ?? this.deps.findLiveSessionFilePath(uuid3) ?? this.deps.findLiveSessionFilePath(lookupId);
146578
+ const filePath2 = await this.locateJsonlPath(uuid3, lookupId);
146511
146579
  if (filePath2) {
146512
146580
  const account = this.cache?.getMetaById(lookupId)?.account ?? void 0;
146513
146581
  const coldScanner = this.scannerManager.current ?? this.scannerManager.newScanner();
@@ -146559,7 +146627,7 @@ var ConversationHandlers = class {
146559
146627
  return fromIndex;
146560
146628
  }
146561
146629
  if (this.scanProfiles) return null;
146562
- const filePath = this.findJsonlPath(lookupId) ?? this.deps.findLiveSessionFilePath(uuid3) ?? this.deps.findLiveSessionFilePath(lookupId);
146630
+ const filePath = await this.locateJsonlPath(uuid3, lookupId);
146563
146631
  if (!filePath) return null;
146564
146632
  if (this.scannerManager.ready) {
146565
146633
  const account = this.cache?.getMetaById(lookupId)?.account ?? void 0;
@@ -151283,6 +151351,13 @@ function createApiDeps(deps) {
151283
151351
  );
151284
151352
  }
151285
151353
  }
151354
+ if (msg.type === "unsubscribe_session" && typeof msg.sessionId === "string") {
151355
+ if (!wsAllows(principal, "history:read")) {
151356
+ deny(msg.type, "history:read");
151357
+ return;
151358
+ }
151359
+ deps.removeSessionSubscriber(msg.sessionId, ws2);
151360
+ }
151286
151361
  if (msg.type === "hold_session" && typeof msg.sessionId === "string") {
151287
151362
  if (!wsAllows(principal, "session:control")) {
151288
151363
  deny(msg.type, "session:control");
@@ -154512,6 +154587,7 @@ var StreamerServer = class {
154512
154587
  withReconciledLifecycle: (sessions) => this.withReconciledLifecycle(sessions),
154513
154588
  currentWarmupState: () => this.currentWarmupState(),
154514
154589
  addSessionSubscriber: (sessionId, ws2) => this.addSessionSubscriber(sessionId, ws2),
154590
+ removeSessionSubscriber: (sessionId, ws2) => this.removeSessionSubscriber(sessionId, ws2),
154515
154591
  startGraceTimer: (sessionId, delayMs) => this.startGraceTimer(sessionId, delayMs),
154516
154592
  armHoldWhenIdle: (sessionId) => this.armHoldWhenIdle(sessionId),
154517
154593
  handleSessionsCount: (res) => this.handleSessionsCount(res),
@@ -154641,6 +154717,12 @@ var StreamerServer = class {
154641
154717
  );
154642
154718
  }
154643
154719
  }
154720
+ removeSessionSubscriber(sessionId, ws2) {
154721
+ const subs = this.sessionSubscribers.get(sessionId);
154722
+ if (!subs) return;
154723
+ subs.delete(ws2);
154724
+ if (subs.size === 0) this.sessionSubscribers.delete(sessionId);
154725
+ }
154644
154726
  /**
154645
154727
  * Bring up Live Activity push, if credentials are present (Feature 12).
154646
154728
  *