@threadbase-sh/streamer 1.17.0 → 1.18.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/index.d.cts CHANGED
@@ -291,6 +291,7 @@ interface ServerConfig {
291
291
  enabled: boolean;
292
292
  emoji: string;
293
293
  }>;
294
+ codexRoots?: string[];
294
295
  ptyGracePeriodMs?: number;
295
296
  cacheDir?: string;
296
297
  tailSize?: number;
@@ -341,6 +342,7 @@ interface ConversationListItem {
341
342
  lastMessage: string | null;
342
343
  preview: string | null;
343
344
  source: string | null;
345
+ provider: "claude-code" | "codex-cli";
344
346
  }
345
347
  interface CachedTailMessage {
346
348
  role: string;
@@ -368,6 +370,7 @@ interface ScannerMeta {
368
370
  firstMessage?: unknown;
369
371
  lastMessage?: unknown;
370
372
  preview?: string;
373
+ provider?: "claude-code" | "codex-cli";
371
374
  }
372
375
  declare class ConversationCache {
373
376
  private db;
@@ -413,6 +416,7 @@ declare class ConversationCache {
413
416
  populateTailFromFile(convId: string, filePath: string): boolean;
414
417
  listConversations(opts: {
415
418
  project?: string;
419
+ provider?: string;
416
420
  limit: number;
417
421
  offset: number;
418
422
  }): {
@@ -772,6 +776,7 @@ declare class StreamerServer {
772
776
  private tailSize;
773
777
  private directoryDebounceMs;
774
778
  private markScannerStaleDebounced;
779
+ private codexRoots;
775
780
  private includeAgents;
776
781
  private agentEntrypoints;
777
782
  private honoApp;
@@ -810,6 +815,7 @@ declare class StreamerServer {
810
815
  private handleGetPopularProjects;
811
816
  private handleListProjectChats;
812
817
  private buildStatCache;
818
+ private codexScanOpts;
813
819
  private getScanner;
814
820
  private getFreshScanner;
815
821
  private findJsonlPath;
package/dist/index.d.ts CHANGED
@@ -291,6 +291,7 @@ interface ServerConfig {
291
291
  enabled: boolean;
292
292
  emoji: string;
293
293
  }>;
294
+ codexRoots?: string[];
294
295
  ptyGracePeriodMs?: number;
295
296
  cacheDir?: string;
296
297
  tailSize?: number;
@@ -341,6 +342,7 @@ interface ConversationListItem {
341
342
  lastMessage: string | null;
342
343
  preview: string | null;
343
344
  source: string | null;
345
+ provider: "claude-code" | "codex-cli";
344
346
  }
345
347
  interface CachedTailMessage {
346
348
  role: string;
@@ -368,6 +370,7 @@ interface ScannerMeta {
368
370
  firstMessage?: unknown;
369
371
  lastMessage?: unknown;
370
372
  preview?: string;
373
+ provider?: "claude-code" | "codex-cli";
371
374
  }
372
375
  declare class ConversationCache {
373
376
  private db;
@@ -413,6 +416,7 @@ declare class ConversationCache {
413
416
  populateTailFromFile(convId: string, filePath: string): boolean;
414
417
  listConversations(opts: {
415
418
  project?: string;
419
+ provider?: string;
416
420
  limit: number;
417
421
  offset: number;
418
422
  }): {
@@ -772,6 +776,7 @@ declare class StreamerServer {
772
776
  private tailSize;
773
777
  private directoryDebounceMs;
774
778
  private markScannerStaleDebounced;
779
+ private codexRoots;
775
780
  private includeAgents;
776
781
  private agentEntrypoints;
777
782
  private honoApp;
@@ -810,6 +815,7 @@ declare class StreamerServer {
810
815
  private handleGetPopularProjects;
811
816
  private handleListProjectChats;
812
817
  private buildStatCache;
818
+ private codexScanOpts;
813
819
  private getScanner;
814
820
  private getFreshScanner;
815
821
  private findJsonlPath;
package/dist/index.js CHANGED
@@ -2285,6 +2285,14 @@ function runSqliteMigrations(db, migrationsDir) {
2285
2285
  return { applied, skipped };
2286
2286
  }
2287
2287
 
2288
+ // src/providers.ts
2289
+ var CLAUDE_CODE_PROVIDER = "claude-code";
2290
+ var CODEX_CLI_PROVIDER = "codex-cli";
2291
+ function isProviderResumable(provider, availabilityResumable) {
2292
+ if (provider === CODEX_CLI_PROVIDER) return false;
2293
+ return availabilityResumable;
2294
+ }
2295
+
2288
2296
  // src/services/conversations/isAgentConversation.ts
2289
2297
  import { closeSync, openSync, readSync, statSync } from "fs";
2290
2298
  var DEFAULT_AGENT_ENTRYPOINTS = /* @__PURE__ */ new Set(["sdk-cli", "claude-vscode"]);
@@ -2452,11 +2460,11 @@ var ConversationCache = class _ConversationCache {
2452
2460
  INSERT INTO conversation_meta
2453
2461
  (id, file_path, project_path, project_name, title, model, account, branch,
2454
2462
  message_count, last_activity, first_message, last_message, preview, updated_at,
2455
- mtime_ms, file_size)
2463
+ mtime_ms, file_size, provider)
2456
2464
  VALUES
2457
2465
  (@id, @file_path, @project_path, @project_name, @title, @model, @account, @branch,
2458
2466
  @message_count, @last_activity, @first_message, @last_message, @preview, @updated_at,
2459
- @mtime_ms, @file_size)
2467
+ @mtime_ms, @file_size, @provider)
2460
2468
  ON CONFLICT(id) DO UPDATE SET
2461
2469
  file_path = excluded.file_path,
2462
2470
  project_path = excluded.project_path,
@@ -2472,7 +2480,8 @@ var ConversationCache = class _ConversationCache {
2472
2480
  preview = excluded.preview,
2473
2481
  updated_at = excluded.updated_at,
2474
2482
  mtime_ms = excluded.mtime_ms,
2475
- file_size = excluded.file_size
2483
+ file_size = excluded.file_size,
2484
+ provider = excluded.provider
2476
2485
  WHERE conversation_meta.updated_at < excluded.updated_at
2477
2486
  `),
2478
2487
  getTail: db.prepare("SELECT * FROM conversation_tail WHERE conversation_id = ?"),
@@ -2496,6 +2505,10 @@ var ConversationCache = class _ConversationCache {
2496
2505
  countByProject: db.prepare(
2497
2506
  "SELECT COUNT(*) as n FROM conversation_meta WHERE project_path = ?"
2498
2507
  ),
2508
+ listByProvider: db.prepare(
2509
+ "SELECT * FROM conversation_meta WHERE provider = ? ORDER BY last_activity DESC LIMIT ? OFFSET ?"
2510
+ ),
2511
+ countByProvider: db.prepare("SELECT COUNT(*) as n FROM conversation_meta WHERE provider = ?"),
2499
2512
  deleteById: db.prepare("DELETE FROM conversation_meta WHERE id = ?"),
2500
2513
  deleteTailById: db.prepare("DELETE FROM conversation_tail WHERE conversation_id = ?"),
2501
2514
  deleteAll: db.prepare("DELETE FROM conversation_meta"),
@@ -2753,7 +2766,8 @@ var ConversationCache = class _ConversationCache {
2753
2766
  preview: m.preview ?? null,
2754
2767
  updated_at: 0,
2755
2768
  mtime_ms: mtimeMs,
2756
- file_size: fileSize
2769
+ file_size: fileSize,
2770
+ provider: m.provider ?? CLAUDE_CODE_PROVIDER
2757
2771
  });
2758
2772
  if (this.fileIndexLoaded) this.fileIndex.set(m.filePath, id);
2759
2773
  upsertedIds.push(id);
@@ -2832,12 +2846,15 @@ var ConversationCache = class _ConversationCache {
2832
2846
  return true;
2833
2847
  }
2834
2848
  listConversations(opts) {
2835
- const { project, limit, offset } = opts;
2849
+ const { project, provider, limit, offset } = opts;
2836
2850
  let total;
2837
2851
  let rows;
2838
2852
  if (project) {
2839
2853
  total = this.stmts.countByProject.get(project).n;
2840
2854
  rows = limit === 0 ? [] : this.stmts.listByProject.all(project, limit, offset);
2855
+ } else if (provider) {
2856
+ total = this.stmts.countByProvider.get(provider).n;
2857
+ rows = limit === 0 ? [] : this.stmts.listByProvider.all(provider, limit, offset);
2841
2858
  } else {
2842
2859
  total = this.stmts.count.get().n;
2843
2860
  rows = limit === 0 ? [] : this.stmts.list.all(limit, offset);
@@ -2859,7 +2876,8 @@ var ConversationCache = class _ConversationCache {
2859
2876
  firstMessage: r.first_message,
2860
2877
  lastMessage: r.last_message,
2861
2878
  preview: r.preview,
2862
- source: r.source
2879
+ source: r.source,
2880
+ provider: r.provider ?? CLAUDE_CODE_PROVIDER
2863
2881
  }))
2864
2882
  };
2865
2883
  }
@@ -2892,7 +2910,8 @@ var ConversationCache = class _ConversationCache {
2892
2910
  firstMessage: row.first_message,
2893
2911
  lastMessage: row.last_message,
2894
2912
  preview: row.preview,
2895
- source: row.source
2913
+ source: row.source,
2914
+ provider: row.provider ?? CLAUDE_CODE_PROVIDER
2896
2915
  };
2897
2916
  }
2898
2917
  setConversationProjectId(conversationId, projectId) {
@@ -3403,6 +3422,7 @@ function normalizeConversationToProjectChat(conversation) {
3403
3422
  createdAt: null,
3404
3423
  status: "resumable",
3405
3424
  source: "hdd-cache",
3425
+ provider: conversation.provider ?? CLAUDE_CODE_PROVIDER,
3406
3426
  indexedAt: null,
3407
3427
  fileMtime: null,
3408
3428
  filePath: conversation.filePath ?? null,
@@ -4327,6 +4347,7 @@ var StreamerServer = class {
4327
4347
  // in the constructor body (NOT a field initializer) so directoryDebounceMs
4328
4348
  // is already set when debounce() captures the wait.
4329
4349
  markScannerStaleDebounced;
4350
+ codexRoots;
4330
4351
  includeAgents;
4331
4352
  agentEntrypoints;
4332
4353
  honoApp;
@@ -4341,6 +4362,7 @@ var StreamerServer = class {
4341
4362
  this.verbose = config.verbose ?? false;
4342
4363
  this.disableDb = config.disableDb ?? false;
4343
4364
  this.scanProfiles = config.scanProfiles;
4365
+ this.codexRoots = config.codexRoots ?? [join12(homedir5(), ".codex", "sessions")];
4344
4366
  this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
4345
4367
  this.cacheDir = config.cacheDir ?? loadCacheDir() ?? join12(homedir5(), ".threadbase", "cache");
4346
4368
  this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
@@ -4758,9 +4780,13 @@ var StreamerServer = class {
4758
4780
  const warmupScanner = new ConversationScanner();
4759
4781
  const warmupStatCache = this.buildStatCache(null);
4760
4782
  const shouldEmitProgress = createScanProgressThrottle();
4761
- warmupScanner.scan({
4783
+ const scanOpts = {
4762
4784
  ...this.scanProfiles ? { profiles: this.scanProfiles } : {},
4763
- ...warmupStatCache ? { statCache: warmupStatCache } : {},
4785
+ ...this.codexScanOpts(),
4786
+ ...warmupStatCache ? { statCache: warmupStatCache } : {}
4787
+ };
4788
+ warmupScanner.scan({
4789
+ ...scanOpts,
4764
4790
  onProgress: (scanned, total) => {
4765
4791
  if (shouldEmitProgress(scanned, total)) {
4766
4792
  this.wsHub.broadcast({ type: "scan_progress", scanned, total });
@@ -4985,6 +5011,7 @@ var StreamerServer = class {
4985
5011
  const offset = intParam(url, "offset", 0);
4986
5012
  const sort = url.searchParams.get("sort") ?? "recent";
4987
5013
  const project = url.searchParams.get("project") ?? void 0;
5014
+ const providerFilter = url.searchParams.get("provider") ?? void 0;
4988
5015
  const bustCache = url.searchParams.get("refresh") === "1";
4989
5016
  if (bustCache) {
4990
5017
  this.cache?.invalidate();
@@ -4992,7 +5019,12 @@ var StreamerServer = class {
4992
5019
  this.scannerReady = null;
4993
5020
  }
4994
5021
  if (this.cache && !bustCache) {
4995
- const { conversations, total: total2 } = this.cache.listConversations({ project, limit, offset });
5022
+ const { conversations, total: total2 } = this.cache.listConversations({
5023
+ project,
5024
+ provider: providerFilter,
5025
+ limit,
5026
+ offset
5027
+ });
4996
5028
  const adapted2 = conversations.map((c) => ({
4997
5029
  id: c.id,
4998
5030
  title: deriveProjectChatTitle({
@@ -5011,7 +5043,8 @@ var StreamerServer = class {
5011
5043
  lastActivity: c.lastActivity,
5012
5044
  firstMessage: c.firstMessage ? JSON.parse(c.firstMessage) : void 0,
5013
5045
  lastMessage: c.lastMessage ? JSON.parse(c.lastMessage) : void 0,
5014
- model: c.model ?? void 0
5046
+ model: c.model ?? void 0,
5047
+ provider: c.provider ?? CLAUDE_CODE_PROVIDER
5015
5048
  }));
5016
5049
  json(res, 200, { conversations: adapted2, hasMore: offset + limit < total2, offset, total: total2 });
5017
5050
  return;
@@ -5020,6 +5053,8 @@ var StreamerServer = class {
5020
5053
  let metas = [...scanner.getMetadataCache().values()];
5021
5054
  metas = applyIncludeFilter(metas, "conversations");
5022
5055
  if (project) metas = applyProjectFilter(metas, project);
5056
+ if (providerFilter)
5057
+ metas = metas.filter((m) => (m.provider ?? CLAUDE_CODE_PROVIDER) === providerFilter);
5023
5058
  metas = applySort(metas, sort);
5024
5059
  const total = metas.length;
5025
5060
  const page = applyPagination(metas, limit, offset);
@@ -5043,7 +5078,8 @@ var StreamerServer = class {
5043
5078
  lastActivity: c.timestamp,
5044
5079
  firstMessage: c.firstMessage ?? void 0,
5045
5080
  lastMessage: c.lastMessage ?? void 0,
5046
- model: c.model ?? void 0
5081
+ model: c.model ?? void 0,
5082
+ provider: c.provider ?? CLAUDE_CODE_PROVIDER
5047
5083
  };
5048
5084
  });
5049
5085
  json(res, 200, { conversations: adapted, hasMore: offset + limit < total, offset, total });
@@ -5056,9 +5092,15 @@ var StreamerServer = class {
5056
5092
  }
5057
5093
  async handleConversationsCount(url, res) {
5058
5094
  const project = url.searchParams.get("project") ?? void 0;
5095
+ const providerFilter = url.searchParams.get("provider") ?? void 0;
5059
5096
  const bustCache = url.searchParams.get("refresh") === "1";
5060
5097
  if (this.cache) {
5061
- const { total } = this.cache.listConversations({ project, limit: 0, offset: 0 });
5098
+ const { total } = this.cache.listConversations({
5099
+ project,
5100
+ provider: providerFilter,
5101
+ limit: 0,
5102
+ offset: 0
5103
+ });
5062
5104
  json(res, 200, { total });
5063
5105
  if (bustCache) this.refreshCountInBackground();
5064
5106
  return;
@@ -5067,6 +5109,8 @@ var StreamerServer = class {
5067
5109
  let metas = [...scanner.getMetadataCache().values()];
5068
5110
  metas = applyIncludeFilter(metas, "conversations");
5069
5111
  if (project) metas = applyProjectFilter(metas, project);
5112
+ if (providerFilter)
5113
+ metas = metas.filter((m) => (m.provider ?? CLAUDE_CODE_PROVIDER) === providerFilter);
5070
5114
  json(res, 200, { total: metas.length });
5071
5115
  }
5072
5116
  // Fire-and-forget full rescan that reconciles the SQLite cache from disk so a
@@ -5155,6 +5199,14 @@ var StreamerServer = class {
5155
5199
  }
5156
5200
  return statCache.size > 0 ? statCache : void 0;
5157
5201
  }
5202
+ // Returns the provider + codexRoots fragment to spread into every scan()/search() call.
5203
+ // codexRoots=[] disables codex scanning (safe no-op per scanner contract).
5204
+ codexScanOpts() {
5205
+ return {
5206
+ providers: [CLAUDE_CODE_PROVIDER, CODEX_CLI_PROVIDER],
5207
+ codexRoots: this.codexRoots
5208
+ };
5209
+ }
5158
5210
  // skipStaleRescan: when an indexed scanner already exists, return it directly
5159
5211
  // even if scannerStale is set, leaving the flag untouched so the next
5160
5212
  // list-level call still rescans. The single-conversation detail path passes
@@ -5180,6 +5232,7 @@ var StreamerServer = class {
5180
5232
  this.scanner = new ConversationScanner();
5181
5233
  this.scannerReady = this.scanner.scan({
5182
5234
  ...this.scanProfiles ? { profiles: this.scanProfiles } : {},
5235
+ ...this.codexScanOpts(),
5183
5236
  ...statCache ? { statCache } : {}
5184
5237
  });
5185
5238
  await this.scannerReady;
@@ -5283,6 +5336,7 @@ var StreamerServer = class {
5283
5336
  const tail = this.cache.getConversationTail(id);
5284
5337
  if (tail && tail.messages.length > 0) {
5285
5338
  const cachedMeta = this.cache.getMetaById(id);
5339
+ const cachedProvider = cachedMeta?.provider ?? CLAUDE_CODE_PROVIDER;
5286
5340
  const availability2 = classifyResumability(cachedMeta?.projectPath);
5287
5341
  const messagesPayload2 = tail.messages.map((m, idx) => ({
5288
5342
  message_index: idx,
@@ -5301,7 +5355,8 @@ var StreamerServer = class {
5301
5355
  file_path: cachedMeta?.filePath ?? void 0,
5302
5356
  last_updated_at: cachedMeta?.lastActivity ?? void 0,
5303
5357
  message_count: cachedMeta?.messageCount ?? void 0,
5304
- resumable: availability2.resumable,
5358
+ provider: cachedProvider,
5359
+ resumable: isProviderResumable(cachedProvider, availability2.resumable),
5305
5360
  ...availability2.unavailable_reason && {
5306
5361
  unavailable_reason: availability2.unavailable_reason
5307
5362
  }
@@ -5399,6 +5454,8 @@ var StreamerServer = class {
5399
5454
  };
5400
5455
  });
5401
5456
  const conv = conversation;
5457
+ const cachedConvMeta = this.cache?.getMetaById(id);
5458
+ const convProvider = conv.provider ?? cachedConvMeta?.provider ?? CLAUDE_CODE_PROVIDER;
5402
5459
  const availability = classifyResumability(conv.projectPath);
5403
5460
  const body = {
5404
5461
  meta: {
@@ -5410,7 +5467,8 @@ var StreamerServer = class {
5410
5467
  last_updated_at: conv.timestamp,
5411
5468
  message_count: conv.messageCount,
5412
5469
  last_prompt: conv.lastPrompt ?? void 0,
5413
- resumable: availability.resumable,
5470
+ provider: convProvider,
5471
+ resumable: isProviderResumable(convProvider, availability.resumable),
5414
5472
  ...availability.unavailable_reason && {
5415
5473
  unavailable_reason: availability.unavailable_reason
5416
5474
  }
@@ -5445,7 +5503,8 @@ var StreamerServer = class {
5445
5503
  {
5446
5504
  limit,
5447
5505
  include: "conversations",
5448
- ...this.scanProfiles ? { profiles: this.scanProfiles } : {}
5506
+ ...this.scanProfiles ? { profiles: this.scanProfiles } : {},
5507
+ ...this.codexScanOpts()
5449
5508
  },
5450
5509
  scanner
5451
5510
  );
@@ -5461,7 +5520,8 @@ var StreamerServer = class {
5461
5520
  messageCount: r.meta.messageCount,
5462
5521
  lastActivity: r.meta.timestamp,
5463
5522
  firstMessage: r.meta.firstMessage ?? void 0,
5464
- lastMessage: r.meta.lastMessage ?? void 0
5523
+ lastMessage: r.meta.lastMessage ?? void 0,
5524
+ provider: r.meta.provider ?? CLAUDE_CODE_PROVIDER
5465
5525
  }));
5466
5526
  json(res, 200, {
5467
5527
  conversations: adapted,
@@ -6141,6 +6201,7 @@ function classifyResumability(cwd) {
6141
6201
  }
6142
6202
  function conversationToResumableSession(c) {
6143
6203
  const availability = classifyResumability(c.projectPath);
6204
+ const provider = c.provider ?? CLAUDE_CODE_PROVIDER;
6144
6205
  return {
6145
6206
  type: "conversation",
6146
6207
  id: c.id,
@@ -6165,7 +6226,8 @@ function conversationToResumableSession(c) {
6165
6226
  ...c.firstMessage != null && { firstMessageText: c.firstMessage },
6166
6227
  ...c.lastMessage != null && { lastMessageText: c.lastMessage },
6167
6228
  filePath: c.filePath,
6168
- resumable: availability.resumable,
6229
+ provider,
6230
+ resumable: isProviderResumable(provider, availability.resumable),
6169
6231
  ...availability.unavailable_reason && {
6170
6232
  unavailable_reason: availability.unavailable_reason
6171
6233
  }