@threadbase-sh/streamer 1.28.2 → 1.29.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/cli.cjs +7185 -5564
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +166 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +166 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -5328,6 +5328,89 @@ function sanitizeFilename(name) {
|
|
|
5328
5328
|
return cleaned;
|
|
5329
5329
|
}
|
|
5330
5330
|
|
|
5331
|
+
// src/utils/codexConversationLine.ts
|
|
5332
|
+
function extractCodexText(content) {
|
|
5333
|
+
if (typeof content === "string") return content.trim();
|
|
5334
|
+
if (!Array.isArray(content)) return "";
|
|
5335
|
+
return content.map((item) => {
|
|
5336
|
+
if (typeof item === "string") return item;
|
|
5337
|
+
const block = item;
|
|
5338
|
+
const t = block?.type;
|
|
5339
|
+
if ((t === "input_text" || t === "output_text" || t === "text") && typeof block.text === "string") {
|
|
5340
|
+
return block.text;
|
|
5341
|
+
}
|
|
5342
|
+
return "";
|
|
5343
|
+
}).filter(Boolean).join("").trim();
|
|
5344
|
+
}
|
|
5345
|
+
function normalizeCodexLineToClaudeShape(line) {
|
|
5346
|
+
let entry;
|
|
5347
|
+
try {
|
|
5348
|
+
entry = JSON.parse(line);
|
|
5349
|
+
} catch {
|
|
5350
|
+
return null;
|
|
5351
|
+
}
|
|
5352
|
+
if (entry.type !== "response_item") return null;
|
|
5353
|
+
const payload = entry.payload;
|
|
5354
|
+
if (payload?.type !== "message") return null;
|
|
5355
|
+
const role = payload.role;
|
|
5356
|
+
if (role !== "user" && role !== "assistant") return null;
|
|
5357
|
+
const text = extractCodexText(payload.content);
|
|
5358
|
+
if (!text) return null;
|
|
5359
|
+
if (role === "user" && isCodexInjectedContext(text)) return null;
|
|
5360
|
+
const timestamp = typeof entry.timestamp === "string" ? entry.timestamp : (/* @__PURE__ */ new Date()).toISOString();
|
|
5361
|
+
const uuid = typeof payload.id === "string" && payload.id.length > 0 ? payload.id : `codex-${role}-${timestamp}-${hashPrefix(text)}`;
|
|
5362
|
+
return JSON.stringify({
|
|
5363
|
+
type: role,
|
|
5364
|
+
uuid,
|
|
5365
|
+
timestamp,
|
|
5366
|
+
message: {
|
|
5367
|
+
role,
|
|
5368
|
+
content: [{ type: "text", text }]
|
|
5369
|
+
}
|
|
5370
|
+
});
|
|
5371
|
+
}
|
|
5372
|
+
function isCodexRolloutLine(line) {
|
|
5373
|
+
try {
|
|
5374
|
+
const entry = JSON.parse(line);
|
|
5375
|
+
return entry.type === "response_item" || entry.type === "event_msg" || entry.type === "session_meta" || entry.type === "turn_context";
|
|
5376
|
+
} catch {
|
|
5377
|
+
return false;
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
function toClientConversationLines(lines) {
|
|
5381
|
+
if (lines.length === 0) return lines;
|
|
5382
|
+
const codex = lines.some(isCodexRolloutLine);
|
|
5383
|
+
if (!codex) return lines;
|
|
5384
|
+
const out = [];
|
|
5385
|
+
for (const line of lines) {
|
|
5386
|
+
const normalized = normalizeCodexLineToClaudeShape(line);
|
|
5387
|
+
if (normalized) out.push(normalized);
|
|
5388
|
+
}
|
|
5389
|
+
return out;
|
|
5390
|
+
}
|
|
5391
|
+
function isCodexInjectedContext(text) {
|
|
5392
|
+
if (text.startsWith("# AGENTS.md") || text.includes("<INSTRUCTIONS>")) return true;
|
|
5393
|
+
if (text.startsWith("<permissions instructions>") || text.includes("Filesystem sandboxing defines")) {
|
|
5394
|
+
return true;
|
|
5395
|
+
}
|
|
5396
|
+
if (text.includes("limit the options to at most 3")) return true;
|
|
5397
|
+
if (text.includes("You are working within the project boundary:")) return true;
|
|
5398
|
+
if (text.includes(
|
|
5399
|
+
"Do not read, write, or execute commands that access files or directories outside this boundary"
|
|
5400
|
+
)) {
|
|
5401
|
+
return true;
|
|
5402
|
+
}
|
|
5403
|
+
return false;
|
|
5404
|
+
}
|
|
5405
|
+
function hashPrefix(text) {
|
|
5406
|
+
let h = 0;
|
|
5407
|
+
const sample = text.slice(0, 64);
|
|
5408
|
+
for (let i = 0; i < sample.length; i++) {
|
|
5409
|
+
h = h * 31 + sample.charCodeAt(i) | 0;
|
|
5410
|
+
}
|
|
5411
|
+
return Math.abs(h).toString(36);
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5331
5414
|
// src/utils/conversationEtag.ts
|
|
5332
5415
|
var import_node_crypto3 = require("crypto");
|
|
5333
5416
|
function computeConversationEtag({
|
|
@@ -5712,15 +5795,7 @@ var StreamerServer = class {
|
|
|
5712
5795
|
if (broadcast) this.wsHub.broadcast(m);
|
|
5713
5796
|
}
|
|
5714
5797
|
const seqs = this.pendingLineSeqs.get(filePath);
|
|
5715
|
-
this.
|
|
5716
|
-
type: "conversation_events",
|
|
5717
|
-
sessionId,
|
|
5718
|
-
lines,
|
|
5719
|
-
...seqs && seqs.length === lines.length ? { seqs } : {}
|
|
5720
|
-
});
|
|
5721
|
-
for (const line of lines) {
|
|
5722
|
-
this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
|
|
5723
|
-
}
|
|
5798
|
+
this.broadcastConversationLines(sessionId, lines, seqs);
|
|
5724
5799
|
break;
|
|
5725
5800
|
}
|
|
5726
5801
|
}
|
|
@@ -5917,7 +5992,7 @@ var StreamerServer = class {
|
|
|
5917
5992
|
}
|
|
5918
5993
|
for (const [sessionId, subscribers] of this.sessionSubscribers) {
|
|
5919
5994
|
subscribers.delete(ws);
|
|
5920
|
-
if (subscribers.size === 0) {
|
|
5995
|
+
if (subscribers.size === 0 && this.ptyGracePeriodMs > 0) {
|
|
5921
5996
|
this.startGraceTimer(sessionId, this.ptyGracePeriodMs);
|
|
5922
5997
|
}
|
|
5923
5998
|
}
|
|
@@ -6701,11 +6776,62 @@ var StreamerServer = class {
|
|
|
6701
6776
|
rl.on("error", () => resolve2(null));
|
|
6702
6777
|
});
|
|
6703
6778
|
}
|
|
6779
|
+
/**
|
|
6780
|
+
* Live Codex sessions keep `SessionResponse.conversationId === managed.id`
|
|
6781
|
+
* (stable deep-link / PTY key) and store the rollout UUID separately as
|
|
6782
|
+
* `boundConversationId`. REST history is indexed under the rollout UUID, so
|
|
6783
|
+
* resolve the placeholder → bound id before looking up the scanner.
|
|
6784
|
+
*/
|
|
6785
|
+
resolveConversationLookupId(uuid) {
|
|
6786
|
+
const managed = this.sessionStore.getManaged(uuid);
|
|
6787
|
+
if (managed?.boundConversationId) return managed.boundConversationId;
|
|
6788
|
+
return uuid;
|
|
6789
|
+
}
|
|
6790
|
+
/** File path for a live managed session (placeholder id or bound Codex id). */
|
|
6791
|
+
findLiveSessionFilePath(uuid) {
|
|
6792
|
+
const direct = this.sessionFileMap.get(uuid);
|
|
6793
|
+
if (direct) return direct;
|
|
6794
|
+
for (const s of this.sessionStore.listManaged()) {
|
|
6795
|
+
if (s.boundConversationId === uuid) {
|
|
6796
|
+
return this.sessionFileMap.get(s.id) ?? null;
|
|
6797
|
+
}
|
|
6798
|
+
}
|
|
6799
|
+
return null;
|
|
6800
|
+
}
|
|
6801
|
+
/** True when a conversation UUID is the bound rollout of a live PTY session. */
|
|
6802
|
+
isBoundConversationLive(boundId) {
|
|
6803
|
+
for (const s of this.sessionStore.listManaged()) {
|
|
6804
|
+
if (s.boundConversationId === boundId && this.ptyManager.hasSession(s.id)) {
|
|
6805
|
+
return true;
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
return false;
|
|
6809
|
+
}
|
|
6810
|
+
/**
|
|
6811
|
+
* Broadcast conversation JSONL lines to WS clients. Codex rollout lines are
|
|
6812
|
+
* normalized to the Claude `type:user|assistant` shape mobile understands;
|
|
6813
|
+
* Claude lines pass through unchanged so seq alignment stays intact.
|
|
6814
|
+
*/
|
|
6815
|
+
broadcastConversationLines(sessionId, lines, seqs) {
|
|
6816
|
+
const clientLines = toClientConversationLines(lines);
|
|
6817
|
+
if (clientLines.length === 0) return;
|
|
6818
|
+
const seqsOk = !!seqs && seqs.length === lines.length && clientLines.length === lines.length;
|
|
6819
|
+
this.wsHub.broadcast({
|
|
6820
|
+
type: "conversation_events",
|
|
6821
|
+
sessionId,
|
|
6822
|
+
lines: clientLines,
|
|
6823
|
+
...seqsOk ? { seqs } : {}
|
|
6824
|
+
});
|
|
6825
|
+
for (const line of clientLines) {
|
|
6826
|
+
this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6704
6829
|
async findConversationByUuid(uuid) {
|
|
6830
|
+
const lookupId = this.resolveConversationLookupId(uuid);
|
|
6705
6831
|
if (!this.scannerReady && !this.scanProfiles) {
|
|
6706
|
-
const filePath2 = this.findJsonlPath(uuid);
|
|
6832
|
+
const filePath2 = this.findJsonlPath(lookupId) ?? this.findLiveSessionFilePath(uuid) ?? this.findLiveSessionFilePath(lookupId);
|
|
6707
6833
|
if (filePath2) {
|
|
6708
|
-
const account = this.cache?.getMetaById(
|
|
6834
|
+
const account = this.cache?.getMetaById(lookupId)?.account ?? void 0;
|
|
6709
6835
|
const coldScanner = this.scanner ?? this.newScanner();
|
|
6710
6836
|
const page = await coldScanner.parseSingleFilePage(filePath2, account, {
|
|
6711
6837
|
limit: Number.MAX_SAFE_INTEGER
|
|
@@ -6715,9 +6841,27 @@ var StreamerServer = class {
|
|
|
6715
6841
|
return null;
|
|
6716
6842
|
}
|
|
6717
6843
|
const scanner = await this.getScanner(true);
|
|
6718
|
-
const fromIndex = await scanner.getConversation(
|
|
6844
|
+
const fromIndex = await scanner.getConversation(lookupId);
|
|
6719
6845
|
if (fromIndex) {
|
|
6720
|
-
if (this.ptyManager.hasSession(uuid)) {
|
|
6846
|
+
if (this.ptyManager.hasSession(uuid) || this.ptyManager.hasSession(lookupId) || this.isBoundConversationLive(lookupId)) {
|
|
6847
|
+
const livePath = this.findLiveSessionFilePath(uuid) ?? this.findLiveSessionFilePath(lookupId) ?? fromIndex.filePath ?? null;
|
|
6848
|
+
const isCodexLive = this.isBoundConversationLive(lookupId) || this.sessionStore.getManaged(uuid)?.provider === CODEX_CLI_PROVIDER;
|
|
6849
|
+
if (isCodexLive && livePath) {
|
|
6850
|
+
try {
|
|
6851
|
+
const account = this.cache?.getMetaById(lookupId)?.account ?? fromIndex.account ?? void 0;
|
|
6852
|
+
const page = await scanner.parseSingleFilePage(livePath, account, {
|
|
6853
|
+
limit: Number.MAX_SAFE_INTEGER
|
|
6854
|
+
});
|
|
6855
|
+
if (page?.conversation) return page.conversation;
|
|
6856
|
+
} catch (err) {
|
|
6857
|
+
this.log.warn("codex.live_reparse_failed", {
|
|
6858
|
+
event: "codex.live_reparse_failed",
|
|
6859
|
+
conversationId: lookupId,
|
|
6860
|
+
filePath: livePath,
|
|
6861
|
+
err
|
|
6862
|
+
});
|
|
6863
|
+
}
|
|
6864
|
+
}
|
|
6721
6865
|
return fromIndex;
|
|
6722
6866
|
}
|
|
6723
6867
|
if (fromIndex.filePath && this.isConversationSnapshotStale(fromIndex)) {
|
|
@@ -6737,12 +6881,12 @@ var StreamerServer = class {
|
|
|
6737
6881
|
return fromIndex;
|
|
6738
6882
|
}
|
|
6739
6883
|
if (this.scanProfiles) return null;
|
|
6740
|
-
const filePath = this.findJsonlPath(uuid);
|
|
6884
|
+
const filePath = this.findJsonlPath(lookupId) ?? this.findLiveSessionFilePath(uuid) ?? this.findLiveSessionFilePath(lookupId);
|
|
6741
6885
|
if (!filePath) return null;
|
|
6742
6886
|
this.scanner = null;
|
|
6743
6887
|
this.scannerReady = null;
|
|
6744
6888
|
const freshScanner = await this.getScanner();
|
|
6745
|
-
return freshScanner.getConversation(
|
|
6889
|
+
return freshScanner.getConversation(lookupId);
|
|
6746
6890
|
}
|
|
6747
6891
|
// True when the JSONL on disk is meaningfully newer than the scanned
|
|
6748
6892
|
// snapshot's last-activity timestamp — i.e. the file grew after the scan.
|
|
@@ -6823,7 +6967,9 @@ var StreamerServer = class {
|
|
|
6823
6967
|
res.end();
|
|
6824
6968
|
return;
|
|
6825
6969
|
}
|
|
6826
|
-
const filtered = conversation.messages
|
|
6970
|
+
const filtered = conversation.messages.filter(
|
|
6971
|
+
(m) => !(m.role === "user" && typeof m.text === "string" && isCodexInjectedContext(m.text))
|
|
6972
|
+
);
|
|
6827
6973
|
const total = filtered.length;
|
|
6828
6974
|
const hasAnchor = url.searchParams.has("anchor_index");
|
|
6829
6975
|
const hasAfter = url.searchParams.has("after_index");
|
|
@@ -6881,8 +7027,7 @@ var StreamerServer = class {
|
|
|
6881
7027
|
})
|
|
6882
7028
|
);
|
|
6883
7029
|
}
|
|
6884
|
-
const
|
|
6885
|
-
const page = indexWindow ?? (scanLimit > 0 && pagedScanner && typeof pagedScanner.getConversationPage === "function" ? await pagedScanner.getConversationPage(id, { beforeIndex, limit: scanLimit }) : null);
|
|
7030
|
+
const page = indexWindow;
|
|
6886
7031
|
if (indexWindow) indexTotal = indexWindow.total;
|
|
6887
7032
|
const start = page?.fromIndex ?? windowStart;
|
|
6888
7033
|
slice = page?.messages ?? filtered.slice(start, beforeIndex);
|
|
@@ -7690,10 +7835,7 @@ var StreamerServer = class {
|
|
|
7690
7835
|
try {
|
|
7691
7836
|
const existing = (0, import_fs12.readFileSync)(resolvedFilePath, "utf8").split("\n").filter(Boolean);
|
|
7692
7837
|
if (existing.length > 0) {
|
|
7693
|
-
this.
|
|
7694
|
-
for (const line of existing) {
|
|
7695
|
-
this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
|
|
7696
|
-
}
|
|
7838
|
+
this.broadcastConversationLines(sessionId, existing);
|
|
7697
7839
|
}
|
|
7698
7840
|
} catch {
|
|
7699
7841
|
}
|
|
@@ -7794,10 +7936,7 @@ var StreamerServer = class {
|
|
|
7794
7936
|
try {
|
|
7795
7937
|
const existing = (0, import_fs12.readFileSync)(candidatePath, "utf8").split("\n").filter(Boolean);
|
|
7796
7938
|
if (existing.length > 0) {
|
|
7797
|
-
this.
|
|
7798
|
-
for (const line of existing) {
|
|
7799
|
-
this.wsHub.broadcast({ type: "conversation_event", sessionId, line });
|
|
7800
|
-
}
|
|
7939
|
+
this.broadcastConversationLines(sessionId, existing);
|
|
7801
7940
|
}
|
|
7802
7941
|
} catch {
|
|
7803
7942
|
}
|