@ccpocket/bridge 1.63.5 → 1.64.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.
Files changed (43) hide show
  1. package/README.md +5 -1
  2. package/dist/bridge-port.d.ts +2 -0
  3. package/dist/bridge-port.js +20 -0
  4. package/dist/bridge-port.js.map +1 -0
  5. package/dist/cli-args.js +3 -0
  6. package/dist/cli-args.js.map +1 -1
  7. package/dist/cli.js +8 -2
  8. package/dist/cli.js.map +1 -1
  9. package/dist/codex-permissions.d.ts +10 -0
  10. package/dist/codex-permissions.js +62 -0
  11. package/dist/codex-permissions.js.map +1 -0
  12. package/dist/codex-process.d.ts +1 -1
  13. package/dist/codex-process.js +8 -2
  14. package/dist/codex-process.js.map +1 -1
  15. package/dist/git-operations.d.ts +13 -0
  16. package/dist/git-operations.js +171 -3
  17. package/dist/git-operations.js.map +1 -1
  18. package/dist/index.js +22 -16
  19. package/dist/index.js.map +1 -1
  20. package/dist/parser.d.ts +2 -0
  21. package/dist/parser.js +6 -3
  22. package/dist/parser.js.map +1 -1
  23. package/dist/path-utils.d.ts +1 -0
  24. package/dist/path-utils.js +16 -0
  25. package/dist/path-utils.js.map +1 -1
  26. package/dist/sdk-process.d.ts +16 -1
  27. package/dist/sdk-process.js +126 -23
  28. package/dist/sdk-process.js.map +1 -1
  29. package/dist/server-listen.d.ts +2 -0
  30. package/dist/server-listen.js +23 -0
  31. package/dist/server-listen.js.map +1 -0
  32. package/dist/session.js +18 -4
  33. package/dist/session.js.map +1 -1
  34. package/dist/sessions-index.js +96 -11
  35. package/dist/sessions-index.js.map +1 -1
  36. package/dist/setup-launchd.js +3 -2
  37. package/dist/setup-launchd.js.map +1 -1
  38. package/dist/setup-systemd.js +3 -2
  39. package/dist/setup-systemd.js.map +1 -1
  40. package/dist/websocket.d.ts +32 -5
  41. package/dist/websocket.js +432 -150
  42. package/dist/websocket.js.map +1 -1
  43. package/package.json +1 -1
package/dist/websocket.js CHANGED
@@ -14,7 +14,7 @@ import { getAllRecentSessions, getCodexSessionHistory, getSessionHistory, codexU
14
14
  import { ArchiveStore } from "./archive-store.js";
15
15
  import { WorktreeStore } from "./worktree-store.js";
16
16
  import { listWorktrees, removeWorktree, worktreeExists, getMainBranch, } from "./worktree.js";
17
- import { stageFiles, stageHunks, unstageFiles, unstageHunks, gitCommit, gitPush, listProjectFilesAndDirectories, listBranches, createBranch, checkoutBranch, revertFiles, revertHunks, gitFetch, gitPull, gitRemoteStatus, gitStatus, } from "./git-operations.js";
17
+ import { stageFiles, stageHunks, unstageFiles, unstageHunks, gitCommit, gitPush, listProjectFilesAndDirectoriesForClient, listBranches, createBranch, checkoutBranch, revertFiles, revertHunks, gitFetch, gitPull, gitRemoteStatus, gitStatus, } from "./git-operations.js";
18
18
  import { generateCommitMessage } from "./git-assist.js";
19
19
  import { listWindows, takeScreenshot } from "./screenshot.js";
20
20
  import { DebugTraceStore } from "./debug-trace-store.js";
@@ -23,6 +23,7 @@ import { normalizePushLocale, t } from "./push-i18n.js";
23
23
  import { fetchAllUsage } from "./usage.js";
24
24
  import { getPackageVersion } from "./version.js";
25
25
  import { isPathWithinAllowedDirectory, resolvePlatformPath, resolvePlatformPathFrom, } from "./path-utils.js";
26
+ import { deriveCodexPermissionsMode, normalizeCodexPermissionsMode, withDerivedCodexPermissionsMode, } from "./codex-permissions.js";
26
27
  // ---- Available model lists (delivered to clients via session_list) ----
27
28
  const FALLBACK_CLAUDE_MODELS = [
28
29
  "claude-opus-4-7",
@@ -43,6 +44,9 @@ const FALLBACK_CLAUDE_MODEL_EFFORTS = {
43
44
  "claude-haiku-4-6": [],
44
45
  };
45
46
  const FALLBACK_CODEX_MODELS = [
47
+ "gpt-5.6-sol",
48
+ "gpt-5.6-terra",
49
+ "gpt-5.6-luna",
46
50
  "gpt-5.5",
47
51
  "gpt-5.4",
48
52
  "gpt-5.4-mini",
@@ -50,6 +54,22 @@ const FALLBACK_CODEX_MODELS = [
50
54
  "gpt-5.3-codex-spark",
51
55
  ];
52
56
  const FALLBACK_CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh"];
57
+ const FALLBACK_GPT_5_6_REASONING_EFFORTS = [
58
+ ...FALLBACK_CODEX_REASONING_EFFORTS,
59
+ "max",
60
+ ];
61
+ const FALLBACK_GPT_5_6_ULTRA_REASONING_EFFORTS = [
62
+ ...FALLBACK_GPT_5_6_REASONING_EFFORTS,
63
+ "ultra",
64
+ ];
65
+ function fallbackCodexReasoningEfforts(model) {
66
+ if (model === "gpt-5.6-sol" || model === "gpt-5.6-terra") {
67
+ return FALLBACK_GPT_5_6_ULTRA_REASONING_EFFORTS;
68
+ }
69
+ if (model === "gpt-5.6-luna")
70
+ return FALLBACK_GPT_5_6_REASONING_EFFORTS;
71
+ return FALLBACK_CODEX_REASONING_EFFORTS;
72
+ }
53
73
  const CODEX_RECENT_THREAD_SOURCE_KINDS = [
54
74
  "cli",
55
75
  "vscode",
@@ -207,17 +227,6 @@ function normalizeCodexApprovalPolicy(value) {
207
227
  return "on-request";
208
228
  }
209
229
  }
210
- function normalizeCodexPermissionsMode(value) {
211
- switch (value) {
212
- case "default":
213
- case "autoReview":
214
- case "fullAccess":
215
- case "custom":
216
- return value;
217
- default:
218
- return undefined;
219
- }
220
- }
221
230
  function sanitizeCodexModel(model) {
222
231
  if (typeof model !== "string")
223
232
  return undefined;
@@ -253,21 +262,6 @@ function codexSettingsFromPermissionsMode(mode) {
253
262
  return { codexPermissionsMode: mode };
254
263
  }
255
264
  }
256
- function inferCodexPermissionsMode(params) {
257
- const approvalPolicy = params.approvalPolicy;
258
- const approvalsReviewer = params.approvalsReviewer ?? "user";
259
- const sandboxMode = params.sandboxMode;
260
- if (approvalPolicy === "never" && sandboxMode === "danger-full-access") {
261
- return "fullAccess";
262
- }
263
- if (approvalPolicy === "on-request" && sandboxMode === "workspace-write") {
264
- return approvalsReviewer === "auto_review" ||
265
- approvalsReviewer === "guardian_subagent"
266
- ? "autoReview"
267
- : "default";
268
- }
269
- return undefined;
270
- }
271
265
  function errorMessageOf(err) {
272
266
  return err instanceof Error ? err.message : String(err);
273
267
  }
@@ -344,6 +338,38 @@ function envFlagEnabled(name) {
344
338
  const value = process.env[name]?.trim().toLowerCase();
345
339
  return value === "1" || value === "true" || value === "yes" || value === "on";
346
340
  }
341
+ const MAX_TIMER_DELAY_MS = 2_147_483_647;
342
+ function positiveEnvInt(name, fallback) {
343
+ const raw = process.env[name]?.trim();
344
+ if (!raw)
345
+ return fallback;
346
+ const value = Number(raw);
347
+ return Number.isSafeInteger(value) && value > 0 ? value : fallback;
348
+ }
349
+ function nonNegativeEnvInt(name, fallback) {
350
+ const raw = process.env[name]?.trim();
351
+ if (!raw)
352
+ return fallback;
353
+ const value = Number(raw);
354
+ return Number.isSafeInteger(value) &&
355
+ value >= 0 &&
356
+ value <= MAX_TIMER_DELAY_MS
357
+ ? value
358
+ : fallback;
359
+ }
360
+ function normalizePositiveLimit(value, fallback) {
361
+ return value !== undefined && Number.isSafeInteger(value) && value > 0
362
+ ? value
363
+ : fallback;
364
+ }
365
+ function normalizeNonNegativeLimit(value, fallback) {
366
+ return value !== undefined &&
367
+ Number.isSafeInteger(value) &&
368
+ value >= 0 &&
369
+ value <= MAX_TIMER_DELAY_MS
370
+ ? value
371
+ : fallback;
372
+ }
347
373
  function codexThreadToRecentSession(thread, indexed) {
348
374
  // thread/list only exposes a single preview blob; prefer the real
349
375
  // first/last/summary texts parsed from the rollout file so display-mode
@@ -391,6 +417,11 @@ function mergeRecentSessionPages(sessions) {
391
417
  export class BridgeWebSocketServer {
392
418
  static MAX_DEBUG_EVENTS = 800;
393
419
  static MAX_HISTORY_SUMMARY_ITEMS = 300;
420
+ static CONNECT_METADATA_REFRESH_COOLDOWN_MS = 5 * 60 * 1000;
421
+ static DEFAULT_FILE_LIST_MAX_ENTRIES = 5000;
422
+ static DEFAULT_FILE_LIST_MAX_BYTES = 512 * 1024;
423
+ static DEFAULT_DELTA_BATCH_MS = 100;
424
+ static DEFAULT_DELTA_BATCH_MAX_CHARS = 4096;
394
425
  wss;
395
426
  sessionManager;
396
427
  apiKey;
@@ -410,7 +441,8 @@ export class BridgeWebSocketServer {
410
441
  archiveStore;
411
442
  codexProfiles = [];
412
443
  defaultCodexProfile;
413
- codexProfilesRequest = null;
444
+ codexMetadataRequest = null;
445
+ lastConnectMetadataRefreshAt = null;
414
446
  claudeModels = FALLBACK_CLAUDE_MODELS;
415
447
  claudeModelEfforts = {
416
448
  ...FALLBACK_CLAUDE_MODEL_EFFORTS,
@@ -419,18 +451,23 @@ export class BridgeWebSocketServer {
419
451
  codexModels = FALLBACK_CODEX_MODELS;
420
452
  codexModelReasoningEfforts = Object.fromEntries(FALLBACK_CODEX_MODELS.map((model) => [
421
453
  model,
422
- FALLBACK_CODEX_REASONING_EFFORTS,
454
+ fallbackCodexReasoningEfforts(model),
423
455
  ]));
424
- codexModelsRequest = null;
425
456
  /** FCM token → push notification locale */
426
457
  tokenLocales = new Map();
427
458
  tokenPrivacyMode = new Map();
428
459
  failSetPermissionMode = envFlagEnabled("BRIDGE_FAIL_SET_PERMISSION_MODE");
429
460
  failSetSandboxMode = envFlagEnabled("BRIDGE_FAIL_SET_SANDBOX_MODE");
461
+ fileListMaxEntries;
462
+ fileListMaxBytes;
463
+ deltaBatchMs;
464
+ deltaBatchMaxChars;
465
+ deltaBatches = new Map();
430
466
  platform;
431
467
  clientSupportedServerMessages = new WeakMap();
468
+ pendingClaudeResumeInputs = new WeakMap();
432
469
  constructor(options) {
433
- const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, promptHistoryStore, platform, } = options;
470
+ const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, promptHistoryStore, platform, fileListMaxEntries, fileListMaxBytes, deltaBatchMs, deltaBatchMaxChars, } = options;
434
471
  this.apiKey = apiKey ?? null;
435
472
  this.allowedDirs = allowedDirs ?? [];
436
473
  this.imageStore = imageStore ?? null;
@@ -443,6 +480,10 @@ export class BridgeWebSocketServer {
443
480
  this.promptHistoryBackup = promptHistoryBackup ?? null;
444
481
  this.promptHistoryStore = promptHistoryStore ?? null;
445
482
  this.platform = platform ?? process.platform;
483
+ this.fileListMaxEntries = normalizePositiveLimit(fileListMaxEntries, positiveEnvInt("BRIDGE_FILE_LIST_MAX_ENTRIES", BridgeWebSocketServer.DEFAULT_FILE_LIST_MAX_ENTRIES));
484
+ this.fileListMaxBytes = normalizePositiveLimit(fileListMaxBytes, positiveEnvInt("BRIDGE_FILE_LIST_MAX_BYTES", BridgeWebSocketServer.DEFAULT_FILE_LIST_MAX_BYTES));
485
+ this.deltaBatchMs = normalizeNonNegativeLimit(deltaBatchMs, nonNegativeEnvInt("BRIDGE_DELTA_BATCH_MS", BridgeWebSocketServer.DEFAULT_DELTA_BATCH_MS));
486
+ this.deltaBatchMaxChars = normalizePositiveLimit(deltaBatchMaxChars, positiveEnvInt("BRIDGE_DELTA_BATCH_MAX_CHARS", BridgeWebSocketServer.DEFAULT_DELTA_BATCH_MAX_CHARS));
446
487
  this.archiveStore = new ArchiveStore();
447
488
  void this.debugTraceStore.init().catch((err) => {
448
489
  console.error("[ws] Failed to initialize debug trace store:", err);
@@ -529,6 +570,9 @@ export class BridgeWebSocketServer {
529
570
  }
530
571
  buildSessionCreatedMessage(params) {
531
572
  const { sessionId, provider, projectPath, session, permissionMode, executionMode, planMode, approvalsReviewer, codexPermissionsMode, sandboxMode, slashCommands, skills, skillMetadata, apps, appMetadata, plugins, pluginMetadata, sourceSessionId, } = params;
573
+ const derivedCodexSettings = provider === "codex"
574
+ ? withDerivedCodexPermissionsMode(session?.codexSettings)
575
+ : session?.codexSettings;
532
576
  const msg = {
533
577
  type: "system",
534
578
  subtype: "session_created",
@@ -540,15 +584,15 @@ export class BridgeWebSocketServer {
540
584
  permissionMode: permissionMode,
541
585
  }
542
586
  : {}),
543
- ...((approvalsReviewer ?? session?.codexSettings?.approvalsReviewer)
587
+ ...((approvalsReviewer ?? derivedCodexSettings?.approvalsReviewer)
544
588
  ? {
545
- approvalsReviewer: approvalsReviewer ?? session?.codexSettings?.approvalsReviewer,
589
+ approvalsReviewer: approvalsReviewer ?? derivedCodexSettings?.approvalsReviewer,
546
590
  }
547
591
  : {}),
548
- ...((codexPermissionsMode ?? session?.codexSettings?.codexPermissionsMode)
592
+ ...((codexPermissionsMode ?? derivedCodexSettings?.codexPermissionsMode)
549
593
  ? {
550
594
  codexPermissionsMode: (codexPermissionsMode ??
551
- session?.codexSettings?.codexPermissionsMode),
595
+ derivedCodexSettings?.codexPermissionsMode),
552
596
  }
553
597
  : {}),
554
598
  ...((executionMode ??
@@ -621,28 +665,28 @@ export class BridgeWebSocketServer {
621
665
  : {}),
622
666
  ...(sourceSessionId ? { sourceSessionId } : {}),
623
667
  };
624
- if (provider === "codex" && session?.codexSettings) {
625
- if (session.codexSettings.model !== undefined) {
626
- msg.model = session.codexSettings.model;
668
+ if (provider === "codex" && derivedCodexSettings) {
669
+ if (derivedCodexSettings.model !== undefined) {
670
+ msg.model = derivedCodexSettings.model;
627
671
  }
628
- if (session.codexSettings.approvalPolicy !== undefined) {
629
- msg.approvalPolicy = session.codexSettings.approvalPolicy;
672
+ if (derivedCodexSettings.approvalPolicy !== undefined) {
673
+ msg.approvalPolicy = derivedCodexSettings.approvalPolicy;
630
674
  }
631
- if (session.codexSettings.codexPermissionsMode !== undefined) {
632
- msg.codexPermissionsMode = session.codexSettings.codexPermissionsMode;
675
+ if (derivedCodexSettings.codexPermissionsMode !== undefined) {
676
+ msg.codexPermissionsMode = derivedCodexSettings.codexPermissionsMode;
633
677
  }
634
- if (session.codexSettings.modelReasoningEffort !== undefined) {
635
- msg.modelReasoningEffort = session.codexSettings.modelReasoningEffort;
678
+ if (derivedCodexSettings.modelReasoningEffort !== undefined) {
679
+ msg.modelReasoningEffort = derivedCodexSettings.modelReasoningEffort;
636
680
  }
637
- if (session.codexSettings.networkAccessEnabled !== undefined) {
638
- msg.networkAccessEnabled = session.codexSettings.networkAccessEnabled;
681
+ if (derivedCodexSettings.networkAccessEnabled !== undefined) {
682
+ msg.networkAccessEnabled = derivedCodexSettings.networkAccessEnabled;
639
683
  }
640
- if (session.codexSettings.webSearchMode !== undefined) {
641
- msg.webSearchMode = session.codexSettings.webSearchMode;
684
+ if (derivedCodexSettings.webSearchMode !== undefined) {
685
+ msg.webSearchMode = derivedCodexSettings.webSearchMode;
642
686
  }
643
- if (session.codexSettings.additionalWritableRoots !== undefined) {
687
+ if (derivedCodexSettings.additionalWritableRoots !== undefined) {
644
688
  msg.additionalWritableRoots =
645
- session.codexSettings.additionalWritableRoots;
689
+ derivedCodexSettings.additionalWritableRoots;
646
690
  }
647
691
  }
648
692
  return msg;
@@ -741,7 +785,7 @@ export class BridgeWebSocketServer {
741
785
  expectedUserTurns: targetOrdinal - 1,
742
786
  fallback: buildCodexHistoryPrefix(session, targetOrdinal - 1),
743
787
  });
744
- this.sessionManager.destroy(sessionId);
788
+ this.destroySession(sessionId);
745
789
  const newSessionId = this.sessionManager.create(projectPath, undefined, pastMessages, worktreeOpts, "codex", {
746
790
  ...(codexSettings ?? {}),
747
791
  threadId,
@@ -1345,7 +1389,9 @@ export class BridgeWebSocketServer {
1345
1389
  }
1346
1390
  close() {
1347
1391
  console.log("[ws] Shutting down...");
1392
+ this.flushAllDeltaBatches();
1348
1393
  this.sessionManager.destroyAll();
1394
+ this.flushAllDeltaBatches();
1349
1395
  stopManagedCodexAppServers();
1350
1396
  this.debugEvents.clear();
1351
1397
  this.wss.close();
@@ -1360,9 +1406,7 @@ export class BridgeWebSocketServer {
1360
1406
  }
1361
1407
  handleConnection(ws) {
1362
1408
  // Send session list and project history on connect
1363
- void this.refreshCodexProfiles();
1364
- void this.refreshCodexModels();
1365
- void this.refreshClaudeModels();
1409
+ this.refreshConnectionMetadata();
1366
1410
  this.sendSessionList(ws);
1367
1411
  const projects = this.projectHistory?.getProjects() ?? [];
1368
1412
  this.send(ws, { type: "project_history", projects });
@@ -1393,11 +1437,25 @@ export class BridgeWebSocketServer {
1393
1437
  });
1394
1438
  ws.on("close", () => {
1395
1439
  console.log("[ws] Client disconnected");
1440
+ this.discardClientDeltaBatches(ws);
1441
+ this.clearPendingClaudeResumeInputs(ws);
1396
1442
  });
1397
1443
  ws.on("error", (err) => {
1398
1444
  console.error("[ws] Client error:", err.message);
1399
1445
  });
1400
1446
  }
1447
+ refreshConnectionMetadata(now = Date.now()) {
1448
+ const lastRefreshAt = this.lastConnectMetadataRefreshAt;
1449
+ if (lastRefreshAt !== null &&
1450
+ now >= lastRefreshAt &&
1451
+ now - lastRefreshAt <
1452
+ BridgeWebSocketServer.CONNECT_METADATA_REFRESH_COOLDOWN_MS) {
1453
+ return;
1454
+ }
1455
+ this.lastConnectMetadataRefreshAt = now;
1456
+ void this.refreshCodexMetadata();
1457
+ void this.refreshClaudeModels();
1458
+ }
1401
1459
  async handleClientMessage(msg, ws) {
1402
1460
  if (msg.type === "client_capabilities") {
1403
1461
  this.clientSupportedServerMessages.set(ws, new Set(msg.supportedServerMessages ?? []));
@@ -1522,7 +1580,8 @@ export class BridgeWebSocketServer {
1522
1580
  ? codexPermissionSettings.sandboxMode
1523
1581
  : sandboxModeToInternal(msg.sandboxMode),
1524
1582
  model: msg.model,
1525
- modelReasoningEffort: msg.modelReasoningEffort ?? undefined,
1583
+ modelReasoningEffort: msg.modelReasoningEffort ??
1584
+ undefined,
1526
1585
  networkAccessEnabled: msg.networkAccessEnabled,
1527
1586
  webSearchMode: msg.webSearchMode ??
1528
1587
  undefined,
@@ -1577,7 +1636,7 @@ export class BridgeWebSocketServer {
1577
1636
  }));
1578
1637
  this.broadcastSessionList();
1579
1638
  if (provider === "codex") {
1580
- void this.refreshCodexModels(projectPath);
1639
+ void this.refreshCodexMetadata(projectPath);
1581
1640
  }
1582
1641
  else {
1583
1642
  void this.refreshClaudeModels(projectPath);
@@ -1616,6 +1675,13 @@ export class BridgeWebSocketServer {
1616
1675
  case "input": {
1617
1676
  const session = this.resolveSession(msg.sessionId);
1618
1677
  if (!session) {
1678
+ const pendingInputs = msg.sessionId
1679
+ ? this.pendingClaudeResumeInputs.get(ws)?.get(msg.sessionId)
1680
+ : undefined;
1681
+ if (pendingInputs) {
1682
+ pendingInputs.push(msg);
1683
+ return;
1684
+ }
1619
1685
  this.send(ws, {
1620
1686
  type: "error",
1621
1687
  message: "No active session. Send 'start' first.",
@@ -1814,11 +1880,20 @@ export class BridgeWebSocketServer {
1814
1880
  // Claude Code input path — enqueue first, then interrupt if busy
1815
1881
  const claudeProc = session.process;
1816
1882
  let wasQueued = false;
1883
+ let shouldInterrupt = false;
1817
1884
  if (images.length > 0) {
1818
1885
  console.log(`[ws] Sending message with ${images.length} inline Base64 image(s)`);
1819
- const result = claudeProc.sendInputWithImages(text, images);
1820
- wasQueued =
1821
- typeof result === "boolean" ? result : isAgentBusySnapshot;
1886
+ if (typeof claudeProc.dispatchInputWithImages === "function") {
1887
+ const result = claudeProc.dispatchInputWithImages(text, images);
1888
+ wasQueued = result.queued;
1889
+ shouldInterrupt = result.shouldInterrupt;
1890
+ }
1891
+ else {
1892
+ const result = claudeProc.sendInputWithImages(text, images);
1893
+ wasQueued =
1894
+ typeof result === "boolean" ? result : isAgentBusySnapshot;
1895
+ shouldInterrupt = wasQueued;
1896
+ }
1822
1897
  }
1823
1898
  // Legacy imageId mode (backward compatibility)
1824
1899
  else if (msg.imageId && this.galleryStore) {
@@ -1834,39 +1909,74 @@ export class BridgeWebSocketServer {
1834
1909
  .then((imageData) => {
1835
1910
  let queuedAfterResolve = false;
1836
1911
  if (imageData) {
1837
- const result = claudeProc.sendInputWithImages(text, [
1838
- imageData,
1839
- ]);
1840
- queuedAfterResolve =
1841
- typeof result === "boolean" ? result : isAgentBusySnapshot;
1912
+ if (typeof claudeProc.dispatchInputWithImages === "function") {
1913
+ const result = claudeProc.dispatchInputWithImages(text, [
1914
+ imageData,
1915
+ ]);
1916
+ queuedAfterResolve = result.queued;
1917
+ if (result.shouldInterrupt)
1918
+ claudeProc.interrupt();
1919
+ }
1920
+ else {
1921
+ const result = claudeProc.sendInputWithImages(text, [
1922
+ imageData,
1923
+ ]);
1924
+ queuedAfterResolve =
1925
+ typeof result === "boolean"
1926
+ ? result
1927
+ : isAgentBusySnapshot;
1928
+ if (queuedAfterResolve)
1929
+ claudeProc.interrupt();
1930
+ }
1842
1931
  }
1843
1932
  else {
1844
1933
  console.warn(`[ws] Image not found: ${msg.imageId}`);
1845
- const result = session.process.sendInput(text);
1846
- queuedAfterResolve =
1847
- typeof result === "boolean" ? result : isAgentBusySnapshot;
1848
- }
1849
- if (queuedAfterResolve) {
1850
- console.log(`[ws] Agent is busy — will queue input and interrupt current turn`);
1851
- claudeProc.interrupt();
1934
+ if (typeof claudeProc.dispatchInput === "function") {
1935
+ const result = claudeProc.dispatchInput(text);
1936
+ queuedAfterResolve = result.queued;
1937
+ if (result.shouldInterrupt)
1938
+ claudeProc.interrupt();
1939
+ }
1940
+ else {
1941
+ const result = session.process.sendInput(text);
1942
+ queuedAfterResolve =
1943
+ typeof result === "boolean"
1944
+ ? result
1945
+ : isAgentBusySnapshot;
1946
+ if (queuedAfterResolve)
1947
+ claudeProc.interrupt();
1948
+ }
1852
1949
  }
1853
1950
  })
1854
1951
  .catch((err) => {
1855
1952
  console.error(`[ws] Failed to load image: ${err}`);
1856
- const result = session.process.sendInput(text);
1857
- const queuedAfterResolve = typeof result === "boolean" ? result : isAgentBusySnapshot;
1858
- if (queuedAfterResolve) {
1859
- console.log(`[ws] Agent is busy — will queue input and interrupt current turn`);
1860
- claudeProc.interrupt();
1953
+ if (typeof claudeProc.dispatchInput === "function") {
1954
+ const result = claudeProc.dispatchInput(text);
1955
+ if (result.shouldInterrupt)
1956
+ claudeProc.interrupt();
1957
+ }
1958
+ else {
1959
+ const result = session.process.sendInput(text);
1960
+ const queuedAfterResolve = typeof result === "boolean" ? result : isAgentBusySnapshot;
1961
+ if (queuedAfterResolve)
1962
+ claudeProc.interrupt();
1861
1963
  }
1862
1964
  });
1863
1965
  break;
1864
1966
  }
1865
1967
  // Text-only message
1866
1968
  else {
1867
- const result = session.process.sendInput(text);
1868
- wasQueued =
1869
- typeof result === "boolean" ? result : isAgentBusySnapshot;
1969
+ if (typeof claudeProc.dispatchInput === "function") {
1970
+ const result = claudeProc.dispatchInput(text);
1971
+ wasQueued = result.queued;
1972
+ shouldInterrupt = result.shouldInterrupt;
1973
+ }
1974
+ else {
1975
+ const result = session.process.sendInput(text);
1976
+ wasQueued =
1977
+ typeof result === "boolean" ? result : isAgentBusySnapshot;
1978
+ shouldInterrupt = wasQueued;
1979
+ }
1870
1980
  }
1871
1981
  // Acknowledge receipt so the client can mark the message state.
1872
1982
  // queued=true means the input was enqueued instead of being consumed
@@ -1878,7 +1988,7 @@ export class BridgeWebSocketServer {
1878
1988
  acceptedSeq,
1879
1989
  queued: wasQueued,
1880
1990
  });
1881
- if (wasQueued) {
1991
+ if (shouldInterrupt) {
1882
1992
  console.log(`[ws] Agent is busy — will queue input and interrupt current turn`);
1883
1993
  claudeProc.interrupt();
1884
1994
  }
@@ -2059,7 +2169,7 @@ export class BridgeWebSocketServer {
2059
2169
  : currentSandboxMode;
2060
2170
  const newPermissionsMode = codexPermissionSettings?.codexPermissionsMode ??
2061
2171
  (collaborationOnlyChange ? currentPermissionsMode : undefined) ??
2062
- inferCodexPermissionsMode({
2172
+ deriveCodexPermissionsMode({
2063
2173
  approvalPolicy: newApproval,
2064
2174
  approvalsReviewer: codexPermissionSettings?.approvalsReviewer ??
2065
2175
  msg.approvalsReviewer ??
@@ -2133,7 +2243,7 @@ export class BridgeWebSocketServer {
2133
2243
  const worktreePath = session.worktreePath;
2134
2244
  const worktreeBranch = session.worktreeBranch;
2135
2245
  const sessionName = session.name;
2136
- this.sessionManager.destroy(oldSessionId);
2246
+ this.destroySession(oldSessionId);
2137
2247
  console.log(`[ws] Permission mode change: destroyed session ${oldSessionId}`);
2138
2248
  const hasUserMessages = session.history?.some((m) => m.type === "user_input" || m.type === "assistant") ||
2139
2249
  (session.pastMessages && session.pastMessages.length > 0);
@@ -2364,7 +2474,7 @@ export class BridgeWebSocketServer {
2364
2474
  const sessionName = session.name;
2365
2475
  const permissionMode = session.process.permissionMode;
2366
2476
  const model = session.process.model;
2367
- this.sessionManager.destroy(oldSessionId);
2477
+ this.destroySession(oldSessionId);
2368
2478
  console.log(`[ws] Claude sandbox change: destroyed session ${oldSessionId}`);
2369
2479
  const newId = this.sessionManager.create(projectPath, {
2370
2480
  sessionId: claudeSessionId,
@@ -2420,7 +2530,7 @@ export class BridgeWebSocketServer {
2420
2530
  const executionMode = oldSettings.approvalPolicy === "never" ? "fullAccess" : "default";
2421
2531
  const planMode = collaborationMode === "plan";
2422
2532
  const legacyPermissionMode = modesToLegacyPermissionMode("codex", executionMode, planMode);
2423
- this.sessionManager.destroy(oldSessionId);
2533
+ this.destroySession(oldSessionId);
2424
2534
  console.log(`[ws] Sandbox mode change: destroyed session ${oldSessionId}`);
2425
2535
  // Check if the user actually exchanged messages in this session.
2426
2536
  // session.history always contains system events (init, status, etc.)
@@ -2557,7 +2667,7 @@ export class BridgeWebSocketServer {
2557
2667
  const permissionMode = sdkProc.permissionMode;
2558
2668
  const worktreePath = session.worktreePath;
2559
2669
  const worktreeBranch = session.worktreeBranch;
2560
- this.sessionManager.destroy(sessionId);
2670
+ this.destroySession(sessionId);
2561
2671
  console.log(`[ws] Clear context: destroyed session ${sessionId}`);
2562
2672
  const newId = this.sessionManager.create(projectPath, {
2563
2673
  ...(claudeSessionId
@@ -2642,7 +2752,7 @@ export class BridgeWebSocketServer {
2642
2752
  subtype: "stopped",
2643
2753
  sessionId: session.claudeSessionId,
2644
2754
  });
2645
- this.sessionManager.destroy(msg.sessionId);
2755
+ this.destroySession(msg.sessionId);
2646
2756
  this.recordDebugEvent(msg.sessionId, {
2647
2757
  direction: "internal",
2648
2758
  channel: "bridge",
@@ -3020,7 +3130,8 @@ export class BridgeWebSocketServer {
3020
3130
  ? codexPermissionSettings.sandboxMode
3021
3131
  : sandboxModeToInternal(msg.sandboxMode),
3022
3132
  model: msg.model,
3023
- modelReasoningEffort: msg.modelReasoningEffort ?? undefined,
3133
+ modelReasoningEffort: msg.modelReasoningEffort ??
3134
+ undefined,
3024
3135
  networkAccessEnabled: msg.networkAccessEnabled,
3025
3136
  webSearchMode: msg.webSearchMode ??
3026
3137
  undefined,
@@ -3065,6 +3176,19 @@ export class BridgeWebSocketServer {
3065
3176
  }
3066
3177
  const claudeSessionId = sessionRefId;
3067
3178
  const cached = this.sessionManager.getCachedCommands(resumeProjectPath);
3179
+ let pendingResumes = this.pendingClaudeResumeInputs.get(ws);
3180
+ if (!pendingResumes) {
3181
+ pendingResumes = new Map();
3182
+ this.pendingClaudeResumeInputs.set(ws, pendingResumes);
3183
+ }
3184
+ if (pendingResumes.has(claudeSessionId)) {
3185
+ this.send(ws, {
3186
+ type: "error",
3187
+ message: `Session resume already in progress: ${claudeSessionId}`,
3188
+ });
3189
+ break;
3190
+ }
3191
+ pendingResumes.set(claudeSessionId, []);
3068
3192
  // Look up worktree mapping for this Claude session
3069
3193
  const wtMapping = this.worktreeStore.get(claudeSessionId);
3070
3194
  let worktreeOpts;
@@ -3106,7 +3230,7 @@ export class BridgeWebSocketServer {
3106
3230
  worktreeOptions: worktreeOpts,
3107
3231
  });
3108
3232
  const createdSession = this.sessionManager.get(sessionId);
3109
- void this.loadAndSetSessionName(createdSession, "claude", resumeProjectPath, claudeSessionId).then(() => {
3233
+ const finishResume = () => {
3110
3234
  this.send(ws, {
3111
3235
  ...this.buildSessionCreatedMessage({
3112
3236
  sessionId,
@@ -3137,10 +3261,19 @@ export class BridgeWebSocketServer {
3137
3261
  }),
3138
3262
  claudeSessionId,
3139
3263
  });
3264
+ const queuedInputs = pendingResumes.get(claudeSessionId) ?? [];
3265
+ pendingResumes.delete(claudeSessionId);
3266
+ for (const input of queuedInputs) {
3267
+ void this.handleClientMessage({ ...input, sessionId }, ws);
3268
+ }
3140
3269
  this.broadcastSessionList();
3141
3270
  if (autoFallbackUsed) {
3142
3271
  this.sendTip(ws, sessionId, "auto_mode_fallback_default", createdSession);
3143
3272
  }
3273
+ };
3274
+ void this.loadAndSetSessionName(createdSession, "claude", resumeProjectPath, claudeSessionId).then(finishResume, (err) => {
3275
+ console.error("[ws] Failed to load resumed session name:", err);
3276
+ finishResume();
3144
3277
  });
3145
3278
  this.debugEvents.set(sessionId, []);
3146
3279
  this.recordDebugEvent(sessionId, {
@@ -3152,6 +3285,18 @@ export class BridgeWebSocketServer {
3152
3285
  this.projectHistory?.addProject(resumeProjectPath);
3153
3286
  })
3154
3287
  .catch((err) => {
3288
+ const queuedInputs = pendingResumes.get(claudeSessionId) ?? [];
3289
+ pendingResumes.delete(claudeSessionId);
3290
+ for (const input of queuedInputs) {
3291
+ if (input.clientMessageId) {
3292
+ this.send(ws, {
3293
+ type: "input_rejected",
3294
+ sessionId: claudeSessionId,
3295
+ clientMessageId: input.clientMessageId,
3296
+ reason: "Session resume failed",
3297
+ });
3298
+ }
3299
+ }
3155
3300
  this.send(ws, {
3156
3301
  type: "error",
3157
3302
  message: `Failed to load session history: ${err}`,
@@ -3389,8 +3534,16 @@ export class BridgeWebSocketServer {
3389
3534
  }
3390
3535
  void (async () => {
3391
3536
  try {
3392
- const files = await listProjectFilesAndDirectories(msg.projectPath);
3393
- this.send(ws, { type: "file_list", files });
3537
+ const result = await listProjectFilesAndDirectoriesForClient(msg.projectPath, {
3538
+ maxEntries: this.fileListMaxEntries,
3539
+ maxBytes: this.fileListMaxBytes,
3540
+ });
3541
+ this.send(ws, {
3542
+ type: "file_list",
3543
+ files: result.files,
3544
+ totalFiles: result.totalFiles,
3545
+ truncated: result.truncated,
3546
+ });
3394
3547
  }
3395
3548
  catch (err) {
3396
3549
  const message = err instanceof Error ? err.message : String(err);
@@ -4061,6 +4214,7 @@ export class BridgeWebSocketServer {
4061
4214
  else if (msg.mode === "conversation") {
4062
4215
  // Conversation-only rewind: restart session at the target UUID
4063
4216
  try {
4217
+ this.flushSessionDeltaBatches(msg.sessionId);
4064
4218
  this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
4065
4219
  this.send(ws, {
4066
4220
  type: "rewind_result",
@@ -4102,6 +4256,7 @@ export class BridgeWebSocketServer {
4102
4256
  return;
4103
4257
  }
4104
4258
  try {
4259
+ this.flushSessionDeltaBatches(msg.sessionId);
4105
4260
  this.sessionManager.rewindConversation(msg.sessionId, msg.targetUuid, (newSessionId) => {
4106
4261
  this.send(ws, {
4107
4262
  type: "rewind_result",
@@ -4450,6 +4605,10 @@ export class BridgeWebSocketServer {
4450
4605
  }
4451
4606
  }
4452
4607
  }
4608
+ clearPendingClaudeResumeInputs(ws) {
4609
+ this.pendingClaudeResumeInputs.get(ws)?.clear();
4610
+ this.pendingClaudeResumeInputs.delete(ws);
4611
+ }
4453
4612
  /**
4454
4613
  * Load the saved session name from CLI storage and set it on the SessionInfo.
4455
4614
  * Called after SessionManager.create() so that session_created carries the name.
@@ -4592,6 +4751,121 @@ export class BridgeWebSocketServer {
4592
4751
  });
4593
4752
  }
4594
4753
  broadcastSessionMessage(sessionId, msg, exclude) {
4754
+ if (this.shouldBatchDelta(msg, exclude)) {
4755
+ this.trackSessionMessage(sessionId, msg);
4756
+ const chunks = this.splitDeltaText(msg.text);
4757
+ for (const client of this.wss.clients) {
4758
+ if (client.readyState !== WebSocket.OPEN)
4759
+ continue;
4760
+ if (!this.shouldSendToClient(client, msg))
4761
+ continue;
4762
+ this.queueDeltaForClient(client, sessionId, msg.type, chunks);
4763
+ }
4764
+ return;
4765
+ }
4766
+ this.flushSessionDeltaBatches(sessionId);
4767
+ this.trackSessionMessage(sessionId, msg);
4768
+ this.broadcastSessionMessageNow(sessionId, msg, exclude);
4769
+ }
4770
+ shouldBatchDelta(msg, exclude) {
4771
+ if (this.deltaBatchMs === 0 || exclude)
4772
+ return false;
4773
+ return msg.type === "stream_delta" || msg.type === "thinking_delta";
4774
+ }
4775
+ queueDeltaForClient(client, sessionId, type, chunks) {
4776
+ for (const chunk of chunks) {
4777
+ let batch = this.deltaBatches.get(client)?.get(sessionId);
4778
+ if (batch &&
4779
+ batch.charCount > 0 &&
4780
+ batch.charCount + chunk.charCount > this.deltaBatchMaxChars) {
4781
+ this.flushClientDeltaBatch(client, sessionId);
4782
+ batch = undefined;
4783
+ }
4784
+ if (!batch) {
4785
+ const clientBatches = this.deltaBatches.get(client) ?? new Map();
4786
+ batch = {
4787
+ messages: [],
4788
+ charCount: 0,
4789
+ timer: setTimeout(() => {
4790
+ this.flushClientDeltaBatch(client, sessionId);
4791
+ }, this.deltaBatchMs),
4792
+ };
4793
+ clientBatches.set(sessionId, batch);
4794
+ this.deltaBatches.set(client, clientBatches);
4795
+ }
4796
+ const last = batch.messages.at(-1);
4797
+ if (last?.type === type) {
4798
+ last.text += chunk.text;
4799
+ }
4800
+ else {
4801
+ batch.messages.push({ type, text: chunk.text });
4802
+ }
4803
+ batch.charCount += chunk.charCount;
4804
+ if (batch.charCount >= this.deltaBatchMaxChars) {
4805
+ this.flushClientDeltaBatch(client, sessionId);
4806
+ }
4807
+ }
4808
+ }
4809
+ splitDeltaText(text) {
4810
+ if (text.length === 0)
4811
+ return [{ text, charCount: 0 }];
4812
+ const chunks = [];
4813
+ let chars = [];
4814
+ let charCount = 0;
4815
+ for (const char of text) {
4816
+ chars.push(char);
4817
+ charCount += 1;
4818
+ if (charCount >= this.deltaBatchMaxChars) {
4819
+ chunks.push({ text: chars.join(""), charCount });
4820
+ chars = [];
4821
+ charCount = 0;
4822
+ }
4823
+ }
4824
+ if (chars.length > 0) {
4825
+ chunks.push({ text: chars.join(""), charCount });
4826
+ }
4827
+ return chunks;
4828
+ }
4829
+ flushSessionDeltaBatches(sessionId) {
4830
+ for (const client of Array.from(this.deltaBatches.keys())) {
4831
+ this.flushClientDeltaBatch(client, sessionId);
4832
+ }
4833
+ }
4834
+ flushAllDeltaBatches() {
4835
+ for (const [client, batches] of Array.from(this.deltaBatches.entries())) {
4836
+ for (const sessionId of Array.from(batches.keys())) {
4837
+ this.flushClientDeltaBatch(client, sessionId);
4838
+ }
4839
+ }
4840
+ }
4841
+ flushClientDeltaBatch(client, sessionId) {
4842
+ const clientBatches = this.deltaBatches.get(client);
4843
+ const batch = clientBatches?.get(sessionId);
4844
+ if (!batch)
4845
+ return;
4846
+ clearTimeout(batch.timer);
4847
+ clientBatches?.delete(sessionId);
4848
+ if (clientBatches?.size === 0)
4849
+ this.deltaBatches.delete(client);
4850
+ if (client.readyState !== WebSocket.OPEN)
4851
+ return;
4852
+ for (const msg of batch.messages) {
4853
+ client.send(JSON.stringify({ ...msg, sessionId }));
4854
+ }
4855
+ }
4856
+ discardClientDeltaBatches(client) {
4857
+ const batches = this.deltaBatches.get(client);
4858
+ if (!batches)
4859
+ return;
4860
+ for (const batch of batches.values())
4861
+ clearTimeout(batch.timer);
4862
+ this.deltaBatches.delete(client);
4863
+ }
4864
+ destroySession(sessionId) {
4865
+ this.flushSessionDeltaBatches(sessionId);
4866
+ this.sessionManager.destroy(sessionId);
4867
+ }
4868
+ trackSessionMessage(sessionId, msg) {
4595
4869
  this.maybeSendPushNotification(sessionId, msg);
4596
4870
  this.recordDebugEvent(sessionId, {
4597
4871
  direction: "outgoing",
@@ -4614,7 +4888,8 @@ export class BridgeWebSocketServer {
4614
4888
  });
4615
4889
  }
4616
4890
  }
4617
- // Wrap the message with sessionId
4891
+ }
4892
+ broadcastSessionMessageNow(sessionId, msg, exclude) {
4618
4893
  const data = JSON.stringify({ ...msg, sessionId });
4619
4894
  for (const client of this.wss.clients) {
4620
4895
  if (client === exclude)
@@ -4691,28 +4966,58 @@ export class BridgeWebSocketServer {
4691
4966
  });
4692
4967
  }
4693
4968
  }
4694
- async refreshCodexModels(projectPath) {
4695
- if (this.codexModelsRequest)
4696
- return this.codexModelsRequest;
4697
- this.codexModelsRequest = this.loadCodexModels(projectPath)
4698
- .then((models) => {
4699
- if (models.length > 0) {
4700
- this.applyCodexModels(models);
4969
+ async refreshCodexMetadata(projectPath) {
4970
+ if (this.codexMetadataRequest) {
4971
+ if (projectPath) {
4972
+ return this.codexMetadataRequest.then(() => this.refreshCodexMetadata(projectPath));
4701
4973
  }
4702
- else {
4703
- this.applyFallbackCodexModels();
4704
- }
4705
- this.broadcastSessionList();
4706
- })
4974
+ return this.codexMetadataRequest;
4975
+ }
4976
+ this.codexMetadataRequest = this.loadAndApplyCodexMetadata(projectPath)
4707
4977
  .catch((err) => {
4708
- console.warn(`[ws] Failed to load Codex models: ${err}`);
4978
+ console.warn(`[ws] Failed to load Codex metadata: ${err}`);
4979
+ this.codexProfiles = [];
4980
+ this.defaultCodexProfile = undefined;
4709
4981
  this.applyFallbackCodexModels();
4710
4982
  this.broadcastSessionList();
4711
4983
  })
4712
4984
  .finally(() => {
4713
- this.codexModelsRequest = null;
4985
+ this.codexMetadataRequest = null;
4714
4986
  });
4715
- return this.codexModelsRequest;
4987
+ return this.codexMetadataRequest;
4988
+ }
4989
+ async loadAndApplyCodexMetadata(projectPath) {
4990
+ const activeProcess = this.getActiveCodexProcess();
4991
+ const codexProcess = activeProcess ?? (await this.createStandaloneCodexProcess(projectPath));
4992
+ try {
4993
+ const [profileResult, modelResult] = await Promise.allSettled([
4994
+ codexProcess.readProfileConfig(projectPath),
4995
+ this.readCodexModels(codexProcess),
4996
+ ]);
4997
+ if (profileResult.status === "fulfilled") {
4998
+ this.codexProfiles = profileResult.value.profiles;
4999
+ this.defaultCodexProfile = profileResult.value.defaultProfile;
5000
+ }
5001
+ else {
5002
+ console.warn(`[ws] Failed to load Codex profiles: ${profileResult.reason}`);
5003
+ this.codexProfiles = [];
5004
+ this.defaultCodexProfile = undefined;
5005
+ }
5006
+ if (modelResult.status === "fulfilled" && modelResult.value.length > 0) {
5007
+ this.applyCodexModels(modelResult.value);
5008
+ }
5009
+ else {
5010
+ if (modelResult.status === "rejected") {
5011
+ console.warn(`[ws] Failed to load Codex models: ${modelResult.reason}`);
5012
+ }
5013
+ this.applyFallbackCodexModels();
5014
+ }
5015
+ this.broadcastSessionList();
5016
+ }
5017
+ finally {
5018
+ if (!activeProcess)
5019
+ codexProcess.stop();
5020
+ }
4716
5021
  }
4717
5022
  async refreshClaudeModels(projectPath) {
4718
5023
  if (this.claudeModelsRequest)
@@ -4756,28 +5061,18 @@ export class BridgeWebSocketServer {
4756
5061
  this.claudeModels = FALLBACK_CLAUDE_MODELS;
4757
5062
  this.claudeModelEfforts = { ...FALLBACK_CLAUDE_MODEL_EFFORTS };
4758
5063
  }
4759
- async loadCodexModels(projectPath) {
4760
- const process = this.getActiveCodexProcess() ??
4761
- (await this.createStandaloneCodexProcess(projectPath));
4762
- const isStandalone = process !== this.getActiveCodexProcess();
4763
- try {
4764
- const modelSource = process;
4765
- if (typeof modelSource.listAvailableModelMetadata === "function") {
4766
- return await modelSource.listAvailableModelMetadata();
4767
- }
4768
- const models = typeof modelSource.listAvailableModels === "function"
4769
- ? await modelSource.listAvailableModels()
4770
- : [];
4771
- return models.map((model) => ({
4772
- model,
4773
- supportedReasoningEfforts: FALLBACK_CODEX_REASONING_EFFORTS,
4774
- }));
4775
- }
4776
- finally {
4777
- if (isStandalone) {
4778
- process.stop();
4779
- }
5064
+ async readCodexModels(codexProcess) {
5065
+ const modelSource = codexProcess;
5066
+ if (typeof modelSource.listAvailableModelMetadata === "function") {
5067
+ return modelSource.listAvailableModelMetadata();
4780
5068
  }
5069
+ const models = typeof modelSource.listAvailableModels === "function"
5070
+ ? await modelSource.listAvailableModels()
5071
+ : [];
5072
+ return models.map((model) => ({
5073
+ model,
5074
+ supportedReasoningEfforts: fallbackCodexReasoningEfforts(model),
5075
+ }));
4781
5076
  }
4782
5077
  applyCodexModels(models) {
4783
5078
  this.codexModels = models.map((model) => model.model);
@@ -4785,35 +5080,16 @@ export class BridgeWebSocketServer {
4785
5080
  model.model,
4786
5081
  model.supportedReasoningEfforts.length > 0
4787
5082
  ? model.supportedReasoningEfforts
4788
- : FALLBACK_CODEX_REASONING_EFFORTS,
5083
+ : fallbackCodexReasoningEfforts(model.model),
4789
5084
  ]));
4790
5085
  }
4791
5086
  applyFallbackCodexModels() {
4792
5087
  this.codexModels = FALLBACK_CODEX_MODELS;
4793
5088
  this.codexModelReasoningEfforts = Object.fromEntries(FALLBACK_CODEX_MODELS.map((model) => [
4794
5089
  model,
4795
- FALLBACK_CODEX_REASONING_EFFORTS,
5090
+ fallbackCodexReasoningEfforts(model),
4796
5091
  ]));
4797
5092
  }
4798
- async refreshCodexProfiles(projectPath) {
4799
- if (this.codexProfilesRequest)
4800
- return this.codexProfilesRequest;
4801
- this.codexProfilesRequest = this.loadCodexProfiles(projectPath)
4802
- .then(({ profiles, defaultProfile }) => {
4803
- this.codexProfiles = profiles;
4804
- this.defaultCodexProfile = defaultProfile;
4805
- this.broadcastSessionList();
4806
- })
4807
- .catch((err) => {
4808
- console.warn(`[ws] Failed to load Codex profiles: ${err}`);
4809
- this.codexProfiles = [];
4810
- this.defaultCodexProfile = undefined;
4811
- })
4812
- .finally(() => {
4813
- this.codexProfilesRequest = null;
4814
- });
4815
- return this.codexProfilesRequest;
4816
- }
4817
5093
  async loadCodexProfiles(projectPath) {
4818
5094
  const process = this.getActiveCodexProcess() ??
4819
5095
  (await this.createStandaloneCodexProcess(projectPath));
@@ -4925,8 +5201,14 @@ export class BridgeWebSocketServer {
4925
5201
  }
4926
5202
  async createStandaloneCodexProcess(projectPath) {
4927
5203
  const proc = new CodexProcess();
4928
- await proc.initializeOnly(projectPath ?? process.cwd());
4929
- return proc;
5204
+ try {
5205
+ await proc.initializeOnly(projectPath ?? process.cwd());
5206
+ return proc;
5207
+ }
5208
+ catch (err) {
5209
+ proc.stop();
5210
+ throw err;
5211
+ }
4930
5212
  }
4931
5213
  /** Extract a short project label from the full projectPath (last directory name). */
4932
5214
  projectLabel(sessionId) {