@ccpocket/bridge 1.68.2 → 1.69.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.
@@ -52,6 +52,8 @@ export declare class BridgeWebSocketServer {
52
52
  private archiveStore;
53
53
  private codexProfiles;
54
54
  private defaultCodexProfile;
55
+ private codexAutoReviewDisabled;
56
+ private codexAutoReviewPolicyLoaded;
55
57
  private codexMetadataRequest;
56
58
  private lastConnectMetadataRefreshAt;
57
59
  private claudeModels;
@@ -161,12 +163,14 @@ export declare class BridgeWebSocketServer {
161
163
  private applyClaudeModels;
162
164
  private applyFallbackClaudeModels;
163
165
  private readCodexModels;
166
+ private readCodexAutoReviewDisabled;
164
167
  private applyCodexModels;
165
168
  private applyFallbackCodexModels;
166
169
  private loadCodexProfiles;
167
170
  private validateCodexProfile;
168
171
  private resolveCodexResumeProfile;
169
172
  private getActiveCodexProcess;
173
+ private withCodexAutoReviewPolicy;
170
174
  private getActiveClaudeProcess;
171
175
  private listRecentCodexThreads;
172
176
  private createStandaloneCodexProcess;
@@ -180,6 +184,7 @@ export declare class BridgeWebSocketServer {
180
184
  private sessionLabel;
181
185
  private maybeSendPushNotification;
182
186
  private broadcast;
187
+ private prepareServerMessageForClient;
183
188
  private shouldSendToClient;
184
189
  private hasInputConflictSince;
185
190
  private sendCodexQueueState;
package/dist/websocket.js CHANGED
@@ -7,7 +7,7 @@ import { promisify } from "node:util";
7
7
  import { WebSocketServer, WebSocket } from "ws";
8
8
  import { SessionManager, MAX_HISTORY_PER_SESSION, } from "./session.js";
9
9
  import { SdkProcess, listAvailableClaudeModels, } from "./sdk-process.js";
10
- import { CodexProcess, } from "./codex-process.js";
10
+ import { codexErrorMessage, CodexRpcError, CodexProcess, } from "./codex-process.js";
11
11
  import { stopManagedCodexAppServers } from "./codex-transport.js";
12
12
  import { parseClientMessage, } from "./parser.js";
13
13
  import { getAllRecentSessions, getCodexSessionHistory, getSessionHistory, codexUserTurnUuid, codexThreadToSessionHistory, findSessionsByClaudeIds, extractMessageImages, getClaudeSessionName, getCodexSessionIndexMetadata, loadCodexSessionNames, renameClaudeSession, renameCodexSession, saveCodexSessionProfile, } from "./sessions-index.js";
@@ -90,8 +90,12 @@ const CODEX_USER_TURN_UUID_RE = /^codex:user-turn:(\d+)$/;
90
90
  const OPT_IN_SERVER_MESSAGES = new Set([
91
91
  "conversation_queue",
92
92
  "goal_state",
93
+ "guardian_approval",
93
94
  "prompt_history_status",
94
95
  ]);
96
+ function isRecord(value) {
97
+ return typeof value === "object" && value !== null && !Array.isArray(value);
98
+ }
95
99
  function parseCodexUserTurnOrdinal(uuid) {
96
100
  if (!uuid)
97
101
  return null;
@@ -239,6 +243,9 @@ function normalizeCodexApprovalPolicy(value) {
239
243
  return "on-request";
240
244
  }
241
245
  }
246
+ function isCodexAutoReviewApprovalsReviewer(value) {
247
+ return value === "auto_review" || value === "guardian_subagent";
248
+ }
242
249
  function sanitizeCodexModel(model) {
243
250
  if (typeof model !== "string")
244
251
  return undefined;
@@ -453,6 +460,8 @@ export class BridgeWebSocketServer {
453
460
  archiveStore;
454
461
  codexProfiles = [];
455
462
  defaultCodexProfile;
463
+ codexAutoReviewDisabled = false;
464
+ codexAutoReviewPolicyLoaded = false;
456
465
  codexMetadataRequest = null;
457
466
  lastConnectMetadataRefreshAt = null;
458
467
  claudeModels = FALLBACK_CLAUDE_MODELS;
@@ -805,10 +814,10 @@ export class BridgeWebSocketServer {
805
814
  fallback: buildCodexHistoryPrefix(session, targetOrdinal - 1),
806
815
  });
807
816
  this.destroySession(sessionId);
808
- const newSessionId = this.sessionManager.create(projectPath, undefined, pastMessages, worktreeOpts, "codex", {
817
+ const newSessionId = this.sessionManager.create(projectPath, undefined, pastMessages, worktreeOpts, "codex", this.withCodexAutoReviewPolicy({
809
818
  ...(codexSettings ?? {}),
810
819
  threadId,
811
- });
820
+ }));
812
821
  const newSession = this.sessionManager.get(newSessionId);
813
822
  this.send(ws, {
814
823
  type: "rewind_result",
@@ -820,8 +829,8 @@ export class BridgeWebSocketServer {
820
829
  provider: "codex",
821
830
  projectPath,
822
831
  session: newSession,
823
- approvalsReviewer: codexSettings?.approvalsReviewer,
824
- sandboxMode: codexSettings?.sandboxMode,
832
+ approvalsReviewer: newSession?.codexSettings?.approvalsReviewer,
833
+ sandboxMode: newSession?.codexSettings?.sandboxMode,
825
834
  sourceSessionId: sessionId,
826
835
  }));
827
836
  this.sendSessionList(ws);
@@ -892,18 +901,18 @@ export class BridgeWebSocketServer {
892
901
  expectedUserTurns: targetOrdinal,
893
902
  fallback: buildCodexHistoryPrefix(session, targetOrdinal),
894
903
  });
895
- const newSessionId = this.sessionManager.create(projectPath, undefined, pastMessages, worktreeOpts, "codex", {
904
+ const newSessionId = this.sessionManager.create(projectPath, undefined, pastMessages, worktreeOpts, "codex", this.withCodexAutoReviewPolicy({
896
905
  ...(codexSettings ?? {}),
897
906
  threadId: forkedThreadId,
898
- });
907
+ }));
899
908
  const newSession = this.sessionManager.get(newSessionId);
900
909
  this.send(ws, this.buildSessionCreatedMessage({
901
910
  sessionId: newSessionId,
902
911
  provider: "codex",
903
912
  projectPath,
904
913
  session: newSession,
905
- approvalsReviewer: codexSettings?.approvalsReviewer,
906
- sandboxMode: codexSettings?.sandboxMode,
914
+ approvalsReviewer: newSession?.codexSettings?.approvalsReviewer,
915
+ sandboxMode: newSession?.codexSettings?.sandboxMode,
907
916
  sourceSessionId: sessionId,
908
917
  }));
909
918
  this.sendSessionList(ws);
@@ -1641,9 +1650,13 @@ export class BridgeWebSocketServer {
1641
1650
  }
1642
1651
  try {
1643
1652
  const provider = msg.provider ?? "claude";
1644
- const requestedCodexPermissionsMode = provider === "codex"
1653
+ const normalizedCodexPermissionsMode = provider === "codex"
1645
1654
  ? normalizeCodexPermissionsMode(msg.codexPermissionsMode)
1646
1655
  : undefined;
1656
+ const requestedCodexPermissionsMode = this.codexAutoReviewDisabled &&
1657
+ normalizedCodexPermissionsMode === "autoReview"
1658
+ ? "default"
1659
+ : normalizedCodexPermissionsMode;
1647
1660
  const codexPermissionSettings = requestedCodexPermissionsMode
1648
1661
  ? codexSettingsFromPermissionsMode(requestedCodexPermissionsMode)
1649
1662
  : undefined;
@@ -1719,7 +1732,7 @@ export class BridgeWebSocketServer {
1719
1732
  useWorktree: msg.useWorktree,
1720
1733
  worktreeBranch: msg.worktreeBranch,
1721
1734
  existingWorktreePath: msg.existingWorktreePath,
1722
- }, provider, {
1735
+ }, provider, this.withCodexAutoReviewPolicy({
1723
1736
  profile: msg.profile,
1724
1737
  approvalPolicy: codexPermissionSettings
1725
1738
  ? codexPermissionSettings.approvalPolicy
@@ -1727,9 +1740,8 @@ export class BridgeWebSocketServer {
1727
1740
  normalizeCodexApprovalPolicy(executionMode === "fullAccess"
1728
1741
  ? "never"
1729
1742
  : "on-request")),
1730
- approvalsReviewer: codexPermissionSettings
1731
- ? codexPermissionSettings.approvalsReviewer
1732
- : msg.approvalsReviewer,
1743
+ approvalsReviewer: codexPermissionSettings?.approvalsReviewer ??
1744
+ msg.approvalsReviewer,
1733
1745
  codexPermissionsMode: codexPermissionSettings?.codexPermissionsMode,
1734
1746
  sandboxMode: codexPermissionSettings
1735
1747
  ? codexPermissionSettings.sandboxMode
@@ -1746,7 +1758,7 @@ export class BridgeWebSocketServer {
1746
1758
  collaborationMode: planMode
1747
1759
  ? "plan"
1748
1760
  : "default",
1749
- }),
1761
+ })),
1750
1762
  permissionMode: claudePermissionMode,
1751
1763
  executionMode,
1752
1764
  planMode,
@@ -2286,7 +2298,15 @@ export class BridgeWebSocketServer {
2286
2298
  // Permission mode for Codex requires a session restart (like sandbox mode).
2287
2299
  // approvalPolicy and collaborationMode are thread-level settings that
2288
2300
  // only take effect reliably at thread/start or thread/resume time.
2289
- const requestedCodexPermissionsMode = normalizeCodexPermissionsMode(msg.codexPermissionsMode);
2301
+ const normalizedCodexPermissionsMode = normalizeCodexPermissionsMode(msg.codexPermissionsMode);
2302
+ const requestedCodexPermissionsMode = this.codexAutoReviewDisabled &&
2303
+ normalizedCodexPermissionsMode === "autoReview"
2304
+ ? "default"
2305
+ : normalizedCodexPermissionsMode;
2306
+ const requestedApprovalsReviewer = this.codexAutoReviewDisabled &&
2307
+ isCodexAutoReviewApprovalsReviewer(msg.approvalsReviewer)
2308
+ ? "user"
2309
+ : msg.approvalsReviewer;
2290
2310
  const codexPermissionSettings = requestedCodexPermissionsMode
2291
2311
  ? codexSettingsFromPermissionsMode(requestedCodexPermissionsMode)
2292
2312
  : undefined;
@@ -2324,23 +2344,28 @@ export class BridgeWebSocketServer {
2324
2344
  const newSandboxMode = codexPermissionSettings
2325
2345
  ? codexPermissionSettings.sandboxMode
2326
2346
  : currentSandboxMode;
2327
- const newPermissionsMode = codexPermissionSettings?.codexPermissionsMode ??
2347
+ const configuredReviewer = requestedCodexPermissionsMode === "custom"
2348
+ ? undefined
2349
+ : (codexPermissionSettings?.approvalsReviewer ??
2350
+ requestedApprovalsReviewer ??
2351
+ currentReviewer);
2352
+ const newReviewer = this.codexAutoReviewDisabled
2353
+ ? "user"
2354
+ : configuredReviewer;
2355
+ const derivedPermissionsMode = codexPermissionSettings?.codexPermissionsMode ??
2328
2356
  (collaborationOnlyChange ? currentPermissionsMode : undefined) ??
2329
2357
  deriveCodexPermissionsMode({
2330
2358
  approvalPolicy: newApproval,
2331
- approvalsReviewer: codexPermissionSettings?.approvalsReviewer ??
2332
- msg.approvalsReviewer ??
2333
- currentReviewer,
2359
+ approvalsReviewer: newReviewer,
2334
2360
  sandboxMode: newSandboxMode,
2335
2361
  });
2362
+ const newPermissionsMode = this.codexAutoReviewDisabled &&
2363
+ derivedPermissionsMode === "autoReview"
2364
+ ? "default"
2365
+ : derivedPermissionsMode;
2336
2366
  const newCollaboration = planMode
2337
2367
  ? "plan"
2338
2368
  : "default";
2339
- const newReviewer = requestedCodexPermissionsMode === "custom"
2340
- ? undefined
2341
- : (codexPermissionSettings?.approvalsReviewer ??
2342
- msg.approvalsReviewer ??
2343
- currentReviewer);
2344
2369
  const currentCollaboration = process.collaborationMode;
2345
2370
  if (newApproval === currentApproval &&
2346
2371
  newReviewer === currentReviewer &&
@@ -2351,7 +2376,9 @@ export class BridgeWebSocketServer {
2351
2376
  }
2352
2377
  const canApplyModeInPlace = session.status === "idle" &&
2353
2378
  requestedCodexPermissionsMode !== "custom" &&
2354
- newSandboxMode === currentSandboxMode;
2379
+ newSandboxMode === currentSandboxMode &&
2380
+ (this.codexAutoReviewPolicyLoaded ||
2381
+ !isCodexAutoReviewApprovalsReviewer(newReviewer));
2355
2382
  if (canApplyModeInPlace) {
2356
2383
  const process = session.process;
2357
2384
  if (newApproval && newApproval !== currentApproval) {
@@ -2407,7 +2434,7 @@ export class BridgeWebSocketServer {
2407
2434
  if (!threadId || !hasUserMessages) {
2408
2435
  const newId = this.sessionManager.create(projectPath, undefined, undefined, worktreePath
2409
2436
  ? { existingWorktreePath: worktreePath, worktreeBranch }
2410
- : undefined, "codex", {
2437
+ : undefined, "codex", this.withCodexAutoReviewPolicy({
2411
2438
  approvalPolicy: newApproval,
2412
2439
  approvalsReviewer: newReviewer,
2413
2440
  codexPermissionsMode: newPermissionsMode,
@@ -2418,7 +2445,7 @@ export class BridgeWebSocketServer {
2418
2445
  networkAccessEnabled: oldSettings.networkAccessEnabled,
2419
2446
  webSearchMode: oldSettings.webSearchMode,
2420
2447
  collaborationMode: newCollaboration,
2421
- });
2448
+ }));
2422
2449
  const newSession = this.sessionManager.get(newId);
2423
2450
  if (newSession && sessionName)
2424
2451
  newSession.name = sessionName;
@@ -2467,7 +2494,7 @@ export class BridgeWebSocketServer {
2467
2494
  }
2468
2495
  this.getCodexThreadHistory(threadId, effectiveProjectPath)
2469
2496
  .then((pastMessages) => {
2470
- const newId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", {
2497
+ const newId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", this.withCodexAutoReviewPolicy({
2471
2498
  threadId,
2472
2499
  approvalPolicy: newApproval,
2473
2500
  approvalsReviewer: newReviewer,
@@ -2479,7 +2506,7 @@ export class BridgeWebSocketServer {
2479
2506
  networkAccessEnabled: oldSettings.networkAccessEnabled,
2480
2507
  webSearchMode: oldSettings.webSearchMode,
2481
2508
  collaborationMode: newCollaboration,
2482
- });
2509
+ }));
2483
2510
  const newSession = this.sessionManager.get(newId);
2484
2511
  if (newSession && sessionName) {
2485
2512
  newSession.name = sessionName;
@@ -2818,8 +2845,10 @@ export class BridgeWebSocketServer {
2818
2845
  // "no rollout found for thread id".)
2819
2846
  const newId = this.sessionManager.create(projectPath, undefined, undefined, worktreePath
2820
2847
  ? { existingWorktreePath: worktreePath, worktreeBranch }
2821
- : undefined, "codex", {
2848
+ : undefined, "codex", this.withCodexAutoReviewPolicy({
2822
2849
  approvalPolicy: oldSettings.approvalPolicy,
2850
+ approvalsReviewer: oldSettings.approvalsReviewer,
2851
+ codexPermissionsMode: oldSettings.codexPermissionsMode,
2823
2852
  sandboxMode: newSandboxMode,
2824
2853
  model: oldSettings.model,
2825
2854
  modelReasoningEffort: oldSettings.modelReasoningEffort,
@@ -2827,7 +2856,7 @@ export class BridgeWebSocketServer {
2827
2856
  networkAccessEnabled: oldSettings.networkAccessEnabled,
2828
2857
  webSearchMode: oldSettings.webSearchMode,
2829
2858
  collaborationMode,
2830
- });
2859
+ }));
2831
2860
  const newSession = this.sessionManager.get(newId);
2832
2861
  if (newSession && sessionName)
2833
2862
  newSession.name = sessionName;
@@ -2869,9 +2898,11 @@ export class BridgeWebSocketServer {
2869
2898
  }
2870
2899
  this.getCodexThreadHistory(threadId, effectiveProjectPath)
2871
2900
  .then((pastMessages) => {
2872
- const newId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", {
2901
+ const newId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", this.withCodexAutoReviewPolicy({
2873
2902
  threadId,
2874
2903
  approvalPolicy: oldSettings.approvalPolicy,
2904
+ approvalsReviewer: oldSettings.approvalsReviewer,
2905
+ codexPermissionsMode: oldSettings.codexPermissionsMode,
2875
2906
  sandboxMode: newSandboxMode,
2876
2907
  model: oldSettings.model,
2877
2908
  modelReasoningEffort: oldSettings.modelReasoningEffort,
@@ -2879,7 +2910,7 @@ export class BridgeWebSocketServer {
2879
2910
  networkAccessEnabled: oldSettings.networkAccessEnabled,
2880
2911
  webSearchMode: oldSettings.webSearchMode,
2881
2912
  collaborationMode,
2882
- });
2913
+ }));
2883
2914
  // Restore session name
2884
2915
  const newSession = this.sessionManager.get(newId);
2885
2916
  if (newSession && sessionName) {
@@ -3316,25 +3347,39 @@ export class BridgeWebSocketServer {
3316
3347
  }
3317
3348
  case "archive_session": {
3318
3349
  const { sessionId, provider, projectPath } = msg;
3319
- this.archiveStore
3320
- .archive(sessionId, provider, projectPath)
3321
- .then(() => {
3322
- // For Codex sessions, also call thread/archive RPC (best-effort).
3323
- // Requires a running Codex app-server process; skip if none active.
3350
+ const archiveProjectPath = resolvePlatformPath(projectPath, this.platform);
3351
+ if (!this.isPathAllowed(archiveProjectPath)) {
3352
+ const pathError = this.buildPathNotAllowedError(projectPath);
3353
+ this.send(ws, {
3354
+ type: "archive_result",
3355
+ sessionId,
3356
+ success: false,
3357
+ error: pathError.message,
3358
+ });
3359
+ break;
3360
+ }
3361
+ void (async () => {
3324
3362
  if (provider === "codex") {
3325
- const activeSessions = this.sessionManager.list();
3326
- const codexSession = activeSessions.find((s) => s.provider === "codex");
3327
- if (codexSession) {
3328
- const session = this.sessionManager.get(codexSession.id);
3329
- if (session) {
3330
- session.process
3331
- .archiveThread(sessionId)
3332
- .catch((err) => {
3333
- console.warn(`[ws] Codex thread/archive failed (non-fatal): ${err}`);
3334
- });
3363
+ const activeProcess = this.getActiveCodexProcess();
3364
+ const codexProcess = activeProcess ??
3365
+ (await this.createStandaloneCodexProcess(archiveProjectPath));
3366
+ try {
3367
+ await codexProcess.archiveThread(sessionId);
3368
+ }
3369
+ catch (err) {
3370
+ if (!(err instanceof CodexRpcError && err.code === -32601)) {
3371
+ throw err;
3335
3372
  }
3373
+ console.warn("[ws] thread/archive unsupported; using local archive marker");
3374
+ }
3375
+ finally {
3376
+ if (!activeProcess)
3377
+ codexProcess.stop();
3336
3378
  }
3337
3379
  }
3380
+ await this.archiveStore.archive(sessionId, provider, archiveProjectPath);
3381
+ })()
3382
+ .then(() => {
3338
3383
  this.send(ws, {
3339
3384
  type: "archive_result",
3340
3385
  sessionId,
@@ -3346,7 +3391,7 @@ export class BridgeWebSocketServer {
3346
3391
  type: "archive_result",
3347
3392
  sessionId,
3348
3393
  success: false,
3349
- error: String(err),
3394
+ error: codexErrorMessage(err),
3350
3395
  });
3351
3396
  });
3352
3397
  break;
@@ -3359,9 +3404,13 @@ export class BridgeWebSocketServer {
3359
3404
  break;
3360
3405
  }
3361
3406
  const provider = msg.provider ?? "claude";
3362
- const requestedCodexPermissionsMode = provider === "codex"
3407
+ const normalizedCodexPermissionsMode = provider === "codex"
3363
3408
  ? normalizeCodexPermissionsMode(msg.codexPermissionsMode)
3364
3409
  : undefined;
3410
+ const requestedCodexPermissionsMode = this.codexAutoReviewDisabled &&
3411
+ normalizedCodexPermissionsMode === "autoReview"
3412
+ ? "default"
3413
+ : normalizedCodexPermissionsMode;
3365
3414
  const codexPermissionSettings = requestedCodexPermissionsMode
3366
3415
  ? codexSettingsFromPermissionsMode(requestedCodexPermissionsMode)
3367
3416
  : undefined;
@@ -3420,16 +3469,15 @@ export class BridgeWebSocketServer {
3420
3469
  }
3421
3470
  try {
3422
3471
  const pastMessages = await this.getCodexThreadHistory(sessionRefId, effectiveProjectPath);
3423
- const sessionId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", {
3472
+ const sessionId = this.sessionManager.create(effectiveProjectPath, undefined, pastMessages, worktreeOpts, "codex", this.withCodexAutoReviewPolicy({
3424
3473
  threadId: sessionRefId,
3425
3474
  profile: effectiveProfile,
3426
3475
  approvalPolicy: codexPermissionSettings
3427
3476
  ? codexPermissionSettings.approvalPolicy
3428
3477
  : (codexApprovalPolicy ??
3429
3478
  normalizeCodexApprovalPolicy(executionMode === "fullAccess" ? "never" : "on-request")),
3430
- approvalsReviewer: codexPermissionSettings
3431
- ? codexPermissionSettings.approvalsReviewer
3432
- : msg.approvalsReviewer,
3479
+ approvalsReviewer: codexPermissionSettings?.approvalsReviewer ??
3480
+ msg.approvalsReviewer,
3433
3481
  codexPermissionsMode: codexPermissionSettings?.codexPermissionsMode,
3434
3482
  sandboxMode: codexPermissionSettings
3435
3483
  ? codexPermissionSettings.sandboxMode
@@ -3445,7 +3493,7 @@ export class BridgeWebSocketServer {
3445
3493
  collaborationMode: planMode
3446
3494
  ? "plan"
3447
3495
  : "default",
3448
- });
3496
+ }));
3449
3497
  const createdSession = this.sessionManager.get(sessionId);
3450
3498
  const cached = this.sessionManager.getCachedCommands("codex", createdSession?.worktreePath ?? effectiveProjectPath);
3451
3499
  await this.loadAndSetSessionName(createdSession, "codex", effectiveProjectPath, sessionRefId);
@@ -5029,6 +5077,7 @@ export class BridgeWebSocketServer {
5029
5077
  codexModelServiceTiers: this.codexModelServiceTiers,
5030
5078
  codexProfiles: this.codexProfiles,
5031
5079
  defaultCodexProfile: this.defaultCodexProfile,
5080
+ codexAutoReviewDisabled: this.codexAutoReviewDisabled,
5032
5081
  bridgeVersion: getPackageVersion(),
5033
5082
  });
5034
5083
  }
@@ -5060,6 +5109,7 @@ export class BridgeWebSocketServer {
5060
5109
  codexModelServiceTiers: this.codexModelServiceTiers,
5061
5110
  codexProfiles: this.codexProfiles,
5062
5111
  defaultCodexProfile: this.defaultCodexProfile,
5112
+ codexAutoReviewDisabled: this.codexAutoReviewDisabled,
5063
5113
  bridgeVersion: getPackageVersion(),
5064
5114
  });
5065
5115
  }
@@ -5216,14 +5266,18 @@ export class BridgeWebSocketServer {
5216
5266
  }
5217
5267
  }
5218
5268
  broadcastSessionMessageNow(sessionId, msg, exclude) {
5219
- const data = JSON.stringify({ ...msg, sessionId });
5220
5269
  for (const client of this.wss.clients) {
5221
5270
  if (client === exclude)
5222
5271
  continue;
5223
5272
  if (client.readyState === WebSocket.OPEN) {
5224
- if (!this.shouldSendToClient(client, msg))
5273
+ const outboundMsg = {
5274
+ ...msg,
5275
+ sessionId,
5276
+ };
5277
+ const compatibleMsg = this.prepareServerMessageForClient(client, outboundMsg);
5278
+ if (!compatibleMsg)
5225
5279
  continue;
5226
- client.send(data);
5280
+ client.send(JSON.stringify(compatibleMsg));
5227
5281
  }
5228
5282
  }
5229
5283
  }
@@ -5304,6 +5358,7 @@ export class BridgeWebSocketServer {
5304
5358
  console.warn(`[ws] Failed to load Codex metadata: ${err}`);
5305
5359
  this.codexProfiles = [];
5306
5360
  this.defaultCodexProfile = undefined;
5361
+ this.codexAutoReviewPolicyLoaded = false;
5307
5362
  this.applyFallbackCodexModels();
5308
5363
  this.broadcastSessionList();
5309
5364
  })
@@ -5316,9 +5371,10 @@ export class BridgeWebSocketServer {
5316
5371
  const activeProcess = this.getActiveCodexProcess();
5317
5372
  const codexProcess = activeProcess ?? (await this.createStandaloneCodexProcess(projectPath));
5318
5373
  try {
5319
- const [profileResult, modelResult] = await Promise.allSettled([
5374
+ const [profileResult, modelResult, requirementsResult] = await Promise.allSettled([
5320
5375
  codexProcess.readProfileConfig(projectPath),
5321
5376
  this.readCodexModels(codexProcess),
5377
+ this.readCodexAutoReviewDisabled(codexProcess),
5322
5378
  ]);
5323
5379
  if (profileResult.status === "fulfilled") {
5324
5380
  this.codexProfiles = profileResult.value.profiles;
@@ -5338,6 +5394,14 @@ export class BridgeWebSocketServer {
5338
5394
  }
5339
5395
  this.applyFallbackCodexModels();
5340
5396
  }
5397
+ if (requirementsResult.status === "fulfilled") {
5398
+ this.codexAutoReviewDisabled = requirementsResult.value;
5399
+ this.codexAutoReviewPolicyLoaded = true;
5400
+ }
5401
+ else {
5402
+ console.warn(`[ws] Failed to load Codex config requirements: ${requirementsResult.reason}`);
5403
+ this.codexAutoReviewPolicyLoaded = false;
5404
+ }
5341
5405
  this.broadcastSessionList();
5342
5406
  }
5343
5407
  finally {
@@ -5401,6 +5465,14 @@ export class BridgeWebSocketServer {
5401
5465
  supportedServiceTiers: fallbackCodexServiceTiers(model),
5402
5466
  }));
5403
5467
  }
5468
+ async readCodexAutoReviewDisabled(codexProcess) {
5469
+ const requirementsSource = codexProcess;
5470
+ if (typeof requirementsSource.readConfigRequirements !== "function") {
5471
+ return false;
5472
+ }
5473
+ return (await requirementsSource.readConfigRequirements())
5474
+ .autoReviewDisabled;
5475
+ }
5404
5476
  applyCodexModels(models) {
5405
5477
  this.codexModels = models.map((model) => model.model);
5406
5478
  this.codexModelReasoningEfforts = Object.fromEntries(models.map((model) => [
@@ -5479,6 +5551,19 @@ export class BridgeWebSocketServer {
5479
5551
  ? session.process
5480
5552
  : null;
5481
5553
  }
5554
+ withCodexAutoReviewPolicy(options) {
5555
+ const disableAutoReview = this.codexAutoReviewDisabled;
5556
+ return {
5557
+ ...options,
5558
+ ...(disableAutoReview ? { approvalsReviewer: "user" } : {}),
5559
+ ...(disableAutoReview && options.codexPermissionsMode === "autoReview"
5560
+ ? { codexPermissionsMode: "default" }
5561
+ : {}),
5562
+ autoReviewDisabledByPolicy: this.codexAutoReviewPolicyLoaded
5563
+ ? disableAutoReview
5564
+ : null,
5565
+ };
5566
+ }
5482
5567
  getActiveClaudeProcess() {
5483
5568
  const summary = this.sessionManager
5484
5569
  .list()
@@ -5726,15 +5811,39 @@ export class BridgeWebSocketServer {
5726
5811
  }
5727
5812
  }
5728
5813
  broadcast(msg) {
5729
- const data = JSON.stringify(msg);
5730
5814
  for (const client of this.wss.clients) {
5731
5815
  if (client.readyState === WebSocket.OPEN) {
5732
- if (!this.shouldSendToClient(client, msg))
5816
+ const compatibleMsg = this.prepareServerMessageForClient(client, msg);
5817
+ if (!compatibleMsg)
5733
5818
  continue;
5734
- client.send(data);
5819
+ client.send(JSON.stringify(compatibleMsg));
5735
5820
  }
5736
5821
  }
5737
5822
  }
5823
+ prepareServerMessageForClient(ws, msg) {
5824
+ if (!this.shouldSendToClient(ws, msg))
5825
+ return null;
5826
+ if (!("messages" in msg) || !Array.isArray(msg.messages))
5827
+ return msg;
5828
+ const messages = msg.messages;
5829
+ if (msg.type === "history") {
5830
+ return {
5831
+ ...msg,
5832
+ messages: messages.filter((message) => isRecord(message) && this.shouldSendToClient(ws, message)),
5833
+ };
5834
+ }
5835
+ if (msg.type === "history_delta" || msg.type === "history_snapshot") {
5836
+ return {
5837
+ ...msg,
5838
+ messages: messages.filter((entry) => {
5839
+ if (!isRecord(entry) || !isRecord(entry.message))
5840
+ return true;
5841
+ return this.shouldSendToClient(ws, entry.message);
5842
+ }),
5843
+ };
5844
+ }
5845
+ return msg;
5846
+ }
5738
5847
  shouldSendToClient(ws, msg) {
5739
5848
  const type = typeof msg.type === "string" ? msg.type : "";
5740
5849
  if (!OPT_IN_SERVER_MESSAGES.has(type))
@@ -5828,19 +5937,20 @@ export class BridgeWebSocketServer {
5828
5937
  this.send(ws, msg);
5829
5938
  }
5830
5939
  send(ws, msg) {
5831
- if (!this.shouldSendToClient(ws, msg))
5940
+ const compatibleMsg = this.prepareServerMessageForClient(ws, msg);
5941
+ if (!compatibleMsg)
5832
5942
  return;
5833
- const sessionId = this.extractSessionIdFromServerMessage(msg);
5943
+ const sessionId = this.extractSessionIdFromServerMessage(compatibleMsg);
5834
5944
  if (sessionId) {
5835
5945
  this.recordDebugEvent(sessionId, {
5836
5946
  direction: "outgoing",
5837
5947
  channel: "ws",
5838
- type: String(msg.type ?? "unknown"),
5839
- detail: this.summarizeOutboundMessage(msg),
5948
+ type: String(compatibleMsg.type ?? "unknown"),
5949
+ detail: this.summarizeOutboundMessage(compatibleMsg),
5840
5950
  });
5841
5951
  }
5842
5952
  if (ws.readyState === WebSocket.OPEN) {
5843
- ws.send(JSON.stringify(msg));
5953
+ ws.send(JSON.stringify(compatibleMsg));
5844
5954
  }
5845
5955
  }
5846
5956
  /** Broadcast a gallery_new_image message to all connected clients. */