@threadbase-sh/streamer 1.66.0 → 1.67.1
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/api/handlers/conversations.handlers.d.ts +29 -0
- package/dist/api/handlers/conversations.handlers.d.ts.map +1 -1
- package/dist/cli.cjs +97 -15
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +74 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +79 -7
- package/dist/index.js.map +1 -1
- package/dist/server-wiring.d.ts +1 -0
- package/dist/server-wiring.d.ts.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -6477,7 +6477,7 @@ import {
|
|
|
6477
6477
|
search
|
|
6478
6478
|
} from "@threadbase-sh/scanner";
|
|
6479
6479
|
import { createReadStream, existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
6480
|
-
import { join as join12 } from "path";
|
|
6480
|
+
import { basename as basename5, join as join12 } from "path";
|
|
6481
6481
|
import { createInterface } from "readline";
|
|
6482
6482
|
|
|
6483
6483
|
// src/conversation-cache.ts
|
|
@@ -8644,6 +8644,64 @@ var ConversationHandlers = class {
|
|
|
8644
8644
|
}
|
|
8645
8645
|
return null;
|
|
8646
8646
|
}
|
|
8647
|
+
/**
|
|
8648
|
+
* Resolve a conversation id to a JSONL path, in order of authority.
|
|
8649
|
+
*
|
|
8650
|
+
* `findJsonlPath` alone answers 64.0% of this machine's 961 conversations and
|
|
8651
|
+
* 0 of 343 Codex ones — it reconstructs `<projectsDir>/<dir>/<uuid>.jsonl`,
|
|
8652
|
+
* which is Claude Code's layout, and a Codex rollout is
|
|
8653
|
+
* `rollout-<ts>-<uuid>.jsonl` under a date path, so that walk cannot match one
|
|
8654
|
+
* by construction. The ladder below answers 99.7%, leaving only the three
|
|
8655
|
+
* whose file is genuinely gone.
|
|
8656
|
+
*/
|
|
8657
|
+
async locateJsonlPath(uuid, lookupId) {
|
|
8658
|
+
const live = this.deps.findLiveSessionFilePath(uuid) ?? this.deps.findLiveSessionFilePath(lookupId);
|
|
8659
|
+
if (live) return live;
|
|
8660
|
+
const cached3 = this.cache?.getMetaById(lookupId)?.filePath;
|
|
8661
|
+
if (cached3 && await this.isJsonlPathFor(cached3, lookupId)) return cached3;
|
|
8662
|
+
return this.findJsonlPath(lookupId);
|
|
8663
|
+
}
|
|
8664
|
+
/**
|
|
8665
|
+
* Does `filePath` actually hold the conversation `requestedId` names?
|
|
8666
|
+
*
|
|
8667
|
+
* The filename settles it for both providers: Claude writes `<uuid>.jsonl`
|
|
8668
|
+
* (or `agent-<agentId>.jsonl`), Codex writes `rollout-<ts>-<uuid>.jsonl`.
|
|
8669
|
+
* Only when the name says nothing do we open the file — and there the naive
|
|
8670
|
+
* rule is wrong, because **a Claude subagent transcript carries the PARENT's
|
|
8671
|
+
* `sessionId`**. Matching on `sessionId` alone therefore verifies exactly the
|
|
8672
|
+
* file this check exists to reject, so a sidechain is refused outright unless
|
|
8673
|
+
* it was asked for by its own `agent-<agentId>` name, which the filename
|
|
8674
|
+
* branch above already covers.
|
|
8675
|
+
*/
|
|
8676
|
+
async isJsonlPathFor(filePath, requestedId) {
|
|
8677
|
+
if (!existsSync8(filePath)) return false;
|
|
8678
|
+
const stem = basename5(filePath).replace(/\.jsonl$/, "");
|
|
8679
|
+
if (stem === requestedId || stem.includes(requestedId)) return true;
|
|
8680
|
+
const first = await this.readFirstJsonlEntry(filePath);
|
|
8681
|
+
if (!first || first.isSidechain === true) return false;
|
|
8682
|
+
return first.sessionId === requestedId;
|
|
8683
|
+
}
|
|
8684
|
+
/** First parseable JSONL line, for identity checks. Null on an empty or unreadable file. */
|
|
8685
|
+
async readFirstJsonlEntry(filePath) {
|
|
8686
|
+
return new Promise((resolve2) => {
|
|
8687
|
+
const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });
|
|
8688
|
+
let done = false;
|
|
8689
|
+
rl.on("line", (line) => {
|
|
8690
|
+
if (done) return;
|
|
8691
|
+
try {
|
|
8692
|
+
const entry = JSON.parse(line);
|
|
8693
|
+
done = true;
|
|
8694
|
+
rl.close();
|
|
8695
|
+
resolve2(entry);
|
|
8696
|
+
} catch {
|
|
8697
|
+
}
|
|
8698
|
+
});
|
|
8699
|
+
rl.on("close", () => {
|
|
8700
|
+
if (!done) resolve2(null);
|
|
8701
|
+
});
|
|
8702
|
+
rl.on("error", () => resolve2(null));
|
|
8703
|
+
});
|
|
8704
|
+
}
|
|
8647
8705
|
async readCwdFromJsonl(filePath) {
|
|
8648
8706
|
return new Promise((resolve2) => {
|
|
8649
8707
|
const rl = createInterface({ input: createReadStream(filePath), crlfDelay: Infinity });
|
|
@@ -8669,7 +8727,7 @@ var ConversationHandlers = class {
|
|
|
8669
8727
|
async findConversationByUuid(uuid) {
|
|
8670
8728
|
const lookupId = this.deps.resolveConversationLookupId(uuid);
|
|
8671
8729
|
if (!this.scannerManager.ready && !this.scanProfiles) {
|
|
8672
|
-
const filePath2 =
|
|
8730
|
+
const filePath2 = await this.locateJsonlPath(uuid, lookupId);
|
|
8673
8731
|
if (filePath2) {
|
|
8674
8732
|
const account = this.cache?.getMetaById(lookupId)?.account ?? void 0;
|
|
8675
8733
|
const coldScanner = this.scannerManager.current ?? this.scannerManager.newScanner();
|
|
@@ -8721,7 +8779,7 @@ var ConversationHandlers = class {
|
|
|
8721
8779
|
return fromIndex;
|
|
8722
8780
|
}
|
|
8723
8781
|
if (this.scanProfiles) return null;
|
|
8724
|
-
const filePath =
|
|
8782
|
+
const filePath = await this.locateJsonlPath(uuid, lookupId);
|
|
8725
8783
|
if (!filePath) return null;
|
|
8726
8784
|
if (this.scannerManager.ready) {
|
|
8727
8785
|
const account = this.cache?.getMetaById(lookupId)?.account ?? void 0;
|
|
@@ -9135,7 +9193,7 @@ var ConversationHandlers = class {
|
|
|
9135
9193
|
|
|
9136
9194
|
// src/api/handlers/sessions.handlers.ts
|
|
9137
9195
|
import { existsSync as existsSync10 } from "fs";
|
|
9138
|
-
import { basename as
|
|
9196
|
+
import { basename as basename6, dirname as dirname9, join as join16 } from "path";
|
|
9139
9197
|
|
|
9140
9198
|
// node_modules/nanoid/index.js
|
|
9141
9199
|
import crypto2 from "crypto";
|
|
@@ -10659,7 +10717,7 @@ var SessionHandlers = class {
|
|
|
10659
10717
|
const jsonlCwd = jsonlPath ? await this.deps.readCwdFromJsonl(jsonlPath) : null;
|
|
10660
10718
|
if (jsonlCwd) {
|
|
10661
10719
|
projectPath = jsonlCwd;
|
|
10662
|
-
projectName = projectName ||
|
|
10720
|
+
projectName = projectName || basename6(jsonlCwd);
|
|
10663
10721
|
}
|
|
10664
10722
|
}
|
|
10665
10723
|
if (!projectPath) {
|
|
@@ -12979,6 +13037,13 @@ function createApiDeps(deps) {
|
|
|
12979
13037
|
);
|
|
12980
13038
|
}
|
|
12981
13039
|
}
|
|
13040
|
+
if (msg.type === "unsubscribe_session" && typeof msg.sessionId === "string") {
|
|
13041
|
+
if (!wsAllows(principal, "history:read")) {
|
|
13042
|
+
deny(msg.type, "history:read");
|
|
13043
|
+
return;
|
|
13044
|
+
}
|
|
13045
|
+
deps.removeSessionSubscriber(msg.sessionId, ws);
|
|
13046
|
+
}
|
|
12982
13047
|
if (msg.type === "hold_session" && typeof msg.sessionId === "string") {
|
|
12983
13048
|
if (!wsAllows(principal, "session:control")) {
|
|
12984
13049
|
deny(msg.type, "session:control");
|
|
@@ -15249,7 +15314,7 @@ function discoveredToResponse(d, conversationId) {
|
|
|
15249
15314
|
// src/session-watchers.ts
|
|
15250
15315
|
import { existsSync as existsSync15, watch as fsWatch, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync13 } from "fs";
|
|
15251
15316
|
import { homedir as homedir13 } from "os";
|
|
15252
|
-
import { basename as
|
|
15317
|
+
import { basename as basename7, join as join24 } from "path";
|
|
15253
15318
|
var SessionWatchers = class {
|
|
15254
15319
|
constructor(deps) {
|
|
15255
15320
|
this.deps = deps;
|
|
@@ -15355,7 +15420,7 @@ var SessionWatchers = class {
|
|
|
15355
15420
|
try {
|
|
15356
15421
|
const now = Date.now();
|
|
15357
15422
|
const match = readdirSync7(projectsDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, mtime: statSync13(join24(projectsDir, f)).mtimeMs })).filter(({ mtime }) => now - mtime < 5e3).filter(
|
|
15358
|
-
({ f }) =>
|
|
15423
|
+
({ f }) => basename7(f, ".jsonl") === sessionId || this.readFirstLineSessionId(join24(projectsDir, f)) === sessionId
|
|
15359
15424
|
).sort((a, b) => b.mtime - a.mtime)[0];
|
|
15360
15425
|
if (match) resolvedFilePath = join24(projectsDir, match.f);
|
|
15361
15426
|
} catch {
|
|
@@ -16164,6 +16229,7 @@ var StreamerServer = class {
|
|
|
16164
16229
|
withReconciledLifecycle: (sessions) => this.withReconciledLifecycle(sessions),
|
|
16165
16230
|
currentWarmupState: () => this.currentWarmupState(),
|
|
16166
16231
|
addSessionSubscriber: (sessionId, ws) => this.addSessionSubscriber(sessionId, ws),
|
|
16232
|
+
removeSessionSubscriber: (sessionId, ws) => this.removeSessionSubscriber(sessionId, ws),
|
|
16167
16233
|
startGraceTimer: (sessionId, delayMs) => this.startGraceTimer(sessionId, delayMs),
|
|
16168
16234
|
armHoldWhenIdle: (sessionId) => this.armHoldWhenIdle(sessionId),
|
|
16169
16235
|
handleSessionsCount: (res) => this.handleSessionsCount(res),
|
|
@@ -16293,6 +16359,12 @@ var StreamerServer = class {
|
|
|
16293
16359
|
);
|
|
16294
16360
|
}
|
|
16295
16361
|
}
|
|
16362
|
+
removeSessionSubscriber(sessionId, ws) {
|
|
16363
|
+
const subs = this.sessionSubscribers.get(sessionId);
|
|
16364
|
+
if (!subs) return;
|
|
16365
|
+
subs.delete(ws);
|
|
16366
|
+
if (subs.size === 0) this.sessionSubscribers.delete(sessionId);
|
|
16367
|
+
}
|
|
16296
16368
|
/**
|
|
16297
16369
|
* Bring up Live Activity push, if credentials are present (Feature 12).
|
|
16298
16370
|
*
|