@ccpocket/bridge 1.86.2 → 1.87.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/codex-process.d.ts +11 -0
- package/dist/codex-process.js +99 -1
- package/dist/codex-process.js.map +1 -1
- package/dist/codex-recovery.d.ts +35 -0
- package/dist/codex-recovery.js +121 -0
- package/dist/codex-recovery.js.map +1 -0
- package/dist/parser.d.ts +12 -0
- package/dist/parser.js +10 -0
- package/dist/parser.js.map +1 -1
- package/dist/session.js +3 -1
- package/dist/session.js.map +1 -1
- package/dist/sessions-index.js +138 -30
- package/dist/sessions-index.js.map +1 -1
- package/dist/websocket.js +54 -16
- package/dist/websocket.js.map +1 -1
- package/package.json +1 -1
package/dist/sessions-index.js
CHANGED
|
@@ -145,6 +145,7 @@ const HEAD_BYTES = 16384; // 16KB — covers first user entry + metadata
|
|
|
145
145
|
const TAIL_BYTES = 8192; // 8KB — covers last entries for modified/lastPrompt
|
|
146
146
|
const CODEX_HEAD_BYTES = 131072; // 128KB — Codex turn_context can be large
|
|
147
147
|
const CODEX_TAIL_BYTES = 16384;
|
|
148
|
+
const CODEX_MAX_TAIL_BYTES = 4 * 1024 * 1024;
|
|
148
149
|
/**
|
|
149
150
|
* Run async tasks with a concurrency limit.
|
|
150
151
|
* Returns results in the same order as the input tasks.
|
|
@@ -1165,12 +1166,28 @@ function parseCodexSessionJsonl(raw, fallbackSessionId) {
|
|
|
1165
1166
|
}
|
|
1166
1167
|
if (entry.type === "response_item") {
|
|
1167
1168
|
const payload = entry.payload;
|
|
1168
|
-
if (!payload || payload.type !== "message"
|
|
1169
|
+
if (!payload || payload.type !== "message") {
|
|
1169
1170
|
continue;
|
|
1170
1171
|
}
|
|
1171
1172
|
const content = payload.content;
|
|
1172
1173
|
if (!Array.isArray(content))
|
|
1173
1174
|
continue;
|
|
1175
|
+
if (payload.role === "user") {
|
|
1176
|
+
const text = content
|
|
1177
|
+
.filter((item) => item.type === "input_text" && typeof item.text === "string")
|
|
1178
|
+
.map((item) => item.text)
|
|
1179
|
+
.join("\n")
|
|
1180
|
+
.trim();
|
|
1181
|
+
if (text && !isCodexInjectedUserContext(text)) {
|
|
1182
|
+
hasMessages = true;
|
|
1183
|
+
if (!firstPrompt)
|
|
1184
|
+
firstPrompt = text;
|
|
1185
|
+
lastPrompt = text;
|
|
1186
|
+
}
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
if (payload.role !== "assistant")
|
|
1190
|
+
continue;
|
|
1174
1191
|
const text = content
|
|
1175
1192
|
.filter((item) => item.type === "output_text" && typeof item.text === "string")
|
|
1176
1193
|
.map((item) => item.text)
|
|
@@ -1228,6 +1245,39 @@ function parseCodexSessionJsonl(raw, fallbackSessionId) {
|
|
|
1228
1245
|
codexRepositoryUrls.set(entry, repositoryUrl);
|
|
1229
1246
|
return { threadId, entry };
|
|
1230
1247
|
}
|
|
1248
|
+
function hasCodexUserMessage(raw) {
|
|
1249
|
+
for (const line of raw.split("\n")) {
|
|
1250
|
+
if (!line.trim())
|
|
1251
|
+
continue;
|
|
1252
|
+
let entry;
|
|
1253
|
+
try {
|
|
1254
|
+
entry = JSON.parse(line);
|
|
1255
|
+
}
|
|
1256
|
+
catch {
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
const payload = entry.payload;
|
|
1260
|
+
if (entry.type === "event_msg"
|
|
1261
|
+
&& payload?.type === "user_message"
|
|
1262
|
+
&& typeof payload.message === "string"
|
|
1263
|
+
&& !isCodexInjectedUserContext(payload.message)) {
|
|
1264
|
+
return true;
|
|
1265
|
+
}
|
|
1266
|
+
if (entry.type === "response_item"
|
|
1267
|
+
&& payload?.type === "message"
|
|
1268
|
+
&& payload.role === "user"
|
|
1269
|
+
&& Array.isArray(payload.content)) {
|
|
1270
|
+
const text = payload.content
|
|
1271
|
+
.filter((item) => item.type === "input_text" && typeof item.text === "string")
|
|
1272
|
+
.map((item) => item.text)
|
|
1273
|
+
.join("\n")
|
|
1274
|
+
.trim();
|
|
1275
|
+
if (text && !isCodexInjectedUserContext(text))
|
|
1276
|
+
return true;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1231
1281
|
/**
|
|
1232
1282
|
* Fast parse a Codex JSONL file for recent-session list metadata.
|
|
1233
1283
|
* The first chunk contains session_meta / first prompt; the tail chunk contains
|
|
@@ -1253,13 +1303,21 @@ async function parseCodexSessionJsonlFast(filePath, fallbackSessionId) {
|
|
|
1253
1303
|
}
|
|
1254
1304
|
const headBuf = Buffer.alloc(CODEX_HEAD_BYTES);
|
|
1255
1305
|
await fh.read(headBuf, 0, CODEX_HEAD_BYTES, 0);
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1306
|
+
let tailBytes = CODEX_TAIL_BYTES;
|
|
1307
|
+
while (true) {
|
|
1308
|
+
const tailBuf = Buffer.alloc(tailBytes);
|
|
1309
|
+
await fh.read(tailBuf, 0, tailBytes, fileSize - tailBytes);
|
|
1310
|
+
const tailRaw = tailBuf.toString("utf-8");
|
|
1311
|
+
const firstNewline = tailRaw.indexOf("\n");
|
|
1312
|
+
const cleanTail = firstNewline >= 0 ? tailRaw.slice(firstNewline + 1) : "";
|
|
1313
|
+
const partialRaw = `${headBuf.toString("utf-8")}\n${cleanTail}`;
|
|
1314
|
+
const parsed = parseCodexSessionJsonl(partialRaw, fallbackSessionId);
|
|
1315
|
+
if (hasCodexUserMessage(cleanTail)
|
|
1316
|
+
|| tailBytes >= Math.min(fileSize, CODEX_MAX_TAIL_BYTES)) {
|
|
1317
|
+
return parsed;
|
|
1318
|
+
}
|
|
1319
|
+
tailBytes = Math.min(tailBytes * 2, fileSize, CODEX_MAX_TAIL_BYTES);
|
|
1320
|
+
}
|
|
1263
1321
|
}
|
|
1264
1322
|
finally {
|
|
1265
1323
|
await fh.close();
|
|
@@ -1413,8 +1471,13 @@ export async function loadCodexSessionNames() {
|
|
|
1413
1471
|
continue;
|
|
1414
1472
|
try {
|
|
1415
1473
|
const entry = JSON.parse(line);
|
|
1416
|
-
if (entry.id && entry.thread_name) {
|
|
1417
|
-
|
|
1474
|
+
if (entry.id && typeof entry.thread_name === "string") {
|
|
1475
|
+
if (entry.thread_name) {
|
|
1476
|
+
names.set(entry.id, entry.thread_name);
|
|
1477
|
+
}
|
|
1478
|
+
else {
|
|
1479
|
+
names.delete(entry.id);
|
|
1480
|
+
}
|
|
1418
1481
|
}
|
|
1419
1482
|
}
|
|
1420
1483
|
catch {
|
|
@@ -1583,8 +1646,10 @@ function matchingCodexThreadIdFromFilePath(filePath, wantedThreadIds) {
|
|
|
1583
1646
|
if (wantedThreadIds.has(fallbackSessionId))
|
|
1584
1647
|
return fallbackSessionId;
|
|
1585
1648
|
for (const threadId of wantedThreadIds) {
|
|
1586
|
-
if (fallbackSessionId.endsWith(`-${threadId}`)
|
|
1649
|
+
if (fallbackSessionId.endsWith(`-${threadId}`)
|
|
1650
|
+
|| fallbackSessionId.includes(`-${threadId}_`)) {
|
|
1587
1651
|
return threadId;
|
|
1652
|
+
}
|
|
1588
1653
|
}
|
|
1589
1654
|
return null;
|
|
1590
1655
|
}
|
|
@@ -1594,25 +1659,30 @@ export async function getCodexSessionIndexMetadata(threadIds) {
|
|
|
1594
1659
|
if (wantedThreadIds.size === 0)
|
|
1595
1660
|
return result;
|
|
1596
1661
|
const files = await listCodexSessionFiles();
|
|
1597
|
-
const targets =
|
|
1598
|
-
const matchedThreadIds = new Set();
|
|
1599
|
-
for (const filePath of files) {
|
|
1600
|
-
const threadId = matchingCodexThreadIdFromFilePath(filePath, wantedThreadIds);
|
|
1601
|
-
if (!threadId || matchedThreadIds.has(threadId))
|
|
1602
|
-
continue;
|
|
1603
|
-
targets.push(filePath);
|
|
1604
|
-
matchedThreadIds.add(threadId);
|
|
1605
|
-
if (matchedThreadIds.size === wantedThreadIds.size)
|
|
1606
|
-
break;
|
|
1607
|
-
}
|
|
1662
|
+
const targets = files.filter((filePath) => matchingCodexThreadIdFromFilePath(filePath, wantedThreadIds) !== null);
|
|
1608
1663
|
const parsedResults = await parallelMap(targets, PARALLEL_FILE_READ_LIMIT, async (filePath) => {
|
|
1609
1664
|
const fallbackSessionId = basename(filePath, ".jsonl");
|
|
1610
|
-
|
|
1611
|
-
});
|
|
1612
|
-
for (const parsed of parsedResults) {
|
|
1665
|
+
const parsed = await parseCodexSessionJsonlFast(filePath, fallbackSessionId);
|
|
1613
1666
|
if (!parsed || !wantedThreadIds.has(parsed.threadId))
|
|
1667
|
+
return null;
|
|
1668
|
+
try {
|
|
1669
|
+
return { parsed, modified: (await stat(filePath)).mtimeMs };
|
|
1670
|
+
}
|
|
1671
|
+
catch {
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
});
|
|
1675
|
+
const newestByThread = new Map();
|
|
1676
|
+
for (const candidate of parsedResults) {
|
|
1677
|
+
if (!candidate)
|
|
1614
1678
|
continue;
|
|
1615
|
-
|
|
1679
|
+
const existing = newestByThread.get(candidate.parsed.threadId);
|
|
1680
|
+
if (!existing || candidate.modified > existing.modified) {
|
|
1681
|
+
newestByThread.set(candidate.parsed.threadId, candidate);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
for (const [threadId, { parsed }] of newestByThread) {
|
|
1685
|
+
result.set(threadId, {
|
|
1616
1686
|
...(parsed.entry.codexSettings
|
|
1617
1687
|
? { codexSettings: parsed.entry.codexSettings }
|
|
1618
1688
|
: {}),
|
|
@@ -2203,12 +2273,50 @@ async function findSessionJsonlPath(sessionId) {
|
|
|
2203
2273
|
}
|
|
2204
2274
|
async function findCodexSessionJsonlPath(threadId) {
|
|
2205
2275
|
const files = await listCodexSessionFiles();
|
|
2206
|
-
|
|
2276
|
+
const candidates = files.filter((filePath) => {
|
|
2207
2277
|
const fallbackSessionId = basename(filePath, ".jsonl");
|
|
2208
|
-
|
|
2209
|
-
fallbackSessionId.endsWith(`-${threadId}`)
|
|
2210
|
-
|
|
2278
|
+
return fallbackSessionId === threadId ||
|
|
2279
|
+
fallbackSessionId.endsWith(`-${threadId}`) ||
|
|
2280
|
+
fallbackSessionId.includes(`-${threadId}_`);
|
|
2281
|
+
});
|
|
2282
|
+
const matches = await parallelMap(candidates, PARALLEL_FILE_READ_LIMIT, async (filePath) => {
|
|
2283
|
+
let file;
|
|
2284
|
+
try {
|
|
2285
|
+
file = await open(filePath, "r");
|
|
2286
|
+
const metadata = await file.stat();
|
|
2287
|
+
const buffer = Buffer.alloc(Math.min(metadata.size, CODEX_HEAD_BYTES));
|
|
2288
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
|
|
2289
|
+
// History/image lookup must not depend on list metadata (cwd or text).
|
|
2290
|
+
// Older rollouts have no session_meta, so retain the filename fallback.
|
|
2291
|
+
for (const line of buffer.subarray(0, bytesRead).toString("utf-8").split("\n")) {
|
|
2292
|
+
try {
|
|
2293
|
+
const entry = JSON.parse(line);
|
|
2294
|
+
if (entry?.type === "session_meta" && typeof entry.payload?.id === "string") {
|
|
2295
|
+
if (entry.payload.id !== threadId)
|
|
2296
|
+
return null;
|
|
2297
|
+
break;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
catch {
|
|
2301
|
+
// Ignore partial or malformed header records.
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
return { filePath, modified: metadata.mtimeMs };
|
|
2305
|
+
}
|
|
2306
|
+
catch {
|
|
2307
|
+
return null;
|
|
2211
2308
|
}
|
|
2309
|
+
finally {
|
|
2310
|
+
await file?.close();
|
|
2311
|
+
}
|
|
2312
|
+
});
|
|
2313
|
+
const newest = matches
|
|
2314
|
+
.filter((match) => match !== null)
|
|
2315
|
+
.sort((a, b) => b.modified - a.modified)[0];
|
|
2316
|
+
if (newest)
|
|
2317
|
+
return newest.filePath;
|
|
2318
|
+
for (const filePath of files) {
|
|
2319
|
+
const fallbackSessionId = basename(filePath, ".jsonl");
|
|
2212
2320
|
let raw;
|
|
2213
2321
|
try {
|
|
2214
2322
|
raw = await readFile(filePath, "utf-8");
|