@ccpocket/bridge 1.85.1 → 1.86.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/README.md CHANGED
@@ -39,6 +39,7 @@ ccpocket-bridge --version
39
39
  | Environment Variable | Default | Description |
40
40
  |---------------------|---------|-------------|
41
41
  | `BRIDGE_PORT` | `8765` | WebSocket port |
42
+ | `BRIDGE_NOTIFY_GOAL_TURN_COMPLETED` | `false` | Set exactly to `true` to notify intermediate Codex turns while a Goal is active. Goal completion, blockers, limits, approvals and errors remain enabled. Read at session creation; restart Bridge to apply to all sessions. |
42
43
  | `BRIDGE_HOST` | `0.0.0.0` | Bind address |
43
44
  | `BRIDGE_API_KEY` | (none) | API key authentication (enabled when set) |
44
45
  | `BRIDGE_ALLOWED_DIRS` | `$HOME` | Comma-separated list of project directories the Bridge may access; set exactly to `*` to allow any directory |
@@ -0,0 +1,5 @@
1
+ /** Bound unusually large tool payloads before retaining all history pages. */
2
+ export declare function compactCodexHistoryItem(item: Record<string, unknown>): {
3
+ item: Record<string, unknown>;
4
+ chars: number;
5
+ };
@@ -0,0 +1,53 @@
1
+ // These limits apply only to the Bridge's display copy, never to Codex's
2
+ // persisted history or the model's context.
3
+ const MAX_TOOL_ITEM_CHARS = 256 * 1024;
4
+ const MAX_TOOL_STRING_CHARS = 16 * 1024;
5
+ const OMITTED = "[Large tool detail omitted from Bridge history]";
6
+ const TRUNCATED = "\n[Truncated in Bridge history]";
7
+ const TOOL_TYPES = new Set([
8
+ "commandExecution", "fileChange", "mcpToolCall", "dynamicToolCall",
9
+ "functionCallOutput", "imageGeneration",
10
+ ]);
11
+ /** Bound unusually large tool payloads before retaining all history pages. */
12
+ export function compactCodexHistoryItem(item) {
13
+ const chars = JSON.stringify(item).length;
14
+ if (!TOOL_TYPES.has(String(item.type)) || chars <= MAX_TOOL_ITEM_CHARS) {
15
+ return { item, chars };
16
+ }
17
+ const compacted = compactValue(item, 0);
18
+ // Generated images use a bare base64 string. Never pass truncated base64 to
19
+ // the image decoder; show an explicit text result instead.
20
+ if (item.type === "imageGeneration") {
21
+ delete compacted.result;
22
+ compacted.revisedPrompt = `${compacted.revisedPrompt ?? ""}\n${OMITTED}`;
23
+ }
24
+ return { item: compacted, chars: JSON.stringify(compacted).length };
25
+ }
26
+ function compactValue(value, depth) {
27
+ if (typeof value === "string") {
28
+ if (value.length <= MAX_TOOL_STRING_CHARS)
29
+ return value;
30
+ if (value.startsWith("data:image/"))
31
+ return OMITTED;
32
+ return value.slice(0, MAX_TOOL_STRING_CHARS) + TRUNCATED;
33
+ }
34
+ if (value == null || typeof value !== "object")
35
+ return value;
36
+ if (depth >= 20)
37
+ return OMITTED;
38
+ if (Array.isArray(value)) {
39
+ return value.map((entry) => compactValue(entry, depth + 1));
40
+ }
41
+ const record = value;
42
+ if (record.type === "image" && typeof record.data === "string"
43
+ && record.data.length > MAX_TOOL_STRING_CHARS) {
44
+ return { type: "text", text: OMITTED };
45
+ }
46
+ if (record.type === "inputImage" && typeof record.imageUrl === "string"
47
+ && record.imageUrl.length > MAX_TOOL_STRING_CHARS) {
48
+ return { type: "inputText", text: OMITTED };
49
+ }
50
+ return Object.fromEntries(Object.entries(record)
51
+ .map(([key, entry]) => [key, compactValue(entry, depth + 1)]));
52
+ }
53
+ //# sourceMappingURL=codex-history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-history.js","sourceRoot":"","sources":["../src/codex-history.ts"],"names":[],"mappings":"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"}
@@ -146,6 +146,7 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
146
146
  * copied at most once instead of repeatedly rebuilding one growing string.
147
147
  */
148
148
  private stdoutLineChunks;
149
+ private stdoutLineChars;
149
150
  private _approvalPolicy;
150
151
  private _approvalsReviewer;
151
152
  private _codexPermissionsMode;
@@ -211,7 +212,7 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
211
212
  */
212
213
  renameThread(name: string): Promise<void>;
213
214
  /** Read the persisted goal attached to this Codex thread. */
214
- getGoal(): Promise<CodexGoal | null>;
215
+ getGoal(timeoutMs?: number): Promise<CodexGoal | null>;
215
216
  /** Create or update the persisted goal attached to this Codex thread. */
216
217
  setGoal(update: {
217
218
  objective?: string;
@@ -226,6 +227,9 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
226
227
  */
227
228
  archiveThread(threadId: string): Promise<void>;
228
229
  readThread(threadId: string, includeTurns?: boolean): Promise<Record<string, unknown>>;
230
+ private checkHistorySize;
231
+ private readTurnPages;
232
+ private readHistoryPages;
229
233
  /** Fork at a complete turn boundary without mutating the source history. */
230
234
  forkThreadAtUserTurn(userTurnOrdinal: number): Promise<{
231
235
  threadId: string;
@@ -341,7 +345,7 @@ export declare class CodexProcess extends EventEmitter<CodexProcessEvents> {
341
345
  private emitCompletionEntitiesSnapshot;
342
346
  private runInputLoop;
343
347
  private handleStdoutChunk;
344
- private completeStdoutLine;
348
+ private failStdout;
345
349
  private handleRpcEnvelope;
346
350
  private handleRpcResponse;
347
351
  private handleServerRequest;
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import { rm, writeFile } from "node:fs/promises";
7
7
  import { createCodexTransport, buildCodexSpawnSpec, } from "./codex-transport.js";
8
+ import { compactCodexHistoryItem } from "./codex-history.js";
8
9
  import { codexThreadToSessionHistory } from "./sessions-index.js";
9
10
  import { codexCliJoinTarget } from "./codex-app-server-config.js";
10
11
  import { normalizeCodexServiceTierForClient, normalizeCodexServiceTierForRpc, } from "./codex-service-tier.js";
@@ -13,6 +14,8 @@ export { buildCodexSpawnSpec };
13
14
  const DEFAULT_CODEX_MODEL = "gpt-5.5";
14
15
  const COMPLETION_FETCH_COOLDOWN_MS = 1000;
15
16
  const ARCHIVE_RPC_TIMEOUT_MS = 15_000;
17
+ const MAX_STDOUT_LINE_CHARS = 64 * 1024 * 1024;
18
+ const MAX_RETAINED_HISTORY_CHARS = 64 * 1024 * 1024;
16
19
  const UNKNOWN_AGENT_ITEM_ID = "__unknown_agent_message__";
17
20
  const CODEX_CLI_NOT_FOUND_MESSAGE = "Codex CLI is not installed or not available on PATH on the Bridge machine. Install it with `curl -fsSL https://chatgpt.com/codex/install.sh | sh`, then restart Bridge.";
18
21
  export class CodexRpcError extends Error {
@@ -102,6 +105,7 @@ export class CodexProcess extends EventEmitter {
102
105
  * copied at most once instead of repeatedly rebuilding one growing string.
103
106
  */
104
107
  stdoutLineChunks = [];
108
+ stdoutLineChars = 0;
105
109
  // Collaboration mode & plan completion state
106
110
  _approvalPolicy = undefined;
107
111
  _approvalsReviewer = undefined;
@@ -237,13 +241,11 @@ export class CodexProcess extends EventEmitter {
237
241
  });
238
242
  }
239
243
  /** Read the persisted goal attached to this Codex thread. */
240
- async getGoal() {
244
+ async getGoal(timeoutMs = 3_000) {
241
245
  if (!this._threadId) {
242
246
  throw new Error("No thread ID available for goal lookup");
243
247
  }
244
- const response = (await this.request("thread/goal/get", {
245
- threadId: this._threadId,
246
- }));
248
+ const response = (await this.request("thread/goal/get", { threadId: this._threadId }, timeoutMs));
247
249
  return response.goal == null ? null : parseCodexGoal(response.goal);
248
250
  }
249
251
  /** Create or update the persisted goal attached to this Codex thread. */
@@ -292,55 +294,133 @@ export class CodexProcess extends EventEmitter {
292
294
  }
293
295
  if (!includeTurns)
294
296
  return thread;
297
+ try {
298
+ // A single autonomous turn can contain thousands of screenshots/tool
299
+ // results. Page turn metadata separately, then hydrate by item count.
300
+ const turns = await this.readTurnPages(threadId, "notLoaded");
301
+ const byId = new Map();
302
+ for (const turn of turns) {
303
+ if (typeof turn.id !== "string")
304
+ throw new Error("Codex turn has no id");
305
+ turn.items = [];
306
+ byId.set(turn.id, turn);
307
+ }
308
+ let retainedChars = JSON.stringify(turns).length;
309
+ await this.readHistoryPages("thread/items/list", {
310
+ threadId, limit: 10, sortDirection: "asc",
311
+ }, (entries) => {
312
+ for (const rawEntry of entries) {
313
+ const entry = asRecord(rawEntry);
314
+ const item = asRecord(entry?.item);
315
+ const turn = typeof entry?.turnId === "string"
316
+ ? byId.get(entry.turnId) : undefined;
317
+ if (!item || !turn) {
318
+ throw new Error("Codex history changed while reading; retry opening the session");
319
+ }
320
+ const compacted = compactCodexHistoryItem(item);
321
+ retainedChars += compacted.chars;
322
+ this.checkHistorySize(retainedChars);
323
+ turn.items.push(compacted.item);
324
+ }
325
+ });
326
+ for (const turn of turns)
327
+ turn.itemsView = "full";
328
+ return { ...thread, turns };
329
+ }
330
+ catch (err) {
331
+ if (err instanceof CodexRpcError && err.code === -32600 &&
332
+ err.message.includes("is not materialized yet")) {
333
+ return { ...thread, turns: [] };
334
+ }
335
+ // Unsupported methods/views permit the compatibility path. Never retry
336
+ // a size/transport failure with a larger full-history response.
337
+ const unsupportedMetadataView = err instanceof CodexRpcError &&
338
+ err.code === -32602 && /unknown variant.*notLoaded/i.test(err.message);
339
+ if (!unsupportedMetadataView &&
340
+ (!(err instanceof CodexRpcError) || err.code !== -32601))
341
+ throw err;
342
+ }
343
+ try {
344
+ return { ...thread, turns: await this.readTurnPages(threadId, "full") };
345
+ }
346
+ catch (err) {
347
+ if (!(err instanceof CodexRpcError) || err.code !== -32601)
348
+ throw err;
349
+ }
350
+ const legacy = (await this.request("thread/read", {
351
+ threadId, includeTurns: true,
352
+ }));
353
+ const legacyThread = asRecord(legacy.thread);
354
+ if (!legacyThread)
355
+ throw new Error("thread/read returned no thread");
356
+ if (Array.isArray(legacyThread.turns)) {
357
+ let chars = 0;
358
+ for (const rawTurn of legacyThread.turns) {
359
+ const turn = asRecord(rawTurn);
360
+ if (!turn || !Array.isArray(turn.items))
361
+ continue;
362
+ turn.items = turn.items.map((rawItem) => {
363
+ const item = asRecord(rawItem);
364
+ if (!item)
365
+ return rawItem;
366
+ const compacted = compactCodexHistoryItem(item);
367
+ chars += compacted.chars;
368
+ this.checkHistorySize(chars);
369
+ return compacted.item;
370
+ });
371
+ }
372
+ }
373
+ return legacyThread;
374
+ }
375
+ checkHistorySize(chars) {
376
+ if (chars > MAX_RETAINED_HISTORY_CHARS) {
377
+ throw new Error("Codex display history exceeds the Bridge 64 Mi-character limit");
378
+ }
379
+ }
380
+ async readTurnPages(threadId, itemsView) {
295
381
  const turns = [];
382
+ let retainedChars = 0;
383
+ await this.readHistoryPages("thread/turns/list", {
384
+ threadId, limit: itemsView === "full" ? 1 : 50,
385
+ sortDirection: "asc", itemsView,
386
+ }, (data) => {
387
+ for (const rawTurn of data) {
388
+ const turn = asRecord(rawTurn);
389
+ if (!turn)
390
+ throw new Error("thread/turns/list returned an invalid turn");
391
+ if (itemsView === "full" && Array.isArray(turn.items)) {
392
+ turn.items = turn.items.map((rawItem) => {
393
+ const item = asRecord(rawItem);
394
+ return item ? compactCodexHistoryItem(item).item : rawItem;
395
+ });
396
+ }
397
+ retainedChars += JSON.stringify(turn).length;
398
+ this.checkHistorySize(retainedChars);
399
+ turns.push(turn);
400
+ }
401
+ });
402
+ return turns;
403
+ }
404
+ async readHistoryPages(method, params, consume) {
296
405
  const seenCursors = new Set();
297
406
  let cursor;
298
407
  do {
299
- let page;
300
- try {
301
- page = (await this.request("thread/turns/list", {
302
- threadId,
303
- ...(cursor !== undefined ? { cursor } : {}),
304
- limit: 50,
305
- sortDirection: "asc",
306
- itemsView: "full",
307
- }));
308
- }
309
- catch (err) {
310
- // New threads have metadata but no persisted turn history yet.
311
- if (cursor === undefined &&
312
- err instanceof CodexRpcError &&
313
- err.code === -32600 &&
314
- err.message.includes("is not materialized yet; thread/turns/list is unavailable before first user message")) {
315
- return { ...thread, turns: [] };
316
- }
317
- // Older app-servers do not expose turn pagination.
318
- if (!(err instanceof CodexRpcError) || err.code !== -32601)
319
- throw err;
320
- const legacy = (await this.request("thread/read", {
321
- threadId,
322
- includeTurns: true,
323
- }));
324
- if (!legacy.thread)
325
- throw new Error("thread/read returned no thread");
326
- return legacy.thread;
327
- }
328
- if (!Array.isArray(page.data)) {
329
- throw new Error("thread/turns/list returned invalid data");
330
- }
331
- turns.push(...page.data);
408
+ const page = (await this.request(method, {
409
+ ...params, ...(cursor !== undefined ? { cursor } : {}),
410
+ }));
411
+ if (!Array.isArray(page.data))
412
+ throw new Error(`${method} returned invalid data`);
413
+ consume(page.data);
332
414
  if (page.nextCursor != null && typeof page.nextCursor !== "string") {
333
- throw new Error("thread/turns/list returned an invalid cursor");
415
+ throw new Error(`${method} returned an invalid cursor`);
334
416
  }
335
417
  cursor = page.nextCursor ?? undefined;
336
418
  if (cursor !== undefined) {
337
- if (seenCursors.has(cursor)) {
338
- throw new Error("thread/turns/list returned a repeated cursor");
339
- }
419
+ if (seenCursors.has(cursor))
420
+ throw new Error(`${method} returned a repeated cursor`);
340
421
  seenCursors.add(cursor);
341
422
  }
342
423
  } while (cursor !== undefined);
343
- return { ...thread, turns };
344
424
  }
345
425
  /** Fork at a complete turn boundary without mutating the source history. */
346
426
  async forkThreadAtUserTurn(userTurnOrdinal) {
@@ -520,6 +600,8 @@ export class CodexProcess extends EventEmitter {
520
600
  }
521
601
  this.pendingApprovals.clear();
522
602
  this.pendingUserInputs.clear();
603
+ this.stdoutLineChunks = [];
604
+ this.stdoutLineChars = 0;
523
605
  this.cleanupSteerTempPaths();
524
606
  this.rejectAllPending(new Error("stopped"));
525
607
  if (this.transport) {
@@ -570,6 +652,7 @@ export class CodexProcess extends EventEmitter {
570
652
  this._pendingPlanInput = null;
571
653
  this._projectPath = projectPath;
572
654
  this.stdoutLineChunks = [];
655
+ this.stdoutLineChars = 0;
573
656
  }
574
657
  launchAppServer(projectPath, options) {
575
658
  const sandboxLog = options?.sandboxMode ?? "config";
@@ -1566,17 +1649,37 @@ export class CodexProcess extends EventEmitter {
1566
1649
  }
1567
1650
  }
1568
1651
  handleStdoutChunk(chunk) {
1652
+ if (this.stopped)
1653
+ return;
1569
1654
  let lineStart = 0;
1570
1655
  while (lineStart < chunk.length) {
1571
1656
  const newlineIndex = chunk.indexOf("\n", lineStart);
1657
+ const end = newlineIndex < 0 ? chunk.length : newlineIndex;
1658
+ const fragment = chunk.slice(lineStart, end);
1659
+ this.stdoutLineChars += fragment.length;
1660
+ if (this.stdoutLineChars > MAX_STDOUT_LINE_CHARS) {
1661
+ this.failStdout(new Error("Codex app-server response exceeds the Bridge 64 Mi-character limit " +
1662
+ `(pending: ${[...this.pendingRpc.values()].map((rpc) => rpc.method).join(", ") || "notification"}). ` +
1663
+ "Only this Codex connection was closed; Bridge is still running."));
1664
+ return;
1665
+ }
1572
1666
  if (newlineIndex < 0) {
1573
- this.stdoutLineChunks.push(chunk.slice(lineStart));
1667
+ this.stdoutLineChunks.push(fragment);
1574
1668
  return;
1575
1669
  }
1576
- const lineFragment = chunk.slice(lineStart, newlineIndex);
1577
- const line = this.stdoutLineChunks.length === 0
1578
- ? lineFragment.trim()
1579
- : this.completeStdoutLine(lineFragment);
1670
+ let line;
1671
+ try {
1672
+ // Guard both fragmented and single-chunk records before joining, and
1673
+ // keep allocation errors inside the connection's failure boundary.
1674
+ this.stdoutLineChunks.push(fragment);
1675
+ line = this.stdoutLineChunks.join("").trim();
1676
+ }
1677
+ catch (err) {
1678
+ this.failStdout(err instanceof Error ? err : new Error(String(err)));
1679
+ return;
1680
+ }
1681
+ this.stdoutLineChunks = [];
1682
+ this.stdoutLineChars = 0;
1580
1683
  lineStart = newlineIndex + 1;
1581
1684
  if (!line)
1582
1685
  continue;
@@ -1585,7 +1688,7 @@ export class CodexProcess extends EventEmitter {
1585
1688
  this.handleRpcEnvelope(envelope);
1586
1689
  }
1587
1690
  catch (err) {
1588
- console.warn(`[codex-process] failed to parse app-server JSON line: ${line.slice(0, 200)}`);
1691
+ console.warn("[codex-process] failed to parse app-server JSON line");
1589
1692
  if (!this.stopped) {
1590
1693
  this.emitMessage({
1591
1694
  type: "error",
@@ -1595,11 +1698,12 @@ export class CodexProcess extends EventEmitter {
1595
1698
  }
1596
1699
  }
1597
1700
  }
1598
- completeStdoutLine(finalChunk) {
1599
- this.stdoutLineChunks.push(finalChunk);
1600
- const line = this.stdoutLineChunks.join("").trim();
1601
- this.stdoutLineChunks = [];
1602
- return line;
1701
+ failStdout(error) {
1702
+ console.error(`[codex-process] ${error.message}`);
1703
+ this.rejectReadiness(error);
1704
+ this.rejectAllPending(error);
1705
+ this.emitMessage({ type: "error", message: error.message });
1706
+ this.stop();
1603
1707
  }
1604
1708
  handleRpcEnvelope(envelope) {
1605
1709
  if (envelope.id != null &&