@ccpocket/bridge 1.59.0 → 1.59.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.
@@ -75,6 +75,10 @@ export declare function isWorktreeSlug(dirSlug: string, projectSlug: string): bo
75
75
  */
76
76
  export declare function scanJsonlDir(dirPath: string, options?: ScanJsonlDirOptions): Promise<SessionIndexEntry[]>;
77
77
  export declare function getAllRecentSessions(options?: GetRecentSessionsOptions): Promise<GetRecentSessionsResult>;
78
+ export interface CodexSessionIndexMetadata {
79
+ codexSettings?: SessionIndexEntry["codexSettings"];
80
+ resumeCwd?: string;
81
+ }
78
82
  /**
79
83
  * Look up the saved name (customTitle) for a Claude Code session.
80
84
  * Returns the name if found, or undefined.
@@ -98,6 +102,7 @@ export declare function saveCodexSessionAdditionalWritableRoots(threadId: string
98
102
  * Passing `null` or empty name writes an empty thread_name to effectively clear it.
99
103
  */
100
104
  export declare function renameCodexSession(threadId: string, name: string | null): Promise<boolean>;
105
+ export declare function getCodexSessionIndexMetadata(threadIds: readonly string[]): Promise<Map<string, CodexSessionIndexMetadata>>;
101
106
  type SessionHistoryContentItem = {
102
107
  type: string;
103
108
  text?: string;
@@ -85,6 +85,8 @@ const PARALLEL_FILE_READ_LIMIT = 32;
85
85
  /** Head/Tail byte sizes for partial JSONL reads. */
86
86
  const HEAD_BYTES = 16384; // 16KB — covers first user entry + metadata
87
87
  const TAIL_BYTES = 8192; // 8KB — covers last entries for modified/lastPrompt
88
+ const CODEX_HEAD_BYTES = 131072; // 128KB — Codex turn_context can be large
89
+ const CODEX_TAIL_BYTES = 16384;
88
90
  /**
89
91
  * Run async tasks with a concurrency limit.
90
92
  * Returns results in the same order as the input tasks.
@@ -112,6 +114,10 @@ const RE_IS_SIDECHAIN = /"isSidechain"\s*:\s*true/;
112
114
  const RE_PERMISSION_MODE = /"permissionMode"\s*:\s*"([^"]+)"/;
113
115
  const RE_TYPE_CUSTOM_TITLE = /"type"\s*:\s*"custom-title"/;
114
116
  const RE_CUSTOM_TITLE = /"customTitle"\s*:\s*"([^"]+)"/;
117
+ const RE_CODEX_PARTIAL_TIMESTAMP = /"timestamp"\s*:\s*"([^"]+)"/;
118
+ const RE_CODEX_PARTIAL_USER_MESSAGE = /"type"\s*:\s*"event_msg"[\s\S]*"payload"\s*:\s*\{[\s\S]*"type"\s*:\s*"user_message"[\s\S]*"message"\s*:\s*"((?:\\.|[^"\\])*)/;
119
+ const RE_CODEX_PARTIAL_AGENT_MESSAGE = /"type"\s*:\s*"event_msg"[\s\S]*"payload"\s*:\s*\{[\s\S]*"type"\s*:\s*"agent_message"[\s\S]*"message"\s*:\s*"((?:\\.|[^"\\])*)/;
120
+ const RE_CODEX_PARTIAL_OUTPUT_TEXT = /"type"\s*:\s*"response_item"[\s\S]*"role"\s*:\s*"assistant"[\s\S]*"type"\s*:\s*"output_text"[\s\S]*"text"\s*:\s*"((?:\\.|[^"\\])*)/;
115
121
  /**
116
122
  * Detect system-injected messages that should be skipped when determining
117
123
  * the user's first/last prompt text (e.g. local-command-caveat, stderr/stdout
@@ -124,6 +130,43 @@ function isSystemInjectedText(text) {
124
130
  function isCodexAutoRenameSession(firstPrompt, model) {
125
131
  return model === CODEX_ASSIST_MODEL && isAutoRenamePromptText(firstPrompt);
126
132
  }
133
+ function decodeJsonStringPrefix(fragment) {
134
+ let candidate = fragment;
135
+ while (candidate.length > 0) {
136
+ try {
137
+ return JSON.parse(`"${candidate}"`);
138
+ }
139
+ catch {
140
+ candidate = candidate.slice(0, -1);
141
+ }
142
+ }
143
+ return "";
144
+ }
145
+ function parsePartialCodexLine(line) {
146
+ const timestamp = line.match(RE_CODEX_PARTIAL_TIMESTAMP)?.[1];
147
+ const userMessage = line.match(RE_CODEX_PARTIAL_USER_MESSAGE)?.[1];
148
+ if (userMessage !== undefined) {
149
+ return {
150
+ timestamp,
151
+ userMessage: decodeJsonStringPrefix(userMessage),
152
+ };
153
+ }
154
+ const agentMessage = line.match(RE_CODEX_PARTIAL_AGENT_MESSAGE)?.[1];
155
+ if (agentMessage !== undefined) {
156
+ return {
157
+ timestamp,
158
+ assistantText: decodeJsonStringPrefix(agentMessage),
159
+ };
160
+ }
161
+ const outputText = line.match(RE_CODEX_PARTIAL_OUTPUT_TEXT)?.[1];
162
+ if (outputText !== undefined) {
163
+ return {
164
+ timestamp,
165
+ assistantText: decodeJsonStringPrefix(outputText),
166
+ };
167
+ }
168
+ return timestamp ? { timestamp } : {};
169
+ }
127
170
  /** Extract user prompt text from a parsed JSONL entry. */
128
171
  function extractUserPromptText(entry) {
129
172
  const message = entry.message;
@@ -919,6 +962,22 @@ function parseCodexSessionJsonl(raw, fallbackSessionId) {
919
962
  entry = JSON.parse(line);
920
963
  }
921
964
  catch {
965
+ const partial = parsePartialCodexLine(line);
966
+ if (partial.timestamp) {
967
+ if (!created)
968
+ created = partial.timestamp;
969
+ modified = partial.timestamp;
970
+ }
971
+ if (partial.userMessage !== undefined) {
972
+ hasMessages = true;
973
+ if (!firstPrompt)
974
+ firstPrompt = partial.userMessage;
975
+ lastPrompt = partial.userMessage;
976
+ }
977
+ if (partial.assistantText) {
978
+ hasMessages = true;
979
+ lastAssistantText = partial.assistantText;
980
+ }
922
981
  continue;
923
982
  }
924
983
  const timestamp = entry.timestamp;
@@ -994,6 +1053,12 @@ function parseCodexSessionJsonl(raw, fallbackSessionId) {
994
1053
  firstPrompt = payload.message;
995
1054
  lastPrompt = payload.message;
996
1055
  }
1056
+ else if (payload?.type === "agent_message"
1057
+ && typeof payload.message === "string"
1058
+ && payload.message.trim().length > 0) {
1059
+ hasMessages = true;
1060
+ lastAssistantText = payload.message;
1061
+ }
997
1062
  continue;
998
1063
  }
999
1064
  if (entry.type === "response_item") {
@@ -1059,6 +1124,43 @@ function parseCodexSessionJsonl(raw, fallbackSessionId) {
1059
1124
  },
1060
1125
  };
1061
1126
  }
1127
+ /**
1128
+ * Fast parse a Codex JSONL file for recent-session list metadata.
1129
+ * The first chunk contains session_meta / first prompt; the tail chunk contains
1130
+ * the latest prompt, latest assistant summary, and modified timestamp.
1131
+ */
1132
+ async function parseCodexSessionJsonlFast(filePath, fallbackSessionId) {
1133
+ let fh;
1134
+ try {
1135
+ fh = await open(filePath, "r");
1136
+ }
1137
+ catch {
1138
+ return null;
1139
+ }
1140
+ try {
1141
+ const fileStat = await fh.stat();
1142
+ const fileSize = fileStat.size;
1143
+ if (fileSize === 0)
1144
+ return null;
1145
+ if (fileSize <= CODEX_HEAD_BYTES + CODEX_TAIL_BYTES) {
1146
+ const buf = Buffer.alloc(fileSize);
1147
+ await fh.read(buf, 0, fileSize, 0);
1148
+ return parseCodexSessionJsonl(buf.toString("utf-8"), fallbackSessionId);
1149
+ }
1150
+ const headBuf = Buffer.alloc(CODEX_HEAD_BYTES);
1151
+ await fh.read(headBuf, 0, CODEX_HEAD_BYTES, 0);
1152
+ const tailBuf = Buffer.alloc(CODEX_TAIL_BYTES);
1153
+ await fh.read(tailBuf, 0, CODEX_TAIL_BYTES, fileSize - CODEX_TAIL_BYTES);
1154
+ const tailRaw = tailBuf.toString("utf-8");
1155
+ const firstNewline = tailRaw.indexOf("\n");
1156
+ const cleanTail = firstNewline >= 0 ? tailRaw.slice(firstNewline + 1) : "";
1157
+ const partialRaw = `${headBuf.toString("utf-8")}\n${cleanTail}`;
1158
+ return parseCodexSessionJsonl(partialRaw, fallbackSessionId);
1159
+ }
1160
+ finally {
1161
+ await fh.close();
1162
+ }
1163
+ }
1062
1164
  function isCodexInternalSessionSource(source) {
1063
1165
  const sourceObj = asObject(source);
1064
1166
  return sourceObj?.subagent !== undefined;
@@ -1343,17 +1445,12 @@ async function getAllRecentCodexSessions(options = {}) {
1343
1445
  const threadNames = await loadCodexSessionNames();
1344
1446
  const threadProfiles = await loadCodexSessionProfiles();
1345
1447
  const threadAdditionalWritableRoots = await loadCodexSessionAdditionalWritableRoots();
1346
- for (const filePath of files) {
1347
- let raw;
1348
- try {
1349
- raw = await readFile(filePath, "utf-8");
1350
- }
1351
- catch {
1352
- continue;
1353
- }
1354
- options.perfStats && (options.perfStats.filesRead += 1);
1448
+ const parsedResults = await parallelMap(files, PARALLEL_FILE_READ_LIMIT, async (filePath) => {
1355
1449
  const fallbackSessionId = basename(filePath, ".jsonl");
1356
- const parsed = parseCodexSessionJsonl(raw, fallbackSessionId);
1450
+ return parseCodexSessionJsonlFast(filePath, fallbackSessionId);
1451
+ });
1452
+ for (const parsed of parsedResults) {
1453
+ options.perfStats && (options.perfStats.filesRead += 1);
1357
1454
  if (!parsed)
1358
1455
  continue;
1359
1456
  if (normalizedProjectPath && parsed.entry.projectPath !== normalizedProjectPath) {
@@ -1383,6 +1480,49 @@ async function getAllRecentCodexSessions(options = {}) {
1383
1480
  }
1384
1481
  return entries;
1385
1482
  }
1483
+ function matchingCodexThreadIdFromFilePath(filePath, wantedThreadIds) {
1484
+ const fallbackSessionId = basename(filePath, ".jsonl");
1485
+ if (wantedThreadIds.has(fallbackSessionId))
1486
+ return fallbackSessionId;
1487
+ for (const threadId of wantedThreadIds) {
1488
+ if (fallbackSessionId.endsWith(`-${threadId}`))
1489
+ return threadId;
1490
+ }
1491
+ return null;
1492
+ }
1493
+ export async function getCodexSessionIndexMetadata(threadIds) {
1494
+ const wantedThreadIds = new Set(threadIds.filter((id) => id.length > 0));
1495
+ const result = new Map();
1496
+ if (wantedThreadIds.size === 0)
1497
+ return result;
1498
+ const files = await listCodexSessionFiles();
1499
+ const targets = [];
1500
+ const matchedThreadIds = new Set();
1501
+ for (const filePath of files) {
1502
+ const threadId = matchingCodexThreadIdFromFilePath(filePath, wantedThreadIds);
1503
+ if (!threadId || matchedThreadIds.has(threadId))
1504
+ continue;
1505
+ targets.push(filePath);
1506
+ matchedThreadIds.add(threadId);
1507
+ if (matchedThreadIds.size === wantedThreadIds.size)
1508
+ break;
1509
+ }
1510
+ const parsedResults = await parallelMap(targets, PARALLEL_FILE_READ_LIMIT, async (filePath) => {
1511
+ const fallbackSessionId = basename(filePath, ".jsonl");
1512
+ return parseCodexSessionJsonlFast(filePath, fallbackSessionId);
1513
+ });
1514
+ for (const parsed of parsedResults) {
1515
+ if (!parsed || !wantedThreadIds.has(parsed.threadId))
1516
+ continue;
1517
+ result.set(parsed.threadId, {
1518
+ ...(parsed.entry.codexSettings
1519
+ ? { codexSettings: parsed.entry.codexSettings }
1520
+ : {}),
1521
+ ...(parsed.entry.resumeCwd ? { resumeCwd: parsed.entry.resumeCwd } : {}),
1522
+ });
1523
+ }
1524
+ return result;
1525
+ }
1386
1526
  export function codexUserTurnUuid(ordinal) {
1387
1527
  return `codex:user-turn:${ordinal}`;
1388
1528
  }