@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.cjs CHANGED
@@ -2319,6 +2319,14 @@ function runSqliteMigrations(db, migrationsDir) {
2319
2319
  return { applied, skipped };
2320
2320
  }
2321
2321
 
2322
+ // src/providers.ts
2323
+ var CLAUDE_CODE_PROVIDER = "claude-code";
2324
+ var CODEX_CLI_PROVIDER = "codex-cli";
2325
+ function isProviderResumable(provider, availabilityResumable) {
2326
+ if (provider === CODEX_CLI_PROVIDER) return false;
2327
+ return availabilityResumable;
2328
+ }
2329
+
2322
2330
  // src/services/conversations/isAgentConversation.ts
2323
2331
  var import_fs6 = require("fs");
2324
2332
  var DEFAULT_AGENT_ENTRYPOINTS = /* @__PURE__ */ new Set(["sdk-cli", "claude-vscode"]);
@@ -2486,11 +2494,11 @@ var ConversationCache = class _ConversationCache {
2486
2494
  INSERT INTO conversation_meta
2487
2495
  (id, file_path, project_path, project_name, title, model, account, branch,
2488
2496
  message_count, last_activity, first_message, last_message, preview, updated_at,
2489
- mtime_ms, file_size)
2497
+ mtime_ms, file_size, provider)
2490
2498
  VALUES
2491
2499
  (@id, @file_path, @project_path, @project_name, @title, @model, @account, @branch,
2492
2500
  @message_count, @last_activity, @first_message, @last_message, @preview, @updated_at,
2493
- @mtime_ms, @file_size)
2501
+ @mtime_ms, @file_size, @provider)
2494
2502
  ON CONFLICT(id) DO UPDATE SET
2495
2503
  file_path = excluded.file_path,
2496
2504
  project_path = excluded.project_path,
@@ -2506,7 +2514,8 @@ var ConversationCache = class _ConversationCache {
2506
2514
  preview = excluded.preview,
2507
2515
  updated_at = excluded.updated_at,
2508
2516
  mtime_ms = excluded.mtime_ms,
2509
- file_size = excluded.file_size
2517
+ file_size = excluded.file_size,
2518
+ provider = excluded.provider
2510
2519
  WHERE conversation_meta.updated_at < excluded.updated_at
2511
2520
  `),
2512
2521
  getTail: db.prepare("SELECT * FROM conversation_tail WHERE conversation_id = ?"),
@@ -2530,6 +2539,10 @@ var ConversationCache = class _ConversationCache {
2530
2539
  countByProject: db.prepare(
2531
2540
  "SELECT COUNT(*) as n FROM conversation_meta WHERE project_path = ?"
2532
2541
  ),
2542
+ listByProvider: db.prepare(
2543
+ "SELECT * FROM conversation_meta WHERE provider = ? ORDER BY last_activity DESC LIMIT ? OFFSET ?"
2544
+ ),
2545
+ countByProvider: db.prepare("SELECT COUNT(*) as n FROM conversation_meta WHERE provider = ?"),
2533
2546
  deleteById: db.prepare("DELETE FROM conversation_meta WHERE id = ?"),
2534
2547
  deleteTailById: db.prepare("DELETE FROM conversation_tail WHERE conversation_id = ?"),
2535
2548
  deleteAll: db.prepare("DELETE FROM conversation_meta"),
@@ -2787,7 +2800,8 @@ var ConversationCache = class _ConversationCache {
2787
2800
  preview: m.preview ?? null,
2788
2801
  updated_at: 0,
2789
2802
  mtime_ms: mtimeMs,
2790
- file_size: fileSize
2803
+ file_size: fileSize,
2804
+ provider: m.provider ?? CLAUDE_CODE_PROVIDER
2791
2805
  });
2792
2806
  if (this.fileIndexLoaded) this.fileIndex.set(m.filePath, id);
2793
2807
  upsertedIds.push(id);
@@ -2866,12 +2880,15 @@ var ConversationCache = class _ConversationCache {
2866
2880
  return true;
2867
2881
  }
2868
2882
  listConversations(opts) {
2869
- const { project, limit, offset } = opts;
2883
+ const { project, provider, limit, offset } = opts;
2870
2884
  let total;
2871
2885
  let rows;
2872
2886
  if (project) {
2873
2887
  total = this.stmts.countByProject.get(project).n;
2874
2888
  rows = limit === 0 ? [] : this.stmts.listByProject.all(project, limit, offset);
2889
+ } else if (provider) {
2890
+ total = this.stmts.countByProvider.get(provider).n;
2891
+ rows = limit === 0 ? [] : this.stmts.listByProvider.all(provider, limit, offset);
2875
2892
  } else {
2876
2893
  total = this.stmts.count.get().n;
2877
2894
  rows = limit === 0 ? [] : this.stmts.list.all(limit, offset);
@@ -2893,7 +2910,8 @@ var ConversationCache = class _ConversationCache {
2893
2910
  firstMessage: r.first_message,
2894
2911
  lastMessage: r.last_message,
2895
2912
  preview: r.preview,
2896
- source: r.source
2913
+ source: r.source,
2914
+ provider: r.provider ?? CLAUDE_CODE_PROVIDER
2897
2915
  }))
2898
2916
  };
2899
2917
  }
@@ -2926,7 +2944,8 @@ var ConversationCache = class _ConversationCache {
2926
2944
  firstMessage: row.first_message,
2927
2945
  lastMessage: row.last_message,
2928
2946
  preview: row.preview,
2929
- source: row.source
2947
+ source: row.source,
2948
+ provider: row.provider ?? CLAUDE_CODE_PROVIDER
2930
2949
  };
2931
2950
  }
2932
2951
  setConversationProjectId(conversationId, projectId) {
@@ -3437,6 +3456,7 @@ function normalizeConversationToProjectChat(conversation) {
3437
3456
  createdAt: null,
3438
3457
  status: "resumable",
3439
3458
  source: "hdd-cache",
3459
+ provider: conversation.provider ?? CLAUDE_CODE_PROVIDER,
3440
3460
  indexedAt: null,
3441
3461
  fileMtime: null,
3442
3462
  filePath: conversation.filePath ?? null,
@@ -4361,6 +4381,7 @@ var StreamerServer = class {
4361
4381
  // in the constructor body (NOT a field initializer) so directoryDebounceMs
4362
4382
  // is already set when debounce() captures the wait.
4363
4383
  markScannerStaleDebounced;
4384
+ codexRoots;
4364
4385
  includeAgents;
4365
4386
  agentEntrypoints;
4366
4387
  honoApp;
@@ -4375,6 +4396,7 @@ var StreamerServer = class {
4375
4396
  this.verbose = config.verbose ?? false;
4376
4397
  this.disableDb = config.disableDb ?? false;
4377
4398
  this.scanProfiles = config.scanProfiles;
4399
+ this.codexRoots = config.codexRoots ?? [(0, import_path11.join)((0, import_os6.homedir)(), ".codex", "sessions")];
4378
4400
  this.ptyGracePeriodMs = config.ptyGracePeriodMs ?? DEFAULT_PTY_GRACE_PERIOD_MS;
4379
4401
  this.cacheDir = config.cacheDir ?? loadCacheDir() ?? (0, import_path11.join)((0, import_os6.homedir)(), ".threadbase", "cache");
4380
4402
  this.tailSize = config.tailSize ?? loadTailSize() ?? 10;
@@ -4792,9 +4814,13 @@ var StreamerServer = class {
4792
4814
  const warmupScanner = new import_scanner2.ConversationScanner();
4793
4815
  const warmupStatCache = this.buildStatCache(null);
4794
4816
  const shouldEmitProgress = createScanProgressThrottle();
4795
- warmupScanner.scan({
4817
+ const scanOpts = {
4796
4818
  ...this.scanProfiles ? { profiles: this.scanProfiles } : {},
4797
- ...warmupStatCache ? { statCache: warmupStatCache } : {},
4819
+ ...this.codexScanOpts(),
4820
+ ...warmupStatCache ? { statCache: warmupStatCache } : {}
4821
+ };
4822
+ warmupScanner.scan({
4823
+ ...scanOpts,
4798
4824
  onProgress: (scanned, total) => {
4799
4825
  if (shouldEmitProgress(scanned, total)) {
4800
4826
  this.wsHub.broadcast({ type: "scan_progress", scanned, total });
@@ -5019,6 +5045,7 @@ var StreamerServer = class {
5019
5045
  const offset = intParam(url, "offset", 0);
5020
5046
  const sort = url.searchParams.get("sort") ?? "recent";
5021
5047
  const project = url.searchParams.get("project") ?? void 0;
5048
+ const providerFilter = url.searchParams.get("provider") ?? void 0;
5022
5049
  const bustCache = url.searchParams.get("refresh") === "1";
5023
5050
  if (bustCache) {
5024
5051
  this.cache?.invalidate();
@@ -5026,7 +5053,12 @@ var StreamerServer = class {
5026
5053
  this.scannerReady = null;
5027
5054
  }
5028
5055
  if (this.cache && !bustCache) {
5029
- const { conversations, total: total2 } = this.cache.listConversations({ project, limit, offset });
5056
+ const { conversations, total: total2 } = this.cache.listConversations({
5057
+ project,
5058
+ provider: providerFilter,
5059
+ limit,
5060
+ offset
5061
+ });
5030
5062
  const adapted2 = conversations.map((c) => ({
5031
5063
  id: c.id,
5032
5064
  title: deriveProjectChatTitle({
@@ -5045,7 +5077,8 @@ var StreamerServer = class {
5045
5077
  lastActivity: c.lastActivity,
5046
5078
  firstMessage: c.firstMessage ? JSON.parse(c.firstMessage) : void 0,
5047
5079
  lastMessage: c.lastMessage ? JSON.parse(c.lastMessage) : void 0,
5048
- model: c.model ?? void 0
5080
+ model: c.model ?? void 0,
5081
+ provider: c.provider ?? CLAUDE_CODE_PROVIDER
5049
5082
  }));
5050
5083
  json(res, 200, { conversations: adapted2, hasMore: offset + limit < total2, offset, total: total2 });
5051
5084
  return;
@@ -5054,6 +5087,8 @@ var StreamerServer = class {
5054
5087
  let metas = [...scanner.getMetadataCache().values()];
5055
5088
  metas = (0, import_scanner2.applyIncludeFilter)(metas, "conversations");
5056
5089
  if (project) metas = (0, import_scanner2.applyProjectFilter)(metas, project);
5090
+ if (providerFilter)
5091
+ metas = metas.filter((m) => (m.provider ?? CLAUDE_CODE_PROVIDER) === providerFilter);
5057
5092
  metas = (0, import_scanner2.applySort)(metas, sort);
5058
5093
  const total = metas.length;
5059
5094
  const page = (0, import_scanner2.applyPagination)(metas, limit, offset);
@@ -5077,7 +5112,8 @@ var StreamerServer = class {
5077
5112
  lastActivity: c.timestamp,
5078
5113
  firstMessage: c.firstMessage ?? void 0,
5079
5114
  lastMessage: c.lastMessage ?? void 0,
5080
- model: c.model ?? void 0
5115
+ model: c.model ?? void 0,
5116
+ provider: c.provider ?? CLAUDE_CODE_PROVIDER
5081
5117
  };
5082
5118
  });
5083
5119
  json(res, 200, { conversations: adapted, hasMore: offset + limit < total, offset, total });
@@ -5090,9 +5126,15 @@ var StreamerServer = class {
5090
5126
  }
5091
5127
  async handleConversationsCount(url, res) {
5092
5128
  const project = url.searchParams.get("project") ?? void 0;
5129
+ const providerFilter = url.searchParams.get("provider") ?? void 0;
5093
5130
  const bustCache = url.searchParams.get("refresh") === "1";
5094
5131
  if (this.cache) {
5095
- const { total } = this.cache.listConversations({ project, limit: 0, offset: 0 });
5132
+ const { total } = this.cache.listConversations({
5133
+ project,
5134
+ provider: providerFilter,
5135
+ limit: 0,
5136
+ offset: 0
5137
+ });
5096
5138
  json(res, 200, { total });
5097
5139
  if (bustCache) this.refreshCountInBackground();
5098
5140
  return;
@@ -5101,6 +5143,8 @@ var StreamerServer = class {
5101
5143
  let metas = [...scanner.getMetadataCache().values()];
5102
5144
  metas = (0, import_scanner2.applyIncludeFilter)(metas, "conversations");
5103
5145
  if (project) metas = (0, import_scanner2.applyProjectFilter)(metas, project);
5146
+ if (providerFilter)
5147
+ metas = metas.filter((m) => (m.provider ?? CLAUDE_CODE_PROVIDER) === providerFilter);
5104
5148
  json(res, 200, { total: metas.length });
5105
5149
  }
5106
5150
  // Fire-and-forget full rescan that reconciles the SQLite cache from disk so a
@@ -5189,6 +5233,14 @@ var StreamerServer = class {
5189
5233
  }
5190
5234
  return statCache.size > 0 ? statCache : void 0;
5191
5235
  }
5236
+ // Returns the provider + codexRoots fragment to spread into every scan()/search() call.
5237
+ // codexRoots=[] disables codex scanning (safe no-op per scanner contract).
5238
+ codexScanOpts() {
5239
+ return {
5240
+ providers: [CLAUDE_CODE_PROVIDER, CODEX_CLI_PROVIDER],
5241
+ codexRoots: this.codexRoots
5242
+ };
5243
+ }
5192
5244
  // skipStaleRescan: when an indexed scanner already exists, return it directly
5193
5245
  // even if scannerStale is set, leaving the flag untouched so the next
5194
5246
  // list-level call still rescans. The single-conversation detail path passes
@@ -5214,6 +5266,7 @@ var StreamerServer = class {
5214
5266
  this.scanner = new import_scanner2.ConversationScanner();
5215
5267
  this.scannerReady = this.scanner.scan({
5216
5268
  ...this.scanProfiles ? { profiles: this.scanProfiles } : {},
5269
+ ...this.codexScanOpts(),
5217
5270
  ...statCache ? { statCache } : {}
5218
5271
  });
5219
5272
  await this.scannerReady;
@@ -5317,6 +5370,7 @@ var StreamerServer = class {
5317
5370
  const tail = this.cache.getConversationTail(id);
5318
5371
  if (tail && tail.messages.length > 0) {
5319
5372
  const cachedMeta = this.cache.getMetaById(id);
5373
+ const cachedProvider = cachedMeta?.provider ?? CLAUDE_CODE_PROVIDER;
5320
5374
  const availability2 = classifyResumability(cachedMeta?.projectPath);
5321
5375
  const messagesPayload2 = tail.messages.map((m, idx) => ({
5322
5376
  message_index: idx,
@@ -5335,7 +5389,8 @@ var StreamerServer = class {
5335
5389
  file_path: cachedMeta?.filePath ?? void 0,
5336
5390
  last_updated_at: cachedMeta?.lastActivity ?? void 0,
5337
5391
  message_count: cachedMeta?.messageCount ?? void 0,
5338
- resumable: availability2.resumable,
5392
+ provider: cachedProvider,
5393
+ resumable: isProviderResumable(cachedProvider, availability2.resumable),
5339
5394
  ...availability2.unavailable_reason && {
5340
5395
  unavailable_reason: availability2.unavailable_reason
5341
5396
  }
@@ -5433,6 +5488,8 @@ var StreamerServer = class {
5433
5488
  };
5434
5489
  });
5435
5490
  const conv = conversation;
5491
+ const cachedConvMeta = this.cache?.getMetaById(id);
5492
+ const convProvider = conv.provider ?? cachedConvMeta?.provider ?? CLAUDE_CODE_PROVIDER;
5436
5493
  const availability = classifyResumability(conv.projectPath);
5437
5494
  const body = {
5438
5495
  meta: {
@@ -5444,7 +5501,8 @@ var StreamerServer = class {
5444
5501
  last_updated_at: conv.timestamp,
5445
5502
  message_count: conv.messageCount,
5446
5503
  last_prompt: conv.lastPrompt ?? void 0,
5447
- resumable: availability.resumable,
5504
+ provider: convProvider,
5505
+ resumable: isProviderResumable(convProvider, availability.resumable),
5448
5506
  ...availability.unavailable_reason && {
5449
5507
  unavailable_reason: availability.unavailable_reason
5450
5508
  }
@@ -5479,7 +5537,8 @@ var StreamerServer = class {
5479
5537
  {
5480
5538
  limit,
5481
5539
  include: "conversations",
5482
- ...this.scanProfiles ? { profiles: this.scanProfiles } : {}
5540
+ ...this.scanProfiles ? { profiles: this.scanProfiles } : {},
5541
+ ...this.codexScanOpts()
5483
5542
  },
5484
5543
  scanner
5485
5544
  );
@@ -5495,7 +5554,8 @@ var StreamerServer = class {
5495
5554
  messageCount: r.meta.messageCount,
5496
5555
  lastActivity: r.meta.timestamp,
5497
5556
  firstMessage: r.meta.firstMessage ?? void 0,
5498
- lastMessage: r.meta.lastMessage ?? void 0
5557
+ lastMessage: r.meta.lastMessage ?? void 0,
5558
+ provider: r.meta.provider ?? CLAUDE_CODE_PROVIDER
5499
5559
  }));
5500
5560
  json(res, 200, {
5501
5561
  conversations: adapted,
@@ -6175,6 +6235,7 @@ function classifyResumability(cwd) {
6175
6235
  }
6176
6236
  function conversationToResumableSession(c) {
6177
6237
  const availability = classifyResumability(c.projectPath);
6238
+ const provider = c.provider ?? CLAUDE_CODE_PROVIDER;
6178
6239
  return {
6179
6240
  type: "conversation",
6180
6241
  id: c.id,
@@ -6199,7 +6260,8 @@ function conversationToResumableSession(c) {
6199
6260
  ...c.firstMessage != null && { firstMessageText: c.firstMessage },
6200
6261
  ...c.lastMessage != null && { lastMessageText: c.lastMessage },
6201
6262
  filePath: c.filePath,
6202
- resumable: availability.resumable,
6263
+ provider,
6264
+ resumable: isProviderResumable(provider, availability.resumable),
6203
6265
  ...availability.unavailable_reason && {
6204
6266
  unavailable_reason: availability.unavailable_reason
6205
6267
  }