@threadbase-sh/streamer 1.45.0 → 1.46.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
@@ -142383,7 +142383,10 @@ var createMiscRoutes = (deps) => {
142383
142383
  // Same contract: this server serves GET /api/config/feature-flags. Lives
142384
142384
  // here rather than behind /api/config (admin-only) so a read-only client
142385
142385
  // still learns the server supports flags even if it can't read values.
142386
- featureFlags: true
142386
+ featureFlags: true,
142387
+ // Same contract: this server serves GET /api/projects/summary, which the
142388
+ // Hub's grouped views need before they can draw a tree.
142389
+ projectSummary: true
142387
142390
  });
142388
142391
  });
142389
142392
  app.get("/api/profiles", (c) => c.json([]));
@@ -142658,6 +142661,11 @@ var createProjectRoutes = (deps) => {
142658
142661
  deps.handleGetPopularProjects(url2, c.env.outgoing);
142659
142662
  return alreadyHandled4();
142660
142663
  });
142664
+ app.get("/summary", (c) => {
142665
+ const url2 = new URL(c.req.url);
142666
+ deps.handleGetProjectSummaries(url2, c.env.outgoing);
142667
+ return alreadyHandled4();
142668
+ });
142661
142669
  return app;
142662
142670
  };
142663
142671
 
@@ -143533,6 +143541,21 @@ var ConversationCache = class _ConversationCache {
143533
143541
  ORDER BY cnt DESC
143534
143542
  LIMIT ?`
143535
143543
  ),
143544
+ // Same table, same rows and same NULL filter /api/conversations lists
143545
+ // from, so a group's count/last-activity can never disagree with the
143546
+ // page it opens. Bare project_name is the one from the MAX(last_activity)
143547
+ // row (SQLite's documented min/max-aggregate bare-column rule).
143548
+ projectSummaries: db.prepare(
143549
+ `SELECT project_path, project_name, COUNT(*) as cnt, MAX(last_activity) as latest
143550
+ FROM conversation_meta
143551
+ WHERE project_path IS NOT NULL
143552
+ GROUP BY project_path
143553
+ ORDER BY latest DESC, project_path ASC
143554
+ LIMIT ? OFFSET ?`
143555
+ ),
143556
+ projectSummaryCount: db.prepare(
143557
+ "SELECT COUNT(DISTINCT project_path) as n FROM conversation_meta WHERE project_path IS NOT NULL"
143558
+ ),
143536
143559
  getFileState: db.prepare("SELECT * FROM conversation_file_state WHERE path = ?"),
143537
143560
  upsertFileState: db.prepare(
143538
143561
  `INSERT INTO conversation_file_state
@@ -143908,6 +143931,23 @@ var ConversationCache = class _ConversationCache {
143908
143931
  sessionCount: r.cnt
143909
143932
  }));
143910
143933
  }
143934
+ /** Every project with at least one cached conversation, most recently active
143935
+ * first. Paths are the raw `project_path` values, which is what
143936
+ * /api/conversations?project= matches on exactly — so a summary row is
143937
+ * always joinable against the page it describes. */
143938
+ listProjectSummaries(opts) {
143939
+ const total = this.stmts.projectSummaryCount.get().n;
143940
+ const rows = opts.limit === 0 ? [] : this.stmts.projectSummaries.all(opts.limit, opts.offset);
143941
+ return {
143942
+ total,
143943
+ projects: rows.map((r) => ({
143944
+ path: r.project_path,
143945
+ name: r.project_name ?? r.project_path.split(/[/\\]/).pop() ?? r.project_path,
143946
+ conversationCount: r.cnt,
143947
+ lastActivity: new Date(r.latest ?? 0).toISOString()
143948
+ }))
143949
+ };
143950
+ }
143911
143951
  ensureFileIndex() {
143912
143952
  if (this.fileIndexLoaded) return;
143913
143953
  const rows = this.stmts.allFilePaths.all();
@@ -148633,6 +148673,7 @@ var StreamerServer = class {
148633
148673
  handleSearchTarget: (id, req, res) => this.handleSearchTarget(id, req, res),
148634
148674
  handleListProjects: (url2, res) => handleListProjects(url2, res),
148635
148675
  handleGetPopularProjects: (url2, res) => this.handleGetPopularProjects(url2, res),
148676
+ handleGetProjectSummaries: (url2, res) => this.handleGetProjectSummaries(url2, res),
148636
148677
  handlePairStart: (res) => this.handlePairStart(res),
148637
148678
  handlePairExchange: (req, res) => this.handlePairExchange(req, res),
148638
148679
  handleBrowse: (url2, res) => this.handleBrowse(url2, res),
@@ -150198,6 +150239,20 @@ var StreamerServer = class {
150198
150239
  const projects = this.cache.getPopularProjects(limit);
150199
150240
  json2(res, 200, { projects, total: projects.length });
150200
150241
  }
150242
+ handleGetProjectSummaries(url2, res) {
150243
+ if (this.rejectIfWarmingUp(res)) return;
150244
+ const limit = intParam(url2, "limit", 200);
150245
+ const offset = intParam(url2, "offset", 0);
150246
+ if (!this.cache) {
150247
+ json2(res, 503, {
150248
+ error: "Conversation cache unavailable",
150249
+ code: "CACHE_UNAVAILABLE"
150250
+ });
150251
+ return;
150252
+ }
150253
+ const { projects, total } = this.cache.listProjectSummaries({ limit, offset });
150254
+ json2(res, 200, { projects, total, offset, hasMore: offset + projects.length < total });
150255
+ }
150201
150256
  buildStatCache(previousScanner) {
150202
150257
  if (!this.cache) return void 0;
150203
150258
  if (!previousScanner) {