@threadbase-sh/streamer 1.44.4 → 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 +136 -16
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +134 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +134 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -824,6 +824,22 @@ declare class ConversationCache {
|
|
|
824
824
|
name: string;
|
|
825
825
|
sessionCount: number;
|
|
826
826
|
}>;
|
|
827
|
+
/** Every project with at least one cached conversation, most recently active
|
|
828
|
+
* first. Paths are the raw `project_path` values, which is what
|
|
829
|
+
* /api/conversations?project= matches on exactly — so a summary row is
|
|
830
|
+
* always joinable against the page it describes. */
|
|
831
|
+
listProjectSummaries(opts: {
|
|
832
|
+
limit: number;
|
|
833
|
+
offset: number;
|
|
834
|
+
}): {
|
|
835
|
+
projects: Array<{
|
|
836
|
+
path: string;
|
|
837
|
+
name: string;
|
|
838
|
+
conversationCount: number;
|
|
839
|
+
lastActivity: string;
|
|
840
|
+
}>;
|
|
841
|
+
total: number;
|
|
842
|
+
};
|
|
827
843
|
private ensureFileIndex;
|
|
828
844
|
updateFromLine(filePath: string, rawLine: string): void;
|
|
829
845
|
/**
|
|
@@ -1689,6 +1705,7 @@ type ApiDeps = {
|
|
|
1689
1705
|
handleSearchTarget: (id: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1690
1706
|
handleListProjects: (url: URL, res: ServerResponse) => void;
|
|
1691
1707
|
handleGetPopularProjects: (url: URL, res: ServerResponse) => void;
|
|
1708
|
+
handleGetProjectSummaries: (url: URL, res: ServerResponse) => void;
|
|
1692
1709
|
handlePairStart: (res: ServerResponse) => void;
|
|
1693
1710
|
handlePairExchange: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1694
1711
|
handleBrowse: (url: URL, res: ServerResponse) => Promise<void>;
|
|
@@ -1775,8 +1792,11 @@ declare class PTYManager implements SessionRunner {
|
|
|
1775
1792
|
private onLiveQuestionGone;
|
|
1776
1793
|
private onUserMessage;
|
|
1777
1794
|
private permissionOpen;
|
|
1795
|
+
private oscTail;
|
|
1796
|
+
private lastDetectAt;
|
|
1778
1797
|
private lastScreenQuestionKey;
|
|
1779
1798
|
private shellPromptOpen;
|
|
1799
|
+
private closedGateKey;
|
|
1780
1800
|
private pendingReady;
|
|
1781
1801
|
private queuedInputs;
|
|
1782
1802
|
private log;
|
|
@@ -2116,6 +2136,7 @@ declare class StreamerServer {
|
|
|
2116
2136
|
private handleSessionsCount;
|
|
2117
2137
|
private handleGetRecentSessions;
|
|
2118
2138
|
private handleGetPopularProjects;
|
|
2139
|
+
private handleGetProjectSummaries;
|
|
2119
2140
|
private buildStatCache;
|
|
2120
2141
|
private codexScanOpts;
|
|
2121
2142
|
private newScanner;
|
package/dist/index.d.ts
CHANGED
|
@@ -824,6 +824,22 @@ declare class ConversationCache {
|
|
|
824
824
|
name: string;
|
|
825
825
|
sessionCount: number;
|
|
826
826
|
}>;
|
|
827
|
+
/** Every project with at least one cached conversation, most recently active
|
|
828
|
+
* first. Paths are the raw `project_path` values, which is what
|
|
829
|
+
* /api/conversations?project= matches on exactly — so a summary row is
|
|
830
|
+
* always joinable against the page it describes. */
|
|
831
|
+
listProjectSummaries(opts: {
|
|
832
|
+
limit: number;
|
|
833
|
+
offset: number;
|
|
834
|
+
}): {
|
|
835
|
+
projects: Array<{
|
|
836
|
+
path: string;
|
|
837
|
+
name: string;
|
|
838
|
+
conversationCount: number;
|
|
839
|
+
lastActivity: string;
|
|
840
|
+
}>;
|
|
841
|
+
total: number;
|
|
842
|
+
};
|
|
827
843
|
private ensureFileIndex;
|
|
828
844
|
updateFromLine(filePath: string, rawLine: string): void;
|
|
829
845
|
/**
|
|
@@ -1689,6 +1705,7 @@ type ApiDeps = {
|
|
|
1689
1705
|
handleSearchTarget: (id: string, req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1690
1706
|
handleListProjects: (url: URL, res: ServerResponse) => void;
|
|
1691
1707
|
handleGetPopularProjects: (url: URL, res: ServerResponse) => void;
|
|
1708
|
+
handleGetProjectSummaries: (url: URL, res: ServerResponse) => void;
|
|
1692
1709
|
handlePairStart: (res: ServerResponse) => void;
|
|
1693
1710
|
handlePairExchange: (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
1694
1711
|
handleBrowse: (url: URL, res: ServerResponse) => Promise<void>;
|
|
@@ -1775,8 +1792,11 @@ declare class PTYManager implements SessionRunner {
|
|
|
1775
1792
|
private onLiveQuestionGone;
|
|
1776
1793
|
private onUserMessage;
|
|
1777
1794
|
private permissionOpen;
|
|
1795
|
+
private oscTail;
|
|
1796
|
+
private lastDetectAt;
|
|
1778
1797
|
private lastScreenQuestionKey;
|
|
1779
1798
|
private shellPromptOpen;
|
|
1799
|
+
private closedGateKey;
|
|
1780
1800
|
private pendingReady;
|
|
1781
1801
|
private queuedInputs;
|
|
1782
1802
|
private log;
|
|
@@ -2116,6 +2136,7 @@ declare class StreamerServer {
|
|
|
2116
2136
|
private handleSessionsCount;
|
|
2117
2137
|
private handleGetRecentSessions;
|
|
2118
2138
|
private handleGetPopularProjects;
|
|
2139
|
+
private handleGetProjectSummaries;
|
|
2119
2140
|
private buildStatCache;
|
|
2120
2141
|
private codexScanOpts;
|
|
2121
2142
|
private newScanner;
|
package/dist/index.js
CHANGED
|
@@ -2120,14 +2120,19 @@ function scrapePermissionGate(lines) {
|
|
|
2120
2120
|
const options = [];
|
|
2121
2121
|
let cursor;
|
|
2122
2122
|
let firstOptionLine = -1;
|
|
2123
|
-
for (let i =
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2123
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2124
|
+
const stripped = stripGutter(lines[i]);
|
|
2125
|
+
const m = OPTION_RE.exec(stripped);
|
|
2126
|
+
if (m) {
|
|
2127
|
+
const index = Number.parseInt(m[2], 10);
|
|
2128
|
+
if (!Number.isFinite(index)) continue;
|
|
2129
|
+
firstOptionLine = i;
|
|
2130
|
+
if (m[1]) cursor = index;
|
|
2131
|
+
options.unshift({ index, label: m[3] });
|
|
2132
|
+
continue;
|
|
2133
|
+
}
|
|
2134
|
+
if (options.length === 0) continue;
|
|
2135
|
+
if (stripped.trim().length === 0 || BOX_ONLY_RE.test(lines[i].trim())) break;
|
|
2131
2136
|
}
|
|
2132
2137
|
if (options.length === 0) return null;
|
|
2133
2138
|
let prompt;
|
|
@@ -2167,6 +2172,17 @@ function scrapeDetail(lines, promptLine) {
|
|
|
2167
2172
|
}
|
|
2168
2173
|
return collected.length > 0 ? collected.reverse().join("\n") : void 0;
|
|
2169
2174
|
}
|
|
2175
|
+
var GATE_FOOTER_RE = /esc to cancel/i;
|
|
2176
|
+
var ASK_MENU_FOOTER_RE = /Enter to select/i;
|
|
2177
|
+
var YES_NO_LABEL_RE = /^(yes|no)\b/i;
|
|
2178
|
+
function detectGateScreen(lines) {
|
|
2179
|
+
if (lines.some((l) => ASK_MENU_FOOTER_RE.test(l))) return null;
|
|
2180
|
+
if (!lines.some((l) => GATE_FOOTER_RE.test(l))) return null;
|
|
2181
|
+
const gate = scrapePermissionGate(lines);
|
|
2182
|
+
if (!gate || gate.options.length < 2) return null;
|
|
2183
|
+
if (!gate.options.some((o) => YES_NO_LABEL_RE.test(o.label))) return null;
|
|
2184
|
+
return gate;
|
|
2185
|
+
}
|
|
2170
2186
|
|
|
2171
2187
|
// src/services/questions/detectQuestionFromScreen.ts
|
|
2172
2188
|
var ASK_FOOTER_RE = /Enter to select/i;
|
|
@@ -2314,6 +2330,8 @@ var PTY_ROWS2 = 40;
|
|
|
2314
2330
|
var SCREEN_SCROLLBACK2 = 1e3;
|
|
2315
2331
|
var CLAUDE_PROMPT_MARKERS = ["\u256D", "\u276F"];
|
|
2316
2332
|
var QUIET_DETECT_MS2 = 500;
|
|
2333
|
+
var OSC_TAIL_CHARS = 128;
|
|
2334
|
+
var SCRAPE_THROTTLE_MS = 300;
|
|
2317
2335
|
var CLAUDE_READY_FALLBACK_MS = 8e3;
|
|
2318
2336
|
function buildPasteBytes(input) {
|
|
2319
2337
|
return `\x1B[200~${input}\x1B[201~`;
|
|
@@ -2372,6 +2390,12 @@ var PTYManager = class {
|
|
|
2372
2390
|
// the next prompt-ready without a fresh 777 (gate closed). Prevents
|
|
2373
2391
|
// re-broadcasting open/close on every chunk.
|
|
2374
2392
|
permissionOpen = /* @__PURE__ */ new Set();
|
|
2393
|
+
// Last chunk's raw tail per session — prepended to the next chunk before
|
|
2394
|
+
// the OSC regex test so a split escape still matches. Consumed on match.
|
|
2395
|
+
oscTail = /* @__PURE__ */ new Map();
|
|
2396
|
+
// When the last full detection pass ran per session (any trigger) — the
|
|
2397
|
+
// clock the SCRAPE_THROTTLE_MS ceiling is measured against.
|
|
2398
|
+
lastDetectAt = /* @__PURE__ */ new Map();
|
|
2375
2399
|
// Content key of the last AskUserQuestion broadcast from the rendered screen,
|
|
2376
2400
|
// per session — de-dupes the same menu firing on consecutive repaints.
|
|
2377
2401
|
lastScreenQuestionKey = /* @__PURE__ */ new Map();
|
|
@@ -2380,6 +2404,13 @@ var PTYManager = class {
|
|
|
2380
2404
|
// on a prompt-ready/marker return and de-dupe consecutive repaints. Modelled
|
|
2381
2405
|
// on permissionOpen but keyed by content (a shell prompt has no OSC trigger).
|
|
2382
2406
|
shellPromptOpen = /* @__PURE__ */ new Map();
|
|
2407
|
+
// Content key (permissionContentKey, cursor excluded — see arm 3) of the
|
|
2408
|
+
// gate that was still painted on screen when arm 2 closed it. Suppresses
|
|
2409
|
+
// arm 3 re-claiming that same box from a later trigger-less/throttled pass
|
|
2410
|
+
// before Claude erases it — otherwise the paint-time claim reopens the
|
|
2411
|
+
// card it just closed. Cleared once detectGateScreen sees the box is gone,
|
|
2412
|
+
// so a genuinely new (even content-identical) gate is claimed normally.
|
|
2413
|
+
closedGateKey = /* @__PURE__ */ new Map();
|
|
2383
2414
|
// Tracks sessions (both fresh and resume) whose PTY has spawned but Claude
|
|
2384
2415
|
// hasn't yet reached an interactive prompt — i.e. onReady hasn't fired.
|
|
2385
2416
|
pendingReady = /* @__PURE__ */ new Set();
|
|
@@ -2733,8 +2764,11 @@ var PTYManager = class {
|
|
|
2733
2764
|
this.queuedInputs.delete(sessionId);
|
|
2734
2765
|
this.firstChunkAt.delete(sessionId);
|
|
2735
2766
|
this.permissionOpen.delete(sessionId);
|
|
2767
|
+
this.oscTail.delete(sessionId);
|
|
2768
|
+
this.lastDetectAt.delete(sessionId);
|
|
2736
2769
|
this.lastScreenQuestionKey.delete(sessionId);
|
|
2737
2770
|
this.shellPromptOpen.delete(sessionId);
|
|
2771
|
+
this.closedGateKey.delete(sessionId);
|
|
2738
2772
|
this.quietCheckers.get(sessionId)?.cancel();
|
|
2739
2773
|
this.quietCheckers.delete(sessionId);
|
|
2740
2774
|
this.clearReadyFallback(sessionId);
|
|
@@ -2829,8 +2863,11 @@ var PTYManager = class {
|
|
|
2829
2863
|
for (const timer of this.readyFallbackTimers.values()) clearTimeout(timer);
|
|
2830
2864
|
this.readyFallbackTimers.clear();
|
|
2831
2865
|
this.permissionOpen.clear();
|
|
2866
|
+
this.oscTail.clear();
|
|
2867
|
+
this.lastDetectAt.clear();
|
|
2832
2868
|
this.lastScreenQuestionKey.clear();
|
|
2833
2869
|
this.shellPromptOpen.clear();
|
|
2870
|
+
this.closedGateKey.clear();
|
|
2834
2871
|
}
|
|
2835
2872
|
handleOutput(sessionId, data) {
|
|
2836
2873
|
const session = this.sessions.get(sessionId);
|
|
@@ -2894,16 +2931,24 @@ var PTYManager = class {
|
|
|
2894
2931
|
async detectLivePrompts(sessionId, rawData, stripped) {
|
|
2895
2932
|
const session = this.sessions.get(sessionId);
|
|
2896
2933
|
if (!session) return;
|
|
2897
|
-
const
|
|
2898
|
-
const
|
|
2934
|
+
const oscWindow = (this.oscTail.get(sessionId) ?? "") + rawData;
|
|
2935
|
+
const oscPermission = hasPermissionOsc(oscWindow);
|
|
2936
|
+
const oscWaitingForInput = hasWaitingForInputOsc(oscWindow);
|
|
2937
|
+
if (rawData !== "") {
|
|
2938
|
+
if (oscPermission || oscWaitingForInput) this.oscTail.delete(sessionId);
|
|
2939
|
+
else this.oscTail.set(sessionId, oscWindow.slice(-OSC_TAIL_CHARS));
|
|
2940
|
+
}
|
|
2899
2941
|
const hasAskFooter = /Enter to select/i.test(stripped);
|
|
2900
2942
|
const hasPromptMarker = CLAUDE_PROMPT_MARKERS.some((m) => stripped.includes(m));
|
|
2901
2943
|
const hasShellPromptHint = /[[(]\s*y\s*\/\s*n\s*[\])]|press\s+(enter|return|any key)|\bcontinue\b\s*\?|^\s*(?:❯|>)?\s*\d+[.)]\s+\S/im.test(
|
|
2902
2944
|
stripped
|
|
2903
2945
|
);
|
|
2904
|
-
|
|
2946
|
+
const nowMs = Date.now();
|
|
2947
|
+
const scrapeDue = nowMs - (this.lastDetectAt.get(sessionId) ?? 0) >= SCRAPE_THROTTLE_MS;
|
|
2948
|
+
if (!oscPermission && !oscWaitingForInput && !hasAskFooter && !hasShellPromptHint && !scrapeDue && !this.permissionOpen.has(sessionId) && !this.shellPromptOpen.has(sessionId) && !this.lastScreenQuestionKey.has(sessionId)) {
|
|
2905
2949
|
return;
|
|
2906
2950
|
}
|
|
2951
|
+
this.lastDetectAt.set(sessionId, nowMs);
|
|
2907
2952
|
const lines = await this.getOutputLines(sessionId, 60);
|
|
2908
2953
|
const askFooterOnScreen = lines.some((l) => /Enter to select/i.test(l));
|
|
2909
2954
|
if (oscPermission || hasAskFooter || askFooterOnScreen) {
|
|
@@ -2923,13 +2968,30 @@ var PTYManager = class {
|
|
|
2923
2968
|
this.permissionOpen.add(sessionId);
|
|
2924
2969
|
this.onPermissionChange?.(sessionId, gate ?? { options: [] });
|
|
2925
2970
|
} else if (this.permissionOpen.has(sessionId) && !askFooterOnScreen) {
|
|
2926
|
-
const gate =
|
|
2927
|
-
|
|
2971
|
+
const gate = detectGateScreen(lines);
|
|
2972
|
+
const stillPainted = gate !== null || scrapePermissionGate(lines) !== null;
|
|
2973
|
+
if (oscWaitingForInput || !stillPainted && hasPromptMarker) {
|
|
2928
2974
|
this.permissionOpen.delete(sessionId);
|
|
2929
2975
|
this.onPermissionChange?.(sessionId, null);
|
|
2976
|
+
if (gate) {
|
|
2977
|
+
this.closedGateKey.set(sessionId, permissionContentKey({ ...gate, cursor: void 0 }));
|
|
2978
|
+
} else {
|
|
2979
|
+
this.closedGateKey.delete(sessionId);
|
|
2980
|
+
}
|
|
2930
2981
|
} else if (gate) {
|
|
2931
2982
|
this.onPermissionChange?.(sessionId, gate);
|
|
2932
2983
|
}
|
|
2984
|
+
} else if (!askFooterOnScreen && !oscWaitingForInput) {
|
|
2985
|
+
const gate = detectGateScreen(lines);
|
|
2986
|
+
if (gate) {
|
|
2987
|
+
const key = permissionContentKey({ ...gate, cursor: void 0 });
|
|
2988
|
+
if (this.closedGateKey.get(sessionId) !== key) {
|
|
2989
|
+
this.permissionOpen.add(sessionId);
|
|
2990
|
+
this.onPermissionChange?.(sessionId, gate);
|
|
2991
|
+
}
|
|
2992
|
+
} else {
|
|
2993
|
+
this.closedGateKey.delete(sessionId);
|
|
2994
|
+
}
|
|
2933
2995
|
}
|
|
2934
2996
|
if (askFooterOnScreen) {
|
|
2935
2997
|
const detected = detectQuestionFromScreen(lines);
|
|
@@ -3072,8 +3134,11 @@ var PTYManager = class {
|
|
|
3072
3134
|
this.queuedInputs.delete(sessionId);
|
|
3073
3135
|
this.firstChunkAt.delete(sessionId);
|
|
3074
3136
|
this.permissionOpen.delete(sessionId);
|
|
3137
|
+
this.oscTail.delete(sessionId);
|
|
3138
|
+
this.lastDetectAt.delete(sessionId);
|
|
3075
3139
|
this.lastScreenQuestionKey.delete(sessionId);
|
|
3076
3140
|
this.shellPromptOpen.delete(sessionId);
|
|
3141
|
+
this.closedGateKey.delete(sessionId);
|
|
3077
3142
|
this.quietCheckers.get(sessionId)?.cancel();
|
|
3078
3143
|
this.quietCheckers.delete(sessionId);
|
|
3079
3144
|
this.clearReadyFallback(sessionId);
|
|
@@ -5241,7 +5306,10 @@ var createMiscRoutes = (deps) => {
|
|
|
5241
5306
|
// Same contract: this server serves GET /api/config/feature-flags. Lives
|
|
5242
5307
|
// here rather than behind /api/config (admin-only) so a read-only client
|
|
5243
5308
|
// still learns the server supports flags even if it can't read values.
|
|
5244
|
-
featureFlags: true
|
|
5309
|
+
featureFlags: true,
|
|
5310
|
+
// Same contract: this server serves GET /api/projects/summary, which the
|
|
5311
|
+
// Hub's grouped views need before they can draw a tree.
|
|
5312
|
+
projectSummary: true
|
|
5245
5313
|
});
|
|
5246
5314
|
});
|
|
5247
5315
|
app.get("/api/profiles", (c) => c.json([]));
|
|
@@ -5388,6 +5456,11 @@ var createProjectRoutes = (deps) => {
|
|
|
5388
5456
|
deps.handleGetPopularProjects(url, c.env.outgoing);
|
|
5389
5457
|
return alreadyHandled4();
|
|
5390
5458
|
});
|
|
5459
|
+
app.get("/summary", (c) => {
|
|
5460
|
+
const url = new URL(c.req.url);
|
|
5461
|
+
deps.handleGetProjectSummaries(url, c.env.outgoing);
|
|
5462
|
+
return alreadyHandled4();
|
|
5463
|
+
});
|
|
5391
5464
|
return app;
|
|
5392
5465
|
};
|
|
5393
5466
|
|
|
@@ -6255,6 +6328,21 @@ var ConversationCache = class _ConversationCache {
|
|
|
6255
6328
|
ORDER BY cnt DESC
|
|
6256
6329
|
LIMIT ?`
|
|
6257
6330
|
),
|
|
6331
|
+
// Same table, same rows and same NULL filter /api/conversations lists
|
|
6332
|
+
// from, so a group's count/last-activity can never disagree with the
|
|
6333
|
+
// page it opens. Bare project_name is the one from the MAX(last_activity)
|
|
6334
|
+
// row (SQLite's documented min/max-aggregate bare-column rule).
|
|
6335
|
+
projectSummaries: db.prepare(
|
|
6336
|
+
`SELECT project_path, project_name, COUNT(*) as cnt, MAX(last_activity) as latest
|
|
6337
|
+
FROM conversation_meta
|
|
6338
|
+
WHERE project_path IS NOT NULL
|
|
6339
|
+
GROUP BY project_path
|
|
6340
|
+
ORDER BY latest DESC, project_path ASC
|
|
6341
|
+
LIMIT ? OFFSET ?`
|
|
6342
|
+
),
|
|
6343
|
+
projectSummaryCount: db.prepare(
|
|
6344
|
+
"SELECT COUNT(DISTINCT project_path) as n FROM conversation_meta WHERE project_path IS NOT NULL"
|
|
6345
|
+
),
|
|
6258
6346
|
getFileState: db.prepare("SELECT * FROM conversation_file_state WHERE path = ?"),
|
|
6259
6347
|
upsertFileState: db.prepare(
|
|
6260
6348
|
`INSERT INTO conversation_file_state
|
|
@@ -6630,6 +6718,23 @@ var ConversationCache = class _ConversationCache {
|
|
|
6630
6718
|
sessionCount: r.cnt
|
|
6631
6719
|
}));
|
|
6632
6720
|
}
|
|
6721
|
+
/** Every project with at least one cached conversation, most recently active
|
|
6722
|
+
* first. Paths are the raw `project_path` values, which is what
|
|
6723
|
+
* /api/conversations?project= matches on exactly — so a summary row is
|
|
6724
|
+
* always joinable against the page it describes. */
|
|
6725
|
+
listProjectSummaries(opts) {
|
|
6726
|
+
const total = this.stmts.projectSummaryCount.get().n;
|
|
6727
|
+
const rows = opts.limit === 0 ? [] : this.stmts.projectSummaries.all(opts.limit, opts.offset);
|
|
6728
|
+
return {
|
|
6729
|
+
total,
|
|
6730
|
+
projects: rows.map((r) => ({
|
|
6731
|
+
path: r.project_path,
|
|
6732
|
+
name: r.project_name ?? r.project_path.split(/[/\\]/).pop() ?? r.project_path,
|
|
6733
|
+
conversationCount: r.cnt,
|
|
6734
|
+
lastActivity: new Date(r.latest ?? 0).toISOString()
|
|
6735
|
+
}))
|
|
6736
|
+
};
|
|
6737
|
+
}
|
|
6633
6738
|
ensureFileIndex() {
|
|
6634
6739
|
if (this.fileIndexLoaded) return;
|
|
6635
6740
|
const rows = this.stmts.allFilePaths.all();
|
|
@@ -10771,6 +10876,7 @@ var StreamerServer = class {
|
|
|
10771
10876
|
handleSearchTarget: (id, req, res) => this.handleSearchTarget(id, req, res),
|
|
10772
10877
|
handleListProjects: (url, res) => handleListProjects(url, res),
|
|
10773
10878
|
handleGetPopularProjects: (url, res) => this.handleGetPopularProjects(url, res),
|
|
10879
|
+
handleGetProjectSummaries: (url, res) => this.handleGetProjectSummaries(url, res),
|
|
10774
10880
|
handlePairStart: (res) => this.handlePairStart(res),
|
|
10775
10881
|
handlePairExchange: (req, res) => this.handlePairExchange(req, res),
|
|
10776
10882
|
handleBrowse: (url, res) => this.handleBrowse(url, res),
|
|
@@ -12336,6 +12442,20 @@ var StreamerServer = class {
|
|
|
12336
12442
|
const projects = this.cache.getPopularProjects(limit);
|
|
12337
12443
|
json(res, 200, { projects, total: projects.length });
|
|
12338
12444
|
}
|
|
12445
|
+
handleGetProjectSummaries(url, res) {
|
|
12446
|
+
if (this.rejectIfWarmingUp(res)) return;
|
|
12447
|
+
const limit = intParam(url, "limit", 200);
|
|
12448
|
+
const offset = intParam(url, "offset", 0);
|
|
12449
|
+
if (!this.cache) {
|
|
12450
|
+
json(res, 503, {
|
|
12451
|
+
error: "Conversation cache unavailable",
|
|
12452
|
+
code: "CACHE_UNAVAILABLE"
|
|
12453
|
+
});
|
|
12454
|
+
return;
|
|
12455
|
+
}
|
|
12456
|
+
const { projects, total } = this.cache.listProjectSummaries({ limit, offset });
|
|
12457
|
+
json(res, 200, { projects, total, offset, hasMore: offset + projects.length < total });
|
|
12458
|
+
}
|
|
12339
12459
|
buildStatCache(previousScanner) {
|
|
12340
12460
|
if (!this.cache) return void 0;
|
|
12341
12461
|
if (!previousScanner) {
|