@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/cli.cjs
CHANGED
|
@@ -114168,14 +114168,19 @@ function scrapePermissionGate(lines) {
|
|
|
114168
114168
|
const options = [];
|
|
114169
114169
|
let cursor;
|
|
114170
114170
|
let firstOptionLine = -1;
|
|
114171
|
-
for (let i =
|
|
114172
|
-
const
|
|
114173
|
-
|
|
114174
|
-
|
|
114175
|
-
|
|
114176
|
-
|
|
114177
|
-
|
|
114178
|
-
|
|
114171
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
114172
|
+
const stripped = stripGutter(lines[i]);
|
|
114173
|
+
const m2 = OPTION_RE.exec(stripped);
|
|
114174
|
+
if (m2) {
|
|
114175
|
+
const index = Number.parseInt(m2[2], 10);
|
|
114176
|
+
if (!Number.isFinite(index)) continue;
|
|
114177
|
+
firstOptionLine = i;
|
|
114178
|
+
if (m2[1]) cursor = index;
|
|
114179
|
+
options.unshift({ index, label: m2[3] });
|
|
114180
|
+
continue;
|
|
114181
|
+
}
|
|
114182
|
+
if (options.length === 0) continue;
|
|
114183
|
+
if (stripped.trim().length === 0 || BOX_ONLY_RE.test(lines[i].trim())) break;
|
|
114179
114184
|
}
|
|
114180
114185
|
if (options.length === 0) return null;
|
|
114181
114186
|
let prompt;
|
|
@@ -114214,7 +114219,15 @@ function scrapeDetail(lines, promptLine) {
|
|
|
114214
114219
|
}
|
|
114215
114220
|
return collected.length > 0 ? collected.reverse().join("\n") : void 0;
|
|
114216
114221
|
}
|
|
114217
|
-
|
|
114222
|
+
function detectGateScreen(lines) {
|
|
114223
|
+
if (lines.some((l) => ASK_MENU_FOOTER_RE.test(l))) return null;
|
|
114224
|
+
if (!lines.some((l) => GATE_FOOTER_RE.test(l))) return null;
|
|
114225
|
+
const gate = scrapePermissionGate(lines);
|
|
114226
|
+
if (!gate || gate.options.length < 2) return null;
|
|
114227
|
+
if (!gate.options.some((o) => YES_NO_LABEL_RE.test(o.label))) return null;
|
|
114228
|
+
return gate;
|
|
114229
|
+
}
|
|
114230
|
+
var OSC_777_PERMISSION_RE, OSC_777_WAITING_RE, OPTION_RE, FOOTER_RE, BOX_ONLY_RE, PROMPT_ARROW_RE, MAX_DETAIL_LINES, GATE_FOOTER_RE, ASK_MENU_FOOTER_RE, YES_NO_LABEL_RE;
|
|
114218
114231
|
var init_detectPermissionGate = __esm({
|
|
114219
114232
|
"src/services/questions/detectPermissionGate.ts"() {
|
|
114220
114233
|
"use strict";
|
|
@@ -114225,6 +114238,9 @@ var init_detectPermissionGate = __esm({
|
|
|
114225
114238
|
BOX_ONLY_RE = /^[\s│─┌┐└┘├┤┬┴┼╭╮╰╯╱╲=_-]+$/;
|
|
114226
114239
|
PROMPT_ARROW_RE = /^[\s]*[❯›>]\s*$/;
|
|
114227
114240
|
MAX_DETAIL_LINES = 6;
|
|
114241
|
+
GATE_FOOTER_RE = /esc to cancel/i;
|
|
114242
|
+
ASK_MENU_FOOTER_RE = /Enter to select/i;
|
|
114243
|
+
YES_NO_LABEL_RE = /^(yes|no)\b/i;
|
|
114228
114244
|
}
|
|
114229
114245
|
});
|
|
114230
114246
|
|
|
@@ -114448,7 +114464,7 @@ function toPublicSession2(s3) {
|
|
|
114448
114464
|
function stripAnsi2(str) {
|
|
114449
114465
|
return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b\][^\x07]*\x07/g, "");
|
|
114450
114466
|
}
|
|
114451
|
-
var import_headless2, import_crypto9, import_fs22, import_path22, OUTPUT_BUFFER_MAX2, INPUT_HISTORY_MAX2, PTY_COLS2, PTY_ROWS2, SCREEN_SCROLLBACK2, CLAUDE_PROMPT_MARKERS, QUIET_DETECT_MS2, CLAUDE_READY_FALLBACK_MS, SUBMIT_BYTES2, SUBMIT_DELAY_MS, SUBMIT_MAX_WAIT_MS, pty2, PTYManager;
|
|
114467
|
+
var import_headless2, import_crypto9, import_fs22, import_path22, OUTPUT_BUFFER_MAX2, INPUT_HISTORY_MAX2, PTY_COLS2, PTY_ROWS2, SCREEN_SCROLLBACK2, CLAUDE_PROMPT_MARKERS, QUIET_DETECT_MS2, OSC_TAIL_CHARS, SCRAPE_THROTTLE_MS, CLAUDE_READY_FALLBACK_MS, SUBMIT_BYTES2, SUBMIT_DELAY_MS, SUBMIT_MAX_WAIT_MS, pty2, PTYManager;
|
|
114452
114468
|
var init_pty_manager = __esm({
|
|
114453
114469
|
"src/pty-manager.ts"() {
|
|
114454
114470
|
"use strict";
|
|
@@ -114472,6 +114488,8 @@ var init_pty_manager = __esm({
|
|
|
114472
114488
|
SCREEN_SCROLLBACK2 = 1e3;
|
|
114473
114489
|
CLAUDE_PROMPT_MARKERS = ["\u256D", "\u276F"];
|
|
114474
114490
|
QUIET_DETECT_MS2 = 500;
|
|
114491
|
+
OSC_TAIL_CHARS = 128;
|
|
114492
|
+
SCRAPE_THROTTLE_MS = 300;
|
|
114475
114493
|
CLAUDE_READY_FALLBACK_MS = 8e3;
|
|
114476
114494
|
SUBMIT_BYTES2 = "\r";
|
|
114477
114495
|
SUBMIT_DELAY_MS = 16;
|
|
@@ -114490,6 +114508,12 @@ var init_pty_manager = __esm({
|
|
|
114490
114508
|
// the next prompt-ready without a fresh 777 (gate closed). Prevents
|
|
114491
114509
|
// re-broadcasting open/close on every chunk.
|
|
114492
114510
|
permissionOpen = /* @__PURE__ */ new Set();
|
|
114511
|
+
// Last chunk's raw tail per session — prepended to the next chunk before
|
|
114512
|
+
// the OSC regex test so a split escape still matches. Consumed on match.
|
|
114513
|
+
oscTail = /* @__PURE__ */ new Map();
|
|
114514
|
+
// When the last full detection pass ran per session (any trigger) — the
|
|
114515
|
+
// clock the SCRAPE_THROTTLE_MS ceiling is measured against.
|
|
114516
|
+
lastDetectAt = /* @__PURE__ */ new Map();
|
|
114493
114517
|
// Content key of the last AskUserQuestion broadcast from the rendered screen,
|
|
114494
114518
|
// per session — de-dupes the same menu firing on consecutive repaints.
|
|
114495
114519
|
lastScreenQuestionKey = /* @__PURE__ */ new Map();
|
|
@@ -114498,6 +114522,13 @@ var init_pty_manager = __esm({
|
|
|
114498
114522
|
// on a prompt-ready/marker return and de-dupe consecutive repaints. Modelled
|
|
114499
114523
|
// on permissionOpen but keyed by content (a shell prompt has no OSC trigger).
|
|
114500
114524
|
shellPromptOpen = /* @__PURE__ */ new Map();
|
|
114525
|
+
// Content key (permissionContentKey, cursor excluded — see arm 3) of the
|
|
114526
|
+
// gate that was still painted on screen when arm 2 closed it. Suppresses
|
|
114527
|
+
// arm 3 re-claiming that same box from a later trigger-less/throttled pass
|
|
114528
|
+
// before Claude erases it — otherwise the paint-time claim reopens the
|
|
114529
|
+
// card it just closed. Cleared once detectGateScreen sees the box is gone,
|
|
114530
|
+
// so a genuinely new (even content-identical) gate is claimed normally.
|
|
114531
|
+
closedGateKey = /* @__PURE__ */ new Map();
|
|
114501
114532
|
// Tracks sessions (both fresh and resume) whose PTY has spawned but Claude
|
|
114502
114533
|
// hasn't yet reached an interactive prompt — i.e. onReady hasn't fired.
|
|
114503
114534
|
pendingReady = /* @__PURE__ */ new Set();
|
|
@@ -114851,8 +114882,11 @@ var init_pty_manager = __esm({
|
|
|
114851
114882
|
this.queuedInputs.delete(sessionId);
|
|
114852
114883
|
this.firstChunkAt.delete(sessionId);
|
|
114853
114884
|
this.permissionOpen.delete(sessionId);
|
|
114885
|
+
this.oscTail.delete(sessionId);
|
|
114886
|
+
this.lastDetectAt.delete(sessionId);
|
|
114854
114887
|
this.lastScreenQuestionKey.delete(sessionId);
|
|
114855
114888
|
this.shellPromptOpen.delete(sessionId);
|
|
114889
|
+
this.closedGateKey.delete(sessionId);
|
|
114856
114890
|
this.quietCheckers.get(sessionId)?.cancel();
|
|
114857
114891
|
this.quietCheckers.delete(sessionId);
|
|
114858
114892
|
this.clearReadyFallback(sessionId);
|
|
@@ -114947,8 +114981,11 @@ var init_pty_manager = __esm({
|
|
|
114947
114981
|
for (const timer of this.readyFallbackTimers.values()) clearTimeout(timer);
|
|
114948
114982
|
this.readyFallbackTimers.clear();
|
|
114949
114983
|
this.permissionOpen.clear();
|
|
114984
|
+
this.oscTail.clear();
|
|
114985
|
+
this.lastDetectAt.clear();
|
|
114950
114986
|
this.lastScreenQuestionKey.clear();
|
|
114951
114987
|
this.shellPromptOpen.clear();
|
|
114988
|
+
this.closedGateKey.clear();
|
|
114952
114989
|
}
|
|
114953
114990
|
handleOutput(sessionId, data) {
|
|
114954
114991
|
const session = this.sessions.get(sessionId);
|
|
@@ -115012,16 +115049,24 @@ var init_pty_manager = __esm({
|
|
|
115012
115049
|
async detectLivePrompts(sessionId, rawData, stripped) {
|
|
115013
115050
|
const session = this.sessions.get(sessionId);
|
|
115014
115051
|
if (!session) return;
|
|
115015
|
-
const
|
|
115016
|
-
const
|
|
115052
|
+
const oscWindow = (this.oscTail.get(sessionId) ?? "") + rawData;
|
|
115053
|
+
const oscPermission = hasPermissionOsc(oscWindow);
|
|
115054
|
+
const oscWaitingForInput = hasWaitingForInputOsc(oscWindow);
|
|
115055
|
+
if (rawData !== "") {
|
|
115056
|
+
if (oscPermission || oscWaitingForInput) this.oscTail.delete(sessionId);
|
|
115057
|
+
else this.oscTail.set(sessionId, oscWindow.slice(-OSC_TAIL_CHARS));
|
|
115058
|
+
}
|
|
115017
115059
|
const hasAskFooter = /Enter to select/i.test(stripped);
|
|
115018
115060
|
const hasPromptMarker = CLAUDE_PROMPT_MARKERS.some((m2) => stripped.includes(m2));
|
|
115019
115061
|
const hasShellPromptHint = /[[(]\s*y\s*\/\s*n\s*[\])]|press\s+(enter|return|any key)|\bcontinue\b\s*\?|^\s*(?:❯|>)?\s*\d+[.)]\s+\S/im.test(
|
|
115020
115062
|
stripped
|
|
115021
115063
|
);
|
|
115022
|
-
|
|
115064
|
+
const nowMs = Date.now();
|
|
115065
|
+
const scrapeDue = nowMs - (this.lastDetectAt.get(sessionId) ?? 0) >= SCRAPE_THROTTLE_MS;
|
|
115066
|
+
if (!oscPermission && !oscWaitingForInput && !hasAskFooter && !hasShellPromptHint && !scrapeDue && !this.permissionOpen.has(sessionId) && !this.shellPromptOpen.has(sessionId) && !this.lastScreenQuestionKey.has(sessionId)) {
|
|
115023
115067
|
return;
|
|
115024
115068
|
}
|
|
115069
|
+
this.lastDetectAt.set(sessionId, nowMs);
|
|
115025
115070
|
const lines = await this.getOutputLines(sessionId, 60);
|
|
115026
115071
|
const askFooterOnScreen = lines.some((l) => /Enter to select/i.test(l));
|
|
115027
115072
|
if (oscPermission || hasAskFooter || askFooterOnScreen) {
|
|
@@ -115041,13 +115086,30 @@ var init_pty_manager = __esm({
|
|
|
115041
115086
|
this.permissionOpen.add(sessionId);
|
|
115042
115087
|
this.onPermissionChange?.(sessionId, gate ?? { options: [] });
|
|
115043
115088
|
} else if (this.permissionOpen.has(sessionId) && !askFooterOnScreen) {
|
|
115044
|
-
const gate =
|
|
115045
|
-
|
|
115089
|
+
const gate = detectGateScreen(lines);
|
|
115090
|
+
const stillPainted = gate !== null || scrapePermissionGate(lines) !== null;
|
|
115091
|
+
if (oscWaitingForInput || !stillPainted && hasPromptMarker) {
|
|
115046
115092
|
this.permissionOpen.delete(sessionId);
|
|
115047
115093
|
this.onPermissionChange?.(sessionId, null);
|
|
115094
|
+
if (gate) {
|
|
115095
|
+
this.closedGateKey.set(sessionId, permissionContentKey({ ...gate, cursor: void 0 }));
|
|
115096
|
+
} else {
|
|
115097
|
+
this.closedGateKey.delete(sessionId);
|
|
115098
|
+
}
|
|
115048
115099
|
} else if (gate) {
|
|
115049
115100
|
this.onPermissionChange?.(sessionId, gate);
|
|
115050
115101
|
}
|
|
115102
|
+
} else if (!askFooterOnScreen && !oscWaitingForInput) {
|
|
115103
|
+
const gate = detectGateScreen(lines);
|
|
115104
|
+
if (gate) {
|
|
115105
|
+
const key = permissionContentKey({ ...gate, cursor: void 0 });
|
|
115106
|
+
if (this.closedGateKey.get(sessionId) !== key) {
|
|
115107
|
+
this.permissionOpen.add(sessionId);
|
|
115108
|
+
this.onPermissionChange?.(sessionId, gate);
|
|
115109
|
+
}
|
|
115110
|
+
} else {
|
|
115111
|
+
this.closedGateKey.delete(sessionId);
|
|
115112
|
+
}
|
|
115051
115113
|
}
|
|
115052
115114
|
if (askFooterOnScreen) {
|
|
115053
115115
|
const detected = detectQuestionFromScreen(lines);
|
|
@@ -115190,8 +115252,11 @@ var init_pty_manager = __esm({
|
|
|
115190
115252
|
this.queuedInputs.delete(sessionId);
|
|
115191
115253
|
this.firstChunkAt.delete(sessionId);
|
|
115192
115254
|
this.permissionOpen.delete(sessionId);
|
|
115255
|
+
this.oscTail.delete(sessionId);
|
|
115256
|
+
this.lastDetectAt.delete(sessionId);
|
|
115193
115257
|
this.lastScreenQuestionKey.delete(sessionId);
|
|
115194
115258
|
this.shellPromptOpen.delete(sessionId);
|
|
115259
|
+
this.closedGateKey.delete(sessionId);
|
|
115195
115260
|
this.quietCheckers.get(sessionId)?.cancel();
|
|
115196
115261
|
this.quietCheckers.delete(sessionId);
|
|
115197
115262
|
this.clearReadyFallback(sessionId);
|
|
@@ -142318,7 +142383,10 @@ var createMiscRoutes = (deps) => {
|
|
|
142318
142383
|
// Same contract: this server serves GET /api/config/feature-flags. Lives
|
|
142319
142384
|
// here rather than behind /api/config (admin-only) so a read-only client
|
|
142320
142385
|
// still learns the server supports flags even if it can't read values.
|
|
142321
|
-
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
|
|
142322
142390
|
});
|
|
142323
142391
|
});
|
|
142324
142392
|
app.get("/api/profiles", (c) => c.json([]));
|
|
@@ -142593,6 +142661,11 @@ var createProjectRoutes = (deps) => {
|
|
|
142593
142661
|
deps.handleGetPopularProjects(url2, c.env.outgoing);
|
|
142594
142662
|
return alreadyHandled4();
|
|
142595
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
|
+
});
|
|
142596
142669
|
return app;
|
|
142597
142670
|
};
|
|
142598
142671
|
|
|
@@ -143468,6 +143541,21 @@ var ConversationCache = class _ConversationCache {
|
|
|
143468
143541
|
ORDER BY cnt DESC
|
|
143469
143542
|
LIMIT ?`
|
|
143470
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
|
+
),
|
|
143471
143559
|
getFileState: db.prepare("SELECT * FROM conversation_file_state WHERE path = ?"),
|
|
143472
143560
|
upsertFileState: db.prepare(
|
|
143473
143561
|
`INSERT INTO conversation_file_state
|
|
@@ -143843,6 +143931,23 @@ var ConversationCache = class _ConversationCache {
|
|
|
143843
143931
|
sessionCount: r.cnt
|
|
143844
143932
|
}));
|
|
143845
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
|
+
}
|
|
143846
143951
|
ensureFileIndex() {
|
|
143847
143952
|
if (this.fileIndexLoaded) return;
|
|
143848
143953
|
const rows = this.stmts.allFilePaths.all();
|
|
@@ -148568,6 +148673,7 @@ var StreamerServer = class {
|
|
|
148568
148673
|
handleSearchTarget: (id, req, res) => this.handleSearchTarget(id, req, res),
|
|
148569
148674
|
handleListProjects: (url2, res) => handleListProjects(url2, res),
|
|
148570
148675
|
handleGetPopularProjects: (url2, res) => this.handleGetPopularProjects(url2, res),
|
|
148676
|
+
handleGetProjectSummaries: (url2, res) => this.handleGetProjectSummaries(url2, res),
|
|
148571
148677
|
handlePairStart: (res) => this.handlePairStart(res),
|
|
148572
148678
|
handlePairExchange: (req, res) => this.handlePairExchange(req, res),
|
|
148573
148679
|
handleBrowse: (url2, res) => this.handleBrowse(url2, res),
|
|
@@ -150133,6 +150239,20 @@ var StreamerServer = class {
|
|
|
150133
150239
|
const projects = this.cache.getPopularProjects(limit);
|
|
150134
150240
|
json2(res, 200, { projects, total: projects.length });
|
|
150135
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
|
+
}
|
|
150136
150256
|
buildStatCache(previousScanner) {
|
|
150137
150257
|
if (!this.cache) return void 0;
|
|
150138
150258
|
if (!previousScanner) {
|